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, 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: Option<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 #[inline]
515 pub fn is_full(&self) -> bool {
516 matches!(self, Self::Full { .. })
517 }
518
519 #[inline]
520 pub fn is_single_line(&self) -> bool {
521 matches!(self, Self::SingleLine { .. })
522 }
523
524 #[inline]
525 fn is_minimap(&self) -> bool {
526 matches!(self, Self::Minimap { .. })
527 }
528}
529
530#[derive(Copy, Clone, Debug)]
531pub enum SoftWrap {
532 /// Prefer not to wrap at all.
533 ///
534 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
535 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
536 GitDiff,
537 /// Prefer a single line generally, unless an overly long line is encountered.
538 None,
539 /// Soft wrap lines that exceed the editor width.
540 EditorWidth,
541 /// Soft wrap lines at the preferred line length.
542 Column(u32),
543 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
544 Bounded(u32),
545}
546
547#[derive(Clone)]
548pub struct EditorStyle {
549 pub background: Hsla,
550 pub local_player: PlayerColor,
551 pub text: TextStyle,
552 pub scrollbar_width: Pixels,
553 pub syntax: Arc<SyntaxTheme>,
554 pub status: StatusColors,
555 pub inlay_hints_style: HighlightStyle,
556 pub inline_completion_styles: InlineCompletionStyles,
557 pub unnecessary_code_fade: f32,
558 pub show_underlines: bool,
559}
560
561impl Default for EditorStyle {
562 fn default() -> Self {
563 Self {
564 background: Hsla::default(),
565 local_player: PlayerColor::default(),
566 text: TextStyle::default(),
567 scrollbar_width: Pixels::default(),
568 syntax: Default::default(),
569 // HACK: Status colors don't have a real default.
570 // We should look into removing the status colors from the editor
571 // style and retrieve them directly from the theme.
572 status: StatusColors::dark(),
573 inlay_hints_style: HighlightStyle::default(),
574 inline_completion_styles: InlineCompletionStyles {
575 insertion: HighlightStyle::default(),
576 whitespace: HighlightStyle::default(),
577 },
578 unnecessary_code_fade: Default::default(),
579 show_underlines: true,
580 }
581 }
582}
583
584pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
585 let show_background = language_settings::language_settings(None, None, cx)
586 .inlay_hints
587 .show_background;
588
589 HighlightStyle {
590 color: Some(cx.theme().status().hint),
591 background_color: show_background.then(|| cx.theme().status().hint_background),
592 ..HighlightStyle::default()
593 }
594}
595
596pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
597 InlineCompletionStyles {
598 insertion: HighlightStyle {
599 color: Some(cx.theme().status().predictive),
600 ..HighlightStyle::default()
601 },
602 whitespace: HighlightStyle {
603 background_color: Some(cx.theme().status().created_background),
604 ..HighlightStyle::default()
605 },
606 }
607}
608
609type CompletionId = usize;
610
611pub(crate) enum EditDisplayMode {
612 TabAccept,
613 DiffPopover,
614 Inline,
615}
616
617enum InlineCompletion {
618 Edit {
619 edits: Vec<(Range<Anchor>, String)>,
620 edit_preview: Option<EditPreview>,
621 display_mode: EditDisplayMode,
622 snapshot: BufferSnapshot,
623 },
624 Move {
625 target: Anchor,
626 snapshot: BufferSnapshot,
627 },
628}
629
630struct InlineCompletionState {
631 inlay_ids: Vec<InlayId>,
632 completion: InlineCompletion,
633 completion_id: Option<SharedString>,
634 invalidation_range: Range<Anchor>,
635}
636
637enum EditPredictionSettings {
638 Disabled,
639 Enabled {
640 show_in_menu: bool,
641 preview_requires_modifier: bool,
642 },
643}
644
645enum InlineCompletionHighlight {}
646
647#[derive(Debug, Clone)]
648struct InlineDiagnostic {
649 message: SharedString,
650 group_id: usize,
651 is_primary: bool,
652 start: Point,
653 severity: lsp::DiagnosticSeverity,
654}
655
656pub enum MenuInlineCompletionsPolicy {
657 Never,
658 ByProvider,
659}
660
661pub enum EditPredictionPreview {
662 /// Modifier is not pressed
663 Inactive { released_too_fast: bool },
664 /// Modifier pressed
665 Active {
666 since: Instant,
667 previous_scroll_position: Option<ScrollAnchor>,
668 },
669}
670
671impl EditPredictionPreview {
672 pub fn released_too_fast(&self) -> bool {
673 match self {
674 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
675 EditPredictionPreview::Active { .. } => false,
676 }
677 }
678
679 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
680 if let EditPredictionPreview::Active {
681 previous_scroll_position,
682 ..
683 } = self
684 {
685 *previous_scroll_position = scroll_position;
686 }
687 }
688}
689
690pub struct ContextMenuOptions {
691 pub min_entries_visible: usize,
692 pub max_entries_visible: usize,
693 pub placement: Option<ContextMenuPlacement>,
694}
695
696#[derive(Debug, Clone, PartialEq, Eq)]
697pub enum ContextMenuPlacement {
698 Above,
699 Below,
700}
701
702#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
703struct EditorActionId(usize);
704
705impl EditorActionId {
706 pub fn post_inc(&mut self) -> Self {
707 let answer = self.0;
708
709 *self = Self(answer + 1);
710
711 Self(answer)
712 }
713}
714
715// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
716// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
717
718type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
719type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
720
721#[derive(Default)]
722struct ScrollbarMarkerState {
723 scrollbar_size: Size<Pixels>,
724 dirty: bool,
725 markers: Arc<[PaintQuad]>,
726 pending_refresh: Option<Task<Result<()>>>,
727}
728
729impl ScrollbarMarkerState {
730 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
731 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
732 }
733}
734
735#[derive(Clone, Copy, PartialEq, Eq)]
736pub enum MinimapVisibility {
737 Disabled,
738 Enabled {
739 /// The configuration currently present in the users settings.
740 setting_configuration: bool,
741 /// Whether to override the currently set visibility from the users setting.
742 toggle_override: bool,
743 },
744}
745
746impl MinimapVisibility {
747 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
748 if mode.is_full() {
749 Self::Enabled {
750 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
751 toggle_override: false,
752 }
753 } else {
754 Self::Disabled
755 }
756 }
757
758 fn hidden(&self) -> Self {
759 match *self {
760 Self::Enabled {
761 setting_configuration,
762 ..
763 } => Self::Enabled {
764 setting_configuration,
765 toggle_override: setting_configuration,
766 },
767 Self::Disabled => Self::Disabled,
768 }
769 }
770
771 fn disabled(&self) -> bool {
772 match *self {
773 Self::Disabled => true,
774 _ => false,
775 }
776 }
777
778 fn settings_visibility(&self) -> bool {
779 match *self {
780 Self::Enabled {
781 setting_configuration,
782 ..
783 } => setting_configuration,
784 _ => false,
785 }
786 }
787
788 fn visible(&self) -> bool {
789 match *self {
790 Self::Enabled {
791 setting_configuration,
792 toggle_override,
793 } => setting_configuration ^ toggle_override,
794 _ => false,
795 }
796 }
797
798 fn toggle_visibility(&self) -> Self {
799 match *self {
800 Self::Enabled {
801 toggle_override,
802 setting_configuration,
803 } => Self::Enabled {
804 setting_configuration,
805 toggle_override: !toggle_override,
806 },
807 Self::Disabled => Self::Disabled,
808 }
809 }
810}
811
812#[derive(Clone, Debug)]
813struct RunnableTasks {
814 templates: Vec<(TaskSourceKind, TaskTemplate)>,
815 offset: multi_buffer::Anchor,
816 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
817 column: u32,
818 // Values of all named captures, including those starting with '_'
819 extra_variables: HashMap<String, String>,
820 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
821 context_range: Range<BufferOffset>,
822}
823
824impl RunnableTasks {
825 fn resolve<'a>(
826 &'a self,
827 cx: &'a task::TaskContext,
828 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
829 self.templates.iter().filter_map(|(kind, template)| {
830 template
831 .resolve_task(&kind.to_id_base(), cx)
832 .map(|task| (kind.clone(), task))
833 })
834 }
835}
836
837#[derive(Clone)]
838pub struct ResolvedTasks {
839 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
840 position: Anchor,
841}
842
843#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
844struct BufferOffset(usize);
845
846// Addons allow storing per-editor state in other crates (e.g. Vim)
847pub trait Addon: 'static {
848 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
849
850 fn render_buffer_header_controls(
851 &self,
852 _: &ExcerptInfo,
853 _: &Window,
854 _: &App,
855 ) -> Option<AnyElement> {
856 None
857 }
858
859 fn to_any(&self) -> &dyn std::any::Any;
860
861 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
862 None
863 }
864}
865
866/// A set of caret positions, registered when the editor was edited.
867pub struct ChangeList {
868 changes: Vec<Vec<Anchor>>,
869 /// Currently "selected" change.
870 position: Option<usize>,
871}
872
873impl ChangeList {
874 pub fn new() -> Self {
875 Self {
876 changes: Vec::new(),
877 position: None,
878 }
879 }
880
881 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
882 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
883 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
884 if self.changes.is_empty() {
885 return None;
886 }
887
888 let prev = self.position.unwrap_or(self.changes.len());
889 let next = if direction == Direction::Prev {
890 prev.saturating_sub(count)
891 } else {
892 (prev + count).min(self.changes.len() - 1)
893 };
894 self.position = Some(next);
895 self.changes.get(next).map(|anchors| anchors.as_slice())
896 }
897
898 /// Adds a new change to the list, resetting the change list position.
899 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
900 self.position.take();
901 if pop_state {
902 self.changes.pop();
903 }
904 self.changes.push(new_positions.clone());
905 }
906
907 pub fn last(&self) -> Option<&[Anchor]> {
908 self.changes.last().map(|anchors| anchors.as_slice())
909 }
910}
911
912#[derive(Clone)]
913struct InlineBlamePopoverState {
914 scroll_handle: ScrollHandle,
915 commit_message: Option<ParsedCommitMessage>,
916 markdown: Entity<Markdown>,
917}
918
919struct InlineBlamePopover {
920 position: gpui::Point<Pixels>,
921 hide_task: Option<Task<()>>,
922 popover_bounds: Option<Bounds<Pixels>>,
923 popover_state: InlineBlamePopoverState,
924}
925
926enum SelectionDragState {
927 /// State when no drag related activity is detected.
928 None,
929 /// State when the mouse is down on a selection that is about to be dragged.
930 ReadyToDrag {
931 selection: Selection<Anchor>,
932 click_position: gpui::Point<Pixels>,
933 mouse_down_time: Instant,
934 },
935 /// State when the mouse is dragging the selection in the editor.
936 Dragging {
937 selection: Selection<Anchor>,
938 drop_cursor: Selection<Anchor>,
939 hide_drop_cursor: bool,
940 },
941}
942
943enum ColumnarSelectionState {
944 FromMouse {
945 selection_tail: Anchor,
946 display_point: Option<DisplayPoint>,
947 },
948 FromSelection {
949 selection_tail: Anchor,
950 },
951}
952
953/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
954/// a breakpoint on them.
955#[derive(Clone, Copy, Debug, PartialEq, Eq)]
956struct PhantomBreakpointIndicator {
957 display_row: DisplayRow,
958 /// There's a small debounce between hovering over the line and showing the indicator.
959 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
960 is_active: bool,
961 collides_with_existing_breakpoint: bool,
962}
963
964/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
965///
966/// See the [module level documentation](self) for more information.
967pub struct Editor {
968 focus_handle: FocusHandle,
969 last_focused_descendant: Option<WeakFocusHandle>,
970 /// The text buffer being edited
971 buffer: Entity<MultiBuffer>,
972 /// Map of how text in the buffer should be displayed.
973 /// Handles soft wraps, folds, fake inlay text insertions, etc.
974 pub display_map: Entity<DisplayMap>,
975 pub selections: SelectionsCollection,
976 pub scroll_manager: ScrollManager,
977 /// When inline assist editors are linked, they all render cursors because
978 /// typing enters text into each of them, even the ones that aren't focused.
979 pub(crate) show_cursor_when_unfocused: bool,
980 columnar_selection_state: Option<ColumnarSelectionState>,
981 add_selections_state: Option<AddSelectionsState>,
982 select_next_state: Option<SelectNextState>,
983 select_prev_state: Option<SelectNextState>,
984 selection_history: SelectionHistory,
985 defer_selection_effects: bool,
986 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
987 autoclose_regions: Vec<AutocloseRegion>,
988 snippet_stack: InvalidationStack<SnippetState>,
989 select_syntax_node_history: SelectSyntaxNodeHistory,
990 ime_transaction: Option<TransactionId>,
991 pub diagnostics_max_severity: DiagnosticSeverity,
992 active_diagnostics: ActiveDiagnostic,
993 show_inline_diagnostics: bool,
994 inline_diagnostics_update: Task<()>,
995 inline_diagnostics_enabled: bool,
996 diagnostics_enabled: bool,
997 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
998 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
999 hard_wrap: Option<usize>,
1000
1001 // TODO: make this a access method
1002 pub project: Option<Entity<Project>>,
1003 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1004 completion_provider: Option<Rc<dyn CompletionProvider>>,
1005 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1006 blink_manager: Entity<BlinkManager>,
1007 show_cursor_names: bool,
1008 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1009 pub show_local_selections: bool,
1010 mode: EditorMode,
1011 show_breadcrumbs: bool,
1012 show_gutter: bool,
1013 show_scrollbars: ScrollbarAxes,
1014 minimap_visibility: MinimapVisibility,
1015 offset_content: bool,
1016 disable_expand_excerpt_buttons: bool,
1017 show_line_numbers: Option<bool>,
1018 use_relative_line_numbers: Option<bool>,
1019 show_git_diff_gutter: Option<bool>,
1020 show_code_actions: Option<bool>,
1021 show_runnables: Option<bool>,
1022 show_breakpoints: Option<bool>,
1023 show_wrap_guides: Option<bool>,
1024 show_indent_guides: Option<bool>,
1025 placeholder_text: Option<Arc<str>>,
1026 highlight_order: usize,
1027 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1028 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1029 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1030 scrollbar_marker_state: ScrollbarMarkerState,
1031 active_indent_guides_state: ActiveIndentGuidesState,
1032 nav_history: Option<ItemNavHistory>,
1033 context_menu: RefCell<Option<CodeContextMenu>>,
1034 context_menu_options: Option<ContextMenuOptions>,
1035 mouse_context_menu: Option<MouseContextMenu>,
1036 completion_tasks: Vec<(CompletionId, Task<()>)>,
1037 inline_blame_popover: Option<InlineBlamePopover>,
1038 inline_blame_popover_show_task: Option<Task<()>>,
1039 signature_help_state: SignatureHelpState,
1040 auto_signature_help: Option<bool>,
1041 find_all_references_task_sources: Vec<Anchor>,
1042 next_completion_id: CompletionId,
1043 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1044 code_actions_task: Option<Task<Result<()>>>,
1045 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1046 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1047 document_highlights_task: Option<Task<()>>,
1048 linked_editing_range_task: Option<Task<Option<()>>>,
1049 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1050 pending_rename: Option<RenameState>,
1051 searchable: bool,
1052 cursor_shape: CursorShape,
1053 current_line_highlight: Option<CurrentLineHighlight>,
1054 collapse_matches: bool,
1055 autoindent_mode: Option<AutoindentMode>,
1056 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1057 input_enabled: bool,
1058 use_modal_editing: bool,
1059 read_only: bool,
1060 leader_id: Option<CollaboratorId>,
1061 remote_id: Option<ViewId>,
1062 pub hover_state: HoverState,
1063 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1064 gutter_hovered: bool,
1065 hovered_link_state: Option<HoveredLinkState>,
1066 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1067 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1068 active_inline_completion: Option<InlineCompletionState>,
1069 /// Used to prevent flickering as the user types while the menu is open
1070 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1071 edit_prediction_settings: EditPredictionSettings,
1072 inline_completions_hidden_for_vim_mode: bool,
1073 show_inline_completions_override: Option<bool>,
1074 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1075 edit_prediction_preview: EditPredictionPreview,
1076 edit_prediction_indent_conflict: bool,
1077 edit_prediction_requires_modifier_in_indent_conflict: bool,
1078 inlay_hint_cache: InlayHintCache,
1079 next_inlay_id: usize,
1080 _subscriptions: Vec<Subscription>,
1081 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1082 gutter_dimensions: GutterDimensions,
1083 style: Option<EditorStyle>,
1084 text_style_refinement: Option<TextStyleRefinement>,
1085 next_editor_action_id: EditorActionId,
1086 editor_actions: Rc<
1087 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1088 >,
1089 use_autoclose: bool,
1090 use_auto_surround: bool,
1091 auto_replace_emoji_shortcode: bool,
1092 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1093 show_git_blame_gutter: bool,
1094 show_git_blame_inline: bool,
1095 show_git_blame_inline_delay_task: Option<Task<()>>,
1096 git_blame_inline_enabled: bool,
1097 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1098 serialize_dirty_buffers: bool,
1099 show_selection_menu: Option<bool>,
1100 blame: Option<Entity<GitBlame>>,
1101 blame_subscription: Option<Subscription>,
1102 custom_context_menu: Option<
1103 Box<
1104 dyn 'static
1105 + Fn(
1106 &mut Self,
1107 DisplayPoint,
1108 &mut Window,
1109 &mut Context<Self>,
1110 ) -> Option<Entity<ui::ContextMenu>>,
1111 >,
1112 >,
1113 last_bounds: Option<Bounds<Pixels>>,
1114 last_position_map: Option<Rc<PositionMap>>,
1115 expect_bounds_change: Option<Bounds<Pixels>>,
1116 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1117 tasks_update_task: Option<Task<()>>,
1118 breakpoint_store: Option<Entity<BreakpointStore>>,
1119 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1120 hovered_diff_hunk_row: Option<DisplayRow>,
1121 pull_diagnostics_task: Task<()>,
1122 in_project_search: bool,
1123 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1124 breadcrumb_header: Option<String>,
1125 focused_block: Option<FocusedBlock>,
1126 next_scroll_position: NextScrollCursorCenterTopBottom,
1127 addons: HashMap<TypeId, Box<dyn Addon>>,
1128 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1129 load_diff_task: Option<Shared<Task<()>>>,
1130 /// Whether we are temporarily displaying a diff other than git's
1131 temporary_diff_override: bool,
1132 selection_mark_mode: bool,
1133 toggle_fold_multiple_buffers: Task<()>,
1134 _scroll_cursor_center_top_bottom_task: Task<()>,
1135 serialize_selections: Task<()>,
1136 serialize_folds: Task<()>,
1137 mouse_cursor_hidden: bool,
1138 minimap: Option<Entity<Self>>,
1139 hide_mouse_mode: HideMouseMode,
1140 pub change_list: ChangeList,
1141 inline_value_cache: InlineValueCache,
1142 selection_drag_state: SelectionDragState,
1143 drag_and_drop_selection_enabled: bool,
1144 next_color_inlay_id: usize,
1145 colors: Option<LspColorData>,
1146}
1147
1148#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1149enum NextScrollCursorCenterTopBottom {
1150 #[default]
1151 Center,
1152 Top,
1153 Bottom,
1154}
1155
1156impl NextScrollCursorCenterTopBottom {
1157 fn next(&self) -> Self {
1158 match self {
1159 Self::Center => Self::Top,
1160 Self::Top => Self::Bottom,
1161 Self::Bottom => Self::Center,
1162 }
1163 }
1164}
1165
1166#[derive(Clone)]
1167pub struct EditorSnapshot {
1168 pub mode: EditorMode,
1169 show_gutter: bool,
1170 show_line_numbers: Option<bool>,
1171 show_git_diff_gutter: Option<bool>,
1172 show_code_actions: Option<bool>,
1173 show_runnables: Option<bool>,
1174 show_breakpoints: Option<bool>,
1175 git_blame_gutter_max_author_length: Option<usize>,
1176 pub display_snapshot: DisplaySnapshot,
1177 pub placeholder_text: Option<Arc<str>>,
1178 is_focused: bool,
1179 scroll_anchor: ScrollAnchor,
1180 ongoing_scroll: OngoingScroll,
1181 current_line_highlight: CurrentLineHighlight,
1182 gutter_hovered: bool,
1183}
1184
1185#[derive(Default, Debug, Clone, Copy)]
1186pub struct GutterDimensions {
1187 pub left_padding: Pixels,
1188 pub right_padding: Pixels,
1189 pub width: Pixels,
1190 pub margin: Pixels,
1191 pub git_blame_entries_width: Option<Pixels>,
1192}
1193
1194impl GutterDimensions {
1195 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1196 Self {
1197 margin: Self::default_gutter_margin(font_id, font_size, cx),
1198 ..Default::default()
1199 }
1200 }
1201
1202 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1203 -cx.text_system().descent(font_id, font_size)
1204 }
1205 /// The full width of the space taken up by the gutter.
1206 pub fn full_width(&self) -> Pixels {
1207 self.margin + self.width
1208 }
1209
1210 /// The width of the space reserved for the fold indicators,
1211 /// use alongside 'justify_end' and `gutter_width` to
1212 /// right align content with the line numbers
1213 pub fn fold_area_width(&self) -> Pixels {
1214 self.margin + self.right_padding
1215 }
1216}
1217
1218#[derive(Debug)]
1219pub struct RemoteSelection {
1220 pub replica_id: ReplicaId,
1221 pub selection: Selection<Anchor>,
1222 pub cursor_shape: CursorShape,
1223 pub collaborator_id: CollaboratorId,
1224 pub line_mode: bool,
1225 pub user_name: Option<SharedString>,
1226 pub color: PlayerColor,
1227}
1228
1229#[derive(Clone, Debug)]
1230struct SelectionHistoryEntry {
1231 selections: Arc<[Selection<Anchor>]>,
1232 select_next_state: Option<SelectNextState>,
1233 select_prev_state: Option<SelectNextState>,
1234 add_selections_state: Option<AddSelectionsState>,
1235}
1236
1237#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1238enum SelectionHistoryMode {
1239 Normal,
1240 Undoing,
1241 Redoing,
1242 Skipping,
1243}
1244
1245#[derive(Clone, PartialEq, Eq, Hash)]
1246struct HoveredCursor {
1247 replica_id: u16,
1248 selection_id: usize,
1249}
1250
1251impl Default for SelectionHistoryMode {
1252 fn default() -> Self {
1253 Self::Normal
1254 }
1255}
1256
1257#[derive(Debug)]
1258pub struct SelectionEffects {
1259 nav_history: bool,
1260 completions: bool,
1261 scroll: Option<Autoscroll>,
1262}
1263
1264impl Default for SelectionEffects {
1265 fn default() -> Self {
1266 Self {
1267 nav_history: true,
1268 completions: true,
1269 scroll: Some(Autoscroll::fit()),
1270 }
1271 }
1272}
1273impl SelectionEffects {
1274 pub fn scroll(scroll: Autoscroll) -> Self {
1275 Self {
1276 scroll: Some(scroll),
1277 ..Default::default()
1278 }
1279 }
1280
1281 pub fn no_scroll() -> Self {
1282 Self {
1283 scroll: None,
1284 ..Default::default()
1285 }
1286 }
1287
1288 pub fn completions(self, completions: bool) -> Self {
1289 Self {
1290 completions,
1291 ..self
1292 }
1293 }
1294
1295 pub fn nav_history(self, nav_history: bool) -> Self {
1296 Self {
1297 nav_history,
1298 ..self
1299 }
1300 }
1301}
1302
1303struct DeferredSelectionEffectsState {
1304 changed: bool,
1305 effects: SelectionEffects,
1306 old_cursor_position: Anchor,
1307 history_entry: SelectionHistoryEntry,
1308}
1309
1310#[derive(Default)]
1311struct SelectionHistory {
1312 #[allow(clippy::type_complexity)]
1313 selections_by_transaction:
1314 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1315 mode: SelectionHistoryMode,
1316 undo_stack: VecDeque<SelectionHistoryEntry>,
1317 redo_stack: VecDeque<SelectionHistoryEntry>,
1318}
1319
1320impl SelectionHistory {
1321 #[track_caller]
1322 fn insert_transaction(
1323 &mut self,
1324 transaction_id: TransactionId,
1325 selections: Arc<[Selection<Anchor>]>,
1326 ) {
1327 if selections.is_empty() {
1328 log::error!(
1329 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1330 std::panic::Location::caller()
1331 );
1332 return;
1333 }
1334 self.selections_by_transaction
1335 .insert(transaction_id, (selections, None));
1336 }
1337
1338 #[allow(clippy::type_complexity)]
1339 fn transaction(
1340 &self,
1341 transaction_id: TransactionId,
1342 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1343 self.selections_by_transaction.get(&transaction_id)
1344 }
1345
1346 #[allow(clippy::type_complexity)]
1347 fn transaction_mut(
1348 &mut self,
1349 transaction_id: TransactionId,
1350 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1351 self.selections_by_transaction.get_mut(&transaction_id)
1352 }
1353
1354 fn push(&mut self, entry: SelectionHistoryEntry) {
1355 if !entry.selections.is_empty() {
1356 match self.mode {
1357 SelectionHistoryMode::Normal => {
1358 self.push_undo(entry);
1359 self.redo_stack.clear();
1360 }
1361 SelectionHistoryMode::Undoing => self.push_redo(entry),
1362 SelectionHistoryMode::Redoing => self.push_undo(entry),
1363 SelectionHistoryMode::Skipping => {}
1364 }
1365 }
1366 }
1367
1368 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1369 if self
1370 .undo_stack
1371 .back()
1372 .map_or(true, |e| e.selections != entry.selections)
1373 {
1374 self.undo_stack.push_back(entry);
1375 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1376 self.undo_stack.pop_front();
1377 }
1378 }
1379 }
1380
1381 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1382 if self
1383 .redo_stack
1384 .back()
1385 .map_or(true, |e| e.selections != entry.selections)
1386 {
1387 self.redo_stack.push_back(entry);
1388 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1389 self.redo_stack.pop_front();
1390 }
1391 }
1392 }
1393}
1394
1395#[derive(Clone, Copy)]
1396pub struct RowHighlightOptions {
1397 pub autoscroll: bool,
1398 pub include_gutter: bool,
1399}
1400
1401impl Default for RowHighlightOptions {
1402 fn default() -> Self {
1403 Self {
1404 autoscroll: Default::default(),
1405 include_gutter: true,
1406 }
1407 }
1408}
1409
1410struct RowHighlight {
1411 index: usize,
1412 range: Range<Anchor>,
1413 color: Hsla,
1414 options: RowHighlightOptions,
1415 type_id: TypeId,
1416}
1417
1418#[derive(Clone, Debug)]
1419struct AddSelectionsState {
1420 groups: Vec<AddSelectionsGroup>,
1421}
1422
1423#[derive(Clone, Debug)]
1424struct AddSelectionsGroup {
1425 above: bool,
1426 stack: Vec<usize>,
1427}
1428
1429#[derive(Clone)]
1430struct SelectNextState {
1431 query: AhoCorasick,
1432 wordwise: bool,
1433 done: bool,
1434}
1435
1436impl std::fmt::Debug for SelectNextState {
1437 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1438 f.debug_struct(std::any::type_name::<Self>())
1439 .field("wordwise", &self.wordwise)
1440 .field("done", &self.done)
1441 .finish()
1442 }
1443}
1444
1445#[derive(Debug)]
1446struct AutocloseRegion {
1447 selection_id: usize,
1448 range: Range<Anchor>,
1449 pair: BracketPair,
1450}
1451
1452#[derive(Debug)]
1453struct SnippetState {
1454 ranges: Vec<Vec<Range<Anchor>>>,
1455 active_index: usize,
1456 choices: Vec<Option<Vec<String>>>,
1457}
1458
1459#[doc(hidden)]
1460pub struct RenameState {
1461 pub range: Range<Anchor>,
1462 pub old_name: Arc<str>,
1463 pub editor: Entity<Editor>,
1464 block_id: CustomBlockId,
1465}
1466
1467struct InvalidationStack<T>(Vec<T>);
1468
1469struct RegisteredInlineCompletionProvider {
1470 provider: Arc<dyn InlineCompletionProviderHandle>,
1471 _subscription: Subscription,
1472}
1473
1474#[derive(Debug, PartialEq, Eq)]
1475pub struct ActiveDiagnosticGroup {
1476 pub active_range: Range<Anchor>,
1477 pub active_message: String,
1478 pub group_id: usize,
1479 pub blocks: HashSet<CustomBlockId>,
1480}
1481
1482#[derive(Debug, PartialEq, Eq)]
1483
1484pub(crate) enum ActiveDiagnostic {
1485 None,
1486 All,
1487 Group(ActiveDiagnosticGroup),
1488}
1489
1490#[derive(Serialize, Deserialize, Clone, Debug)]
1491pub struct ClipboardSelection {
1492 /// The number of bytes in this selection.
1493 pub len: usize,
1494 /// Whether this was a full-line selection.
1495 pub is_entire_line: bool,
1496 /// The indentation of the first line when this content was originally copied.
1497 pub first_line_indent: u32,
1498}
1499
1500// selections, scroll behavior, was newest selection reversed
1501type SelectSyntaxNodeHistoryState = (
1502 Box<[Selection<usize>]>,
1503 SelectSyntaxNodeScrollBehavior,
1504 bool,
1505);
1506
1507#[derive(Default)]
1508struct SelectSyntaxNodeHistory {
1509 stack: Vec<SelectSyntaxNodeHistoryState>,
1510 // disable temporarily to allow changing selections without losing the stack
1511 pub disable_clearing: bool,
1512}
1513
1514impl SelectSyntaxNodeHistory {
1515 pub fn try_clear(&mut self) {
1516 if !self.disable_clearing {
1517 self.stack.clear();
1518 }
1519 }
1520
1521 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1522 self.stack.push(selection);
1523 }
1524
1525 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1526 self.stack.pop()
1527 }
1528}
1529
1530enum SelectSyntaxNodeScrollBehavior {
1531 CursorTop,
1532 FitSelection,
1533 CursorBottom,
1534}
1535
1536#[derive(Debug)]
1537pub(crate) struct NavigationData {
1538 cursor_anchor: Anchor,
1539 cursor_position: Point,
1540 scroll_anchor: ScrollAnchor,
1541 scroll_top_row: u32,
1542}
1543
1544#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1545pub enum GotoDefinitionKind {
1546 Symbol,
1547 Declaration,
1548 Type,
1549 Implementation,
1550}
1551
1552#[derive(Debug, Clone)]
1553enum InlayHintRefreshReason {
1554 ModifiersChanged(bool),
1555 Toggle(bool),
1556 SettingsChange(InlayHintSettings),
1557 NewLinesShown,
1558 BufferEdited(HashSet<Arc<Language>>),
1559 RefreshRequested,
1560 ExcerptsRemoved(Vec<ExcerptId>),
1561}
1562
1563impl InlayHintRefreshReason {
1564 fn description(&self) -> &'static str {
1565 match self {
1566 Self::ModifiersChanged(_) => "modifiers changed",
1567 Self::Toggle(_) => "toggle",
1568 Self::SettingsChange(_) => "settings change",
1569 Self::NewLinesShown => "new lines shown",
1570 Self::BufferEdited(_) => "buffer edited",
1571 Self::RefreshRequested => "refresh requested",
1572 Self::ExcerptsRemoved(_) => "excerpts removed",
1573 }
1574 }
1575}
1576
1577pub enum FormatTarget {
1578 Buffers(HashSet<Entity<Buffer>>),
1579 Ranges(Vec<Range<MultiBufferPoint>>),
1580}
1581
1582pub(crate) struct FocusedBlock {
1583 id: BlockId,
1584 focus_handle: WeakFocusHandle,
1585}
1586
1587#[derive(Clone)]
1588enum JumpData {
1589 MultiBufferRow {
1590 row: MultiBufferRow,
1591 line_offset_from_top: u32,
1592 },
1593 MultiBufferPoint {
1594 excerpt_id: ExcerptId,
1595 position: Point,
1596 anchor: text::Anchor,
1597 line_offset_from_top: u32,
1598 },
1599}
1600
1601pub enum MultibufferSelectionMode {
1602 First,
1603 All,
1604}
1605
1606#[derive(Clone, Copy, Debug, Default)]
1607pub struct RewrapOptions {
1608 pub override_language_settings: bool,
1609 pub preserve_existing_whitespace: bool,
1610}
1611
1612impl Editor {
1613 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1614 let buffer = cx.new(|cx| Buffer::local("", cx));
1615 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1616 Self::new(
1617 EditorMode::SingleLine { auto_width: false },
1618 buffer,
1619 None,
1620 window,
1621 cx,
1622 )
1623 }
1624
1625 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1626 let buffer = cx.new(|cx| Buffer::local("", cx));
1627 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1628 Self::new(EditorMode::full(), buffer, None, window, cx)
1629 }
1630
1631 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1632 let buffer = cx.new(|cx| Buffer::local("", cx));
1633 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1634 Self::new(
1635 EditorMode::SingleLine { auto_width: true },
1636 buffer,
1637 None,
1638 window,
1639 cx,
1640 )
1641 }
1642
1643 pub fn auto_height(
1644 min_lines: usize,
1645 max_lines: usize,
1646 window: &mut Window,
1647 cx: &mut Context<Self>,
1648 ) -> Self {
1649 let buffer = cx.new(|cx| Buffer::local("", cx));
1650 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1651 Self::new(
1652 EditorMode::AutoHeight {
1653 min_lines,
1654 max_lines: Some(max_lines),
1655 },
1656 buffer,
1657 None,
1658 window,
1659 cx,
1660 )
1661 }
1662
1663 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1664 /// The editor grows as tall as needed to fit its content.
1665 pub fn auto_height_unbounded(
1666 min_lines: usize,
1667 window: &mut Window,
1668 cx: &mut Context<Self>,
1669 ) -> Self {
1670 let buffer = cx.new(|cx| Buffer::local("", cx));
1671 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1672 Self::new(
1673 EditorMode::AutoHeight {
1674 min_lines,
1675 max_lines: None,
1676 },
1677 buffer,
1678 None,
1679 window,
1680 cx,
1681 )
1682 }
1683
1684 pub fn for_buffer(
1685 buffer: Entity<Buffer>,
1686 project: Option<Entity<Project>>,
1687 window: &mut Window,
1688 cx: &mut Context<Self>,
1689 ) -> Self {
1690 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1691 Self::new(EditorMode::full(), buffer, project, window, cx)
1692 }
1693
1694 pub fn for_multibuffer(
1695 buffer: Entity<MultiBuffer>,
1696 project: Option<Entity<Project>>,
1697 window: &mut Window,
1698 cx: &mut Context<Self>,
1699 ) -> Self {
1700 Self::new(EditorMode::full(), buffer, project, window, cx)
1701 }
1702
1703 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1704 let mut clone = Self::new(
1705 self.mode.clone(),
1706 self.buffer.clone(),
1707 self.project.clone(),
1708 window,
1709 cx,
1710 );
1711 self.display_map.update(cx, |display_map, cx| {
1712 let snapshot = display_map.snapshot(cx);
1713 clone.display_map.update(cx, |display_map, cx| {
1714 display_map.set_state(&snapshot, cx);
1715 });
1716 });
1717 clone.folds_did_change(cx);
1718 clone.selections.clone_state(&self.selections);
1719 clone.scroll_manager.clone_state(&self.scroll_manager);
1720 clone.searchable = self.searchable;
1721 clone.read_only = self.read_only;
1722 clone
1723 }
1724
1725 pub fn new(
1726 mode: EditorMode,
1727 buffer: Entity<MultiBuffer>,
1728 project: Option<Entity<Project>>,
1729 window: &mut Window,
1730 cx: &mut Context<Self>,
1731 ) -> Self {
1732 Editor::new_internal(mode, buffer, project, None, window, cx)
1733 }
1734
1735 fn new_internal(
1736 mode: EditorMode,
1737 buffer: Entity<MultiBuffer>,
1738 project: Option<Entity<Project>>,
1739 display_map: Option<Entity<DisplayMap>>,
1740 window: &mut Window,
1741 cx: &mut Context<Self>,
1742 ) -> Self {
1743 debug_assert!(
1744 display_map.is_none() || mode.is_minimap(),
1745 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1746 );
1747
1748 let full_mode = mode.is_full();
1749 let diagnostics_max_severity = if full_mode {
1750 EditorSettings::get_global(cx)
1751 .diagnostics_max_severity
1752 .unwrap_or(DiagnosticSeverity::Hint)
1753 } else {
1754 DiagnosticSeverity::Off
1755 };
1756 let style = window.text_style();
1757 let font_size = style.font_size.to_pixels(window.rem_size());
1758 let editor = cx.entity().downgrade();
1759 let fold_placeholder = FoldPlaceholder {
1760 constrain_width: true,
1761 render: Arc::new(move |fold_id, fold_range, cx| {
1762 let editor = editor.clone();
1763 div()
1764 .id(fold_id)
1765 .bg(cx.theme().colors().ghost_element_background)
1766 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1767 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1768 .rounded_xs()
1769 .size_full()
1770 .cursor_pointer()
1771 .child("⋯")
1772 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1773 .on_click(move |_, _window, cx| {
1774 editor
1775 .update(cx, |editor, cx| {
1776 editor.unfold_ranges(
1777 &[fold_range.start..fold_range.end],
1778 true,
1779 false,
1780 cx,
1781 );
1782 cx.stop_propagation();
1783 })
1784 .ok();
1785 })
1786 .into_any()
1787 }),
1788 merge_adjacent: true,
1789 ..FoldPlaceholder::default()
1790 };
1791 let display_map = display_map.unwrap_or_else(|| {
1792 cx.new(|cx| {
1793 DisplayMap::new(
1794 buffer.clone(),
1795 style.font(),
1796 font_size,
1797 None,
1798 FILE_HEADER_HEIGHT,
1799 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1800 fold_placeholder,
1801 diagnostics_max_severity,
1802 cx,
1803 )
1804 })
1805 });
1806
1807 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1808
1809 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1810
1811 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1812 .then(|| language_settings::SoftWrap::None);
1813
1814 let mut project_subscriptions = Vec::new();
1815 if mode.is_full() {
1816 if let Some(project) = project.as_ref() {
1817 project_subscriptions.push(cx.subscribe_in(
1818 project,
1819 window,
1820 |editor, _, event, window, cx| match event {
1821 project::Event::RefreshCodeLens => {
1822 // we always query lens with actions, without storing them, always refreshing them
1823 }
1824 project::Event::RefreshInlayHints => {
1825 editor
1826 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1827 }
1828 project::Event::LanguageServerAdded(server_id, ..)
1829 | project::Event::LanguageServerRemoved(server_id) => {
1830 if editor.tasks_update_task.is_none() {
1831 editor.tasks_update_task =
1832 Some(editor.refresh_runnables(window, cx));
1833 }
1834 editor.update_lsp_data(Some(*server_id), None, window, cx);
1835 }
1836 project::Event::SnippetEdit(id, snippet_edits) => {
1837 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1838 let focus_handle = editor.focus_handle(cx);
1839 if focus_handle.is_focused(window) {
1840 let snapshot = buffer.read(cx).snapshot();
1841 for (range, snippet) in snippet_edits {
1842 let editor_range =
1843 language::range_from_lsp(*range).to_offset(&snapshot);
1844 editor
1845 .insert_snippet(
1846 &[editor_range],
1847 snippet.clone(),
1848 window,
1849 cx,
1850 )
1851 .ok();
1852 }
1853 }
1854 }
1855 }
1856 _ => {}
1857 },
1858 ));
1859 if let Some(task_inventory) = project
1860 .read(cx)
1861 .task_store()
1862 .read(cx)
1863 .task_inventory()
1864 .cloned()
1865 {
1866 project_subscriptions.push(cx.observe_in(
1867 &task_inventory,
1868 window,
1869 |editor, _, window, cx| {
1870 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1871 },
1872 ));
1873 };
1874
1875 project_subscriptions.push(cx.subscribe_in(
1876 &project.read(cx).breakpoint_store(),
1877 window,
1878 |editor, _, event, window, cx| match event {
1879 BreakpointStoreEvent::ClearDebugLines => {
1880 editor.clear_row_highlights::<ActiveDebugLine>();
1881 editor.refresh_inline_values(cx);
1882 }
1883 BreakpointStoreEvent::SetDebugLine => {
1884 if editor.go_to_active_debug_line(window, cx) {
1885 cx.stop_propagation();
1886 }
1887
1888 editor.refresh_inline_values(cx);
1889 }
1890 _ => {}
1891 },
1892 ));
1893 let git_store = project.read(cx).git_store().clone();
1894 let project = project.clone();
1895 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1896 match event {
1897 GitStoreEvent::RepositoryUpdated(
1898 _,
1899 RepositoryEvent::Updated {
1900 new_instance: true, ..
1901 },
1902 _,
1903 ) => {
1904 this.load_diff_task = Some(
1905 update_uncommitted_diff_for_buffer(
1906 cx.entity(),
1907 &project,
1908 this.buffer.read(cx).all_buffers(),
1909 this.buffer.clone(),
1910 cx,
1911 )
1912 .shared(),
1913 );
1914 }
1915 _ => {}
1916 }
1917 }));
1918 }
1919 }
1920
1921 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1922
1923 let inlay_hint_settings =
1924 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1925 let focus_handle = cx.focus_handle();
1926 cx.on_focus(&focus_handle, window, Self::handle_focus)
1927 .detach();
1928 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1929 .detach();
1930 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1931 .detach();
1932 cx.on_blur(&focus_handle, window, Self::handle_blur)
1933 .detach();
1934 cx.observe_pending_input(window, Self::observe_pending_input)
1935 .detach();
1936
1937 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1938 Some(false)
1939 } else {
1940 None
1941 };
1942
1943 let breakpoint_store = match (&mode, project.as_ref()) {
1944 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1945 _ => None,
1946 };
1947
1948 let mut code_action_providers = Vec::new();
1949 let mut load_uncommitted_diff = None;
1950 if let Some(project) = project.clone() {
1951 load_uncommitted_diff = Some(
1952 update_uncommitted_diff_for_buffer(
1953 cx.entity(),
1954 &project,
1955 buffer.read(cx).all_buffers(),
1956 buffer.clone(),
1957 cx,
1958 )
1959 .shared(),
1960 );
1961 code_action_providers.push(Rc::new(project) as Rc<_>);
1962 }
1963
1964 let mut editor = Self {
1965 focus_handle,
1966 show_cursor_when_unfocused: false,
1967 last_focused_descendant: None,
1968 buffer: buffer.clone(),
1969 display_map: display_map.clone(),
1970 selections,
1971 scroll_manager: ScrollManager::new(cx),
1972 columnar_selection_state: None,
1973 add_selections_state: None,
1974 select_next_state: None,
1975 select_prev_state: None,
1976 selection_history: SelectionHistory::default(),
1977 defer_selection_effects: false,
1978 deferred_selection_effects_state: None,
1979 autoclose_regions: Vec::new(),
1980 snippet_stack: InvalidationStack::default(),
1981 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1982 ime_transaction: None,
1983 active_diagnostics: ActiveDiagnostic::None,
1984 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1985 inline_diagnostics_update: Task::ready(()),
1986 inline_diagnostics: Vec::new(),
1987 soft_wrap_mode_override,
1988 diagnostics_max_severity,
1989 hard_wrap: None,
1990 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1991 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1992 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1993 project,
1994 blink_manager: blink_manager.clone(),
1995 show_local_selections: true,
1996 show_scrollbars: ScrollbarAxes {
1997 horizontal: full_mode,
1998 vertical: full_mode,
1999 },
2000 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2001 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2002 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2003 show_gutter: mode.is_full(),
2004 show_line_numbers: None,
2005 use_relative_line_numbers: None,
2006 disable_expand_excerpt_buttons: false,
2007 show_git_diff_gutter: None,
2008 show_code_actions: None,
2009 show_runnables: None,
2010 show_breakpoints: None,
2011 show_wrap_guides: None,
2012 show_indent_guides,
2013 placeholder_text: None,
2014 highlight_order: 0,
2015 highlighted_rows: HashMap::default(),
2016 background_highlights: TreeMap::default(),
2017 gutter_highlights: TreeMap::default(),
2018 scrollbar_marker_state: ScrollbarMarkerState::default(),
2019 active_indent_guides_state: ActiveIndentGuidesState::default(),
2020 nav_history: None,
2021 context_menu: RefCell::new(None),
2022 context_menu_options: None,
2023 mouse_context_menu: None,
2024 completion_tasks: Vec::new(),
2025 inline_blame_popover: None,
2026 inline_blame_popover_show_task: None,
2027 signature_help_state: SignatureHelpState::default(),
2028 auto_signature_help: None,
2029 find_all_references_task_sources: Vec::new(),
2030 next_completion_id: 0,
2031 next_inlay_id: 0,
2032 code_action_providers,
2033 available_code_actions: None,
2034 code_actions_task: None,
2035 quick_selection_highlight_task: None,
2036 debounced_selection_highlight_task: None,
2037 document_highlights_task: None,
2038 linked_editing_range_task: None,
2039 pending_rename: None,
2040 searchable: true,
2041 cursor_shape: EditorSettings::get_global(cx)
2042 .cursor_shape
2043 .unwrap_or_default(),
2044 current_line_highlight: None,
2045 autoindent_mode: Some(AutoindentMode::EachLine),
2046 collapse_matches: false,
2047 workspace: None,
2048 input_enabled: true,
2049 use_modal_editing: mode.is_full(),
2050 read_only: mode.is_minimap(),
2051 use_autoclose: true,
2052 use_auto_surround: true,
2053 auto_replace_emoji_shortcode: false,
2054 jsx_tag_auto_close_enabled_in_any_buffer: false,
2055 leader_id: None,
2056 remote_id: None,
2057 hover_state: HoverState::default(),
2058 pending_mouse_down: None,
2059 hovered_link_state: None,
2060 edit_prediction_provider: None,
2061 active_inline_completion: None,
2062 stale_inline_completion_in_menu: None,
2063 edit_prediction_preview: EditPredictionPreview::Inactive {
2064 released_too_fast: false,
2065 },
2066 inline_diagnostics_enabled: mode.is_full(),
2067 diagnostics_enabled: mode.is_full(),
2068 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2069 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2070
2071 gutter_hovered: false,
2072 pixel_position_of_newest_cursor: None,
2073 last_bounds: None,
2074 last_position_map: None,
2075 expect_bounds_change: None,
2076 gutter_dimensions: GutterDimensions::default(),
2077 style: None,
2078 show_cursor_names: false,
2079 hovered_cursors: HashMap::default(),
2080 next_editor_action_id: EditorActionId::default(),
2081 editor_actions: Rc::default(),
2082 inline_completions_hidden_for_vim_mode: false,
2083 show_inline_completions_override: None,
2084 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2085 edit_prediction_settings: EditPredictionSettings::Disabled,
2086 edit_prediction_indent_conflict: false,
2087 edit_prediction_requires_modifier_in_indent_conflict: true,
2088 custom_context_menu: None,
2089 show_git_blame_gutter: false,
2090 show_git_blame_inline: false,
2091 show_selection_menu: None,
2092 show_git_blame_inline_delay_task: None,
2093 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2094 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2095 serialize_dirty_buffers: !mode.is_minimap()
2096 && ProjectSettings::get_global(cx)
2097 .session
2098 .restore_unsaved_buffers,
2099 blame: None,
2100 blame_subscription: None,
2101 tasks: BTreeMap::default(),
2102
2103 breakpoint_store,
2104 gutter_breakpoint_indicator: (None, None),
2105 hovered_diff_hunk_row: None,
2106 _subscriptions: vec![
2107 cx.observe(&buffer, Self::on_buffer_changed),
2108 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2109 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2110 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2111 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2112 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2113 cx.observe_window_activation(window, |editor, window, cx| {
2114 let active = window.is_window_active();
2115 editor.blink_manager.update(cx, |blink_manager, cx| {
2116 if active {
2117 blink_manager.enable(cx);
2118 } else {
2119 blink_manager.disable(cx);
2120 }
2121 });
2122 if active {
2123 editor.show_mouse_cursor(cx);
2124 }
2125 }),
2126 ],
2127 tasks_update_task: None,
2128 pull_diagnostics_task: Task::ready(()),
2129 colors: None,
2130 next_color_inlay_id: 0,
2131 linked_edit_ranges: Default::default(),
2132 in_project_search: false,
2133 previous_search_ranges: None,
2134 breadcrumb_header: None,
2135 focused_block: None,
2136 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2137 addons: HashMap::default(),
2138 registered_buffers: HashMap::default(),
2139 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2140 selection_mark_mode: false,
2141 toggle_fold_multiple_buffers: Task::ready(()),
2142 serialize_selections: Task::ready(()),
2143 serialize_folds: Task::ready(()),
2144 text_style_refinement: None,
2145 load_diff_task: load_uncommitted_diff,
2146 temporary_diff_override: false,
2147 mouse_cursor_hidden: false,
2148 minimap: None,
2149 hide_mouse_mode: EditorSettings::get_global(cx)
2150 .hide_mouse
2151 .unwrap_or_default(),
2152 change_list: ChangeList::new(),
2153 mode,
2154 selection_drag_state: SelectionDragState::None,
2155 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2156 };
2157 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2158 editor
2159 ._subscriptions
2160 .push(cx.observe(breakpoints, |_, _, cx| {
2161 cx.notify();
2162 }));
2163 }
2164 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2165 editor._subscriptions.extend(project_subscriptions);
2166
2167 editor._subscriptions.push(cx.subscribe_in(
2168 &cx.entity(),
2169 window,
2170 |editor, _, e: &EditorEvent, window, cx| match e {
2171 EditorEvent::ScrollPositionChanged { local, .. } => {
2172 if *local {
2173 let new_anchor = editor.scroll_manager.anchor();
2174 let snapshot = editor.snapshot(window, cx);
2175 editor.update_restoration_data(cx, move |data| {
2176 data.scroll_position = (
2177 new_anchor.top_row(&snapshot.buffer_snapshot),
2178 new_anchor.offset,
2179 );
2180 });
2181 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2182 editor.inline_blame_popover.take();
2183 }
2184 }
2185 EditorEvent::Edited { .. } => {
2186 if !vim_enabled(cx) {
2187 let (map, selections) = editor.selections.all_adjusted_display(cx);
2188 let pop_state = editor
2189 .change_list
2190 .last()
2191 .map(|previous| {
2192 previous.len() == selections.len()
2193 && previous.iter().enumerate().all(|(ix, p)| {
2194 p.to_display_point(&map).row()
2195 == selections[ix].head().row()
2196 })
2197 })
2198 .unwrap_or(false);
2199 let new_positions = selections
2200 .into_iter()
2201 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2202 .collect();
2203 editor
2204 .change_list
2205 .push_to_change_list(pop_state, new_positions);
2206 }
2207 }
2208 _ => (),
2209 },
2210 ));
2211
2212 if let Some(dap_store) = editor
2213 .project
2214 .as_ref()
2215 .map(|project| project.read(cx).dap_store())
2216 {
2217 let weak_editor = cx.weak_entity();
2218
2219 editor
2220 ._subscriptions
2221 .push(
2222 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2223 let session_entity = cx.entity();
2224 weak_editor
2225 .update(cx, |editor, cx| {
2226 editor._subscriptions.push(
2227 cx.subscribe(&session_entity, Self::on_debug_session_event),
2228 );
2229 })
2230 .ok();
2231 }),
2232 );
2233
2234 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2235 editor
2236 ._subscriptions
2237 .push(cx.subscribe(&session, Self::on_debug_session_event));
2238 }
2239 }
2240
2241 // skip adding the initial selection to selection history
2242 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2243 editor.end_selection(window, cx);
2244 editor.selection_history.mode = SelectionHistoryMode::Normal;
2245
2246 editor.scroll_manager.show_scrollbars(window, cx);
2247 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2248
2249 if full_mode {
2250 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2251 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2252
2253 if editor.git_blame_inline_enabled {
2254 editor.start_git_blame_inline(false, window, cx);
2255 }
2256
2257 editor.go_to_active_debug_line(window, cx);
2258
2259 if let Some(buffer) = buffer.read(cx).as_singleton() {
2260 if let Some(project) = editor.project.as_ref() {
2261 let handle = project.update(cx, |project, cx| {
2262 project.register_buffer_with_language_servers(&buffer, cx)
2263 });
2264 editor
2265 .registered_buffers
2266 .insert(buffer.read(cx).remote_id(), handle);
2267 }
2268 }
2269
2270 editor.minimap =
2271 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2272 editor.colors = Some(LspColorData::new(cx));
2273 editor.update_lsp_data(None, None, window, cx);
2274 }
2275
2276 editor.report_editor_event("Editor Opened", None, cx);
2277 editor
2278 }
2279
2280 pub fn deploy_mouse_context_menu(
2281 &mut self,
2282 position: gpui::Point<Pixels>,
2283 context_menu: Entity<ContextMenu>,
2284 window: &mut Window,
2285 cx: &mut Context<Self>,
2286 ) {
2287 self.mouse_context_menu = Some(MouseContextMenu::new(
2288 self,
2289 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2290 context_menu,
2291 window,
2292 cx,
2293 ));
2294 }
2295
2296 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2297 self.mouse_context_menu
2298 .as_ref()
2299 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2300 }
2301
2302 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2303 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2304 }
2305
2306 fn key_context_internal(
2307 &self,
2308 has_active_edit_prediction: bool,
2309 window: &Window,
2310 cx: &App,
2311 ) -> KeyContext {
2312 let mut key_context = KeyContext::new_with_defaults();
2313 key_context.add("Editor");
2314 let mode = match self.mode {
2315 EditorMode::SingleLine { .. } => "single_line",
2316 EditorMode::AutoHeight { .. } => "auto_height",
2317 EditorMode::Minimap { .. } => "minimap",
2318 EditorMode::Full { .. } => "full",
2319 };
2320
2321 if EditorSettings::jupyter_enabled(cx) {
2322 key_context.add("jupyter");
2323 }
2324
2325 key_context.set("mode", mode);
2326 if self.pending_rename.is_some() {
2327 key_context.add("renaming");
2328 }
2329
2330 match self.context_menu.borrow().as_ref() {
2331 Some(CodeContextMenu::Completions(_)) => {
2332 key_context.add("menu");
2333 key_context.add("showing_completions");
2334 }
2335 Some(CodeContextMenu::CodeActions(_)) => {
2336 key_context.add("menu");
2337 key_context.add("showing_code_actions")
2338 }
2339 None => {}
2340 }
2341
2342 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2343 if !self.focus_handle(cx).contains_focused(window, cx)
2344 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2345 {
2346 for addon in self.addons.values() {
2347 addon.extend_key_context(&mut key_context, cx)
2348 }
2349 }
2350
2351 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2352 if let Some(extension) = singleton_buffer
2353 .read(cx)
2354 .file()
2355 .and_then(|file| file.path().extension()?.to_str())
2356 {
2357 key_context.set("extension", extension.to_string());
2358 }
2359 } else {
2360 key_context.add("multibuffer");
2361 }
2362
2363 if has_active_edit_prediction {
2364 if self.edit_prediction_in_conflict() {
2365 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2366 } else {
2367 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2368 key_context.add("copilot_suggestion");
2369 }
2370 }
2371
2372 if self.selection_mark_mode {
2373 key_context.add("selection_mode");
2374 }
2375
2376 key_context
2377 }
2378
2379 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2380 if self.mouse_cursor_hidden {
2381 self.mouse_cursor_hidden = false;
2382 cx.notify();
2383 }
2384 }
2385
2386 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2387 let hide_mouse_cursor = match origin {
2388 HideMouseCursorOrigin::TypingAction => {
2389 matches!(
2390 self.hide_mouse_mode,
2391 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2392 )
2393 }
2394 HideMouseCursorOrigin::MovementAction => {
2395 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2396 }
2397 };
2398 if self.mouse_cursor_hidden != hide_mouse_cursor {
2399 self.mouse_cursor_hidden = hide_mouse_cursor;
2400 cx.notify();
2401 }
2402 }
2403
2404 pub fn edit_prediction_in_conflict(&self) -> bool {
2405 if !self.show_edit_predictions_in_menu() {
2406 return false;
2407 }
2408
2409 let showing_completions = self
2410 .context_menu
2411 .borrow()
2412 .as_ref()
2413 .map_or(false, |context| {
2414 matches!(context, CodeContextMenu::Completions(_))
2415 });
2416
2417 showing_completions
2418 || self.edit_prediction_requires_modifier()
2419 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2420 // bindings to insert tab characters.
2421 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2422 }
2423
2424 pub fn accept_edit_prediction_keybind(
2425 &self,
2426 accept_partial: bool,
2427 window: &Window,
2428 cx: &App,
2429 ) -> AcceptEditPredictionBinding {
2430 let key_context = self.key_context_internal(true, window, cx);
2431 let in_conflict = self.edit_prediction_in_conflict();
2432
2433 let bindings = if accept_partial {
2434 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2435 } else {
2436 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2437 };
2438
2439 // TODO: if the binding contains multiple keystrokes, display all of them, not
2440 // just the first one.
2441 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2442 !in_conflict
2443 || binding
2444 .keystrokes()
2445 .first()
2446 .map_or(false, |keystroke| keystroke.modifiers.modified())
2447 }))
2448 }
2449
2450 pub fn new_file(
2451 workspace: &mut Workspace,
2452 _: &workspace::NewFile,
2453 window: &mut Window,
2454 cx: &mut Context<Workspace>,
2455 ) {
2456 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2457 "Failed to create buffer",
2458 window,
2459 cx,
2460 |e, _, _| match e.error_code() {
2461 ErrorCode::RemoteUpgradeRequired => Some(format!(
2462 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2463 e.error_tag("required").unwrap_or("the latest version")
2464 )),
2465 _ => None,
2466 },
2467 );
2468 }
2469
2470 pub fn new_in_workspace(
2471 workspace: &mut Workspace,
2472 window: &mut Window,
2473 cx: &mut Context<Workspace>,
2474 ) -> Task<Result<Entity<Editor>>> {
2475 let project = workspace.project().clone();
2476 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2477
2478 cx.spawn_in(window, async move |workspace, cx| {
2479 let buffer = create.await?;
2480 workspace.update_in(cx, |workspace, window, cx| {
2481 let editor =
2482 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2483 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2484 editor
2485 })
2486 })
2487 }
2488
2489 fn new_file_vertical(
2490 workspace: &mut Workspace,
2491 _: &workspace::NewFileSplitVertical,
2492 window: &mut Window,
2493 cx: &mut Context<Workspace>,
2494 ) {
2495 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2496 }
2497
2498 fn new_file_horizontal(
2499 workspace: &mut Workspace,
2500 _: &workspace::NewFileSplitHorizontal,
2501 window: &mut Window,
2502 cx: &mut Context<Workspace>,
2503 ) {
2504 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2505 }
2506
2507 fn new_file_in_direction(
2508 workspace: &mut Workspace,
2509 direction: SplitDirection,
2510 window: &mut Window,
2511 cx: &mut Context<Workspace>,
2512 ) {
2513 let project = workspace.project().clone();
2514 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2515
2516 cx.spawn_in(window, async move |workspace, cx| {
2517 let buffer = create.await?;
2518 workspace.update_in(cx, move |workspace, window, cx| {
2519 workspace.split_item(
2520 direction,
2521 Box::new(
2522 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2523 ),
2524 window,
2525 cx,
2526 )
2527 })?;
2528 anyhow::Ok(())
2529 })
2530 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2531 match e.error_code() {
2532 ErrorCode::RemoteUpgradeRequired => Some(format!(
2533 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2534 e.error_tag("required").unwrap_or("the latest version")
2535 )),
2536 _ => None,
2537 }
2538 });
2539 }
2540
2541 pub fn leader_id(&self) -> Option<CollaboratorId> {
2542 self.leader_id
2543 }
2544
2545 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2546 &self.buffer
2547 }
2548
2549 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2550 self.workspace.as_ref()?.0.upgrade()
2551 }
2552
2553 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2554 self.buffer().read(cx).title(cx)
2555 }
2556
2557 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2558 let git_blame_gutter_max_author_length = self
2559 .render_git_blame_gutter(cx)
2560 .then(|| {
2561 if let Some(blame) = self.blame.as_ref() {
2562 let max_author_length =
2563 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2564 Some(max_author_length)
2565 } else {
2566 None
2567 }
2568 })
2569 .flatten();
2570
2571 EditorSnapshot {
2572 mode: self.mode.clone(),
2573 show_gutter: self.show_gutter,
2574 show_line_numbers: self.show_line_numbers,
2575 show_git_diff_gutter: self.show_git_diff_gutter,
2576 show_code_actions: self.show_code_actions,
2577 show_runnables: self.show_runnables,
2578 show_breakpoints: self.show_breakpoints,
2579 git_blame_gutter_max_author_length,
2580 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2581 scroll_anchor: self.scroll_manager.anchor(),
2582 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2583 placeholder_text: self.placeholder_text.clone(),
2584 is_focused: self.focus_handle.is_focused(window),
2585 current_line_highlight: self
2586 .current_line_highlight
2587 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2588 gutter_hovered: self.gutter_hovered,
2589 }
2590 }
2591
2592 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2593 self.buffer.read(cx).language_at(point, cx)
2594 }
2595
2596 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2597 self.buffer.read(cx).read(cx).file_at(point).cloned()
2598 }
2599
2600 pub fn active_excerpt(
2601 &self,
2602 cx: &App,
2603 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2604 self.buffer
2605 .read(cx)
2606 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2607 }
2608
2609 pub fn mode(&self) -> &EditorMode {
2610 &self.mode
2611 }
2612
2613 pub fn set_mode(&mut self, mode: EditorMode) {
2614 self.mode = mode;
2615 }
2616
2617 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2618 self.collaboration_hub.as_deref()
2619 }
2620
2621 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2622 self.collaboration_hub = Some(hub);
2623 }
2624
2625 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2626 self.in_project_search = in_project_search;
2627 }
2628
2629 pub fn set_custom_context_menu(
2630 &mut self,
2631 f: impl 'static
2632 + Fn(
2633 &mut Self,
2634 DisplayPoint,
2635 &mut Window,
2636 &mut Context<Self>,
2637 ) -> Option<Entity<ui::ContextMenu>>,
2638 ) {
2639 self.custom_context_menu = Some(Box::new(f))
2640 }
2641
2642 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2643 self.completion_provider = provider;
2644 }
2645
2646 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2647 self.semantics_provider.clone()
2648 }
2649
2650 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2651 self.semantics_provider = provider;
2652 }
2653
2654 pub fn set_edit_prediction_provider<T>(
2655 &mut self,
2656 provider: Option<Entity<T>>,
2657 window: &mut Window,
2658 cx: &mut Context<Self>,
2659 ) where
2660 T: EditPredictionProvider,
2661 {
2662 self.edit_prediction_provider =
2663 provider.map(|provider| RegisteredInlineCompletionProvider {
2664 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2665 if this.focus_handle.is_focused(window) {
2666 this.update_visible_inline_completion(window, cx);
2667 }
2668 }),
2669 provider: Arc::new(provider),
2670 });
2671 self.update_edit_prediction_settings(cx);
2672 self.refresh_inline_completion(false, false, window, cx);
2673 }
2674
2675 pub fn placeholder_text(&self) -> Option<&str> {
2676 self.placeholder_text.as_deref()
2677 }
2678
2679 pub fn set_placeholder_text(
2680 &mut self,
2681 placeholder_text: impl Into<Arc<str>>,
2682 cx: &mut Context<Self>,
2683 ) {
2684 let placeholder_text = Some(placeholder_text.into());
2685 if self.placeholder_text != placeholder_text {
2686 self.placeholder_text = placeholder_text;
2687 cx.notify();
2688 }
2689 }
2690
2691 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2692 self.cursor_shape = cursor_shape;
2693
2694 // Disrupt blink for immediate user feedback that the cursor shape has changed
2695 self.blink_manager.update(cx, BlinkManager::show_cursor);
2696
2697 cx.notify();
2698 }
2699
2700 pub fn set_current_line_highlight(
2701 &mut self,
2702 current_line_highlight: Option<CurrentLineHighlight>,
2703 ) {
2704 self.current_line_highlight = current_line_highlight;
2705 }
2706
2707 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2708 self.collapse_matches = collapse_matches;
2709 }
2710
2711 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2712 let buffers = self.buffer.read(cx).all_buffers();
2713 let Some(project) = self.project.as_ref() else {
2714 return;
2715 };
2716 project.update(cx, |project, cx| {
2717 for buffer in buffers {
2718 self.registered_buffers
2719 .entry(buffer.read(cx).remote_id())
2720 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2721 }
2722 })
2723 }
2724
2725 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2726 if self.collapse_matches {
2727 return range.start..range.start;
2728 }
2729 range.clone()
2730 }
2731
2732 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2733 if self.display_map.read(cx).clip_at_line_ends != clip {
2734 self.display_map
2735 .update(cx, |map, _| map.clip_at_line_ends = clip);
2736 }
2737 }
2738
2739 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2740 self.input_enabled = input_enabled;
2741 }
2742
2743 pub fn set_inline_completions_hidden_for_vim_mode(
2744 &mut self,
2745 hidden: bool,
2746 window: &mut Window,
2747 cx: &mut Context<Self>,
2748 ) {
2749 if hidden != self.inline_completions_hidden_for_vim_mode {
2750 self.inline_completions_hidden_for_vim_mode = hidden;
2751 if hidden {
2752 self.update_visible_inline_completion(window, cx);
2753 } else {
2754 self.refresh_inline_completion(true, false, window, cx);
2755 }
2756 }
2757 }
2758
2759 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2760 self.menu_inline_completions_policy = value;
2761 }
2762
2763 pub fn set_autoindent(&mut self, autoindent: bool) {
2764 if autoindent {
2765 self.autoindent_mode = Some(AutoindentMode::EachLine);
2766 } else {
2767 self.autoindent_mode = None;
2768 }
2769 }
2770
2771 pub fn read_only(&self, cx: &App) -> bool {
2772 self.read_only || self.buffer.read(cx).read_only()
2773 }
2774
2775 pub fn set_read_only(&mut self, read_only: bool) {
2776 self.read_only = read_only;
2777 }
2778
2779 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2780 self.use_autoclose = autoclose;
2781 }
2782
2783 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2784 self.use_auto_surround = auto_surround;
2785 }
2786
2787 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2788 self.auto_replace_emoji_shortcode = auto_replace;
2789 }
2790
2791 pub fn toggle_edit_predictions(
2792 &mut self,
2793 _: &ToggleEditPrediction,
2794 window: &mut Window,
2795 cx: &mut Context<Self>,
2796 ) {
2797 if self.show_inline_completions_override.is_some() {
2798 self.set_show_edit_predictions(None, window, cx);
2799 } else {
2800 let show_edit_predictions = !self.edit_predictions_enabled();
2801 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2802 }
2803 }
2804
2805 pub fn set_show_edit_predictions(
2806 &mut self,
2807 show_edit_predictions: Option<bool>,
2808 window: &mut Window,
2809 cx: &mut Context<Self>,
2810 ) {
2811 self.show_inline_completions_override = show_edit_predictions;
2812 self.update_edit_prediction_settings(cx);
2813
2814 if let Some(false) = show_edit_predictions {
2815 self.discard_inline_completion(false, cx);
2816 } else {
2817 self.refresh_inline_completion(false, true, window, cx);
2818 }
2819 }
2820
2821 fn inline_completions_disabled_in_scope(
2822 &self,
2823 buffer: &Entity<Buffer>,
2824 buffer_position: language::Anchor,
2825 cx: &App,
2826 ) -> bool {
2827 let snapshot = buffer.read(cx).snapshot();
2828 let settings = snapshot.settings_at(buffer_position, cx);
2829
2830 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2831 return false;
2832 };
2833
2834 scope.override_name().map_or(false, |scope_name| {
2835 settings
2836 .edit_predictions_disabled_in
2837 .iter()
2838 .any(|s| s == scope_name)
2839 })
2840 }
2841
2842 pub fn set_use_modal_editing(&mut self, to: bool) {
2843 self.use_modal_editing = to;
2844 }
2845
2846 pub fn use_modal_editing(&self) -> bool {
2847 self.use_modal_editing
2848 }
2849
2850 fn selections_did_change(
2851 &mut self,
2852 local: bool,
2853 old_cursor_position: &Anchor,
2854 effects: SelectionEffects,
2855 window: &mut Window,
2856 cx: &mut Context<Self>,
2857 ) {
2858 window.invalidate_character_coordinates();
2859
2860 // Copy selections to primary selection buffer
2861 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2862 if local {
2863 let selections = self.selections.all::<usize>(cx);
2864 let buffer_handle = self.buffer.read(cx).read(cx);
2865
2866 let mut text = String::new();
2867 for (index, selection) in selections.iter().enumerate() {
2868 let text_for_selection = buffer_handle
2869 .text_for_range(selection.start..selection.end)
2870 .collect::<String>();
2871
2872 text.push_str(&text_for_selection);
2873 if index != selections.len() - 1 {
2874 text.push('\n');
2875 }
2876 }
2877
2878 if !text.is_empty() {
2879 cx.write_to_primary(ClipboardItem::new_string(text));
2880 }
2881 }
2882
2883 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2884 self.buffer.update(cx, |buffer, cx| {
2885 buffer.set_active_selections(
2886 &self.selections.disjoint_anchors(),
2887 self.selections.line_mode,
2888 self.cursor_shape,
2889 cx,
2890 )
2891 });
2892 }
2893 let display_map = self
2894 .display_map
2895 .update(cx, |display_map, cx| display_map.snapshot(cx));
2896 let buffer = &display_map.buffer_snapshot;
2897 if self.selections.count() == 1 {
2898 self.add_selections_state = None;
2899 }
2900 self.select_next_state = None;
2901 self.select_prev_state = None;
2902 self.select_syntax_node_history.try_clear();
2903 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2904 self.snippet_stack
2905 .invalidate(&self.selections.disjoint_anchors(), buffer);
2906 self.take_rename(false, window, cx);
2907
2908 let newest_selection = self.selections.newest_anchor();
2909 let new_cursor_position = newest_selection.head();
2910 let selection_start = newest_selection.start;
2911
2912 if effects.nav_history {
2913 self.push_to_nav_history(
2914 *old_cursor_position,
2915 Some(new_cursor_position.to_point(buffer)),
2916 false,
2917 cx,
2918 );
2919 }
2920
2921 if local {
2922 if let Some(buffer_id) = new_cursor_position.buffer_id {
2923 if !self.registered_buffers.contains_key(&buffer_id) {
2924 if let Some(project) = self.project.as_ref() {
2925 project.update(cx, |project, cx| {
2926 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2927 return;
2928 };
2929 self.registered_buffers.insert(
2930 buffer_id,
2931 project.register_buffer_with_language_servers(&buffer, cx),
2932 );
2933 })
2934 }
2935 }
2936 }
2937
2938 let mut context_menu = self.context_menu.borrow_mut();
2939 let completion_menu = match context_menu.as_ref() {
2940 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2941 Some(CodeContextMenu::CodeActions(_)) => {
2942 *context_menu = None;
2943 None
2944 }
2945 None => None,
2946 };
2947 let completion_position = completion_menu.map(|menu| menu.initial_position);
2948 drop(context_menu);
2949
2950 if effects.completions {
2951 if let Some(completion_position) = completion_position {
2952 let start_offset = selection_start.to_offset(buffer);
2953 let position_matches = start_offset == completion_position.to_offset(buffer);
2954 let continue_showing = if position_matches {
2955 if self.snippet_stack.is_empty() {
2956 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2957 } else {
2958 // Snippet choices can be shown even when the cursor is in whitespace.
2959 // Dismissing the menu with actions like backspace is handled by
2960 // invalidation regions.
2961 true
2962 }
2963 } else {
2964 false
2965 };
2966
2967 if continue_showing {
2968 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2969 } else {
2970 self.hide_context_menu(window, cx);
2971 }
2972 }
2973 }
2974
2975 hide_hover(self, cx);
2976
2977 if old_cursor_position.to_display_point(&display_map).row()
2978 != new_cursor_position.to_display_point(&display_map).row()
2979 {
2980 self.available_code_actions.take();
2981 }
2982 self.refresh_code_actions(window, cx);
2983 self.refresh_document_highlights(cx);
2984 self.refresh_selected_text_highlights(false, window, cx);
2985 refresh_matching_bracket_highlights(self, window, cx);
2986 self.update_visible_inline_completion(window, cx);
2987 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2988 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2989 self.inline_blame_popover.take();
2990 if self.git_blame_inline_enabled {
2991 self.start_inline_blame_timer(window, cx);
2992 }
2993 }
2994
2995 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2996 cx.emit(EditorEvent::SelectionsChanged { local });
2997
2998 let selections = &self.selections.disjoint;
2999 if selections.len() == 1 {
3000 cx.emit(SearchEvent::ActiveMatchChanged)
3001 }
3002 if local {
3003 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3004 let inmemory_selections = selections
3005 .iter()
3006 .map(|s| {
3007 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3008 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3009 })
3010 .collect();
3011 self.update_restoration_data(cx, |data| {
3012 data.selections = inmemory_selections;
3013 });
3014
3015 if WorkspaceSettings::get(None, cx).restore_on_startup
3016 != RestoreOnStartupBehavior::None
3017 {
3018 if let Some(workspace_id) =
3019 self.workspace.as_ref().and_then(|workspace| workspace.1)
3020 {
3021 let snapshot = self.buffer().read(cx).snapshot(cx);
3022 let selections = selections.clone();
3023 let background_executor = cx.background_executor().clone();
3024 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3025 self.serialize_selections = cx.background_spawn(async move {
3026 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3027 let db_selections = selections
3028 .iter()
3029 .map(|selection| {
3030 (
3031 selection.start.to_offset(&snapshot),
3032 selection.end.to_offset(&snapshot),
3033 )
3034 })
3035 .collect();
3036
3037 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3038 .await
3039 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3040 .log_err();
3041 });
3042 }
3043 }
3044 }
3045 }
3046
3047 cx.notify();
3048 }
3049
3050 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3051 use text::ToOffset as _;
3052 use text::ToPoint as _;
3053
3054 if self.mode.is_minimap()
3055 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3056 {
3057 return;
3058 }
3059
3060 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3061 return;
3062 };
3063
3064 let snapshot = singleton.read(cx).snapshot();
3065 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3066 let display_snapshot = display_map.snapshot(cx);
3067
3068 display_snapshot
3069 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3070 .map(|fold| {
3071 fold.range.start.text_anchor.to_point(&snapshot)
3072 ..fold.range.end.text_anchor.to_point(&snapshot)
3073 })
3074 .collect()
3075 });
3076 self.update_restoration_data(cx, |data| {
3077 data.folds = inmemory_folds;
3078 });
3079
3080 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3081 return;
3082 };
3083 let background_executor = cx.background_executor().clone();
3084 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3085 let db_folds = self.display_map.update(cx, |display_map, cx| {
3086 display_map
3087 .snapshot(cx)
3088 .folds_in_range(0..snapshot.len())
3089 .map(|fold| {
3090 (
3091 fold.range.start.text_anchor.to_offset(&snapshot),
3092 fold.range.end.text_anchor.to_offset(&snapshot),
3093 )
3094 })
3095 .collect()
3096 });
3097 self.serialize_folds = cx.background_spawn(async move {
3098 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3099 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3100 .await
3101 .with_context(|| {
3102 format!(
3103 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3104 )
3105 })
3106 .log_err();
3107 });
3108 }
3109
3110 pub fn sync_selections(
3111 &mut self,
3112 other: Entity<Editor>,
3113 cx: &mut Context<Self>,
3114 ) -> gpui::Subscription {
3115 let other_selections = other.read(cx).selections.disjoint.to_vec();
3116 self.selections.change_with(cx, |selections| {
3117 selections.select_anchors(other_selections);
3118 });
3119
3120 let other_subscription =
3121 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3122 EditorEvent::SelectionsChanged { local: true } => {
3123 let other_selections = other.read(cx).selections.disjoint.to_vec();
3124 if other_selections.is_empty() {
3125 return;
3126 }
3127 this.selections.change_with(cx, |selections| {
3128 selections.select_anchors(other_selections);
3129 });
3130 }
3131 _ => {}
3132 });
3133
3134 let this_subscription =
3135 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3136 EditorEvent::SelectionsChanged { local: true } => {
3137 let these_selections = this.selections.disjoint.to_vec();
3138 if these_selections.is_empty() {
3139 return;
3140 }
3141 other.update(cx, |other_editor, cx| {
3142 other_editor.selections.change_with(cx, |selections| {
3143 selections.select_anchors(these_selections);
3144 })
3145 });
3146 }
3147 _ => {}
3148 });
3149
3150 Subscription::join(other_subscription, this_subscription)
3151 }
3152
3153 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3154 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3155 /// effects of selection change occur at the end of the transaction.
3156 pub fn change_selections<R>(
3157 &mut self,
3158 effects: impl Into<SelectionEffects>,
3159 window: &mut Window,
3160 cx: &mut Context<Self>,
3161 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3162 ) -> R {
3163 let effects = effects.into();
3164 if let Some(state) = &mut self.deferred_selection_effects_state {
3165 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3166 state.effects.completions = effects.completions;
3167 state.effects.nav_history |= effects.nav_history;
3168 let (changed, result) = self.selections.change_with(cx, change);
3169 state.changed |= changed;
3170 return result;
3171 }
3172 let mut state = DeferredSelectionEffectsState {
3173 changed: false,
3174 effects,
3175 old_cursor_position: self.selections.newest_anchor().head(),
3176 history_entry: SelectionHistoryEntry {
3177 selections: self.selections.disjoint_anchors(),
3178 select_next_state: self.select_next_state.clone(),
3179 select_prev_state: self.select_prev_state.clone(),
3180 add_selections_state: self.add_selections_state.clone(),
3181 },
3182 };
3183 let (changed, result) = self.selections.change_with(cx, change);
3184 state.changed = state.changed || changed;
3185 if self.defer_selection_effects {
3186 self.deferred_selection_effects_state = Some(state);
3187 } else {
3188 self.apply_selection_effects(state, window, cx);
3189 }
3190 result
3191 }
3192
3193 /// Defers the effects of selection change, so that the effects of multiple calls to
3194 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3195 /// to selection history and the state of popovers based on selection position aren't
3196 /// erroneously updated.
3197 pub fn with_selection_effects_deferred<R>(
3198 &mut self,
3199 window: &mut Window,
3200 cx: &mut Context<Self>,
3201 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3202 ) -> R {
3203 let already_deferred = self.defer_selection_effects;
3204 self.defer_selection_effects = true;
3205 let result = update(self, window, cx);
3206 if !already_deferred {
3207 self.defer_selection_effects = false;
3208 if let Some(state) = self.deferred_selection_effects_state.take() {
3209 self.apply_selection_effects(state, window, cx);
3210 }
3211 }
3212 result
3213 }
3214
3215 fn apply_selection_effects(
3216 &mut self,
3217 state: DeferredSelectionEffectsState,
3218 window: &mut Window,
3219 cx: &mut Context<Self>,
3220 ) {
3221 if state.changed {
3222 self.selection_history.push(state.history_entry);
3223
3224 if let Some(autoscroll) = state.effects.scroll {
3225 self.request_autoscroll(autoscroll, cx);
3226 }
3227
3228 let old_cursor_position = &state.old_cursor_position;
3229
3230 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3231
3232 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3233 self.show_signature_help(&ShowSignatureHelp, window, cx);
3234 }
3235 }
3236 }
3237
3238 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3239 where
3240 I: IntoIterator<Item = (Range<S>, T)>,
3241 S: ToOffset,
3242 T: Into<Arc<str>>,
3243 {
3244 if self.read_only(cx) {
3245 return;
3246 }
3247
3248 self.buffer
3249 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3250 }
3251
3252 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3253 where
3254 I: IntoIterator<Item = (Range<S>, T)>,
3255 S: ToOffset,
3256 T: Into<Arc<str>>,
3257 {
3258 if self.read_only(cx) {
3259 return;
3260 }
3261
3262 self.buffer.update(cx, |buffer, cx| {
3263 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3264 });
3265 }
3266
3267 pub fn edit_with_block_indent<I, S, T>(
3268 &mut self,
3269 edits: I,
3270 original_indent_columns: Vec<Option<u32>>,
3271 cx: &mut Context<Self>,
3272 ) where
3273 I: IntoIterator<Item = (Range<S>, T)>,
3274 S: ToOffset,
3275 T: Into<Arc<str>>,
3276 {
3277 if self.read_only(cx) {
3278 return;
3279 }
3280
3281 self.buffer.update(cx, |buffer, cx| {
3282 buffer.edit(
3283 edits,
3284 Some(AutoindentMode::Block {
3285 original_indent_columns,
3286 }),
3287 cx,
3288 )
3289 });
3290 }
3291
3292 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3293 self.hide_context_menu(window, cx);
3294
3295 match phase {
3296 SelectPhase::Begin {
3297 position,
3298 add,
3299 click_count,
3300 } => self.begin_selection(position, add, click_count, window, cx),
3301 SelectPhase::BeginColumnar {
3302 position,
3303 goal_column,
3304 reset,
3305 mode,
3306 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3307 SelectPhase::Extend {
3308 position,
3309 click_count,
3310 } => self.extend_selection(position, click_count, window, cx),
3311 SelectPhase::Update {
3312 position,
3313 goal_column,
3314 scroll_delta,
3315 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3316 SelectPhase::End => self.end_selection(window, cx),
3317 }
3318 }
3319
3320 fn extend_selection(
3321 &mut self,
3322 position: DisplayPoint,
3323 click_count: usize,
3324 window: &mut Window,
3325 cx: &mut Context<Self>,
3326 ) {
3327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3328 let tail = self.selections.newest::<usize>(cx).tail();
3329 self.begin_selection(position, false, click_count, window, cx);
3330
3331 let position = position.to_offset(&display_map, Bias::Left);
3332 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3333
3334 let mut pending_selection = self
3335 .selections
3336 .pending_anchor()
3337 .expect("extend_selection not called with pending selection");
3338 if position >= tail {
3339 pending_selection.start = tail_anchor;
3340 } else {
3341 pending_selection.end = tail_anchor;
3342 pending_selection.reversed = true;
3343 }
3344
3345 let mut pending_mode = self.selections.pending_mode().unwrap();
3346 match &mut pending_mode {
3347 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3348 _ => {}
3349 }
3350
3351 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3352 SelectionEffects::scroll(Autoscroll::fit())
3353 } else {
3354 SelectionEffects::no_scroll()
3355 };
3356
3357 self.change_selections(effects, window, cx, |s| {
3358 s.set_pending(pending_selection, pending_mode)
3359 });
3360 }
3361
3362 fn begin_selection(
3363 &mut self,
3364 position: DisplayPoint,
3365 add: bool,
3366 click_count: usize,
3367 window: &mut Window,
3368 cx: &mut Context<Self>,
3369 ) {
3370 if !self.focus_handle.is_focused(window) {
3371 self.last_focused_descendant = None;
3372 window.focus(&self.focus_handle);
3373 }
3374
3375 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3376 let buffer = &display_map.buffer_snapshot;
3377 let position = display_map.clip_point(position, Bias::Left);
3378
3379 let start;
3380 let end;
3381 let mode;
3382 let mut auto_scroll;
3383 match click_count {
3384 1 => {
3385 start = buffer.anchor_before(position.to_point(&display_map));
3386 end = start;
3387 mode = SelectMode::Character;
3388 auto_scroll = true;
3389 }
3390 2 => {
3391 let range = movement::surrounding_word(&display_map, position);
3392 start = buffer.anchor_before(range.start.to_point(&display_map));
3393 end = buffer.anchor_before(range.end.to_point(&display_map));
3394 mode = SelectMode::Word(start..end);
3395 auto_scroll = true;
3396 }
3397 3 => {
3398 let position = display_map
3399 .clip_point(position, Bias::Left)
3400 .to_point(&display_map);
3401 let line_start = display_map.prev_line_boundary(position).0;
3402 let next_line_start = buffer.clip_point(
3403 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3404 Bias::Left,
3405 );
3406 start = buffer.anchor_before(line_start);
3407 end = buffer.anchor_before(next_line_start);
3408 mode = SelectMode::Line(start..end);
3409 auto_scroll = true;
3410 }
3411 _ => {
3412 start = buffer.anchor_before(0);
3413 end = buffer.anchor_before(buffer.len());
3414 mode = SelectMode::All;
3415 auto_scroll = false;
3416 }
3417 }
3418 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3419
3420 let point_to_delete: Option<usize> = {
3421 let selected_points: Vec<Selection<Point>> =
3422 self.selections.disjoint_in_range(start..end, cx);
3423
3424 if !add || click_count > 1 {
3425 None
3426 } else if !selected_points.is_empty() {
3427 Some(selected_points[0].id)
3428 } else {
3429 let clicked_point_already_selected =
3430 self.selections.disjoint.iter().find(|selection| {
3431 selection.start.to_point(buffer) == start.to_point(buffer)
3432 || selection.end.to_point(buffer) == end.to_point(buffer)
3433 });
3434
3435 clicked_point_already_selected.map(|selection| selection.id)
3436 }
3437 };
3438
3439 let selections_count = self.selections.count();
3440
3441 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3442 if let Some(point_to_delete) = point_to_delete {
3443 s.delete(point_to_delete);
3444
3445 if selections_count == 1 {
3446 s.set_pending_anchor_range(start..end, mode);
3447 }
3448 } else {
3449 if !add {
3450 s.clear_disjoint();
3451 }
3452
3453 s.set_pending_anchor_range(start..end, mode);
3454 }
3455 });
3456 }
3457
3458 fn begin_columnar_selection(
3459 &mut self,
3460 position: DisplayPoint,
3461 goal_column: u32,
3462 reset: bool,
3463 mode: ColumnarMode,
3464 window: &mut Window,
3465 cx: &mut Context<Self>,
3466 ) {
3467 if !self.focus_handle.is_focused(window) {
3468 self.last_focused_descendant = None;
3469 window.focus(&self.focus_handle);
3470 }
3471
3472 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3473
3474 if reset {
3475 let pointer_position = display_map
3476 .buffer_snapshot
3477 .anchor_before(position.to_point(&display_map));
3478
3479 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3480 s.clear_disjoint();
3481 s.set_pending_anchor_range(
3482 pointer_position..pointer_position,
3483 SelectMode::Character,
3484 );
3485 });
3486 };
3487
3488 let tail = self.selections.newest::<Point>(cx).tail();
3489 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3490 self.columnar_selection_state = match mode {
3491 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3492 selection_tail: selection_anchor,
3493 display_point: if reset {
3494 if position.column() != goal_column {
3495 Some(DisplayPoint::new(position.row(), goal_column))
3496 } else {
3497 None
3498 }
3499 } else {
3500 None
3501 },
3502 }),
3503 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3504 selection_tail: selection_anchor,
3505 }),
3506 };
3507
3508 if !reset {
3509 self.select_columns(position, goal_column, &display_map, window, cx);
3510 }
3511 }
3512
3513 fn update_selection(
3514 &mut self,
3515 position: DisplayPoint,
3516 goal_column: u32,
3517 scroll_delta: gpui::Point<f32>,
3518 window: &mut Window,
3519 cx: &mut Context<Self>,
3520 ) {
3521 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3522
3523 if self.columnar_selection_state.is_some() {
3524 self.select_columns(position, goal_column, &display_map, window, cx);
3525 } else if let Some(mut pending) = self.selections.pending_anchor() {
3526 let buffer = self.buffer.read(cx).snapshot(cx);
3527 let head;
3528 let tail;
3529 let mode = self.selections.pending_mode().unwrap();
3530 match &mode {
3531 SelectMode::Character => {
3532 head = position.to_point(&display_map);
3533 tail = pending.tail().to_point(&buffer);
3534 }
3535 SelectMode::Word(original_range) => {
3536 let original_display_range = original_range.start.to_display_point(&display_map)
3537 ..original_range.end.to_display_point(&display_map);
3538 let original_buffer_range = original_display_range.start.to_point(&display_map)
3539 ..original_display_range.end.to_point(&display_map);
3540 if movement::is_inside_word(&display_map, position)
3541 || original_display_range.contains(&position)
3542 {
3543 let word_range = movement::surrounding_word(&display_map, position);
3544 if word_range.start < original_display_range.start {
3545 head = word_range.start.to_point(&display_map);
3546 } else {
3547 head = word_range.end.to_point(&display_map);
3548 }
3549 } else {
3550 head = position.to_point(&display_map);
3551 }
3552
3553 if head <= original_buffer_range.start {
3554 tail = original_buffer_range.end;
3555 } else {
3556 tail = original_buffer_range.start;
3557 }
3558 }
3559 SelectMode::Line(original_range) => {
3560 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3561
3562 let position = display_map
3563 .clip_point(position, Bias::Left)
3564 .to_point(&display_map);
3565 let line_start = display_map.prev_line_boundary(position).0;
3566 let next_line_start = buffer.clip_point(
3567 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3568 Bias::Left,
3569 );
3570
3571 if line_start < original_range.start {
3572 head = line_start
3573 } else {
3574 head = next_line_start
3575 }
3576
3577 if head <= original_range.start {
3578 tail = original_range.end;
3579 } else {
3580 tail = original_range.start;
3581 }
3582 }
3583 SelectMode::All => {
3584 return;
3585 }
3586 };
3587
3588 if head < tail {
3589 pending.start = buffer.anchor_before(head);
3590 pending.end = buffer.anchor_before(tail);
3591 pending.reversed = true;
3592 } else {
3593 pending.start = buffer.anchor_before(tail);
3594 pending.end = buffer.anchor_before(head);
3595 pending.reversed = false;
3596 }
3597
3598 self.change_selections(None, window, cx, |s| {
3599 s.set_pending(pending, mode);
3600 });
3601 } else {
3602 log::error!("update_selection dispatched with no pending selection");
3603 return;
3604 }
3605
3606 self.apply_scroll_delta(scroll_delta, window, cx);
3607 cx.notify();
3608 }
3609
3610 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3611 self.columnar_selection_state.take();
3612 if self.selections.pending_anchor().is_some() {
3613 let selections = self.selections.all::<usize>(cx);
3614 self.change_selections(None, window, cx, |s| {
3615 s.select(selections);
3616 s.clear_pending();
3617 });
3618 }
3619 }
3620
3621 fn select_columns(
3622 &mut self,
3623 head: DisplayPoint,
3624 goal_column: u32,
3625 display_map: &DisplaySnapshot,
3626 window: &mut Window,
3627 cx: &mut Context<Self>,
3628 ) {
3629 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3630 return;
3631 };
3632
3633 let tail = match columnar_state {
3634 ColumnarSelectionState::FromMouse {
3635 selection_tail,
3636 display_point,
3637 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3638 ColumnarSelectionState::FromSelection { selection_tail } => {
3639 selection_tail.to_display_point(&display_map)
3640 }
3641 };
3642
3643 let start_row = cmp::min(tail.row(), head.row());
3644 let end_row = cmp::max(tail.row(), head.row());
3645 let start_column = cmp::min(tail.column(), goal_column);
3646 let end_column = cmp::max(tail.column(), goal_column);
3647 let reversed = start_column < tail.column();
3648
3649 let selection_ranges = (start_row.0..=end_row.0)
3650 .map(DisplayRow)
3651 .filter_map(|row| {
3652 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3653 || start_column <= display_map.line_len(row))
3654 && !display_map.is_block_line(row)
3655 {
3656 let start = display_map
3657 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3658 .to_point(display_map);
3659 let end = display_map
3660 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3661 .to_point(display_map);
3662 if reversed {
3663 Some(end..start)
3664 } else {
3665 Some(start..end)
3666 }
3667 } else {
3668 None
3669 }
3670 })
3671 .collect::<Vec<_>>();
3672
3673 let ranges = match columnar_state {
3674 ColumnarSelectionState::FromMouse { .. } => {
3675 let mut non_empty_ranges = selection_ranges
3676 .iter()
3677 .filter(|selection_range| selection_range.start != selection_range.end)
3678 .peekable();
3679 if non_empty_ranges.peek().is_some() {
3680 non_empty_ranges.cloned().collect()
3681 } else {
3682 selection_ranges
3683 }
3684 }
3685 _ => selection_ranges,
3686 };
3687
3688 self.change_selections(None, window, cx, |s| {
3689 s.select_ranges(ranges);
3690 });
3691 cx.notify();
3692 }
3693
3694 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3695 self.selections
3696 .all_adjusted(cx)
3697 .iter()
3698 .any(|selection| !selection.is_empty())
3699 }
3700
3701 pub fn has_pending_nonempty_selection(&self) -> bool {
3702 let pending_nonempty_selection = match self.selections.pending_anchor() {
3703 Some(Selection { start, end, .. }) => start != end,
3704 None => false,
3705 };
3706
3707 pending_nonempty_selection
3708 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3709 }
3710
3711 pub fn has_pending_selection(&self) -> bool {
3712 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3713 }
3714
3715 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3716 self.selection_mark_mode = false;
3717 self.selection_drag_state = SelectionDragState::None;
3718
3719 if self.clear_expanded_diff_hunks(cx) {
3720 cx.notify();
3721 return;
3722 }
3723 if self.dismiss_menus_and_popups(true, window, cx) {
3724 return;
3725 }
3726
3727 if self.mode.is_full()
3728 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3729 {
3730 return;
3731 }
3732
3733 cx.propagate();
3734 }
3735
3736 pub fn dismiss_menus_and_popups(
3737 &mut self,
3738 is_user_requested: bool,
3739 window: &mut Window,
3740 cx: &mut Context<Self>,
3741 ) -> bool {
3742 if self.take_rename(false, window, cx).is_some() {
3743 return true;
3744 }
3745
3746 if hide_hover(self, cx) {
3747 return true;
3748 }
3749
3750 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3751 return true;
3752 }
3753
3754 if self.hide_context_menu(window, cx).is_some() {
3755 return true;
3756 }
3757
3758 if self.mouse_context_menu.take().is_some() {
3759 return true;
3760 }
3761
3762 if is_user_requested && self.discard_inline_completion(true, cx) {
3763 return true;
3764 }
3765
3766 if self.snippet_stack.pop().is_some() {
3767 return true;
3768 }
3769
3770 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3771 self.dismiss_diagnostics(cx);
3772 return true;
3773 }
3774
3775 false
3776 }
3777
3778 fn linked_editing_ranges_for(
3779 &self,
3780 selection: Range<text::Anchor>,
3781 cx: &App,
3782 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3783 if self.linked_edit_ranges.is_empty() {
3784 return None;
3785 }
3786 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3787 selection.end.buffer_id.and_then(|end_buffer_id| {
3788 if selection.start.buffer_id != Some(end_buffer_id) {
3789 return None;
3790 }
3791 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3792 let snapshot = buffer.read(cx).snapshot();
3793 self.linked_edit_ranges
3794 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3795 .map(|ranges| (ranges, snapshot, buffer))
3796 })?;
3797 use text::ToOffset as TO;
3798 // find offset from the start of current range to current cursor position
3799 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3800
3801 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3802 let start_difference = start_offset - start_byte_offset;
3803 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3804 let end_difference = end_offset - start_byte_offset;
3805 // Current range has associated linked ranges.
3806 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3807 for range in linked_ranges.iter() {
3808 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3809 let end_offset = start_offset + end_difference;
3810 let start_offset = start_offset + start_difference;
3811 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3812 continue;
3813 }
3814 if self.selections.disjoint_anchor_ranges().any(|s| {
3815 if s.start.buffer_id != selection.start.buffer_id
3816 || s.end.buffer_id != selection.end.buffer_id
3817 {
3818 return false;
3819 }
3820 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3821 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3822 }) {
3823 continue;
3824 }
3825 let start = buffer_snapshot.anchor_after(start_offset);
3826 let end = buffer_snapshot.anchor_after(end_offset);
3827 linked_edits
3828 .entry(buffer.clone())
3829 .or_default()
3830 .push(start..end);
3831 }
3832 Some(linked_edits)
3833 }
3834
3835 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3836 let text: Arc<str> = text.into();
3837
3838 if self.read_only(cx) {
3839 return;
3840 }
3841
3842 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3843
3844 let selections = self.selections.all_adjusted(cx);
3845 let mut bracket_inserted = false;
3846 let mut edits = Vec::new();
3847 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3848 let mut new_selections = Vec::with_capacity(selections.len());
3849 let mut new_autoclose_regions = Vec::new();
3850 let snapshot = self.buffer.read(cx).read(cx);
3851 let mut clear_linked_edit_ranges = false;
3852
3853 for (selection, autoclose_region) in
3854 self.selections_with_autoclose_regions(selections, &snapshot)
3855 {
3856 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3857 // Determine if the inserted text matches the opening or closing
3858 // bracket of any of this language's bracket pairs.
3859 let mut bracket_pair = None;
3860 let mut is_bracket_pair_start = false;
3861 let mut is_bracket_pair_end = false;
3862 if !text.is_empty() {
3863 let mut bracket_pair_matching_end = None;
3864 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3865 // and they are removing the character that triggered IME popup.
3866 for (pair, enabled) in scope.brackets() {
3867 if !pair.close && !pair.surround {
3868 continue;
3869 }
3870
3871 if enabled && pair.start.ends_with(text.as_ref()) {
3872 let prefix_len = pair.start.len() - text.len();
3873 let preceding_text_matches_prefix = prefix_len == 0
3874 || (selection.start.column >= (prefix_len as u32)
3875 && snapshot.contains_str_at(
3876 Point::new(
3877 selection.start.row,
3878 selection.start.column - (prefix_len as u32),
3879 ),
3880 &pair.start[..prefix_len],
3881 ));
3882 if preceding_text_matches_prefix {
3883 bracket_pair = Some(pair.clone());
3884 is_bracket_pair_start = true;
3885 break;
3886 }
3887 }
3888 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3889 {
3890 // take first bracket pair matching end, but don't break in case a later bracket
3891 // pair matches start
3892 bracket_pair_matching_end = Some(pair.clone());
3893 }
3894 }
3895 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3896 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3897 is_bracket_pair_end = true;
3898 }
3899 }
3900
3901 if let Some(bracket_pair) = bracket_pair {
3902 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3903 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3904 let auto_surround =
3905 self.use_auto_surround && snapshot_settings.use_auto_surround;
3906 if selection.is_empty() {
3907 if is_bracket_pair_start {
3908 // If the inserted text is a suffix of an opening bracket and the
3909 // selection is preceded by the rest of the opening bracket, then
3910 // insert the closing bracket.
3911 let following_text_allows_autoclose = snapshot
3912 .chars_at(selection.start)
3913 .next()
3914 .map_or(true, |c| scope.should_autoclose_before(c));
3915
3916 let preceding_text_allows_autoclose = selection.start.column == 0
3917 || snapshot.reversed_chars_at(selection.start).next().map_or(
3918 true,
3919 |c| {
3920 bracket_pair.start != bracket_pair.end
3921 || !snapshot
3922 .char_classifier_at(selection.start)
3923 .is_word(c)
3924 },
3925 );
3926
3927 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3928 && bracket_pair.start.len() == 1
3929 {
3930 let target = bracket_pair.start.chars().next().unwrap();
3931 let current_line_count = snapshot
3932 .reversed_chars_at(selection.start)
3933 .take_while(|&c| c != '\n')
3934 .filter(|&c| c == target)
3935 .count();
3936 current_line_count % 2 == 1
3937 } else {
3938 false
3939 };
3940
3941 if autoclose
3942 && bracket_pair.close
3943 && following_text_allows_autoclose
3944 && preceding_text_allows_autoclose
3945 && !is_closing_quote
3946 {
3947 let anchor = snapshot.anchor_before(selection.end);
3948 new_selections.push((selection.map(|_| anchor), text.len()));
3949 new_autoclose_regions.push((
3950 anchor,
3951 text.len(),
3952 selection.id,
3953 bracket_pair.clone(),
3954 ));
3955 edits.push((
3956 selection.range(),
3957 format!("{}{}", text, bracket_pair.end).into(),
3958 ));
3959 bracket_inserted = true;
3960 continue;
3961 }
3962 }
3963
3964 if let Some(region) = autoclose_region {
3965 // If the selection is followed by an auto-inserted closing bracket,
3966 // then don't insert that closing bracket again; just move the selection
3967 // past the closing bracket.
3968 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3969 && text.as_ref() == region.pair.end.as_str();
3970 if should_skip {
3971 let anchor = snapshot.anchor_after(selection.end);
3972 new_selections
3973 .push((selection.map(|_| anchor), region.pair.end.len()));
3974 continue;
3975 }
3976 }
3977
3978 let always_treat_brackets_as_autoclosed = snapshot
3979 .language_settings_at(selection.start, cx)
3980 .always_treat_brackets_as_autoclosed;
3981 if always_treat_brackets_as_autoclosed
3982 && is_bracket_pair_end
3983 && snapshot.contains_str_at(selection.end, text.as_ref())
3984 {
3985 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3986 // and the inserted text is a closing bracket and the selection is followed
3987 // by the closing bracket then move the selection past the closing bracket.
3988 let anchor = snapshot.anchor_after(selection.end);
3989 new_selections.push((selection.map(|_| anchor), text.len()));
3990 continue;
3991 }
3992 }
3993 // If an opening bracket is 1 character long and is typed while
3994 // text is selected, then surround that text with the bracket pair.
3995 else if auto_surround
3996 && bracket_pair.surround
3997 && is_bracket_pair_start
3998 && bracket_pair.start.chars().count() == 1
3999 {
4000 edits.push((selection.start..selection.start, text.clone()));
4001 edits.push((
4002 selection.end..selection.end,
4003 bracket_pair.end.as_str().into(),
4004 ));
4005 bracket_inserted = true;
4006 new_selections.push((
4007 Selection {
4008 id: selection.id,
4009 start: snapshot.anchor_after(selection.start),
4010 end: snapshot.anchor_before(selection.end),
4011 reversed: selection.reversed,
4012 goal: selection.goal,
4013 },
4014 0,
4015 ));
4016 continue;
4017 }
4018 }
4019 }
4020
4021 if self.auto_replace_emoji_shortcode
4022 && selection.is_empty()
4023 && text.as_ref().ends_with(':')
4024 {
4025 if let Some(possible_emoji_short_code) =
4026 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4027 {
4028 if !possible_emoji_short_code.is_empty() {
4029 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4030 let emoji_shortcode_start = Point::new(
4031 selection.start.row,
4032 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4033 );
4034
4035 // Remove shortcode from buffer
4036 edits.push((
4037 emoji_shortcode_start..selection.start,
4038 "".to_string().into(),
4039 ));
4040 new_selections.push((
4041 Selection {
4042 id: selection.id,
4043 start: snapshot.anchor_after(emoji_shortcode_start),
4044 end: snapshot.anchor_before(selection.start),
4045 reversed: selection.reversed,
4046 goal: selection.goal,
4047 },
4048 0,
4049 ));
4050
4051 // Insert emoji
4052 let selection_start_anchor = snapshot.anchor_after(selection.start);
4053 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4054 edits.push((selection.start..selection.end, emoji.to_string().into()));
4055
4056 continue;
4057 }
4058 }
4059 }
4060 }
4061
4062 // If not handling any auto-close operation, then just replace the selected
4063 // text with the given input and move the selection to the end of the
4064 // newly inserted text.
4065 let anchor = snapshot.anchor_after(selection.end);
4066 if !self.linked_edit_ranges.is_empty() {
4067 let start_anchor = snapshot.anchor_before(selection.start);
4068
4069 let is_word_char = text.chars().next().map_or(true, |char| {
4070 let classifier = snapshot
4071 .char_classifier_at(start_anchor.to_offset(&snapshot))
4072 .ignore_punctuation(true);
4073 classifier.is_word(char)
4074 });
4075
4076 if is_word_char {
4077 if let Some(ranges) = self
4078 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4079 {
4080 for (buffer, edits) in ranges {
4081 linked_edits
4082 .entry(buffer.clone())
4083 .or_default()
4084 .extend(edits.into_iter().map(|range| (range, text.clone())));
4085 }
4086 }
4087 } else {
4088 clear_linked_edit_ranges = true;
4089 }
4090 }
4091
4092 new_selections.push((selection.map(|_| anchor), 0));
4093 edits.push((selection.start..selection.end, text.clone()));
4094 }
4095
4096 drop(snapshot);
4097
4098 self.transact(window, cx, |this, window, cx| {
4099 if clear_linked_edit_ranges {
4100 this.linked_edit_ranges.clear();
4101 }
4102 let initial_buffer_versions =
4103 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4104
4105 this.buffer.update(cx, |buffer, cx| {
4106 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4107 });
4108 for (buffer, edits) in linked_edits {
4109 buffer.update(cx, |buffer, cx| {
4110 let snapshot = buffer.snapshot();
4111 let edits = edits
4112 .into_iter()
4113 .map(|(range, text)| {
4114 use text::ToPoint as TP;
4115 let end_point = TP::to_point(&range.end, &snapshot);
4116 let start_point = TP::to_point(&range.start, &snapshot);
4117 (start_point..end_point, text)
4118 })
4119 .sorted_by_key(|(range, _)| range.start);
4120 buffer.edit(edits, None, cx);
4121 })
4122 }
4123 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4124 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4125 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4126 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4127 .zip(new_selection_deltas)
4128 .map(|(selection, delta)| Selection {
4129 id: selection.id,
4130 start: selection.start + delta,
4131 end: selection.end + delta,
4132 reversed: selection.reversed,
4133 goal: SelectionGoal::None,
4134 })
4135 .collect::<Vec<_>>();
4136
4137 let mut i = 0;
4138 for (position, delta, selection_id, pair) in new_autoclose_regions {
4139 let position = position.to_offset(&map.buffer_snapshot) + delta;
4140 let start = map.buffer_snapshot.anchor_before(position);
4141 let end = map.buffer_snapshot.anchor_after(position);
4142 while let Some(existing_state) = this.autoclose_regions.get(i) {
4143 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4144 Ordering::Less => i += 1,
4145 Ordering::Greater => break,
4146 Ordering::Equal => {
4147 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4148 Ordering::Less => i += 1,
4149 Ordering::Equal => break,
4150 Ordering::Greater => break,
4151 }
4152 }
4153 }
4154 }
4155 this.autoclose_regions.insert(
4156 i,
4157 AutocloseRegion {
4158 selection_id,
4159 range: start..end,
4160 pair,
4161 },
4162 );
4163 }
4164
4165 let had_active_inline_completion = this.has_active_inline_completion();
4166 this.change_selections(
4167 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4168 window,
4169 cx,
4170 |s| s.select(new_selections),
4171 );
4172
4173 if !bracket_inserted {
4174 if let Some(on_type_format_task) =
4175 this.trigger_on_type_formatting(text.to_string(), window, cx)
4176 {
4177 on_type_format_task.detach_and_log_err(cx);
4178 }
4179 }
4180
4181 let editor_settings = EditorSettings::get_global(cx);
4182 if bracket_inserted
4183 && (editor_settings.auto_signature_help
4184 || editor_settings.show_signature_help_after_edits)
4185 {
4186 this.show_signature_help(&ShowSignatureHelp, window, cx);
4187 }
4188
4189 let trigger_in_words =
4190 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4191 if this.hard_wrap.is_some() {
4192 let latest: Range<Point> = this.selections.newest(cx).range();
4193 if latest.is_empty()
4194 && this
4195 .buffer()
4196 .read(cx)
4197 .snapshot(cx)
4198 .line_len(MultiBufferRow(latest.start.row))
4199 == latest.start.column
4200 {
4201 this.rewrap_impl(
4202 RewrapOptions {
4203 override_language_settings: true,
4204 preserve_existing_whitespace: true,
4205 },
4206 cx,
4207 )
4208 }
4209 }
4210 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4211 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4212 this.refresh_inline_completion(true, false, window, cx);
4213 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4214 });
4215 }
4216
4217 fn find_possible_emoji_shortcode_at_position(
4218 snapshot: &MultiBufferSnapshot,
4219 position: Point,
4220 ) -> Option<String> {
4221 let mut chars = Vec::new();
4222 let mut found_colon = false;
4223 for char in snapshot.reversed_chars_at(position).take(100) {
4224 // Found a possible emoji shortcode in the middle of the buffer
4225 if found_colon {
4226 if char.is_whitespace() {
4227 chars.reverse();
4228 return Some(chars.iter().collect());
4229 }
4230 // If the previous character is not a whitespace, we are in the middle of a word
4231 // and we only want to complete the shortcode if the word is made up of other emojis
4232 let mut containing_word = String::new();
4233 for ch in snapshot
4234 .reversed_chars_at(position)
4235 .skip(chars.len() + 1)
4236 .take(100)
4237 {
4238 if ch.is_whitespace() {
4239 break;
4240 }
4241 containing_word.push(ch);
4242 }
4243 let containing_word = containing_word.chars().rev().collect::<String>();
4244 if util::word_consists_of_emojis(containing_word.as_str()) {
4245 chars.reverse();
4246 return Some(chars.iter().collect());
4247 }
4248 }
4249
4250 if char.is_whitespace() || !char.is_ascii() {
4251 return None;
4252 }
4253 if char == ':' {
4254 found_colon = true;
4255 } else {
4256 chars.push(char);
4257 }
4258 }
4259 // Found a possible emoji shortcode at the beginning of the buffer
4260 chars.reverse();
4261 Some(chars.iter().collect())
4262 }
4263
4264 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4265 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4266 self.transact(window, cx, |this, window, cx| {
4267 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4268 let selections = this.selections.all::<usize>(cx);
4269 let multi_buffer = this.buffer.read(cx);
4270 let buffer = multi_buffer.snapshot(cx);
4271 selections
4272 .iter()
4273 .map(|selection| {
4274 let start_point = selection.start.to_point(&buffer);
4275 let mut existing_indent =
4276 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4277 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4278 let start = selection.start;
4279 let end = selection.end;
4280 let selection_is_empty = start == end;
4281 let language_scope = buffer.language_scope_at(start);
4282 let (
4283 comment_delimiter,
4284 doc_delimiter,
4285 insert_extra_newline,
4286 indent_on_newline,
4287 indent_on_extra_newline,
4288 ) = if let Some(language) = &language_scope {
4289 let mut insert_extra_newline =
4290 insert_extra_newline_brackets(&buffer, start..end, language)
4291 || insert_extra_newline_tree_sitter(&buffer, start..end);
4292
4293 // Comment extension on newline is allowed only for cursor selections
4294 let comment_delimiter = maybe!({
4295 if !selection_is_empty {
4296 return None;
4297 }
4298
4299 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4300 return None;
4301 }
4302
4303 let delimiters = language.line_comment_prefixes();
4304 let max_len_of_delimiter =
4305 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4306 let (snapshot, range) =
4307 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4308
4309 let num_of_whitespaces = snapshot
4310 .chars_for_range(range.clone())
4311 .take_while(|c| c.is_whitespace())
4312 .count();
4313 let comment_candidate = snapshot
4314 .chars_for_range(range)
4315 .skip(num_of_whitespaces)
4316 .take(max_len_of_delimiter)
4317 .collect::<String>();
4318 let (delimiter, trimmed_len) = delimiters
4319 .iter()
4320 .filter_map(|delimiter| {
4321 let prefix = delimiter.trim_end();
4322 if comment_candidate.starts_with(prefix) {
4323 Some((delimiter, prefix.len()))
4324 } else {
4325 None
4326 }
4327 })
4328 .max_by_key(|(_, len)| *len)?;
4329
4330 let cursor_is_placed_after_comment_marker =
4331 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4332 if cursor_is_placed_after_comment_marker {
4333 Some(delimiter.clone())
4334 } else {
4335 None
4336 }
4337 });
4338
4339 let mut indent_on_newline = IndentSize::spaces(0);
4340 let mut indent_on_extra_newline = IndentSize::spaces(0);
4341
4342 let doc_delimiter = maybe!({
4343 if !selection_is_empty {
4344 return None;
4345 }
4346
4347 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4348 return None;
4349 }
4350
4351 let DocumentationConfig {
4352 start: start_tag,
4353 end: end_tag,
4354 prefix: delimiter,
4355 tab_size: len,
4356 } = language.documentation()?;
4357
4358 let is_within_block_comment = buffer
4359 .language_scope_at(start_point)
4360 .is_some_and(|scope| scope.override_name() == Some("comment"));
4361 if !is_within_block_comment {
4362 return None;
4363 }
4364
4365 let (snapshot, range) =
4366 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4367
4368 let num_of_whitespaces = snapshot
4369 .chars_for_range(range.clone())
4370 .take_while(|c| c.is_whitespace())
4371 .count();
4372
4373 // 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.
4374 let column = start_point.column;
4375 let cursor_is_after_start_tag = {
4376 let start_tag_len = start_tag.len();
4377 let start_tag_line = snapshot
4378 .chars_for_range(range.clone())
4379 .skip(num_of_whitespaces)
4380 .take(start_tag_len)
4381 .collect::<String>();
4382 if start_tag_line.starts_with(start_tag.as_ref()) {
4383 num_of_whitespaces + start_tag_len <= column as usize
4384 } else {
4385 false
4386 }
4387 };
4388
4389 let cursor_is_after_delimiter = {
4390 let delimiter_trim = delimiter.trim_end();
4391 let delimiter_line = snapshot
4392 .chars_for_range(range.clone())
4393 .skip(num_of_whitespaces)
4394 .take(delimiter_trim.len())
4395 .collect::<String>();
4396 if delimiter_line.starts_with(delimiter_trim) {
4397 num_of_whitespaces + delimiter_trim.len() <= column as usize
4398 } else {
4399 false
4400 }
4401 };
4402
4403 let cursor_is_before_end_tag_if_exists = {
4404 let mut char_position = 0u32;
4405 let mut end_tag_offset = None;
4406
4407 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4408 if let Some(byte_pos) = chunk.find(&**end_tag) {
4409 let chars_before_match =
4410 chunk[..byte_pos].chars().count() as u32;
4411 end_tag_offset =
4412 Some(char_position + chars_before_match);
4413 break 'outer;
4414 }
4415 char_position += chunk.chars().count() as u32;
4416 }
4417
4418 if let Some(end_tag_offset) = end_tag_offset {
4419 let cursor_is_before_end_tag = column <= end_tag_offset;
4420 if cursor_is_after_start_tag {
4421 if cursor_is_before_end_tag {
4422 insert_extra_newline = true;
4423 }
4424 let cursor_is_at_start_of_end_tag =
4425 column == end_tag_offset;
4426 if cursor_is_at_start_of_end_tag {
4427 indent_on_extra_newline.len = (*len).into();
4428 }
4429 }
4430 cursor_is_before_end_tag
4431 } else {
4432 true
4433 }
4434 };
4435
4436 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4437 && cursor_is_before_end_tag_if_exists
4438 {
4439 if cursor_is_after_start_tag {
4440 indent_on_newline.len = (*len).into();
4441 }
4442 Some(delimiter.clone())
4443 } else {
4444 None
4445 }
4446 });
4447
4448 (
4449 comment_delimiter,
4450 doc_delimiter,
4451 insert_extra_newline,
4452 indent_on_newline,
4453 indent_on_extra_newline,
4454 )
4455 } else {
4456 (
4457 None,
4458 None,
4459 false,
4460 IndentSize::default(),
4461 IndentSize::default(),
4462 )
4463 };
4464
4465 let prevent_auto_indent = doc_delimiter.is_some();
4466 let delimiter = comment_delimiter.or(doc_delimiter);
4467
4468 let capacity_for_delimiter =
4469 delimiter.as_deref().map(str::len).unwrap_or_default();
4470 let mut new_text = String::with_capacity(
4471 1 + capacity_for_delimiter
4472 + existing_indent.len as usize
4473 + indent_on_newline.len as usize
4474 + indent_on_extra_newline.len as usize,
4475 );
4476 new_text.push('\n');
4477 new_text.extend(existing_indent.chars());
4478 new_text.extend(indent_on_newline.chars());
4479
4480 if let Some(delimiter) = &delimiter {
4481 new_text.push_str(delimiter);
4482 }
4483
4484 if insert_extra_newline {
4485 new_text.push('\n');
4486 new_text.extend(existing_indent.chars());
4487 new_text.extend(indent_on_extra_newline.chars());
4488 }
4489
4490 let anchor = buffer.anchor_after(end);
4491 let new_selection = selection.map(|_| anchor);
4492 (
4493 ((start..end, new_text), prevent_auto_indent),
4494 (insert_extra_newline, new_selection),
4495 )
4496 })
4497 .unzip()
4498 };
4499
4500 let mut auto_indent_edits = Vec::new();
4501 let mut edits = Vec::new();
4502 for (edit, prevent_auto_indent) in edits_with_flags {
4503 if prevent_auto_indent {
4504 edits.push(edit);
4505 } else {
4506 auto_indent_edits.push(edit);
4507 }
4508 }
4509 if !edits.is_empty() {
4510 this.edit(edits, cx);
4511 }
4512 if !auto_indent_edits.is_empty() {
4513 this.edit_with_autoindent(auto_indent_edits, cx);
4514 }
4515
4516 let buffer = this.buffer.read(cx).snapshot(cx);
4517 let new_selections = selection_info
4518 .into_iter()
4519 .map(|(extra_newline_inserted, new_selection)| {
4520 let mut cursor = new_selection.end.to_point(&buffer);
4521 if extra_newline_inserted {
4522 cursor.row -= 1;
4523 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4524 }
4525 new_selection.map(|_| cursor)
4526 })
4527 .collect();
4528
4529 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4530 s.select(new_selections)
4531 });
4532 this.refresh_inline_completion(true, false, window, cx);
4533 });
4534 }
4535
4536 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4537 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4538
4539 let buffer = self.buffer.read(cx);
4540 let snapshot = buffer.snapshot(cx);
4541
4542 let mut edits = Vec::new();
4543 let mut rows = Vec::new();
4544
4545 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4546 let cursor = selection.head();
4547 let row = cursor.row;
4548
4549 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4550
4551 let newline = "\n".to_string();
4552 edits.push((start_of_line..start_of_line, newline));
4553
4554 rows.push(row + rows_inserted as u32);
4555 }
4556
4557 self.transact(window, cx, |editor, window, cx| {
4558 editor.edit(edits, cx);
4559
4560 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4561 let mut index = 0;
4562 s.move_cursors_with(|map, _, _| {
4563 let row = rows[index];
4564 index += 1;
4565
4566 let point = Point::new(row, 0);
4567 let boundary = map.next_line_boundary(point).1;
4568 let clipped = map.clip_point(boundary, Bias::Left);
4569
4570 (clipped, SelectionGoal::None)
4571 });
4572 });
4573
4574 let mut indent_edits = Vec::new();
4575 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4576 for row in rows {
4577 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4578 for (row, indent) in indents {
4579 if indent.len == 0 {
4580 continue;
4581 }
4582
4583 let text = match indent.kind {
4584 IndentKind::Space => " ".repeat(indent.len as usize),
4585 IndentKind::Tab => "\t".repeat(indent.len as usize),
4586 };
4587 let point = Point::new(row.0, 0);
4588 indent_edits.push((point..point, text));
4589 }
4590 }
4591 editor.edit(indent_edits, cx);
4592 });
4593 }
4594
4595 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4596 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4597
4598 let buffer = self.buffer.read(cx);
4599 let snapshot = buffer.snapshot(cx);
4600
4601 let mut edits = Vec::new();
4602 let mut rows = Vec::new();
4603 let mut rows_inserted = 0;
4604
4605 for selection in self.selections.all_adjusted(cx) {
4606 let cursor = selection.head();
4607 let row = cursor.row;
4608
4609 let point = Point::new(row + 1, 0);
4610 let start_of_line = snapshot.clip_point(point, Bias::Left);
4611
4612 let newline = "\n".to_string();
4613 edits.push((start_of_line..start_of_line, newline));
4614
4615 rows_inserted += 1;
4616 rows.push(row + rows_inserted);
4617 }
4618
4619 self.transact(window, cx, |editor, window, cx| {
4620 editor.edit(edits, cx);
4621
4622 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4623 let mut index = 0;
4624 s.move_cursors_with(|map, _, _| {
4625 let row = rows[index];
4626 index += 1;
4627
4628 let point = Point::new(row, 0);
4629 let boundary = map.next_line_boundary(point).1;
4630 let clipped = map.clip_point(boundary, Bias::Left);
4631
4632 (clipped, SelectionGoal::None)
4633 });
4634 });
4635
4636 let mut indent_edits = Vec::new();
4637 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4638 for row in rows {
4639 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4640 for (row, indent) in indents {
4641 if indent.len == 0 {
4642 continue;
4643 }
4644
4645 let text = match indent.kind {
4646 IndentKind::Space => " ".repeat(indent.len as usize),
4647 IndentKind::Tab => "\t".repeat(indent.len as usize),
4648 };
4649 let point = Point::new(row.0, 0);
4650 indent_edits.push((point..point, text));
4651 }
4652 }
4653 editor.edit(indent_edits, cx);
4654 });
4655 }
4656
4657 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4658 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4659 original_indent_columns: Vec::new(),
4660 });
4661 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4662 }
4663
4664 fn insert_with_autoindent_mode(
4665 &mut self,
4666 text: &str,
4667 autoindent_mode: Option<AutoindentMode>,
4668 window: &mut Window,
4669 cx: &mut Context<Self>,
4670 ) {
4671 if self.read_only(cx) {
4672 return;
4673 }
4674
4675 let text: Arc<str> = text.into();
4676 self.transact(window, cx, |this, window, cx| {
4677 let old_selections = this.selections.all_adjusted(cx);
4678 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4679 let anchors = {
4680 let snapshot = buffer.read(cx);
4681 old_selections
4682 .iter()
4683 .map(|s| {
4684 let anchor = snapshot.anchor_after(s.head());
4685 s.map(|_| anchor)
4686 })
4687 .collect::<Vec<_>>()
4688 };
4689 buffer.edit(
4690 old_selections
4691 .iter()
4692 .map(|s| (s.start..s.end, text.clone())),
4693 autoindent_mode,
4694 cx,
4695 );
4696 anchors
4697 });
4698
4699 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4700 s.select_anchors(selection_anchors);
4701 });
4702
4703 cx.notify();
4704 });
4705 }
4706
4707 fn trigger_completion_on_input(
4708 &mut self,
4709 text: &str,
4710 trigger_in_words: bool,
4711 window: &mut Window,
4712 cx: &mut Context<Self>,
4713 ) {
4714 let completions_source = self
4715 .context_menu
4716 .borrow()
4717 .as_ref()
4718 .and_then(|menu| match menu {
4719 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4720 CodeContextMenu::CodeActions(_) => None,
4721 });
4722
4723 match completions_source {
4724 Some(CompletionsMenuSource::Words) => {
4725 self.show_word_completions(&ShowWordCompletions, window, cx)
4726 }
4727 Some(CompletionsMenuSource::Normal)
4728 | Some(CompletionsMenuSource::SnippetChoices)
4729 | None
4730 if self.is_completion_trigger(
4731 text,
4732 trigger_in_words,
4733 completions_source.is_some(),
4734 cx,
4735 ) =>
4736 {
4737 self.show_completions(
4738 &ShowCompletions {
4739 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4740 },
4741 window,
4742 cx,
4743 )
4744 }
4745 _ => {
4746 self.hide_context_menu(window, cx);
4747 }
4748 }
4749 }
4750
4751 fn is_completion_trigger(
4752 &self,
4753 text: &str,
4754 trigger_in_words: bool,
4755 menu_is_open: bool,
4756 cx: &mut Context<Self>,
4757 ) -> bool {
4758 let position = self.selections.newest_anchor().head();
4759 let multibuffer = self.buffer.read(cx);
4760 let Some(buffer) = position
4761 .buffer_id
4762 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4763 else {
4764 return false;
4765 };
4766
4767 if let Some(completion_provider) = &self.completion_provider {
4768 completion_provider.is_completion_trigger(
4769 &buffer,
4770 position.text_anchor,
4771 text,
4772 trigger_in_words,
4773 menu_is_open,
4774 cx,
4775 )
4776 } else {
4777 false
4778 }
4779 }
4780
4781 /// If any empty selections is touching the start of its innermost containing autoclose
4782 /// region, expand it to select the brackets.
4783 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4784 let selections = self.selections.all::<usize>(cx);
4785 let buffer = self.buffer.read(cx).read(cx);
4786 let new_selections = self
4787 .selections_with_autoclose_regions(selections, &buffer)
4788 .map(|(mut selection, region)| {
4789 if !selection.is_empty() {
4790 return selection;
4791 }
4792
4793 if let Some(region) = region {
4794 let mut range = region.range.to_offset(&buffer);
4795 if selection.start == range.start && range.start >= region.pair.start.len() {
4796 range.start -= region.pair.start.len();
4797 if buffer.contains_str_at(range.start, ®ion.pair.start)
4798 && buffer.contains_str_at(range.end, ®ion.pair.end)
4799 {
4800 range.end += region.pair.end.len();
4801 selection.start = range.start;
4802 selection.end = range.end;
4803
4804 return selection;
4805 }
4806 }
4807 }
4808
4809 let always_treat_brackets_as_autoclosed = buffer
4810 .language_settings_at(selection.start, cx)
4811 .always_treat_brackets_as_autoclosed;
4812
4813 if !always_treat_brackets_as_autoclosed {
4814 return selection;
4815 }
4816
4817 if let Some(scope) = buffer.language_scope_at(selection.start) {
4818 for (pair, enabled) in scope.brackets() {
4819 if !enabled || !pair.close {
4820 continue;
4821 }
4822
4823 if buffer.contains_str_at(selection.start, &pair.end) {
4824 let pair_start_len = pair.start.len();
4825 if buffer.contains_str_at(
4826 selection.start.saturating_sub(pair_start_len),
4827 &pair.start,
4828 ) {
4829 selection.start -= pair_start_len;
4830 selection.end += pair.end.len();
4831
4832 return selection;
4833 }
4834 }
4835 }
4836 }
4837
4838 selection
4839 })
4840 .collect();
4841
4842 drop(buffer);
4843 self.change_selections(None, window, cx, |selections| {
4844 selections.select(new_selections)
4845 });
4846 }
4847
4848 /// Iterate the given selections, and for each one, find the smallest surrounding
4849 /// autoclose region. This uses the ordering of the selections and the autoclose
4850 /// regions to avoid repeated comparisons.
4851 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4852 &'a self,
4853 selections: impl IntoIterator<Item = Selection<D>>,
4854 buffer: &'a MultiBufferSnapshot,
4855 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4856 let mut i = 0;
4857 let mut regions = self.autoclose_regions.as_slice();
4858 selections.into_iter().map(move |selection| {
4859 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4860
4861 let mut enclosing = None;
4862 while let Some(pair_state) = regions.get(i) {
4863 if pair_state.range.end.to_offset(buffer) < range.start {
4864 regions = ®ions[i + 1..];
4865 i = 0;
4866 } else if pair_state.range.start.to_offset(buffer) > range.end {
4867 break;
4868 } else {
4869 if pair_state.selection_id == selection.id {
4870 enclosing = Some(pair_state);
4871 }
4872 i += 1;
4873 }
4874 }
4875
4876 (selection, enclosing)
4877 })
4878 }
4879
4880 /// Remove any autoclose regions that no longer contain their selection.
4881 fn invalidate_autoclose_regions(
4882 &mut self,
4883 mut selections: &[Selection<Anchor>],
4884 buffer: &MultiBufferSnapshot,
4885 ) {
4886 self.autoclose_regions.retain(|state| {
4887 let mut i = 0;
4888 while let Some(selection) = selections.get(i) {
4889 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4890 selections = &selections[1..];
4891 continue;
4892 }
4893 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4894 break;
4895 }
4896 if selection.id == state.selection_id {
4897 return true;
4898 } else {
4899 i += 1;
4900 }
4901 }
4902 false
4903 });
4904 }
4905
4906 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4907 let offset = position.to_offset(buffer);
4908 let (word_range, kind) = buffer.surrounding_word(offset, true);
4909 if offset > word_range.start && kind == Some(CharKind::Word) {
4910 Some(
4911 buffer
4912 .text_for_range(word_range.start..offset)
4913 .collect::<String>(),
4914 )
4915 } else {
4916 None
4917 }
4918 }
4919
4920 pub fn toggle_inline_values(
4921 &mut self,
4922 _: &ToggleInlineValues,
4923 _: &mut Window,
4924 cx: &mut Context<Self>,
4925 ) {
4926 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4927
4928 self.refresh_inline_values(cx);
4929 }
4930
4931 pub fn toggle_inlay_hints(
4932 &mut self,
4933 _: &ToggleInlayHints,
4934 _: &mut Window,
4935 cx: &mut Context<Self>,
4936 ) {
4937 self.refresh_inlay_hints(
4938 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4939 cx,
4940 );
4941 }
4942
4943 pub fn inlay_hints_enabled(&self) -> bool {
4944 self.inlay_hint_cache.enabled
4945 }
4946
4947 pub fn inline_values_enabled(&self) -> bool {
4948 self.inline_value_cache.enabled
4949 }
4950
4951 #[cfg(any(test, feature = "test-support"))]
4952 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4953 self.display_map
4954 .read(cx)
4955 .current_inlays()
4956 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4957 .cloned()
4958 .collect()
4959 }
4960
4961 #[cfg(any(test, feature = "test-support"))]
4962 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
4963 self.display_map
4964 .read(cx)
4965 .current_inlays()
4966 .cloned()
4967 .collect()
4968 }
4969
4970 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4971 if self.semantics_provider.is_none() || !self.mode.is_full() {
4972 return;
4973 }
4974
4975 let reason_description = reason.description();
4976 let ignore_debounce = matches!(
4977 reason,
4978 InlayHintRefreshReason::SettingsChange(_)
4979 | InlayHintRefreshReason::Toggle(_)
4980 | InlayHintRefreshReason::ExcerptsRemoved(_)
4981 | InlayHintRefreshReason::ModifiersChanged(_)
4982 );
4983 let (invalidate_cache, required_languages) = match reason {
4984 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4985 match self.inlay_hint_cache.modifiers_override(enabled) {
4986 Some(enabled) => {
4987 if enabled {
4988 (InvalidationStrategy::RefreshRequested, None)
4989 } else {
4990 self.splice_inlays(
4991 &self
4992 .visible_inlay_hints(cx)
4993 .iter()
4994 .map(|inlay| inlay.id)
4995 .collect::<Vec<InlayId>>(),
4996 Vec::new(),
4997 cx,
4998 );
4999 return;
5000 }
5001 }
5002 None => return,
5003 }
5004 }
5005 InlayHintRefreshReason::Toggle(enabled) => {
5006 if self.inlay_hint_cache.toggle(enabled) {
5007 if enabled {
5008 (InvalidationStrategy::RefreshRequested, None)
5009 } else {
5010 self.splice_inlays(
5011 &self
5012 .visible_inlay_hints(cx)
5013 .iter()
5014 .map(|inlay| inlay.id)
5015 .collect::<Vec<InlayId>>(),
5016 Vec::new(),
5017 cx,
5018 );
5019 return;
5020 }
5021 } else {
5022 return;
5023 }
5024 }
5025 InlayHintRefreshReason::SettingsChange(new_settings) => {
5026 match self.inlay_hint_cache.update_settings(
5027 &self.buffer,
5028 new_settings,
5029 self.visible_inlay_hints(cx),
5030 cx,
5031 ) {
5032 ControlFlow::Break(Some(InlaySplice {
5033 to_remove,
5034 to_insert,
5035 })) => {
5036 self.splice_inlays(&to_remove, to_insert, cx);
5037 return;
5038 }
5039 ControlFlow::Break(None) => return,
5040 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5041 }
5042 }
5043 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5044 if let Some(InlaySplice {
5045 to_remove,
5046 to_insert,
5047 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5048 {
5049 self.splice_inlays(&to_remove, to_insert, cx);
5050 }
5051 self.display_map.update(cx, |display_map, _| {
5052 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5053 });
5054 return;
5055 }
5056 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5057 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5058 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5059 }
5060 InlayHintRefreshReason::RefreshRequested => {
5061 (InvalidationStrategy::RefreshRequested, None)
5062 }
5063 };
5064
5065 if let Some(InlaySplice {
5066 to_remove,
5067 to_insert,
5068 }) = self.inlay_hint_cache.spawn_hint_refresh(
5069 reason_description,
5070 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
5071 invalidate_cache,
5072 ignore_debounce,
5073 cx,
5074 ) {
5075 self.splice_inlays(&to_remove, to_insert, cx);
5076 }
5077 }
5078
5079 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5080 self.display_map
5081 .read(cx)
5082 .current_inlays()
5083 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5084 .cloned()
5085 .collect()
5086 }
5087
5088 pub fn excerpts_for_inlay_hints_query(
5089 &self,
5090 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5091 cx: &mut Context<Editor>,
5092 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5093 let Some(project) = self.project.as_ref() else {
5094 return HashMap::default();
5095 };
5096 let project = project.read(cx);
5097 let multi_buffer = self.buffer().read(cx);
5098 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5099 let multi_buffer_visible_start = self
5100 .scroll_manager
5101 .anchor()
5102 .anchor
5103 .to_point(&multi_buffer_snapshot);
5104 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5105 multi_buffer_visible_start
5106 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5107 Bias::Left,
5108 );
5109 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5110 multi_buffer_snapshot
5111 .range_to_buffer_ranges(multi_buffer_visible_range)
5112 .into_iter()
5113 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5114 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5115 let buffer_file = project::File::from_dyn(buffer.file())?;
5116 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5117 let worktree_entry = buffer_worktree
5118 .read(cx)
5119 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5120 if worktree_entry.is_ignored {
5121 return None;
5122 }
5123
5124 let language = buffer.language()?;
5125 if let Some(restrict_to_languages) = restrict_to_languages {
5126 if !restrict_to_languages.contains(language) {
5127 return None;
5128 }
5129 }
5130 Some((
5131 excerpt_id,
5132 (
5133 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5134 buffer.version().clone(),
5135 excerpt_visible_range,
5136 ),
5137 ))
5138 })
5139 .collect()
5140 }
5141
5142 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5143 TextLayoutDetails {
5144 text_system: window.text_system().clone(),
5145 editor_style: self.style.clone().unwrap(),
5146 rem_size: window.rem_size(),
5147 scroll_anchor: self.scroll_manager.anchor(),
5148 visible_rows: self.visible_line_count(),
5149 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5150 }
5151 }
5152
5153 pub fn splice_inlays(
5154 &self,
5155 to_remove: &[InlayId],
5156 to_insert: Vec<Inlay>,
5157 cx: &mut Context<Self>,
5158 ) {
5159 self.display_map.update(cx, |display_map, cx| {
5160 display_map.splice_inlays(to_remove, to_insert, cx)
5161 });
5162 cx.notify();
5163 }
5164
5165 fn trigger_on_type_formatting(
5166 &self,
5167 input: String,
5168 window: &mut Window,
5169 cx: &mut Context<Self>,
5170 ) -> Option<Task<Result<()>>> {
5171 if input.len() != 1 {
5172 return None;
5173 }
5174
5175 let project = self.project.as_ref()?;
5176 let position = self.selections.newest_anchor().head();
5177 let (buffer, buffer_position) = self
5178 .buffer
5179 .read(cx)
5180 .text_anchor_for_position(position, cx)?;
5181
5182 let settings = language_settings::language_settings(
5183 buffer
5184 .read(cx)
5185 .language_at(buffer_position)
5186 .map(|l| l.name()),
5187 buffer.read(cx).file(),
5188 cx,
5189 );
5190 if !settings.use_on_type_format {
5191 return None;
5192 }
5193
5194 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5195 // hence we do LSP request & edit on host side only — add formats to host's history.
5196 let push_to_lsp_host_history = true;
5197 // If this is not the host, append its history with new edits.
5198 let push_to_client_history = project.read(cx).is_via_collab();
5199
5200 let on_type_formatting = project.update(cx, |project, cx| {
5201 project.on_type_format(
5202 buffer.clone(),
5203 buffer_position,
5204 input,
5205 push_to_lsp_host_history,
5206 cx,
5207 )
5208 });
5209 Some(cx.spawn_in(window, async move |editor, cx| {
5210 if let Some(transaction) = on_type_formatting.await? {
5211 if push_to_client_history {
5212 buffer
5213 .update(cx, |buffer, _| {
5214 buffer.push_transaction(transaction, Instant::now());
5215 buffer.finalize_last_transaction();
5216 })
5217 .ok();
5218 }
5219 editor.update(cx, |editor, cx| {
5220 editor.refresh_document_highlights(cx);
5221 })?;
5222 }
5223 Ok(())
5224 }))
5225 }
5226
5227 pub fn show_word_completions(
5228 &mut self,
5229 _: &ShowWordCompletions,
5230 window: &mut Window,
5231 cx: &mut Context<Self>,
5232 ) {
5233 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5234 }
5235
5236 pub fn show_completions(
5237 &mut self,
5238 options: &ShowCompletions,
5239 window: &mut Window,
5240 cx: &mut Context<Self>,
5241 ) {
5242 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5243 }
5244
5245 fn open_or_update_completions_menu(
5246 &mut self,
5247 requested_source: Option<CompletionsMenuSource>,
5248 trigger: Option<&str>,
5249 window: &mut Window,
5250 cx: &mut Context<Self>,
5251 ) {
5252 if self.pending_rename.is_some() {
5253 return;
5254 }
5255
5256 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5257
5258 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5259 // inserted and selected. To handle that case, the start of the selection is used so that
5260 // the menu starts with all choices.
5261 let position = self
5262 .selections
5263 .newest_anchor()
5264 .start
5265 .bias_right(&multibuffer_snapshot);
5266 if position.diff_base_anchor.is_some() {
5267 return;
5268 }
5269 let (buffer, buffer_position) =
5270 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5271 output
5272 } else {
5273 return;
5274 };
5275 let buffer_snapshot = buffer.read(cx).snapshot();
5276
5277 let query: Option<Arc<String>> =
5278 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5279
5280 drop(multibuffer_snapshot);
5281
5282 let provider = match requested_source {
5283 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5284 Some(CompletionsMenuSource::Words) => None,
5285 Some(CompletionsMenuSource::SnippetChoices) => {
5286 log::error!("bug: SnippetChoices requested_source is not handled");
5287 None
5288 }
5289 };
5290
5291 let sort_completions = provider
5292 .as_ref()
5293 .map_or(false, |provider| provider.sort_completions());
5294
5295 let filter_completions = provider
5296 .as_ref()
5297 .map_or(true, |provider| provider.filter_completions());
5298
5299 let trigger_kind = match trigger {
5300 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5301 CompletionTriggerKind::TRIGGER_CHARACTER
5302 }
5303 _ => CompletionTriggerKind::INVOKED,
5304 };
5305 let completion_context = CompletionContext {
5306 trigger_character: trigger.and_then(|trigger| {
5307 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5308 Some(String::from(trigger))
5309 } else {
5310 None
5311 }
5312 }),
5313 trigger_kind,
5314 };
5315
5316 // Hide the current completions menu when a trigger char is typed. Without this, cached
5317 // completions from before the trigger char may be reused (#32774). Snippet choices could
5318 // involve trigger chars, so this is skipped in that case.
5319 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5320 {
5321 let menu_is_open = matches!(
5322 self.context_menu.borrow().as_ref(),
5323 Some(CodeContextMenu::Completions(_))
5324 );
5325 if menu_is_open {
5326 self.hide_context_menu(window, cx);
5327 }
5328 }
5329
5330 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5331 if filter_completions {
5332 menu.filter(query.clone(), provider.clone(), window, cx);
5333 }
5334 // When `is_incomplete` is false, no need to re-query completions when the current query
5335 // is a suffix of the initial query.
5336 if !menu.is_incomplete {
5337 // If the new query is a suffix of the old query (typing more characters) and
5338 // the previous result was complete, the existing completions can be filtered.
5339 //
5340 // Note that this is always true for snippet completions.
5341 let query_matches = match (&menu.initial_query, &query) {
5342 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5343 (None, _) => true,
5344 _ => false,
5345 };
5346 if query_matches {
5347 let position_matches = if menu.initial_position == position {
5348 true
5349 } else {
5350 let snapshot = self.buffer.read(cx).read(cx);
5351 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5352 };
5353 if position_matches {
5354 return;
5355 }
5356 }
5357 }
5358 };
5359
5360 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5361 buffer_snapshot.surrounding_word(buffer_position)
5362 {
5363 let word_to_exclude = buffer_snapshot
5364 .text_for_range(word_range.clone())
5365 .collect::<String>();
5366 (
5367 buffer_snapshot.anchor_before(word_range.start)
5368 ..buffer_snapshot.anchor_after(buffer_position),
5369 Some(word_to_exclude),
5370 )
5371 } else {
5372 (buffer_position..buffer_position, None)
5373 };
5374
5375 let language = buffer_snapshot
5376 .language_at(buffer_position)
5377 .map(|language| language.name());
5378
5379 let completion_settings =
5380 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5381
5382 let show_completion_documentation = buffer_snapshot
5383 .settings_at(buffer_position, cx)
5384 .show_completion_documentation;
5385
5386 // The document can be large, so stay in reasonable bounds when searching for words,
5387 // otherwise completion pop-up might be slow to appear.
5388 const WORD_LOOKUP_ROWS: u32 = 5_000;
5389 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5390 let min_word_search = buffer_snapshot.clip_point(
5391 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5392 Bias::Left,
5393 );
5394 let max_word_search = buffer_snapshot.clip_point(
5395 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5396 Bias::Right,
5397 );
5398 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5399 ..buffer_snapshot.point_to_offset(max_word_search);
5400
5401 let skip_digits = query
5402 .as_ref()
5403 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5404
5405 let (mut words, provider_responses) = match &provider {
5406 Some(provider) => {
5407 let provider_responses = provider.completions(
5408 position.excerpt_id,
5409 &buffer,
5410 buffer_position,
5411 completion_context,
5412 window,
5413 cx,
5414 );
5415
5416 let words = match completion_settings.words {
5417 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5418 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5419 .background_spawn(async move {
5420 buffer_snapshot.words_in_range(WordsQuery {
5421 fuzzy_contents: None,
5422 range: word_search_range,
5423 skip_digits,
5424 })
5425 }),
5426 };
5427
5428 (words, provider_responses)
5429 }
5430 None => (
5431 cx.background_spawn(async move {
5432 buffer_snapshot.words_in_range(WordsQuery {
5433 fuzzy_contents: None,
5434 range: word_search_range,
5435 skip_digits,
5436 })
5437 }),
5438 Task::ready(Ok(Vec::new())),
5439 ),
5440 };
5441
5442 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5443
5444 let id = post_inc(&mut self.next_completion_id);
5445 let task = cx.spawn_in(window, async move |editor, cx| {
5446 let Ok(()) = editor.update(cx, |this, _| {
5447 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5448 }) else {
5449 return;
5450 };
5451
5452 // TODO: Ideally completions from different sources would be selectively re-queried, so
5453 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5454 let mut completions = Vec::new();
5455 let mut is_incomplete = false;
5456 if let Some(provider_responses) = provider_responses.await.log_err() {
5457 if !provider_responses.is_empty() {
5458 for response in provider_responses {
5459 completions.extend(response.completions);
5460 is_incomplete = is_incomplete || response.is_incomplete;
5461 }
5462 if completion_settings.words == WordsCompletionMode::Fallback {
5463 words = Task::ready(BTreeMap::default());
5464 }
5465 }
5466 }
5467
5468 let mut words = words.await;
5469 if let Some(word_to_exclude) = &word_to_exclude {
5470 words.remove(word_to_exclude);
5471 }
5472 for lsp_completion in &completions {
5473 words.remove(&lsp_completion.new_text);
5474 }
5475 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5476 replace_range: word_replace_range.clone(),
5477 new_text: word.clone(),
5478 label: CodeLabel::plain(word, None),
5479 icon_path: None,
5480 documentation: None,
5481 source: CompletionSource::BufferWord {
5482 word_range,
5483 resolved: false,
5484 },
5485 insert_text_mode: Some(InsertTextMode::AS_IS),
5486 confirm: None,
5487 }));
5488
5489 let menu = if completions.is_empty() {
5490 None
5491 } else {
5492 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5493 let languages = editor
5494 .workspace
5495 .as_ref()
5496 .and_then(|(workspace, _)| workspace.upgrade())
5497 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5498 let menu = CompletionsMenu::new(
5499 id,
5500 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5501 sort_completions,
5502 show_completion_documentation,
5503 position,
5504 query.clone(),
5505 is_incomplete,
5506 buffer.clone(),
5507 completions.into(),
5508 snippet_sort_order,
5509 languages,
5510 language,
5511 cx,
5512 );
5513
5514 let query = if filter_completions { query } else { None };
5515 let matches_task = if let Some(query) = query {
5516 menu.do_async_filtering(query, cx)
5517 } else {
5518 Task::ready(menu.unfiltered_matches())
5519 };
5520 (menu, matches_task)
5521 }) else {
5522 return;
5523 };
5524
5525 let matches = matches_task.await;
5526
5527 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5528 // Newer menu already set, so exit.
5529 match editor.context_menu.borrow().as_ref() {
5530 Some(CodeContextMenu::Completions(prev_menu)) => {
5531 if prev_menu.id > id {
5532 return;
5533 }
5534 }
5535 _ => {}
5536 };
5537
5538 // Only valid to take prev_menu because it the new menu is immediately set
5539 // below, or the menu is hidden.
5540 match editor.context_menu.borrow_mut().take() {
5541 Some(CodeContextMenu::Completions(prev_menu)) => {
5542 let position_matches =
5543 if prev_menu.initial_position == menu.initial_position {
5544 true
5545 } else {
5546 let snapshot = editor.buffer.read(cx).read(cx);
5547 prev_menu.initial_position.to_offset(&snapshot)
5548 == menu.initial_position.to_offset(&snapshot)
5549 };
5550 if position_matches {
5551 // Preserve markdown cache before `set_filter_results` because it will
5552 // try to populate the documentation cache.
5553 menu.preserve_markdown_cache(prev_menu);
5554 }
5555 }
5556 _ => {}
5557 };
5558
5559 menu.set_filter_results(matches, provider, window, cx);
5560 }) else {
5561 return;
5562 };
5563
5564 menu.visible().then_some(menu)
5565 };
5566
5567 editor
5568 .update_in(cx, |editor, window, cx| {
5569 if editor.focus_handle.is_focused(window) {
5570 if let Some(menu) = menu {
5571 *editor.context_menu.borrow_mut() =
5572 Some(CodeContextMenu::Completions(menu));
5573
5574 crate::hover_popover::hide_hover(editor, cx);
5575 if editor.show_edit_predictions_in_menu() {
5576 editor.update_visible_inline_completion(window, cx);
5577 } else {
5578 editor.discard_inline_completion(false, cx);
5579 }
5580
5581 cx.notify();
5582 return;
5583 }
5584 }
5585
5586 if editor.completion_tasks.len() <= 1 {
5587 // If there are no more completion tasks and the last menu was empty, we should hide it.
5588 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5589 // If it was already hidden and we don't show inline completions in the menu, we should
5590 // also show the inline-completion when available.
5591 if was_hidden && editor.show_edit_predictions_in_menu() {
5592 editor.update_visible_inline_completion(window, cx);
5593 }
5594 }
5595 })
5596 .ok();
5597 });
5598
5599 self.completion_tasks.push((id, task));
5600 }
5601
5602 #[cfg(feature = "test-support")]
5603 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5604 let menu = self.context_menu.borrow();
5605 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5606 let completions = menu.completions.borrow();
5607 Some(completions.to_vec())
5608 } else {
5609 None
5610 }
5611 }
5612
5613 pub fn with_completions_menu_matching_id<R>(
5614 &self,
5615 id: CompletionId,
5616 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5617 ) -> R {
5618 let mut context_menu = self.context_menu.borrow_mut();
5619 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5620 return f(None);
5621 };
5622 if completions_menu.id != id {
5623 return f(None);
5624 }
5625 f(Some(completions_menu))
5626 }
5627
5628 pub fn confirm_completion(
5629 &mut self,
5630 action: &ConfirmCompletion,
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::Complete, window, cx)
5636 }
5637
5638 pub fn confirm_completion_insert(
5639 &mut self,
5640 _: &ConfirmCompletionInsert,
5641 window: &mut Window,
5642 cx: &mut Context<Self>,
5643 ) -> Option<Task<Result<()>>> {
5644 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5645 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5646 }
5647
5648 pub fn confirm_completion_replace(
5649 &mut self,
5650 _: &ConfirmCompletionReplace,
5651 window: &mut Window,
5652 cx: &mut Context<Self>,
5653 ) -> Option<Task<Result<()>>> {
5654 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5655 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5656 }
5657
5658 pub fn compose_completion(
5659 &mut self,
5660 action: &ComposeCompletion,
5661 window: &mut Window,
5662 cx: &mut Context<Self>,
5663 ) -> Option<Task<Result<()>>> {
5664 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5665 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5666 }
5667
5668 fn do_completion(
5669 &mut self,
5670 item_ix: Option<usize>,
5671 intent: CompletionIntent,
5672 window: &mut Window,
5673 cx: &mut Context<Editor>,
5674 ) -> Option<Task<Result<()>>> {
5675 use language::ToOffset as _;
5676
5677 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5678 else {
5679 return None;
5680 };
5681
5682 let candidate_id = {
5683 let entries = completions_menu.entries.borrow();
5684 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5685 if self.show_edit_predictions_in_menu() {
5686 self.discard_inline_completion(true, cx);
5687 }
5688 mat.candidate_id
5689 };
5690
5691 let completion = completions_menu
5692 .completions
5693 .borrow()
5694 .get(candidate_id)?
5695 .clone();
5696 cx.stop_propagation();
5697
5698 let buffer_handle = completions_menu.buffer.clone();
5699
5700 let CompletionEdit {
5701 new_text,
5702 snippet,
5703 replace_range,
5704 } = process_completion_for_edit(
5705 &completion,
5706 intent,
5707 &buffer_handle,
5708 &completions_menu.initial_position.text_anchor,
5709 cx,
5710 );
5711
5712 let buffer = buffer_handle.read(cx);
5713 let snapshot = self.buffer.read(cx).snapshot(cx);
5714 let newest_anchor = self.selections.newest_anchor();
5715 let replace_range_multibuffer = {
5716 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5717 let multibuffer_anchor = snapshot
5718 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5719 .unwrap()
5720 ..snapshot
5721 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5722 .unwrap();
5723 multibuffer_anchor.start.to_offset(&snapshot)
5724 ..multibuffer_anchor.end.to_offset(&snapshot)
5725 };
5726 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5727 return None;
5728 }
5729
5730 let old_text = buffer
5731 .text_for_range(replace_range.clone())
5732 .collect::<String>();
5733 let lookbehind = newest_anchor
5734 .start
5735 .text_anchor
5736 .to_offset(buffer)
5737 .saturating_sub(replace_range.start);
5738 let lookahead = replace_range
5739 .end
5740 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5741 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5742 let suffix = &old_text[lookbehind.min(old_text.len())..];
5743
5744 let selections = self.selections.all::<usize>(cx);
5745 let mut ranges = Vec::new();
5746 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5747
5748 for selection in &selections {
5749 let range = if selection.id == newest_anchor.id {
5750 replace_range_multibuffer.clone()
5751 } else {
5752 let mut range = selection.range();
5753
5754 // if prefix is present, don't duplicate it
5755 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5756 range.start = range.start.saturating_sub(lookbehind);
5757
5758 // if suffix is also present, mimic the newest cursor and replace it
5759 if selection.id != newest_anchor.id
5760 && snapshot.contains_str_at(range.end, suffix)
5761 {
5762 range.end += lookahead;
5763 }
5764 }
5765 range
5766 };
5767
5768 ranges.push(range.clone());
5769
5770 if !self.linked_edit_ranges.is_empty() {
5771 let start_anchor = snapshot.anchor_before(range.start);
5772 let end_anchor = snapshot.anchor_after(range.end);
5773 if let Some(ranges) = self
5774 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5775 {
5776 for (buffer, edits) in ranges {
5777 linked_edits
5778 .entry(buffer.clone())
5779 .or_default()
5780 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5781 }
5782 }
5783 }
5784 }
5785
5786 let common_prefix_len = old_text
5787 .chars()
5788 .zip(new_text.chars())
5789 .take_while(|(a, b)| a == b)
5790 .map(|(a, _)| a.len_utf8())
5791 .sum::<usize>();
5792
5793 cx.emit(EditorEvent::InputHandled {
5794 utf16_range_to_replace: None,
5795 text: new_text[common_prefix_len..].into(),
5796 });
5797
5798 self.transact(window, cx, |this, window, cx| {
5799 if let Some(mut snippet) = snippet {
5800 snippet.text = new_text.to_string();
5801 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5802 } else {
5803 this.buffer.update(cx, |buffer, cx| {
5804 let auto_indent = match completion.insert_text_mode {
5805 Some(InsertTextMode::AS_IS) => None,
5806 _ => this.autoindent_mode.clone(),
5807 };
5808 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5809 buffer.edit(edits, auto_indent, cx);
5810 });
5811 }
5812 for (buffer, edits) in linked_edits {
5813 buffer.update(cx, |buffer, cx| {
5814 let snapshot = buffer.snapshot();
5815 let edits = edits
5816 .into_iter()
5817 .map(|(range, text)| {
5818 use text::ToPoint as TP;
5819 let end_point = TP::to_point(&range.end, &snapshot);
5820 let start_point = TP::to_point(&range.start, &snapshot);
5821 (start_point..end_point, text)
5822 })
5823 .sorted_by_key(|(range, _)| range.start);
5824 buffer.edit(edits, None, cx);
5825 })
5826 }
5827
5828 this.refresh_inline_completion(true, false, window, cx);
5829 });
5830
5831 let show_new_completions_on_confirm = completion
5832 .confirm
5833 .as_ref()
5834 .map_or(false, |confirm| confirm(intent, window, cx));
5835 if show_new_completions_on_confirm {
5836 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5837 }
5838
5839 let provider = self.completion_provider.as_ref()?;
5840 drop(completion);
5841 let apply_edits = provider.apply_additional_edits_for_completion(
5842 buffer_handle,
5843 completions_menu.completions.clone(),
5844 candidate_id,
5845 true,
5846 cx,
5847 );
5848
5849 let editor_settings = EditorSettings::get_global(cx);
5850 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5851 // After the code completion is finished, users often want to know what signatures are needed.
5852 // so we should automatically call signature_help
5853 self.show_signature_help(&ShowSignatureHelp, window, cx);
5854 }
5855
5856 Some(cx.foreground_executor().spawn(async move {
5857 apply_edits.await?;
5858 Ok(())
5859 }))
5860 }
5861
5862 pub fn toggle_code_actions(
5863 &mut self,
5864 action: &ToggleCodeActions,
5865 window: &mut Window,
5866 cx: &mut Context<Self>,
5867 ) {
5868 let quick_launch = action.quick_launch;
5869 let mut context_menu = self.context_menu.borrow_mut();
5870 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5871 if code_actions.deployed_from == action.deployed_from {
5872 // Toggle if we're selecting the same one
5873 *context_menu = None;
5874 cx.notify();
5875 return;
5876 } else {
5877 // Otherwise, clear it and start a new one
5878 *context_menu = None;
5879 cx.notify();
5880 }
5881 }
5882 drop(context_menu);
5883 let snapshot = self.snapshot(window, cx);
5884 let deployed_from = action.deployed_from.clone();
5885 let action = action.clone();
5886 self.completion_tasks.clear();
5887 self.discard_inline_completion(false, cx);
5888
5889 let multibuffer_point = match &action.deployed_from {
5890 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5891 DisplayPoint::new(*row, 0).to_point(&snapshot)
5892 }
5893 _ => self.selections.newest::<Point>(cx).head(),
5894 };
5895 let Some((buffer, buffer_row)) = snapshot
5896 .buffer_snapshot
5897 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5898 .and_then(|(buffer_snapshot, range)| {
5899 self.buffer()
5900 .read(cx)
5901 .buffer(buffer_snapshot.remote_id())
5902 .map(|buffer| (buffer, range.start.row))
5903 })
5904 else {
5905 return;
5906 };
5907 let buffer_id = buffer.read(cx).remote_id();
5908 let tasks = self
5909 .tasks
5910 .get(&(buffer_id, buffer_row))
5911 .map(|t| Arc::new(t.to_owned()));
5912
5913 if !self.focus_handle.is_focused(window) {
5914 return;
5915 }
5916 let project = self.project.clone();
5917
5918 let code_actions_task = match deployed_from {
5919 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5920 _ => self.code_actions(buffer_row, window, cx),
5921 };
5922
5923 let runnable_task = match deployed_from {
5924 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5925 _ => {
5926 let mut task_context_task = Task::ready(None);
5927 if let Some(tasks) = &tasks {
5928 if let Some(project) = project {
5929 task_context_task =
5930 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5931 }
5932 }
5933
5934 cx.spawn_in(window, {
5935 let buffer = buffer.clone();
5936 async move |editor, cx| {
5937 let task_context = task_context_task.await;
5938
5939 let resolved_tasks =
5940 tasks
5941 .zip(task_context.clone())
5942 .map(|(tasks, task_context)| ResolvedTasks {
5943 templates: tasks.resolve(&task_context).collect(),
5944 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5945 multibuffer_point.row,
5946 tasks.column,
5947 )),
5948 });
5949 let debug_scenarios = editor
5950 .update(cx, |editor, cx| {
5951 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5952 })?
5953 .await;
5954 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5955 }
5956 })
5957 }
5958 };
5959
5960 cx.spawn_in(window, async move |editor, cx| {
5961 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5962 let code_actions = code_actions_task.await;
5963 let spawn_straight_away = quick_launch
5964 && resolved_tasks
5965 .as_ref()
5966 .map_or(false, |tasks| tasks.templates.len() == 1)
5967 && code_actions
5968 .as_ref()
5969 .map_or(true, |actions| actions.is_empty())
5970 && debug_scenarios.is_empty();
5971
5972 editor.update_in(cx, |editor, window, cx| {
5973 crate::hover_popover::hide_hover(editor, cx);
5974 *editor.context_menu.borrow_mut() =
5975 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5976 buffer,
5977 actions: CodeActionContents::new(
5978 resolved_tasks,
5979 code_actions,
5980 debug_scenarios,
5981 task_context.unwrap_or_default(),
5982 ),
5983 selected_item: Default::default(),
5984 scroll_handle: UniformListScrollHandle::default(),
5985 deployed_from,
5986 }));
5987 cx.notify();
5988 if spawn_straight_away {
5989 if let Some(task) = editor.confirm_code_action(
5990 &ConfirmCodeAction { item_ix: Some(0) },
5991 window,
5992 cx,
5993 ) {
5994 return task;
5995 }
5996 }
5997
5998 Task::ready(Ok(()))
5999 })
6000 })
6001 .detach_and_log_err(cx);
6002 }
6003
6004 fn debug_scenarios(
6005 &mut self,
6006 resolved_tasks: &Option<ResolvedTasks>,
6007 buffer: &Entity<Buffer>,
6008 cx: &mut App,
6009 ) -> Task<Vec<task::DebugScenario>> {
6010 maybe!({
6011 let project = self.project.as_ref()?;
6012 let dap_store = project.read(cx).dap_store();
6013 let mut scenarios = vec![];
6014 let resolved_tasks = resolved_tasks.as_ref()?;
6015 let buffer = buffer.read(cx);
6016 let language = buffer.language()?;
6017 let file = buffer.file();
6018 let debug_adapter = language_settings(language.name().into(), file, cx)
6019 .debuggers
6020 .first()
6021 .map(SharedString::from)
6022 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6023
6024 dap_store.update(cx, |dap_store, cx| {
6025 for (_, task) in &resolved_tasks.templates {
6026 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6027 task.original_task().clone(),
6028 debug_adapter.clone().into(),
6029 task.display_label().to_owned().into(),
6030 cx,
6031 );
6032 scenarios.push(maybe_scenario);
6033 }
6034 });
6035 Some(cx.background_spawn(async move {
6036 let scenarios = futures::future::join_all(scenarios)
6037 .await
6038 .into_iter()
6039 .flatten()
6040 .collect::<Vec<_>>();
6041 scenarios
6042 }))
6043 })
6044 .unwrap_or_else(|| Task::ready(vec![]))
6045 }
6046
6047 fn code_actions(
6048 &mut self,
6049 buffer_row: u32,
6050 window: &mut Window,
6051 cx: &mut Context<Self>,
6052 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6053 let mut task = self.code_actions_task.take();
6054 cx.spawn_in(window, async move |editor, cx| {
6055 while let Some(prev_task) = task {
6056 prev_task.await.log_err();
6057 task = editor
6058 .update(cx, |this, _| this.code_actions_task.take())
6059 .ok()?;
6060 }
6061
6062 editor
6063 .update(cx, |editor, cx| {
6064 editor
6065 .available_code_actions
6066 .clone()
6067 .and_then(|(location, code_actions)| {
6068 let snapshot = location.buffer.read(cx).snapshot();
6069 let point_range = location.range.to_point(&snapshot);
6070 let point_range = point_range.start.row..=point_range.end.row;
6071 if point_range.contains(&buffer_row) {
6072 Some(code_actions)
6073 } else {
6074 None
6075 }
6076 })
6077 })
6078 .ok()
6079 .flatten()
6080 })
6081 }
6082
6083 pub fn confirm_code_action(
6084 &mut self,
6085 action: &ConfirmCodeAction,
6086 window: &mut Window,
6087 cx: &mut Context<Self>,
6088 ) -> Option<Task<Result<()>>> {
6089 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6090
6091 let actions_menu =
6092 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6093 menu
6094 } else {
6095 return None;
6096 };
6097
6098 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6099 let action = actions_menu.actions.get(action_ix)?;
6100 let title = action.label();
6101 let buffer = actions_menu.buffer;
6102 let workspace = self.workspace()?;
6103
6104 match action {
6105 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6106 workspace.update(cx, |workspace, cx| {
6107 workspace.schedule_resolved_task(
6108 task_source_kind,
6109 resolved_task,
6110 false,
6111 window,
6112 cx,
6113 );
6114
6115 Some(Task::ready(Ok(())))
6116 })
6117 }
6118 CodeActionsItem::CodeAction {
6119 excerpt_id,
6120 action,
6121 provider,
6122 } => {
6123 let apply_code_action =
6124 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6125 let workspace = workspace.downgrade();
6126 Some(cx.spawn_in(window, async move |editor, cx| {
6127 let project_transaction = apply_code_action.await?;
6128 Self::open_project_transaction(
6129 &editor,
6130 workspace,
6131 project_transaction,
6132 title,
6133 cx,
6134 )
6135 .await
6136 }))
6137 }
6138 CodeActionsItem::DebugScenario(scenario) => {
6139 let context = actions_menu.actions.context.clone();
6140
6141 workspace.update(cx, |workspace, cx| {
6142 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6143 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6144 });
6145 Some(Task::ready(Ok(())))
6146 }
6147 }
6148 }
6149
6150 pub async fn open_project_transaction(
6151 this: &WeakEntity<Editor>,
6152 workspace: WeakEntity<Workspace>,
6153 transaction: ProjectTransaction,
6154 title: String,
6155 cx: &mut AsyncWindowContext,
6156 ) -> Result<()> {
6157 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6158 cx.update(|_, cx| {
6159 entries.sort_unstable_by_key(|(buffer, _)| {
6160 buffer.read(cx).file().map(|f| f.path().clone())
6161 });
6162 })?;
6163
6164 // If the project transaction's edits are all contained within this editor, then
6165 // avoid opening a new editor to display them.
6166
6167 if let Some((buffer, transaction)) = entries.first() {
6168 if entries.len() == 1 {
6169 let excerpt = this.update(cx, |editor, cx| {
6170 editor
6171 .buffer()
6172 .read(cx)
6173 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6174 })?;
6175 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6176 if excerpted_buffer == *buffer {
6177 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6178 let excerpt_range = excerpt_range.to_offset(buffer);
6179 buffer
6180 .edited_ranges_for_transaction::<usize>(transaction)
6181 .all(|range| {
6182 excerpt_range.start <= range.start
6183 && excerpt_range.end >= range.end
6184 })
6185 })?;
6186
6187 if all_edits_within_excerpt {
6188 return Ok(());
6189 }
6190 }
6191 }
6192 }
6193 } else {
6194 return Ok(());
6195 }
6196
6197 let mut ranges_to_highlight = Vec::new();
6198 let excerpt_buffer = cx.new(|cx| {
6199 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6200 for (buffer_handle, transaction) in &entries {
6201 let edited_ranges = buffer_handle
6202 .read(cx)
6203 .edited_ranges_for_transaction::<Point>(transaction)
6204 .collect::<Vec<_>>();
6205 let (ranges, _) = multibuffer.set_excerpts_for_path(
6206 PathKey::for_buffer(buffer_handle, cx),
6207 buffer_handle.clone(),
6208 edited_ranges,
6209 DEFAULT_MULTIBUFFER_CONTEXT,
6210 cx,
6211 );
6212
6213 ranges_to_highlight.extend(ranges);
6214 }
6215 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6216 multibuffer
6217 })?;
6218
6219 workspace.update_in(cx, |workspace, window, cx| {
6220 let project = workspace.project().clone();
6221 let editor =
6222 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6223 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6224 editor.update(cx, |editor, cx| {
6225 editor.highlight_background::<Self>(
6226 &ranges_to_highlight,
6227 |theme| theme.colors().editor_highlighted_line_background,
6228 cx,
6229 );
6230 });
6231 })?;
6232
6233 Ok(())
6234 }
6235
6236 pub fn clear_code_action_providers(&mut self) {
6237 self.code_action_providers.clear();
6238 self.available_code_actions.take();
6239 }
6240
6241 pub fn add_code_action_provider(
6242 &mut self,
6243 provider: Rc<dyn CodeActionProvider>,
6244 window: &mut Window,
6245 cx: &mut Context<Self>,
6246 ) {
6247 if self
6248 .code_action_providers
6249 .iter()
6250 .any(|existing_provider| existing_provider.id() == provider.id())
6251 {
6252 return;
6253 }
6254
6255 self.code_action_providers.push(provider);
6256 self.refresh_code_actions(window, cx);
6257 }
6258
6259 pub fn remove_code_action_provider(
6260 &mut self,
6261 id: Arc<str>,
6262 window: &mut Window,
6263 cx: &mut Context<Self>,
6264 ) {
6265 self.code_action_providers
6266 .retain(|provider| provider.id() != id);
6267 self.refresh_code_actions(window, cx);
6268 }
6269
6270 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6271 !self.code_action_providers.is_empty()
6272 && EditorSettings::get_global(cx).toolbar.code_actions
6273 }
6274
6275 pub fn has_available_code_actions(&self) -> bool {
6276 self.available_code_actions
6277 .as_ref()
6278 .is_some_and(|(_, actions)| !actions.is_empty())
6279 }
6280
6281 fn render_inline_code_actions(
6282 &self,
6283 icon_size: ui::IconSize,
6284 display_row: DisplayRow,
6285 is_active: bool,
6286 cx: &mut Context<Self>,
6287 ) -> AnyElement {
6288 let show_tooltip = !self.context_menu_visible();
6289 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6290 .icon_size(icon_size)
6291 .shape(ui::IconButtonShape::Square)
6292 .style(ButtonStyle::Transparent)
6293 .icon_color(ui::Color::Hidden)
6294 .toggle_state(is_active)
6295 .when(show_tooltip, |this| {
6296 this.tooltip({
6297 let focus_handle = self.focus_handle.clone();
6298 move |window, cx| {
6299 Tooltip::for_action_in(
6300 "Toggle Code Actions",
6301 &ToggleCodeActions {
6302 deployed_from: None,
6303 quick_launch: false,
6304 },
6305 &focus_handle,
6306 window,
6307 cx,
6308 )
6309 }
6310 })
6311 })
6312 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6313 window.focus(&editor.focus_handle(cx));
6314 editor.toggle_code_actions(
6315 &crate::actions::ToggleCodeActions {
6316 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6317 display_row,
6318 )),
6319 quick_launch: false,
6320 },
6321 window,
6322 cx,
6323 );
6324 }))
6325 .into_any_element()
6326 }
6327
6328 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6329 &self.context_menu
6330 }
6331
6332 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6333 let newest_selection = self.selections.newest_anchor().clone();
6334 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6335 let buffer = self.buffer.read(cx);
6336 if newest_selection.head().diff_base_anchor.is_some() {
6337 return None;
6338 }
6339 let (start_buffer, start) =
6340 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6341 let (end_buffer, end) =
6342 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6343 if start_buffer != end_buffer {
6344 return None;
6345 }
6346
6347 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6348 cx.background_executor()
6349 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6350 .await;
6351
6352 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6353 let providers = this.code_action_providers.clone();
6354 let tasks = this
6355 .code_action_providers
6356 .iter()
6357 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6358 .collect::<Vec<_>>();
6359 (providers, tasks)
6360 })?;
6361
6362 let mut actions = Vec::new();
6363 for (provider, provider_actions) in
6364 providers.into_iter().zip(future::join_all(tasks).await)
6365 {
6366 if let Some(provider_actions) = provider_actions.log_err() {
6367 actions.extend(provider_actions.into_iter().map(|action| {
6368 AvailableCodeAction {
6369 excerpt_id: newest_selection.start.excerpt_id,
6370 action,
6371 provider: provider.clone(),
6372 }
6373 }));
6374 }
6375 }
6376
6377 this.update(cx, |this, cx| {
6378 this.available_code_actions = if actions.is_empty() {
6379 None
6380 } else {
6381 Some((
6382 Location {
6383 buffer: start_buffer,
6384 range: start..end,
6385 },
6386 actions.into(),
6387 ))
6388 };
6389 cx.notify();
6390 })
6391 }));
6392 None
6393 }
6394
6395 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6396 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6397 self.show_git_blame_inline = false;
6398
6399 self.show_git_blame_inline_delay_task =
6400 Some(cx.spawn_in(window, async move |this, cx| {
6401 cx.background_executor().timer(delay).await;
6402
6403 this.update(cx, |this, cx| {
6404 this.show_git_blame_inline = true;
6405 cx.notify();
6406 })
6407 .log_err();
6408 }));
6409 }
6410 }
6411
6412 fn show_blame_popover(
6413 &mut self,
6414 blame_entry: &BlameEntry,
6415 position: gpui::Point<Pixels>,
6416 cx: &mut Context<Self>,
6417 ) {
6418 if let Some(state) = &mut self.inline_blame_popover {
6419 state.hide_task.take();
6420 } else {
6421 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6422 let blame_entry = blame_entry.clone();
6423 let show_task = cx.spawn(async move |editor, cx| {
6424 cx.background_executor()
6425 .timer(std::time::Duration::from_millis(delay))
6426 .await;
6427 editor
6428 .update(cx, |editor, cx| {
6429 editor.inline_blame_popover_show_task.take();
6430 let Some(blame) = editor.blame.as_ref() else {
6431 return;
6432 };
6433 let blame = blame.read(cx);
6434 let details = blame.details_for_entry(&blame_entry);
6435 let markdown = cx.new(|cx| {
6436 Markdown::new(
6437 details
6438 .as_ref()
6439 .map(|message| message.message.clone())
6440 .unwrap_or_default(),
6441 None,
6442 None,
6443 cx,
6444 )
6445 });
6446 editor.inline_blame_popover = Some(InlineBlamePopover {
6447 position,
6448 hide_task: None,
6449 popover_bounds: None,
6450 popover_state: InlineBlamePopoverState {
6451 scroll_handle: ScrollHandle::new(),
6452 commit_message: details,
6453 markdown,
6454 },
6455 });
6456 cx.notify();
6457 })
6458 .ok();
6459 });
6460 self.inline_blame_popover_show_task = Some(show_task);
6461 }
6462 }
6463
6464 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6465 self.inline_blame_popover_show_task.take();
6466 if let Some(state) = &mut self.inline_blame_popover {
6467 let hide_task = cx.spawn(async move |editor, cx| {
6468 cx.background_executor()
6469 .timer(std::time::Duration::from_millis(100))
6470 .await;
6471 editor
6472 .update(cx, |editor, cx| {
6473 editor.inline_blame_popover.take();
6474 cx.notify();
6475 })
6476 .ok();
6477 });
6478 state.hide_task = Some(hide_task);
6479 }
6480 }
6481
6482 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6483 if self.pending_rename.is_some() {
6484 return None;
6485 }
6486
6487 let provider = self.semantics_provider.clone()?;
6488 let buffer = self.buffer.read(cx);
6489 let newest_selection = self.selections.newest_anchor().clone();
6490 let cursor_position = newest_selection.head();
6491 let (cursor_buffer, cursor_buffer_position) =
6492 buffer.text_anchor_for_position(cursor_position, cx)?;
6493 let (tail_buffer, tail_buffer_position) =
6494 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6495 if cursor_buffer != tail_buffer {
6496 return None;
6497 }
6498
6499 let snapshot = cursor_buffer.read(cx).snapshot();
6500 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6501 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6502 if start_word_range != end_word_range {
6503 self.document_highlights_task.take();
6504 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6505 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6506 return None;
6507 }
6508
6509 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6510 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6511 cx.background_executor()
6512 .timer(Duration::from_millis(debounce))
6513 .await;
6514
6515 let highlights = if let Some(highlights) = cx
6516 .update(|cx| {
6517 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6518 })
6519 .ok()
6520 .flatten()
6521 {
6522 highlights.await.log_err()
6523 } else {
6524 None
6525 };
6526
6527 if let Some(highlights) = highlights {
6528 this.update(cx, |this, cx| {
6529 if this.pending_rename.is_some() {
6530 return;
6531 }
6532
6533 let buffer_id = cursor_position.buffer_id;
6534 let buffer = this.buffer.read(cx);
6535 if !buffer
6536 .text_anchor_for_position(cursor_position, cx)
6537 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6538 {
6539 return;
6540 }
6541
6542 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6543 let mut write_ranges = Vec::new();
6544 let mut read_ranges = Vec::new();
6545 for highlight in highlights {
6546 for (excerpt_id, excerpt_range) in
6547 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6548 {
6549 let start = highlight
6550 .range
6551 .start
6552 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6553 let end = highlight
6554 .range
6555 .end
6556 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6557 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6558 continue;
6559 }
6560
6561 let range = Anchor {
6562 buffer_id,
6563 excerpt_id,
6564 text_anchor: start,
6565 diff_base_anchor: None,
6566 }..Anchor {
6567 buffer_id,
6568 excerpt_id,
6569 text_anchor: end,
6570 diff_base_anchor: None,
6571 };
6572 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6573 write_ranges.push(range);
6574 } else {
6575 read_ranges.push(range);
6576 }
6577 }
6578 }
6579
6580 this.highlight_background::<DocumentHighlightRead>(
6581 &read_ranges,
6582 |theme| theme.colors().editor_document_highlight_read_background,
6583 cx,
6584 );
6585 this.highlight_background::<DocumentHighlightWrite>(
6586 &write_ranges,
6587 |theme| theme.colors().editor_document_highlight_write_background,
6588 cx,
6589 );
6590 cx.notify();
6591 })
6592 .log_err();
6593 }
6594 }));
6595 None
6596 }
6597
6598 fn prepare_highlight_query_from_selection(
6599 &mut self,
6600 cx: &mut Context<Editor>,
6601 ) -> Option<(String, Range<Anchor>)> {
6602 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6603 return None;
6604 }
6605 if !EditorSettings::get_global(cx).selection_highlight {
6606 return None;
6607 }
6608 if self.selections.count() != 1 || self.selections.line_mode {
6609 return None;
6610 }
6611 let selection = self.selections.newest::<Point>(cx);
6612 if selection.is_empty() || selection.start.row != selection.end.row {
6613 return None;
6614 }
6615 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6616 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6617 let query = multi_buffer_snapshot
6618 .text_for_range(selection_anchor_range.clone())
6619 .collect::<String>();
6620 if query.trim().is_empty() {
6621 return None;
6622 }
6623 Some((query, selection_anchor_range))
6624 }
6625
6626 fn update_selection_occurrence_highlights(
6627 &mut self,
6628 query_text: String,
6629 query_range: Range<Anchor>,
6630 multi_buffer_range_to_query: Range<Point>,
6631 use_debounce: bool,
6632 window: &mut Window,
6633 cx: &mut Context<Editor>,
6634 ) -> Task<()> {
6635 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6636 cx.spawn_in(window, async move |editor, cx| {
6637 if use_debounce {
6638 cx.background_executor()
6639 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6640 .await;
6641 }
6642 let match_task = cx.background_spawn(async move {
6643 let buffer_ranges = multi_buffer_snapshot
6644 .range_to_buffer_ranges(multi_buffer_range_to_query)
6645 .into_iter()
6646 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6647 let mut match_ranges = Vec::new();
6648 let Ok(regex) = project::search::SearchQuery::text(
6649 query_text.clone(),
6650 false,
6651 false,
6652 false,
6653 Default::default(),
6654 Default::default(),
6655 false,
6656 None,
6657 ) else {
6658 return Vec::default();
6659 };
6660 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6661 match_ranges.extend(
6662 regex
6663 .search(&buffer_snapshot, Some(search_range.clone()))
6664 .await
6665 .into_iter()
6666 .filter_map(|match_range| {
6667 let match_start = buffer_snapshot
6668 .anchor_after(search_range.start + match_range.start);
6669 let match_end = buffer_snapshot
6670 .anchor_before(search_range.start + match_range.end);
6671 let match_anchor_range = Anchor::range_in_buffer(
6672 excerpt_id,
6673 buffer_snapshot.remote_id(),
6674 match_start..match_end,
6675 );
6676 (match_anchor_range != query_range).then_some(match_anchor_range)
6677 }),
6678 );
6679 }
6680 match_ranges
6681 });
6682 let match_ranges = match_task.await;
6683 editor
6684 .update_in(cx, |editor, _, cx| {
6685 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6686 if !match_ranges.is_empty() {
6687 editor.highlight_background::<SelectedTextHighlight>(
6688 &match_ranges,
6689 |theme| theme.colors().editor_document_highlight_bracket_background,
6690 cx,
6691 )
6692 }
6693 })
6694 .log_err();
6695 })
6696 }
6697
6698 fn refresh_selected_text_highlights(
6699 &mut self,
6700 on_buffer_edit: bool,
6701 window: &mut Window,
6702 cx: &mut Context<Editor>,
6703 ) {
6704 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6705 else {
6706 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6707 self.quick_selection_highlight_task.take();
6708 self.debounced_selection_highlight_task.take();
6709 return;
6710 };
6711 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6712 if on_buffer_edit
6713 || self
6714 .quick_selection_highlight_task
6715 .as_ref()
6716 .map_or(true, |(prev_anchor_range, _)| {
6717 prev_anchor_range != &query_range
6718 })
6719 {
6720 let multi_buffer_visible_start = self
6721 .scroll_manager
6722 .anchor()
6723 .anchor
6724 .to_point(&multi_buffer_snapshot);
6725 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6726 multi_buffer_visible_start
6727 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6728 Bias::Left,
6729 );
6730 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6731 self.quick_selection_highlight_task = Some((
6732 query_range.clone(),
6733 self.update_selection_occurrence_highlights(
6734 query_text.clone(),
6735 query_range.clone(),
6736 multi_buffer_visible_range,
6737 false,
6738 window,
6739 cx,
6740 ),
6741 ));
6742 }
6743 if on_buffer_edit
6744 || self
6745 .debounced_selection_highlight_task
6746 .as_ref()
6747 .map_or(true, |(prev_anchor_range, _)| {
6748 prev_anchor_range != &query_range
6749 })
6750 {
6751 let multi_buffer_start = multi_buffer_snapshot
6752 .anchor_before(0)
6753 .to_point(&multi_buffer_snapshot);
6754 let multi_buffer_end = multi_buffer_snapshot
6755 .anchor_after(multi_buffer_snapshot.len())
6756 .to_point(&multi_buffer_snapshot);
6757 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6758 self.debounced_selection_highlight_task = Some((
6759 query_range.clone(),
6760 self.update_selection_occurrence_highlights(
6761 query_text,
6762 query_range,
6763 multi_buffer_full_range,
6764 true,
6765 window,
6766 cx,
6767 ),
6768 ));
6769 }
6770 }
6771
6772 pub fn refresh_inline_completion(
6773 &mut self,
6774 debounce: bool,
6775 user_requested: bool,
6776 window: &mut Window,
6777 cx: &mut Context<Self>,
6778 ) -> Option<()> {
6779 let provider = self.edit_prediction_provider()?;
6780 let cursor = self.selections.newest_anchor().head();
6781 let (buffer, cursor_buffer_position) =
6782 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6783
6784 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6785 self.discard_inline_completion(false, cx);
6786 return None;
6787 }
6788
6789 if !user_requested
6790 && (!self.should_show_edit_predictions()
6791 || !self.is_focused(window)
6792 || buffer.read(cx).is_empty())
6793 {
6794 self.discard_inline_completion(false, cx);
6795 return None;
6796 }
6797
6798 self.update_visible_inline_completion(window, cx);
6799 provider.refresh(
6800 self.project.clone(),
6801 buffer,
6802 cursor_buffer_position,
6803 debounce,
6804 cx,
6805 );
6806 Some(())
6807 }
6808
6809 fn show_edit_predictions_in_menu(&self) -> bool {
6810 match self.edit_prediction_settings {
6811 EditPredictionSettings::Disabled => false,
6812 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6813 }
6814 }
6815
6816 pub fn edit_predictions_enabled(&self) -> bool {
6817 match self.edit_prediction_settings {
6818 EditPredictionSettings::Disabled => false,
6819 EditPredictionSettings::Enabled { .. } => true,
6820 }
6821 }
6822
6823 fn edit_prediction_requires_modifier(&self) -> bool {
6824 match self.edit_prediction_settings {
6825 EditPredictionSettings::Disabled => false,
6826 EditPredictionSettings::Enabled {
6827 preview_requires_modifier,
6828 ..
6829 } => preview_requires_modifier,
6830 }
6831 }
6832
6833 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6834 if self.edit_prediction_provider.is_none() {
6835 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6836 } else {
6837 let selection = self.selections.newest_anchor();
6838 let cursor = selection.head();
6839
6840 if let Some((buffer, cursor_buffer_position)) =
6841 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6842 {
6843 self.edit_prediction_settings =
6844 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6845 }
6846 }
6847 }
6848
6849 fn edit_prediction_settings_at_position(
6850 &self,
6851 buffer: &Entity<Buffer>,
6852 buffer_position: language::Anchor,
6853 cx: &App,
6854 ) -> EditPredictionSettings {
6855 if !self.mode.is_full()
6856 || !self.show_inline_completions_override.unwrap_or(true)
6857 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6858 {
6859 return EditPredictionSettings::Disabled;
6860 }
6861
6862 let buffer = buffer.read(cx);
6863
6864 let file = buffer.file();
6865
6866 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6867 return EditPredictionSettings::Disabled;
6868 };
6869
6870 let by_provider = matches!(
6871 self.menu_inline_completions_policy,
6872 MenuInlineCompletionsPolicy::ByProvider
6873 );
6874
6875 let show_in_menu = by_provider
6876 && self
6877 .edit_prediction_provider
6878 .as_ref()
6879 .map_or(false, |provider| {
6880 provider.provider.show_completions_in_menu()
6881 });
6882
6883 let preview_requires_modifier =
6884 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6885
6886 EditPredictionSettings::Enabled {
6887 show_in_menu,
6888 preview_requires_modifier,
6889 }
6890 }
6891
6892 fn should_show_edit_predictions(&self) -> bool {
6893 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6894 }
6895
6896 pub fn edit_prediction_preview_is_active(&self) -> bool {
6897 matches!(
6898 self.edit_prediction_preview,
6899 EditPredictionPreview::Active { .. }
6900 )
6901 }
6902
6903 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6904 let cursor = self.selections.newest_anchor().head();
6905 if let Some((buffer, cursor_position)) =
6906 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6907 {
6908 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6909 } else {
6910 false
6911 }
6912 }
6913
6914 pub fn supports_minimap(&self, cx: &App) -> bool {
6915 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6916 }
6917
6918 fn edit_predictions_enabled_in_buffer(
6919 &self,
6920 buffer: &Entity<Buffer>,
6921 buffer_position: language::Anchor,
6922 cx: &App,
6923 ) -> bool {
6924 maybe!({
6925 if self.read_only(cx) {
6926 return Some(false);
6927 }
6928 let provider = self.edit_prediction_provider()?;
6929 if !provider.is_enabled(&buffer, buffer_position, cx) {
6930 return Some(false);
6931 }
6932 let buffer = buffer.read(cx);
6933 let Some(file) = buffer.file() else {
6934 return Some(true);
6935 };
6936 let settings = all_language_settings(Some(file), cx);
6937 Some(settings.edit_predictions_enabled_for_file(file, cx))
6938 })
6939 .unwrap_or(false)
6940 }
6941
6942 fn cycle_inline_completion(
6943 &mut self,
6944 direction: Direction,
6945 window: &mut Window,
6946 cx: &mut Context<Self>,
6947 ) -> Option<()> {
6948 let provider = self.edit_prediction_provider()?;
6949 let cursor = self.selections.newest_anchor().head();
6950 let (buffer, cursor_buffer_position) =
6951 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6952 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6953 return None;
6954 }
6955
6956 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6957 self.update_visible_inline_completion(window, cx);
6958
6959 Some(())
6960 }
6961
6962 pub fn show_inline_completion(
6963 &mut self,
6964 _: &ShowEditPrediction,
6965 window: &mut Window,
6966 cx: &mut Context<Self>,
6967 ) {
6968 if !self.has_active_inline_completion() {
6969 self.refresh_inline_completion(false, true, window, cx);
6970 return;
6971 }
6972
6973 self.update_visible_inline_completion(window, cx);
6974 }
6975
6976 pub fn display_cursor_names(
6977 &mut self,
6978 _: &DisplayCursorNames,
6979 window: &mut Window,
6980 cx: &mut Context<Self>,
6981 ) {
6982 self.show_cursor_names(window, cx);
6983 }
6984
6985 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6986 self.show_cursor_names = true;
6987 cx.notify();
6988 cx.spawn_in(window, async move |this, cx| {
6989 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6990 this.update(cx, |this, cx| {
6991 this.show_cursor_names = false;
6992 cx.notify()
6993 })
6994 .ok()
6995 })
6996 .detach();
6997 }
6998
6999 pub fn next_edit_prediction(
7000 &mut self,
7001 _: &NextEditPrediction,
7002 window: &mut Window,
7003 cx: &mut Context<Self>,
7004 ) {
7005 if self.has_active_inline_completion() {
7006 self.cycle_inline_completion(Direction::Next, window, cx);
7007 } else {
7008 let is_copilot_disabled = self
7009 .refresh_inline_completion(false, true, window, cx)
7010 .is_none();
7011 if is_copilot_disabled {
7012 cx.propagate();
7013 }
7014 }
7015 }
7016
7017 pub fn previous_edit_prediction(
7018 &mut self,
7019 _: &PreviousEditPrediction,
7020 window: &mut Window,
7021 cx: &mut Context<Self>,
7022 ) {
7023 if self.has_active_inline_completion() {
7024 self.cycle_inline_completion(Direction::Prev, window, cx);
7025 } else {
7026 let is_copilot_disabled = self
7027 .refresh_inline_completion(false, true, window, cx)
7028 .is_none();
7029 if is_copilot_disabled {
7030 cx.propagate();
7031 }
7032 }
7033 }
7034
7035 pub fn accept_edit_prediction(
7036 &mut self,
7037 _: &AcceptEditPrediction,
7038 window: &mut Window,
7039 cx: &mut Context<Self>,
7040 ) {
7041 if self.show_edit_predictions_in_menu() {
7042 self.hide_context_menu(window, cx);
7043 }
7044
7045 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7046 return;
7047 };
7048
7049 self.report_inline_completion_event(
7050 active_inline_completion.completion_id.clone(),
7051 true,
7052 cx,
7053 );
7054
7055 match &active_inline_completion.completion {
7056 InlineCompletion::Move { target, .. } => {
7057 let target = *target;
7058
7059 if let Some(position_map) = &self.last_position_map {
7060 if position_map
7061 .visible_row_range
7062 .contains(&target.to_display_point(&position_map.snapshot).row())
7063 || !self.edit_prediction_requires_modifier()
7064 {
7065 self.unfold_ranges(&[target..target], true, false, cx);
7066 // Note that this is also done in vim's handler of the Tab action.
7067 self.change_selections(
7068 Some(Autoscroll::newest()),
7069 window,
7070 cx,
7071 |selections| {
7072 selections.select_anchor_ranges([target..target]);
7073 },
7074 );
7075 self.clear_row_highlights::<EditPredictionPreview>();
7076
7077 self.edit_prediction_preview
7078 .set_previous_scroll_position(None);
7079 } else {
7080 self.edit_prediction_preview
7081 .set_previous_scroll_position(Some(
7082 position_map.snapshot.scroll_anchor,
7083 ));
7084
7085 self.highlight_rows::<EditPredictionPreview>(
7086 target..target,
7087 cx.theme().colors().editor_highlighted_line_background,
7088 RowHighlightOptions {
7089 autoscroll: true,
7090 ..Default::default()
7091 },
7092 cx,
7093 );
7094 self.request_autoscroll(Autoscroll::fit(), cx);
7095 }
7096 }
7097 }
7098 InlineCompletion::Edit { edits, .. } => {
7099 if let Some(provider) = self.edit_prediction_provider() {
7100 provider.accept(cx);
7101 }
7102
7103 // Store the transaction ID and selections before applying the edit
7104 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7105
7106 let snapshot = self.buffer.read(cx).snapshot(cx);
7107 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7108
7109 self.buffer.update(cx, |buffer, cx| {
7110 buffer.edit(edits.iter().cloned(), None, cx)
7111 });
7112
7113 self.change_selections(None, window, cx, |s| {
7114 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7115 });
7116
7117 let selections = self.selections.disjoint_anchors();
7118 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7119 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7120 if has_new_transaction {
7121 self.selection_history
7122 .insert_transaction(transaction_id_now, selections);
7123 }
7124 }
7125
7126 self.update_visible_inline_completion(window, cx);
7127 if self.active_inline_completion.is_none() {
7128 self.refresh_inline_completion(true, true, window, cx);
7129 }
7130
7131 cx.notify();
7132 }
7133 }
7134
7135 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7136 }
7137
7138 pub fn accept_partial_inline_completion(
7139 &mut self,
7140 _: &AcceptPartialEditPrediction,
7141 window: &mut Window,
7142 cx: &mut Context<Self>,
7143 ) {
7144 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7145 return;
7146 };
7147 if self.selections.count() != 1 {
7148 return;
7149 }
7150
7151 self.report_inline_completion_event(
7152 active_inline_completion.completion_id.clone(),
7153 true,
7154 cx,
7155 );
7156
7157 match &active_inline_completion.completion {
7158 InlineCompletion::Move { target, .. } => {
7159 let target = *target;
7160 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7161 selections.select_anchor_ranges([target..target]);
7162 });
7163 }
7164 InlineCompletion::Edit { edits, .. } => {
7165 // Find an insertion that starts at the cursor position.
7166 let snapshot = self.buffer.read(cx).snapshot(cx);
7167 let cursor_offset = self.selections.newest::<usize>(cx).head();
7168 let insertion = edits.iter().find_map(|(range, text)| {
7169 let range = range.to_offset(&snapshot);
7170 if range.is_empty() && range.start == cursor_offset {
7171 Some(text)
7172 } else {
7173 None
7174 }
7175 });
7176
7177 if let Some(text) = insertion {
7178 let mut partial_completion = text
7179 .chars()
7180 .by_ref()
7181 .take_while(|c| c.is_alphabetic())
7182 .collect::<String>();
7183 if partial_completion.is_empty() {
7184 partial_completion = text
7185 .chars()
7186 .by_ref()
7187 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7188 .collect::<String>();
7189 }
7190
7191 cx.emit(EditorEvent::InputHandled {
7192 utf16_range_to_replace: None,
7193 text: partial_completion.clone().into(),
7194 });
7195
7196 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7197
7198 self.refresh_inline_completion(true, true, window, cx);
7199 cx.notify();
7200 } else {
7201 self.accept_edit_prediction(&Default::default(), window, cx);
7202 }
7203 }
7204 }
7205 }
7206
7207 fn discard_inline_completion(
7208 &mut self,
7209 should_report_inline_completion_event: bool,
7210 cx: &mut Context<Self>,
7211 ) -> bool {
7212 if should_report_inline_completion_event {
7213 let completion_id = self
7214 .active_inline_completion
7215 .as_ref()
7216 .and_then(|active_completion| active_completion.completion_id.clone());
7217
7218 self.report_inline_completion_event(completion_id, false, cx);
7219 }
7220
7221 if let Some(provider) = self.edit_prediction_provider() {
7222 provider.discard(cx);
7223 }
7224
7225 self.take_active_inline_completion(cx)
7226 }
7227
7228 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7229 let Some(provider) = self.edit_prediction_provider() else {
7230 return;
7231 };
7232
7233 let Some((_, buffer, _)) = self
7234 .buffer
7235 .read(cx)
7236 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7237 else {
7238 return;
7239 };
7240
7241 let extension = buffer
7242 .read(cx)
7243 .file()
7244 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7245
7246 let event_type = match accepted {
7247 true => "Edit Prediction Accepted",
7248 false => "Edit Prediction Discarded",
7249 };
7250 telemetry::event!(
7251 event_type,
7252 provider = provider.name(),
7253 prediction_id = id,
7254 suggestion_accepted = accepted,
7255 file_extension = extension,
7256 );
7257 }
7258
7259 pub fn has_active_inline_completion(&self) -> bool {
7260 self.active_inline_completion.is_some()
7261 }
7262
7263 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7264 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7265 return false;
7266 };
7267
7268 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7269 self.clear_highlights::<InlineCompletionHighlight>(cx);
7270 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7271 true
7272 }
7273
7274 /// Returns true when we're displaying the edit prediction popover below the cursor
7275 /// like we are not previewing and the LSP autocomplete menu is visible
7276 /// or we are in `when_holding_modifier` mode.
7277 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7278 if self.edit_prediction_preview_is_active()
7279 || !self.show_edit_predictions_in_menu()
7280 || !self.edit_predictions_enabled()
7281 {
7282 return false;
7283 }
7284
7285 if self.has_visible_completions_menu() {
7286 return true;
7287 }
7288
7289 has_completion && self.edit_prediction_requires_modifier()
7290 }
7291
7292 fn handle_modifiers_changed(
7293 &mut self,
7294 modifiers: Modifiers,
7295 position_map: &PositionMap,
7296 window: &mut Window,
7297 cx: &mut Context<Self>,
7298 ) {
7299 if self.show_edit_predictions_in_menu() {
7300 self.update_edit_prediction_preview(&modifiers, window, cx);
7301 }
7302
7303 self.update_selection_mode(&modifiers, position_map, window, cx);
7304
7305 let mouse_position = window.mouse_position();
7306 if !position_map.text_hitbox.is_hovered(window) {
7307 return;
7308 }
7309
7310 self.update_hovered_link(
7311 position_map.point_for_position(mouse_position),
7312 &position_map.snapshot,
7313 modifiers,
7314 window,
7315 cx,
7316 )
7317 }
7318
7319 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7320 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7321 if invert {
7322 match multi_cursor_setting {
7323 MultiCursorModifier::Alt => modifiers.alt,
7324 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7325 }
7326 } else {
7327 match multi_cursor_setting {
7328 MultiCursorModifier::Alt => modifiers.secondary(),
7329 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7330 }
7331 }
7332 }
7333
7334 fn columnar_selection_mode(
7335 modifiers: &Modifiers,
7336 cx: &mut Context<Self>,
7337 ) -> Option<ColumnarMode> {
7338 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7339 if Self::multi_cursor_modifier(false, modifiers, cx) {
7340 Some(ColumnarMode::FromMouse)
7341 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7342 Some(ColumnarMode::FromSelection)
7343 } else {
7344 None
7345 }
7346 } else {
7347 None
7348 }
7349 }
7350
7351 fn update_selection_mode(
7352 &mut self,
7353 modifiers: &Modifiers,
7354 position_map: &PositionMap,
7355 window: &mut Window,
7356 cx: &mut Context<Self>,
7357 ) {
7358 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7359 return;
7360 };
7361 if self.selections.pending.is_none() {
7362 return;
7363 }
7364
7365 let mouse_position = window.mouse_position();
7366 let point_for_position = position_map.point_for_position(mouse_position);
7367 let position = point_for_position.previous_valid;
7368
7369 self.select(
7370 SelectPhase::BeginColumnar {
7371 position,
7372 reset: false,
7373 mode,
7374 goal_column: point_for_position.exact_unclipped.column(),
7375 },
7376 window,
7377 cx,
7378 );
7379 }
7380
7381 fn update_edit_prediction_preview(
7382 &mut self,
7383 modifiers: &Modifiers,
7384 window: &mut Window,
7385 cx: &mut Context<Self>,
7386 ) {
7387 let mut modifiers_held = false;
7388 if let Some(accept_keystroke) = self
7389 .accept_edit_prediction_keybind(false, window, cx)
7390 .keystroke()
7391 {
7392 modifiers_held = modifiers_held
7393 || (&accept_keystroke.modifiers == modifiers
7394 && accept_keystroke.modifiers.modified());
7395 };
7396 if let Some(accept_partial_keystroke) = self
7397 .accept_edit_prediction_keybind(true, window, cx)
7398 .keystroke()
7399 {
7400 modifiers_held = modifiers_held
7401 || (&accept_partial_keystroke.modifiers == modifiers
7402 && accept_partial_keystroke.modifiers.modified());
7403 }
7404
7405 if modifiers_held {
7406 if matches!(
7407 self.edit_prediction_preview,
7408 EditPredictionPreview::Inactive { .. }
7409 ) {
7410 self.edit_prediction_preview = EditPredictionPreview::Active {
7411 previous_scroll_position: None,
7412 since: Instant::now(),
7413 };
7414
7415 self.update_visible_inline_completion(window, cx);
7416 cx.notify();
7417 }
7418 } else if let EditPredictionPreview::Active {
7419 previous_scroll_position,
7420 since,
7421 } = self.edit_prediction_preview
7422 {
7423 if let (Some(previous_scroll_position), Some(position_map)) =
7424 (previous_scroll_position, self.last_position_map.as_ref())
7425 {
7426 self.set_scroll_position(
7427 previous_scroll_position
7428 .scroll_position(&position_map.snapshot.display_snapshot),
7429 window,
7430 cx,
7431 );
7432 }
7433
7434 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7435 released_too_fast: since.elapsed() < Duration::from_millis(200),
7436 };
7437 self.clear_row_highlights::<EditPredictionPreview>();
7438 self.update_visible_inline_completion(window, cx);
7439 cx.notify();
7440 }
7441 }
7442
7443 fn update_visible_inline_completion(
7444 &mut self,
7445 _window: &mut Window,
7446 cx: &mut Context<Self>,
7447 ) -> Option<()> {
7448 let selection = self.selections.newest_anchor();
7449 let cursor = selection.head();
7450 let multibuffer = self.buffer.read(cx).snapshot(cx);
7451 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7452 let excerpt_id = cursor.excerpt_id;
7453
7454 let show_in_menu = self.show_edit_predictions_in_menu();
7455 let completions_menu_has_precedence = !show_in_menu
7456 && (self.context_menu.borrow().is_some()
7457 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7458
7459 if completions_menu_has_precedence
7460 || !offset_selection.is_empty()
7461 || self
7462 .active_inline_completion
7463 .as_ref()
7464 .map_or(false, |completion| {
7465 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7466 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7467 !invalidation_range.contains(&offset_selection.head())
7468 })
7469 {
7470 self.discard_inline_completion(false, cx);
7471 return None;
7472 }
7473
7474 self.take_active_inline_completion(cx);
7475 let Some(provider) = self.edit_prediction_provider() else {
7476 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7477 return None;
7478 };
7479
7480 let (buffer, cursor_buffer_position) =
7481 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7482
7483 self.edit_prediction_settings =
7484 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7485
7486 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7487
7488 if self.edit_prediction_indent_conflict {
7489 let cursor_point = cursor.to_point(&multibuffer);
7490
7491 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7492
7493 if let Some((_, indent)) = indents.iter().next() {
7494 if indent.len == cursor_point.column {
7495 self.edit_prediction_indent_conflict = false;
7496 }
7497 }
7498 }
7499
7500 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7501 let edits = inline_completion
7502 .edits
7503 .into_iter()
7504 .flat_map(|(range, new_text)| {
7505 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7506 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7507 Some((start..end, new_text))
7508 })
7509 .collect::<Vec<_>>();
7510 if edits.is_empty() {
7511 return None;
7512 }
7513
7514 let first_edit_start = edits.first().unwrap().0.start;
7515 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7516 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7517
7518 let last_edit_end = edits.last().unwrap().0.end;
7519 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7520 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7521
7522 let cursor_row = cursor.to_point(&multibuffer).row;
7523
7524 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7525
7526 let mut inlay_ids = Vec::new();
7527 let invalidation_row_range;
7528 let move_invalidation_row_range = if cursor_row < edit_start_row {
7529 Some(cursor_row..edit_end_row)
7530 } else if cursor_row > edit_end_row {
7531 Some(edit_start_row..cursor_row)
7532 } else {
7533 None
7534 };
7535 let is_move =
7536 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7537 let completion = if is_move {
7538 invalidation_row_range =
7539 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7540 let target = first_edit_start;
7541 InlineCompletion::Move { target, snapshot }
7542 } else {
7543 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7544 && !self.inline_completions_hidden_for_vim_mode;
7545
7546 if show_completions_in_buffer {
7547 if edits
7548 .iter()
7549 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7550 {
7551 let mut inlays = Vec::new();
7552 for (range, new_text) in &edits {
7553 let inlay = Inlay::inline_completion(
7554 post_inc(&mut self.next_inlay_id),
7555 range.start,
7556 new_text.as_str(),
7557 );
7558 inlay_ids.push(inlay.id);
7559 inlays.push(inlay);
7560 }
7561
7562 self.splice_inlays(&[], inlays, cx);
7563 } else {
7564 let background_color = cx.theme().status().deleted_background;
7565 self.highlight_text::<InlineCompletionHighlight>(
7566 edits.iter().map(|(range, _)| range.clone()).collect(),
7567 HighlightStyle {
7568 background_color: Some(background_color),
7569 ..Default::default()
7570 },
7571 cx,
7572 );
7573 }
7574 }
7575
7576 invalidation_row_range = edit_start_row..edit_end_row;
7577
7578 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7579 if provider.show_tab_accept_marker() {
7580 EditDisplayMode::TabAccept
7581 } else {
7582 EditDisplayMode::Inline
7583 }
7584 } else {
7585 EditDisplayMode::DiffPopover
7586 };
7587
7588 InlineCompletion::Edit {
7589 edits,
7590 edit_preview: inline_completion.edit_preview,
7591 display_mode,
7592 snapshot,
7593 }
7594 };
7595
7596 let invalidation_range = multibuffer
7597 .anchor_before(Point::new(invalidation_row_range.start, 0))
7598 ..multibuffer.anchor_after(Point::new(
7599 invalidation_row_range.end,
7600 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7601 ));
7602
7603 self.stale_inline_completion_in_menu = None;
7604 self.active_inline_completion = Some(InlineCompletionState {
7605 inlay_ids,
7606 completion,
7607 completion_id: inline_completion.id,
7608 invalidation_range,
7609 });
7610
7611 cx.notify();
7612
7613 Some(())
7614 }
7615
7616 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7617 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7618 }
7619
7620 fn clear_tasks(&mut self) {
7621 self.tasks.clear()
7622 }
7623
7624 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7625 if self.tasks.insert(key, value).is_some() {
7626 // This case should hopefully be rare, but just in case...
7627 log::error!(
7628 "multiple different run targets found on a single line, only the last target will be rendered"
7629 )
7630 }
7631 }
7632
7633 /// Get all display points of breakpoints that will be rendered within editor
7634 ///
7635 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7636 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7637 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7638 fn active_breakpoints(
7639 &self,
7640 range: Range<DisplayRow>,
7641 window: &mut Window,
7642 cx: &mut Context<Self>,
7643 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7644 let mut breakpoint_display_points = HashMap::default();
7645
7646 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7647 return breakpoint_display_points;
7648 };
7649
7650 let snapshot = self.snapshot(window, cx);
7651
7652 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7653 let Some(project) = self.project.as_ref() else {
7654 return breakpoint_display_points;
7655 };
7656
7657 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7658 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7659
7660 for (buffer_snapshot, range, excerpt_id) in
7661 multi_buffer_snapshot.range_to_buffer_ranges(range)
7662 {
7663 let Some(buffer) = project
7664 .read(cx)
7665 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7666 else {
7667 continue;
7668 };
7669 let breakpoints = breakpoint_store.read(cx).breakpoints(
7670 &buffer,
7671 Some(
7672 buffer_snapshot.anchor_before(range.start)
7673 ..buffer_snapshot.anchor_after(range.end),
7674 ),
7675 buffer_snapshot,
7676 cx,
7677 );
7678 for (breakpoint, state) in breakpoints {
7679 let multi_buffer_anchor =
7680 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7681 let position = multi_buffer_anchor
7682 .to_point(&multi_buffer_snapshot)
7683 .to_display_point(&snapshot);
7684
7685 breakpoint_display_points.insert(
7686 position.row(),
7687 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7688 );
7689 }
7690 }
7691
7692 breakpoint_display_points
7693 }
7694
7695 fn breakpoint_context_menu(
7696 &self,
7697 anchor: Anchor,
7698 window: &mut Window,
7699 cx: &mut Context<Self>,
7700 ) -> Entity<ui::ContextMenu> {
7701 let weak_editor = cx.weak_entity();
7702 let focus_handle = self.focus_handle(cx);
7703
7704 let row = self
7705 .buffer
7706 .read(cx)
7707 .snapshot(cx)
7708 .summary_for_anchor::<Point>(&anchor)
7709 .row;
7710
7711 let breakpoint = self
7712 .breakpoint_at_row(row, window, cx)
7713 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7714
7715 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7716 "Edit Log Breakpoint"
7717 } else {
7718 "Set Log Breakpoint"
7719 };
7720
7721 let condition_breakpoint_msg = if breakpoint
7722 .as_ref()
7723 .is_some_and(|bp| bp.1.condition.is_some())
7724 {
7725 "Edit Condition Breakpoint"
7726 } else {
7727 "Set Condition Breakpoint"
7728 };
7729
7730 let hit_condition_breakpoint_msg = if breakpoint
7731 .as_ref()
7732 .is_some_and(|bp| bp.1.hit_condition.is_some())
7733 {
7734 "Edit Hit Condition Breakpoint"
7735 } else {
7736 "Set Hit Condition Breakpoint"
7737 };
7738
7739 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7740 "Unset Breakpoint"
7741 } else {
7742 "Set Breakpoint"
7743 };
7744
7745 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7746
7747 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7748 BreakpointState::Enabled => Some("Disable"),
7749 BreakpointState::Disabled => Some("Enable"),
7750 });
7751
7752 let (anchor, breakpoint) =
7753 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7754
7755 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7756 menu.on_blur_subscription(Subscription::new(|| {}))
7757 .context(focus_handle)
7758 .when(run_to_cursor, |this| {
7759 let weak_editor = weak_editor.clone();
7760 this.entry("Run to cursor", None, move |window, cx| {
7761 weak_editor
7762 .update(cx, |editor, cx| {
7763 editor.change_selections(None, window, cx, |s| {
7764 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7765 });
7766 })
7767 .ok();
7768
7769 window.dispatch_action(Box::new(RunToCursor), cx);
7770 })
7771 .separator()
7772 })
7773 .when_some(toggle_state_msg, |this, msg| {
7774 this.entry(msg, None, {
7775 let weak_editor = weak_editor.clone();
7776 let breakpoint = breakpoint.clone();
7777 move |_window, cx| {
7778 weak_editor
7779 .update(cx, |this, cx| {
7780 this.edit_breakpoint_at_anchor(
7781 anchor,
7782 breakpoint.as_ref().clone(),
7783 BreakpointEditAction::InvertState,
7784 cx,
7785 );
7786 })
7787 .log_err();
7788 }
7789 })
7790 })
7791 .entry(set_breakpoint_msg, None, {
7792 let weak_editor = weak_editor.clone();
7793 let breakpoint = breakpoint.clone();
7794 move |_window, cx| {
7795 weak_editor
7796 .update(cx, |this, cx| {
7797 this.edit_breakpoint_at_anchor(
7798 anchor,
7799 breakpoint.as_ref().clone(),
7800 BreakpointEditAction::Toggle,
7801 cx,
7802 );
7803 })
7804 .log_err();
7805 }
7806 })
7807 .entry(log_breakpoint_msg, None, {
7808 let breakpoint = breakpoint.clone();
7809 let weak_editor = weak_editor.clone();
7810 move |window, cx| {
7811 weak_editor
7812 .update(cx, |this, cx| {
7813 this.add_edit_breakpoint_block(
7814 anchor,
7815 breakpoint.as_ref(),
7816 BreakpointPromptEditAction::Log,
7817 window,
7818 cx,
7819 );
7820 })
7821 .log_err();
7822 }
7823 })
7824 .entry(condition_breakpoint_msg, None, {
7825 let breakpoint = breakpoint.clone();
7826 let weak_editor = weak_editor.clone();
7827 move |window, cx| {
7828 weak_editor
7829 .update(cx, |this, cx| {
7830 this.add_edit_breakpoint_block(
7831 anchor,
7832 breakpoint.as_ref(),
7833 BreakpointPromptEditAction::Condition,
7834 window,
7835 cx,
7836 );
7837 })
7838 .log_err();
7839 }
7840 })
7841 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7842 weak_editor
7843 .update(cx, |this, cx| {
7844 this.add_edit_breakpoint_block(
7845 anchor,
7846 breakpoint.as_ref(),
7847 BreakpointPromptEditAction::HitCondition,
7848 window,
7849 cx,
7850 );
7851 })
7852 .log_err();
7853 })
7854 })
7855 }
7856
7857 fn render_breakpoint(
7858 &self,
7859 position: Anchor,
7860 row: DisplayRow,
7861 breakpoint: &Breakpoint,
7862 state: Option<BreakpointSessionState>,
7863 cx: &mut Context<Self>,
7864 ) -> IconButton {
7865 let is_rejected = state.is_some_and(|s| !s.verified);
7866 // Is it a breakpoint that shows up when hovering over gutter?
7867 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7868 (false, false),
7869 |PhantomBreakpointIndicator {
7870 is_active,
7871 display_row,
7872 collides_with_existing_breakpoint,
7873 }| {
7874 (
7875 is_active && display_row == row,
7876 collides_with_existing_breakpoint,
7877 )
7878 },
7879 );
7880
7881 let (color, icon) = {
7882 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7883 (false, false) => ui::IconName::DebugBreakpoint,
7884 (true, false) => ui::IconName::DebugLogBreakpoint,
7885 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7886 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7887 };
7888
7889 let color = if is_phantom {
7890 Color::Hint
7891 } else if is_rejected {
7892 Color::Disabled
7893 } else {
7894 Color::Debugger
7895 };
7896
7897 (color, icon)
7898 };
7899
7900 let breakpoint = Arc::from(breakpoint.clone());
7901
7902 let alt_as_text = gpui::Keystroke {
7903 modifiers: Modifiers::secondary_key(),
7904 ..Default::default()
7905 };
7906 let primary_action_text = if breakpoint.is_disabled() {
7907 "Enable breakpoint"
7908 } else if is_phantom && !collides_with_existing {
7909 "Set breakpoint"
7910 } else {
7911 "Unset breakpoint"
7912 };
7913 let focus_handle = self.focus_handle.clone();
7914
7915 let meta = if is_rejected {
7916 SharedString::from("No executable code is associated with this line.")
7917 } else if collides_with_existing && !breakpoint.is_disabled() {
7918 SharedString::from(format!(
7919 "{alt_as_text}-click to disable,\nright-click for more options."
7920 ))
7921 } else {
7922 SharedString::from("Right-click for more options.")
7923 };
7924 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7925 .icon_size(IconSize::XSmall)
7926 .size(ui::ButtonSize::None)
7927 .when(is_rejected, |this| {
7928 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7929 })
7930 .icon_color(color)
7931 .style(ButtonStyle::Transparent)
7932 .on_click(cx.listener({
7933 let breakpoint = breakpoint.clone();
7934
7935 move |editor, event: &ClickEvent, window, cx| {
7936 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7937 BreakpointEditAction::InvertState
7938 } else {
7939 BreakpointEditAction::Toggle
7940 };
7941
7942 window.focus(&editor.focus_handle(cx));
7943 editor.edit_breakpoint_at_anchor(
7944 position,
7945 breakpoint.as_ref().clone(),
7946 edit_action,
7947 cx,
7948 );
7949 }
7950 }))
7951 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7952 editor.set_breakpoint_context_menu(
7953 row,
7954 Some(position),
7955 event.down.position,
7956 window,
7957 cx,
7958 );
7959 }))
7960 .tooltip(move |window, cx| {
7961 Tooltip::with_meta_in(
7962 primary_action_text,
7963 Some(&ToggleBreakpoint),
7964 meta.clone(),
7965 &focus_handle,
7966 window,
7967 cx,
7968 )
7969 })
7970 }
7971
7972 fn build_tasks_context(
7973 project: &Entity<Project>,
7974 buffer: &Entity<Buffer>,
7975 buffer_row: u32,
7976 tasks: &Arc<RunnableTasks>,
7977 cx: &mut Context<Self>,
7978 ) -> Task<Option<task::TaskContext>> {
7979 let position = Point::new(buffer_row, tasks.column);
7980 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7981 let location = Location {
7982 buffer: buffer.clone(),
7983 range: range_start..range_start,
7984 };
7985 // Fill in the environmental variables from the tree-sitter captures
7986 let mut captured_task_variables = TaskVariables::default();
7987 for (capture_name, value) in tasks.extra_variables.clone() {
7988 captured_task_variables.insert(
7989 task::VariableName::Custom(capture_name.into()),
7990 value.clone(),
7991 );
7992 }
7993 project.update(cx, |project, cx| {
7994 project.task_store().update(cx, |task_store, cx| {
7995 task_store.task_context_for_location(captured_task_variables, location, cx)
7996 })
7997 })
7998 }
7999
8000 pub fn spawn_nearest_task(
8001 &mut self,
8002 action: &SpawnNearestTask,
8003 window: &mut Window,
8004 cx: &mut Context<Self>,
8005 ) {
8006 let Some((workspace, _)) = self.workspace.clone() else {
8007 return;
8008 };
8009 let Some(project) = self.project.clone() else {
8010 return;
8011 };
8012
8013 // Try to find a closest, enclosing node using tree-sitter that has a
8014 // task
8015 let Some((buffer, buffer_row, tasks)) = self
8016 .find_enclosing_node_task(cx)
8017 // Or find the task that's closest in row-distance.
8018 .or_else(|| self.find_closest_task(cx))
8019 else {
8020 return;
8021 };
8022
8023 let reveal_strategy = action.reveal;
8024 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8025 cx.spawn_in(window, async move |_, cx| {
8026 let context = task_context.await?;
8027 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8028
8029 let resolved = &mut resolved_task.resolved;
8030 resolved.reveal = reveal_strategy;
8031
8032 workspace
8033 .update_in(cx, |workspace, window, cx| {
8034 workspace.schedule_resolved_task(
8035 task_source_kind,
8036 resolved_task,
8037 false,
8038 window,
8039 cx,
8040 );
8041 })
8042 .ok()
8043 })
8044 .detach();
8045 }
8046
8047 fn find_closest_task(
8048 &mut self,
8049 cx: &mut Context<Self>,
8050 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8051 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8052
8053 let ((buffer_id, row), tasks) = self
8054 .tasks
8055 .iter()
8056 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8057
8058 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8059 let tasks = Arc::new(tasks.to_owned());
8060 Some((buffer, *row, tasks))
8061 }
8062
8063 fn find_enclosing_node_task(
8064 &mut self,
8065 cx: &mut Context<Self>,
8066 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8067 let snapshot = self.buffer.read(cx).snapshot(cx);
8068 let offset = self.selections.newest::<usize>(cx).head();
8069 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8070 let buffer_id = excerpt.buffer().remote_id();
8071
8072 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8073 let mut cursor = layer.node().walk();
8074
8075 while cursor.goto_first_child_for_byte(offset).is_some() {
8076 if cursor.node().end_byte() == offset {
8077 cursor.goto_next_sibling();
8078 }
8079 }
8080
8081 // Ascend to the smallest ancestor that contains the range and has a task.
8082 loop {
8083 let node = cursor.node();
8084 let node_range = node.byte_range();
8085 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8086
8087 // Check if this node contains our offset
8088 if node_range.start <= offset && node_range.end >= offset {
8089 // If it contains offset, check for task
8090 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8091 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8092 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8093 }
8094 }
8095
8096 if !cursor.goto_parent() {
8097 break;
8098 }
8099 }
8100 None
8101 }
8102
8103 fn render_run_indicator(
8104 &self,
8105 _style: &EditorStyle,
8106 is_active: bool,
8107 row: DisplayRow,
8108 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8109 cx: &mut Context<Self>,
8110 ) -> IconButton {
8111 let color = Color::Muted;
8112 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8113
8114 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8115 .shape(ui::IconButtonShape::Square)
8116 .icon_size(IconSize::XSmall)
8117 .icon_color(color)
8118 .toggle_state(is_active)
8119 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8120 let quick_launch = e.down.button == MouseButton::Left;
8121 window.focus(&editor.focus_handle(cx));
8122 editor.toggle_code_actions(
8123 &ToggleCodeActions {
8124 deployed_from: Some(CodeActionSource::RunMenu(row)),
8125 quick_launch,
8126 },
8127 window,
8128 cx,
8129 );
8130 }))
8131 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8132 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8133 }))
8134 }
8135
8136 pub fn context_menu_visible(&self) -> bool {
8137 !self.edit_prediction_preview_is_active()
8138 && self
8139 .context_menu
8140 .borrow()
8141 .as_ref()
8142 .map_or(false, |menu| menu.visible())
8143 }
8144
8145 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8146 self.context_menu
8147 .borrow()
8148 .as_ref()
8149 .map(|menu| menu.origin())
8150 }
8151
8152 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8153 self.context_menu_options = Some(options);
8154 }
8155
8156 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8157 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8158
8159 fn render_edit_prediction_popover(
8160 &mut self,
8161 text_bounds: &Bounds<Pixels>,
8162 content_origin: gpui::Point<Pixels>,
8163 right_margin: Pixels,
8164 editor_snapshot: &EditorSnapshot,
8165 visible_row_range: Range<DisplayRow>,
8166 scroll_top: f32,
8167 scroll_bottom: f32,
8168 line_layouts: &[LineWithInvisibles],
8169 line_height: Pixels,
8170 scroll_pixel_position: gpui::Point<Pixels>,
8171 newest_selection_head: Option<DisplayPoint>,
8172 editor_width: Pixels,
8173 style: &EditorStyle,
8174 window: &mut Window,
8175 cx: &mut App,
8176 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8177 if self.mode().is_minimap() {
8178 return None;
8179 }
8180 let active_inline_completion = self.active_inline_completion.as_ref()?;
8181
8182 if self.edit_prediction_visible_in_cursor_popover(true) {
8183 return None;
8184 }
8185
8186 match &active_inline_completion.completion {
8187 InlineCompletion::Move { target, .. } => {
8188 let target_display_point = target.to_display_point(editor_snapshot);
8189
8190 if self.edit_prediction_requires_modifier() {
8191 if !self.edit_prediction_preview_is_active() {
8192 return None;
8193 }
8194
8195 self.render_edit_prediction_modifier_jump_popover(
8196 text_bounds,
8197 content_origin,
8198 visible_row_range,
8199 line_layouts,
8200 line_height,
8201 scroll_pixel_position,
8202 newest_selection_head,
8203 target_display_point,
8204 window,
8205 cx,
8206 )
8207 } else {
8208 self.render_edit_prediction_eager_jump_popover(
8209 text_bounds,
8210 content_origin,
8211 editor_snapshot,
8212 visible_row_range,
8213 scroll_top,
8214 scroll_bottom,
8215 line_height,
8216 scroll_pixel_position,
8217 target_display_point,
8218 editor_width,
8219 window,
8220 cx,
8221 )
8222 }
8223 }
8224 InlineCompletion::Edit {
8225 display_mode: EditDisplayMode::Inline,
8226 ..
8227 } => None,
8228 InlineCompletion::Edit {
8229 display_mode: EditDisplayMode::TabAccept,
8230 edits,
8231 ..
8232 } => {
8233 let range = &edits.first()?.0;
8234 let target_display_point = range.end.to_display_point(editor_snapshot);
8235
8236 self.render_edit_prediction_end_of_line_popover(
8237 "Accept",
8238 editor_snapshot,
8239 visible_row_range,
8240 target_display_point,
8241 line_height,
8242 scroll_pixel_position,
8243 content_origin,
8244 editor_width,
8245 window,
8246 cx,
8247 )
8248 }
8249 InlineCompletion::Edit {
8250 edits,
8251 edit_preview,
8252 display_mode: EditDisplayMode::DiffPopover,
8253 snapshot,
8254 } => self.render_edit_prediction_diff_popover(
8255 text_bounds,
8256 content_origin,
8257 right_margin,
8258 editor_snapshot,
8259 visible_row_range,
8260 line_layouts,
8261 line_height,
8262 scroll_pixel_position,
8263 newest_selection_head,
8264 editor_width,
8265 style,
8266 edits,
8267 edit_preview,
8268 snapshot,
8269 window,
8270 cx,
8271 ),
8272 }
8273 }
8274
8275 fn render_edit_prediction_modifier_jump_popover(
8276 &mut self,
8277 text_bounds: &Bounds<Pixels>,
8278 content_origin: gpui::Point<Pixels>,
8279 visible_row_range: Range<DisplayRow>,
8280 line_layouts: &[LineWithInvisibles],
8281 line_height: Pixels,
8282 scroll_pixel_position: gpui::Point<Pixels>,
8283 newest_selection_head: Option<DisplayPoint>,
8284 target_display_point: DisplayPoint,
8285 window: &mut Window,
8286 cx: &mut App,
8287 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8288 let scrolled_content_origin =
8289 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8290
8291 const SCROLL_PADDING_Y: Pixels = px(12.);
8292
8293 if target_display_point.row() < visible_row_range.start {
8294 return self.render_edit_prediction_scroll_popover(
8295 |_| SCROLL_PADDING_Y,
8296 IconName::ArrowUp,
8297 visible_row_range,
8298 line_layouts,
8299 newest_selection_head,
8300 scrolled_content_origin,
8301 window,
8302 cx,
8303 );
8304 } else if target_display_point.row() >= visible_row_range.end {
8305 return self.render_edit_prediction_scroll_popover(
8306 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8307 IconName::ArrowDown,
8308 visible_row_range,
8309 line_layouts,
8310 newest_selection_head,
8311 scrolled_content_origin,
8312 window,
8313 cx,
8314 );
8315 }
8316
8317 const POLE_WIDTH: Pixels = px(2.);
8318
8319 let line_layout =
8320 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8321 let target_column = target_display_point.column() as usize;
8322
8323 let target_x = line_layout.x_for_index(target_column);
8324 let target_y =
8325 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8326
8327 let flag_on_right = target_x < text_bounds.size.width / 2.;
8328
8329 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8330 border_color.l += 0.001;
8331
8332 let mut element = v_flex()
8333 .items_end()
8334 .when(flag_on_right, |el| el.items_start())
8335 .child(if flag_on_right {
8336 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8337 .rounded_bl(px(0.))
8338 .rounded_tl(px(0.))
8339 .border_l_2()
8340 .border_color(border_color)
8341 } else {
8342 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8343 .rounded_br(px(0.))
8344 .rounded_tr(px(0.))
8345 .border_r_2()
8346 .border_color(border_color)
8347 })
8348 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8349 .into_any();
8350
8351 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8352
8353 let mut origin = scrolled_content_origin + point(target_x, target_y)
8354 - point(
8355 if flag_on_right {
8356 POLE_WIDTH
8357 } else {
8358 size.width - POLE_WIDTH
8359 },
8360 size.height - line_height,
8361 );
8362
8363 origin.x = origin.x.max(content_origin.x);
8364
8365 element.prepaint_at(origin, window, cx);
8366
8367 Some((element, origin))
8368 }
8369
8370 fn render_edit_prediction_scroll_popover(
8371 &mut self,
8372 to_y: impl Fn(Size<Pixels>) -> Pixels,
8373 scroll_icon: IconName,
8374 visible_row_range: Range<DisplayRow>,
8375 line_layouts: &[LineWithInvisibles],
8376 newest_selection_head: Option<DisplayPoint>,
8377 scrolled_content_origin: gpui::Point<Pixels>,
8378 window: &mut Window,
8379 cx: &mut App,
8380 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8381 let mut element = self
8382 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8383 .into_any();
8384
8385 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8386
8387 let cursor = newest_selection_head?;
8388 let cursor_row_layout =
8389 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8390 let cursor_column = cursor.column() as usize;
8391
8392 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8393
8394 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8395
8396 element.prepaint_at(origin, window, cx);
8397 Some((element, origin))
8398 }
8399
8400 fn render_edit_prediction_eager_jump_popover(
8401 &mut self,
8402 text_bounds: &Bounds<Pixels>,
8403 content_origin: gpui::Point<Pixels>,
8404 editor_snapshot: &EditorSnapshot,
8405 visible_row_range: Range<DisplayRow>,
8406 scroll_top: f32,
8407 scroll_bottom: f32,
8408 line_height: Pixels,
8409 scroll_pixel_position: gpui::Point<Pixels>,
8410 target_display_point: DisplayPoint,
8411 editor_width: Pixels,
8412 window: &mut Window,
8413 cx: &mut App,
8414 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8415 if target_display_point.row().as_f32() < scroll_top {
8416 let mut element = self
8417 .render_edit_prediction_line_popover(
8418 "Jump to Edit",
8419 Some(IconName::ArrowUp),
8420 window,
8421 cx,
8422 )?
8423 .into_any();
8424
8425 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8426 let offset = point(
8427 (text_bounds.size.width - size.width) / 2.,
8428 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8429 );
8430
8431 let origin = text_bounds.origin + offset;
8432 element.prepaint_at(origin, window, cx);
8433 Some((element, origin))
8434 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8435 let mut element = self
8436 .render_edit_prediction_line_popover(
8437 "Jump to Edit",
8438 Some(IconName::ArrowDown),
8439 window,
8440 cx,
8441 )?
8442 .into_any();
8443
8444 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8445 let offset = point(
8446 (text_bounds.size.width - size.width) / 2.,
8447 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8448 );
8449
8450 let origin = text_bounds.origin + offset;
8451 element.prepaint_at(origin, window, cx);
8452 Some((element, origin))
8453 } else {
8454 self.render_edit_prediction_end_of_line_popover(
8455 "Jump to Edit",
8456 editor_snapshot,
8457 visible_row_range,
8458 target_display_point,
8459 line_height,
8460 scroll_pixel_position,
8461 content_origin,
8462 editor_width,
8463 window,
8464 cx,
8465 )
8466 }
8467 }
8468
8469 fn render_edit_prediction_end_of_line_popover(
8470 self: &mut Editor,
8471 label: &'static str,
8472 editor_snapshot: &EditorSnapshot,
8473 visible_row_range: Range<DisplayRow>,
8474 target_display_point: DisplayPoint,
8475 line_height: Pixels,
8476 scroll_pixel_position: gpui::Point<Pixels>,
8477 content_origin: gpui::Point<Pixels>,
8478 editor_width: Pixels,
8479 window: &mut Window,
8480 cx: &mut App,
8481 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8482 let target_line_end = DisplayPoint::new(
8483 target_display_point.row(),
8484 editor_snapshot.line_len(target_display_point.row()),
8485 );
8486
8487 let mut element = self
8488 .render_edit_prediction_line_popover(label, None, window, cx)?
8489 .into_any();
8490
8491 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8492
8493 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8494
8495 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8496 let mut origin = start_point
8497 + line_origin
8498 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8499 origin.x = origin.x.max(content_origin.x);
8500
8501 let max_x = content_origin.x + editor_width - size.width;
8502
8503 if origin.x > max_x {
8504 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8505
8506 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8507 origin.y += offset;
8508 IconName::ArrowUp
8509 } else {
8510 origin.y -= offset;
8511 IconName::ArrowDown
8512 };
8513
8514 element = self
8515 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8516 .into_any();
8517
8518 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8519
8520 origin.x = content_origin.x + editor_width - size.width - px(2.);
8521 }
8522
8523 element.prepaint_at(origin, window, cx);
8524 Some((element, origin))
8525 }
8526
8527 fn render_edit_prediction_diff_popover(
8528 self: &Editor,
8529 text_bounds: &Bounds<Pixels>,
8530 content_origin: gpui::Point<Pixels>,
8531 right_margin: Pixels,
8532 editor_snapshot: &EditorSnapshot,
8533 visible_row_range: Range<DisplayRow>,
8534 line_layouts: &[LineWithInvisibles],
8535 line_height: Pixels,
8536 scroll_pixel_position: gpui::Point<Pixels>,
8537 newest_selection_head: Option<DisplayPoint>,
8538 editor_width: Pixels,
8539 style: &EditorStyle,
8540 edits: &Vec<(Range<Anchor>, String)>,
8541 edit_preview: &Option<language::EditPreview>,
8542 snapshot: &language::BufferSnapshot,
8543 window: &mut Window,
8544 cx: &mut App,
8545 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8546 let edit_start = edits
8547 .first()
8548 .unwrap()
8549 .0
8550 .start
8551 .to_display_point(editor_snapshot);
8552 let edit_end = edits
8553 .last()
8554 .unwrap()
8555 .0
8556 .end
8557 .to_display_point(editor_snapshot);
8558
8559 let is_visible = visible_row_range.contains(&edit_start.row())
8560 || visible_row_range.contains(&edit_end.row());
8561 if !is_visible {
8562 return None;
8563 }
8564
8565 let highlighted_edits =
8566 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8567
8568 let styled_text = highlighted_edits.to_styled_text(&style.text);
8569 let line_count = highlighted_edits.text.lines().count();
8570
8571 const BORDER_WIDTH: Pixels = px(1.);
8572
8573 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8574 let has_keybind = keybind.is_some();
8575
8576 let mut element = h_flex()
8577 .items_start()
8578 .child(
8579 h_flex()
8580 .bg(cx.theme().colors().editor_background)
8581 .border(BORDER_WIDTH)
8582 .shadow_sm()
8583 .border_color(cx.theme().colors().border)
8584 .rounded_l_lg()
8585 .when(line_count > 1, |el| el.rounded_br_lg())
8586 .pr_1()
8587 .child(styled_text),
8588 )
8589 .child(
8590 h_flex()
8591 .h(line_height + BORDER_WIDTH * 2.)
8592 .px_1p5()
8593 .gap_1()
8594 // Workaround: For some reason, there's a gap if we don't do this
8595 .ml(-BORDER_WIDTH)
8596 .shadow(vec![gpui::BoxShadow {
8597 color: gpui::black().opacity(0.05),
8598 offset: point(px(1.), px(1.)),
8599 blur_radius: px(2.),
8600 spread_radius: px(0.),
8601 }])
8602 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8603 .border(BORDER_WIDTH)
8604 .border_color(cx.theme().colors().border)
8605 .rounded_r_lg()
8606 .id("edit_prediction_diff_popover_keybind")
8607 .when(!has_keybind, |el| {
8608 let status_colors = cx.theme().status();
8609
8610 el.bg(status_colors.error_background)
8611 .border_color(status_colors.error.opacity(0.6))
8612 .child(Icon::new(IconName::Info).color(Color::Error))
8613 .cursor_default()
8614 .hoverable_tooltip(move |_window, cx| {
8615 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8616 })
8617 })
8618 .children(keybind),
8619 )
8620 .into_any();
8621
8622 let longest_row =
8623 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8624 let longest_line_width = if visible_row_range.contains(&longest_row) {
8625 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8626 } else {
8627 layout_line(
8628 longest_row,
8629 editor_snapshot,
8630 style,
8631 editor_width,
8632 |_| false,
8633 window,
8634 cx,
8635 )
8636 .width
8637 };
8638
8639 let viewport_bounds =
8640 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8641 right: -right_margin,
8642 ..Default::default()
8643 });
8644
8645 let x_after_longest =
8646 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8647 - scroll_pixel_position.x;
8648
8649 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8650
8651 // Fully visible if it can be displayed within the window (allow overlapping other
8652 // panes). However, this is only allowed if the popover starts within text_bounds.
8653 let can_position_to_the_right = x_after_longest < text_bounds.right()
8654 && x_after_longest + element_bounds.width < viewport_bounds.right();
8655
8656 let mut origin = if can_position_to_the_right {
8657 point(
8658 x_after_longest,
8659 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8660 - scroll_pixel_position.y,
8661 )
8662 } else {
8663 let cursor_row = newest_selection_head.map(|head| head.row());
8664 let above_edit = edit_start
8665 .row()
8666 .0
8667 .checked_sub(line_count as u32)
8668 .map(DisplayRow);
8669 let below_edit = Some(edit_end.row() + 1);
8670 let above_cursor =
8671 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8672 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8673
8674 // Place the edit popover adjacent to the edit if there is a location
8675 // available that is onscreen and does not obscure the cursor. Otherwise,
8676 // place it adjacent to the cursor.
8677 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8678 .into_iter()
8679 .flatten()
8680 .find(|&start_row| {
8681 let end_row = start_row + line_count as u32;
8682 visible_row_range.contains(&start_row)
8683 && visible_row_range.contains(&end_row)
8684 && cursor_row.map_or(true, |cursor_row| {
8685 !((start_row..end_row).contains(&cursor_row))
8686 })
8687 })?;
8688
8689 content_origin
8690 + point(
8691 -scroll_pixel_position.x,
8692 row_target.as_f32() * line_height - scroll_pixel_position.y,
8693 )
8694 };
8695
8696 origin.x -= BORDER_WIDTH;
8697
8698 window.defer_draw(element, origin, 1);
8699
8700 // Do not return an element, since it will already be drawn due to defer_draw.
8701 None
8702 }
8703
8704 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8705 px(30.)
8706 }
8707
8708 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8709 if self.read_only(cx) {
8710 cx.theme().players().read_only()
8711 } else {
8712 self.style.as_ref().unwrap().local_player
8713 }
8714 }
8715
8716 fn render_edit_prediction_accept_keybind(
8717 &self,
8718 window: &mut Window,
8719 cx: &App,
8720 ) -> Option<AnyElement> {
8721 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8722 let accept_keystroke = accept_binding.keystroke()?;
8723
8724 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8725
8726 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8727 Color::Accent
8728 } else {
8729 Color::Muted
8730 };
8731
8732 h_flex()
8733 .px_0p5()
8734 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8735 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8736 .text_size(TextSize::XSmall.rems(cx))
8737 .child(h_flex().children(ui::render_modifiers(
8738 &accept_keystroke.modifiers,
8739 PlatformStyle::platform(),
8740 Some(modifiers_color),
8741 Some(IconSize::XSmall.rems().into()),
8742 true,
8743 )))
8744 .when(is_platform_style_mac, |parent| {
8745 parent.child(accept_keystroke.key.clone())
8746 })
8747 .when(!is_platform_style_mac, |parent| {
8748 parent.child(
8749 Key::new(
8750 util::capitalize(&accept_keystroke.key),
8751 Some(Color::Default),
8752 )
8753 .size(Some(IconSize::XSmall.rems().into())),
8754 )
8755 })
8756 .into_any()
8757 .into()
8758 }
8759
8760 fn render_edit_prediction_line_popover(
8761 &self,
8762 label: impl Into<SharedString>,
8763 icon: Option<IconName>,
8764 window: &mut Window,
8765 cx: &App,
8766 ) -> Option<Stateful<Div>> {
8767 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8768
8769 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8770 let has_keybind = keybind.is_some();
8771
8772 let result = h_flex()
8773 .id("ep-line-popover")
8774 .py_0p5()
8775 .pl_1()
8776 .pr(padding_right)
8777 .gap_1()
8778 .rounded_md()
8779 .border_1()
8780 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8781 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8782 .shadow_sm()
8783 .when(!has_keybind, |el| {
8784 let status_colors = cx.theme().status();
8785
8786 el.bg(status_colors.error_background)
8787 .border_color(status_colors.error.opacity(0.6))
8788 .pl_2()
8789 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8790 .cursor_default()
8791 .hoverable_tooltip(move |_window, cx| {
8792 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8793 })
8794 })
8795 .children(keybind)
8796 .child(
8797 Label::new(label)
8798 .size(LabelSize::Small)
8799 .when(!has_keybind, |el| {
8800 el.color(cx.theme().status().error.into()).strikethrough()
8801 }),
8802 )
8803 .when(!has_keybind, |el| {
8804 el.child(
8805 h_flex().ml_1().child(
8806 Icon::new(IconName::Info)
8807 .size(IconSize::Small)
8808 .color(cx.theme().status().error.into()),
8809 ),
8810 )
8811 })
8812 .when_some(icon, |element, icon| {
8813 element.child(
8814 div()
8815 .mt(px(1.5))
8816 .child(Icon::new(icon).size(IconSize::Small)),
8817 )
8818 });
8819
8820 Some(result)
8821 }
8822
8823 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8824 let accent_color = cx.theme().colors().text_accent;
8825 let editor_bg_color = cx.theme().colors().editor_background;
8826 editor_bg_color.blend(accent_color.opacity(0.1))
8827 }
8828
8829 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8830 let accent_color = cx.theme().colors().text_accent;
8831 let editor_bg_color = cx.theme().colors().editor_background;
8832 editor_bg_color.blend(accent_color.opacity(0.6))
8833 }
8834
8835 fn render_edit_prediction_cursor_popover(
8836 &self,
8837 min_width: Pixels,
8838 max_width: Pixels,
8839 cursor_point: Point,
8840 style: &EditorStyle,
8841 accept_keystroke: Option<&gpui::Keystroke>,
8842 _window: &Window,
8843 cx: &mut Context<Editor>,
8844 ) -> Option<AnyElement> {
8845 let provider = self.edit_prediction_provider.as_ref()?;
8846
8847 if provider.provider.needs_terms_acceptance(cx) {
8848 return Some(
8849 h_flex()
8850 .min_w(min_width)
8851 .flex_1()
8852 .px_2()
8853 .py_1()
8854 .gap_3()
8855 .elevation_2(cx)
8856 .hover(|style| style.bg(cx.theme().colors().element_hover))
8857 .id("accept-terms")
8858 .cursor_pointer()
8859 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8860 .on_click(cx.listener(|this, _event, window, cx| {
8861 cx.stop_propagation();
8862 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8863 window.dispatch_action(
8864 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8865 cx,
8866 );
8867 }))
8868 .child(
8869 h_flex()
8870 .flex_1()
8871 .gap_2()
8872 .child(Icon::new(IconName::ZedPredict))
8873 .child(Label::new("Accept Terms of Service"))
8874 .child(div().w_full())
8875 .child(
8876 Icon::new(IconName::ArrowUpRight)
8877 .color(Color::Muted)
8878 .size(IconSize::Small),
8879 )
8880 .into_any_element(),
8881 )
8882 .into_any(),
8883 );
8884 }
8885
8886 let is_refreshing = provider.provider.is_refreshing(cx);
8887
8888 fn pending_completion_container() -> Div {
8889 h_flex()
8890 .h_full()
8891 .flex_1()
8892 .gap_2()
8893 .child(Icon::new(IconName::ZedPredict))
8894 }
8895
8896 let completion = match &self.active_inline_completion {
8897 Some(prediction) => {
8898 if !self.has_visible_completions_menu() {
8899 const RADIUS: Pixels = px(6.);
8900 const BORDER_WIDTH: Pixels = px(1.);
8901
8902 return Some(
8903 h_flex()
8904 .elevation_2(cx)
8905 .border(BORDER_WIDTH)
8906 .border_color(cx.theme().colors().border)
8907 .when(accept_keystroke.is_none(), |el| {
8908 el.border_color(cx.theme().status().error)
8909 })
8910 .rounded(RADIUS)
8911 .rounded_tl(px(0.))
8912 .overflow_hidden()
8913 .child(div().px_1p5().child(match &prediction.completion {
8914 InlineCompletion::Move { target, snapshot } => {
8915 use text::ToPoint as _;
8916 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8917 {
8918 Icon::new(IconName::ZedPredictDown)
8919 } else {
8920 Icon::new(IconName::ZedPredictUp)
8921 }
8922 }
8923 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8924 }))
8925 .child(
8926 h_flex()
8927 .gap_1()
8928 .py_1()
8929 .px_2()
8930 .rounded_r(RADIUS - BORDER_WIDTH)
8931 .border_l_1()
8932 .border_color(cx.theme().colors().border)
8933 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8934 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8935 el.child(
8936 Label::new("Hold")
8937 .size(LabelSize::Small)
8938 .when(accept_keystroke.is_none(), |el| {
8939 el.strikethrough()
8940 })
8941 .line_height_style(LineHeightStyle::UiLabel),
8942 )
8943 })
8944 .id("edit_prediction_cursor_popover_keybind")
8945 .when(accept_keystroke.is_none(), |el| {
8946 let status_colors = cx.theme().status();
8947
8948 el.bg(status_colors.error_background)
8949 .border_color(status_colors.error.opacity(0.6))
8950 .child(Icon::new(IconName::Info).color(Color::Error))
8951 .cursor_default()
8952 .hoverable_tooltip(move |_window, cx| {
8953 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8954 .into()
8955 })
8956 })
8957 .when_some(
8958 accept_keystroke.as_ref(),
8959 |el, accept_keystroke| {
8960 el.child(h_flex().children(ui::render_modifiers(
8961 &accept_keystroke.modifiers,
8962 PlatformStyle::platform(),
8963 Some(Color::Default),
8964 Some(IconSize::XSmall.rems().into()),
8965 false,
8966 )))
8967 },
8968 ),
8969 )
8970 .into_any(),
8971 );
8972 }
8973
8974 self.render_edit_prediction_cursor_popover_preview(
8975 prediction,
8976 cursor_point,
8977 style,
8978 cx,
8979 )?
8980 }
8981
8982 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8983 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8984 stale_completion,
8985 cursor_point,
8986 style,
8987 cx,
8988 )?,
8989
8990 None => {
8991 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8992 }
8993 },
8994
8995 None => pending_completion_container().child(Label::new("No Prediction")),
8996 };
8997
8998 let completion = if is_refreshing {
8999 completion
9000 .with_animation(
9001 "loading-completion",
9002 Animation::new(Duration::from_secs(2))
9003 .repeat()
9004 .with_easing(pulsating_between(0.4, 0.8)),
9005 |label, delta| label.opacity(delta),
9006 )
9007 .into_any_element()
9008 } else {
9009 completion.into_any_element()
9010 };
9011
9012 let has_completion = self.active_inline_completion.is_some();
9013
9014 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9015 Some(
9016 h_flex()
9017 .min_w(min_width)
9018 .max_w(max_width)
9019 .flex_1()
9020 .elevation_2(cx)
9021 .border_color(cx.theme().colors().border)
9022 .child(
9023 div()
9024 .flex_1()
9025 .py_1()
9026 .px_2()
9027 .overflow_hidden()
9028 .child(completion),
9029 )
9030 .when_some(accept_keystroke, |el, accept_keystroke| {
9031 if !accept_keystroke.modifiers.modified() {
9032 return el;
9033 }
9034
9035 el.child(
9036 h_flex()
9037 .h_full()
9038 .border_l_1()
9039 .rounded_r_lg()
9040 .border_color(cx.theme().colors().border)
9041 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9042 .gap_1()
9043 .py_1()
9044 .px_2()
9045 .child(
9046 h_flex()
9047 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9048 .when(is_platform_style_mac, |parent| parent.gap_1())
9049 .child(h_flex().children(ui::render_modifiers(
9050 &accept_keystroke.modifiers,
9051 PlatformStyle::platform(),
9052 Some(if !has_completion {
9053 Color::Muted
9054 } else {
9055 Color::Default
9056 }),
9057 None,
9058 false,
9059 ))),
9060 )
9061 .child(Label::new("Preview").into_any_element())
9062 .opacity(if has_completion { 1.0 } else { 0.4 }),
9063 )
9064 })
9065 .into_any(),
9066 )
9067 }
9068
9069 fn render_edit_prediction_cursor_popover_preview(
9070 &self,
9071 completion: &InlineCompletionState,
9072 cursor_point: Point,
9073 style: &EditorStyle,
9074 cx: &mut Context<Editor>,
9075 ) -> Option<Div> {
9076 use text::ToPoint as _;
9077
9078 fn render_relative_row_jump(
9079 prefix: impl Into<String>,
9080 current_row: u32,
9081 target_row: u32,
9082 ) -> Div {
9083 let (row_diff, arrow) = if target_row < current_row {
9084 (current_row - target_row, IconName::ArrowUp)
9085 } else {
9086 (target_row - current_row, IconName::ArrowDown)
9087 };
9088
9089 h_flex()
9090 .child(
9091 Label::new(format!("{}{}", prefix.into(), row_diff))
9092 .color(Color::Muted)
9093 .size(LabelSize::Small),
9094 )
9095 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9096 }
9097
9098 match &completion.completion {
9099 InlineCompletion::Move {
9100 target, snapshot, ..
9101 } => Some(
9102 h_flex()
9103 .px_2()
9104 .gap_2()
9105 .flex_1()
9106 .child(
9107 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9108 Icon::new(IconName::ZedPredictDown)
9109 } else {
9110 Icon::new(IconName::ZedPredictUp)
9111 },
9112 )
9113 .child(Label::new("Jump to Edit")),
9114 ),
9115
9116 InlineCompletion::Edit {
9117 edits,
9118 edit_preview,
9119 snapshot,
9120 display_mode: _,
9121 } => {
9122 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9123
9124 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9125 &snapshot,
9126 &edits,
9127 edit_preview.as_ref()?,
9128 true,
9129 cx,
9130 )
9131 .first_line_preview();
9132
9133 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9134 .with_default_highlights(&style.text, highlighted_edits.highlights);
9135
9136 let preview = h_flex()
9137 .gap_1()
9138 .min_w_16()
9139 .child(styled_text)
9140 .when(has_more_lines, |parent| parent.child("…"));
9141
9142 let left = if first_edit_row != cursor_point.row {
9143 render_relative_row_jump("", cursor_point.row, first_edit_row)
9144 .into_any_element()
9145 } else {
9146 Icon::new(IconName::ZedPredict).into_any_element()
9147 };
9148
9149 Some(
9150 h_flex()
9151 .h_full()
9152 .flex_1()
9153 .gap_2()
9154 .pr_1()
9155 .overflow_x_hidden()
9156 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9157 .child(left)
9158 .child(preview),
9159 )
9160 }
9161 }
9162 }
9163
9164 pub fn render_context_menu(
9165 &self,
9166 style: &EditorStyle,
9167 max_height_in_lines: u32,
9168 window: &mut Window,
9169 cx: &mut Context<Editor>,
9170 ) -> Option<AnyElement> {
9171 let menu = self.context_menu.borrow();
9172 let menu = menu.as_ref()?;
9173 if !menu.visible() {
9174 return None;
9175 };
9176 Some(menu.render(style, max_height_in_lines, window, cx))
9177 }
9178
9179 fn render_context_menu_aside(
9180 &mut self,
9181 max_size: Size<Pixels>,
9182 window: &mut Window,
9183 cx: &mut Context<Editor>,
9184 ) -> Option<AnyElement> {
9185 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9186 if menu.visible() {
9187 menu.render_aside(max_size, window, cx)
9188 } else {
9189 None
9190 }
9191 })
9192 }
9193
9194 fn hide_context_menu(
9195 &mut self,
9196 window: &mut Window,
9197 cx: &mut Context<Self>,
9198 ) -> Option<CodeContextMenu> {
9199 cx.notify();
9200 self.completion_tasks.clear();
9201 let context_menu = self.context_menu.borrow_mut().take();
9202 self.stale_inline_completion_in_menu.take();
9203 self.update_visible_inline_completion(window, cx);
9204 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9205 if let Some(completion_provider) = &self.completion_provider {
9206 completion_provider.selection_changed(None, window, cx);
9207 }
9208 }
9209 context_menu
9210 }
9211
9212 fn show_snippet_choices(
9213 &mut self,
9214 choices: &Vec<String>,
9215 selection: Range<Anchor>,
9216 cx: &mut Context<Self>,
9217 ) {
9218 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9219 (Some(a), Some(b)) if a == b => a,
9220 _ => {
9221 log::error!("expected anchor range to have matching buffer IDs");
9222 return;
9223 }
9224 };
9225 let multi_buffer = self.buffer().read(cx);
9226 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9227 return;
9228 };
9229
9230 let id = post_inc(&mut self.next_completion_id);
9231 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9232 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9233 CompletionsMenu::new_snippet_choices(
9234 id,
9235 true,
9236 choices,
9237 selection,
9238 buffer,
9239 snippet_sort_order,
9240 ),
9241 ));
9242 }
9243
9244 pub fn insert_snippet(
9245 &mut self,
9246 insertion_ranges: &[Range<usize>],
9247 snippet: Snippet,
9248 window: &mut Window,
9249 cx: &mut Context<Self>,
9250 ) -> Result<()> {
9251 struct Tabstop<T> {
9252 is_end_tabstop: bool,
9253 ranges: Vec<Range<T>>,
9254 choices: Option<Vec<String>>,
9255 }
9256
9257 let tabstops = self.buffer.update(cx, |buffer, cx| {
9258 let snippet_text: Arc<str> = snippet.text.clone().into();
9259 let edits = insertion_ranges
9260 .iter()
9261 .cloned()
9262 .map(|range| (range, snippet_text.clone()));
9263 let autoindent_mode = AutoindentMode::Block {
9264 original_indent_columns: Vec::new(),
9265 };
9266 buffer.edit(edits, Some(autoindent_mode), cx);
9267
9268 let snapshot = &*buffer.read(cx);
9269 let snippet = &snippet;
9270 snippet
9271 .tabstops
9272 .iter()
9273 .map(|tabstop| {
9274 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9275 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9276 });
9277 let mut tabstop_ranges = tabstop
9278 .ranges
9279 .iter()
9280 .flat_map(|tabstop_range| {
9281 let mut delta = 0_isize;
9282 insertion_ranges.iter().map(move |insertion_range| {
9283 let insertion_start = insertion_range.start as isize + delta;
9284 delta +=
9285 snippet.text.len() as isize - insertion_range.len() as isize;
9286
9287 let start = ((insertion_start + tabstop_range.start) as usize)
9288 .min(snapshot.len());
9289 let end = ((insertion_start + tabstop_range.end) as usize)
9290 .min(snapshot.len());
9291 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9292 })
9293 })
9294 .collect::<Vec<_>>();
9295 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9296
9297 Tabstop {
9298 is_end_tabstop,
9299 ranges: tabstop_ranges,
9300 choices: tabstop.choices.clone(),
9301 }
9302 })
9303 .collect::<Vec<_>>()
9304 });
9305 if let Some(tabstop) = tabstops.first() {
9306 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9307 // Reverse order so that the first range is the newest created selection.
9308 // Completions will use it and autoscroll will prioritize it.
9309 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9310 });
9311
9312 if let Some(choices) = &tabstop.choices {
9313 if let Some(selection) = tabstop.ranges.first() {
9314 self.show_snippet_choices(choices, selection.clone(), cx)
9315 }
9316 }
9317
9318 // If we're already at the last tabstop and it's at the end of the snippet,
9319 // we're done, we don't need to keep the state around.
9320 if !tabstop.is_end_tabstop {
9321 let choices = tabstops
9322 .iter()
9323 .map(|tabstop| tabstop.choices.clone())
9324 .collect();
9325
9326 let ranges = tabstops
9327 .into_iter()
9328 .map(|tabstop| tabstop.ranges)
9329 .collect::<Vec<_>>();
9330
9331 self.snippet_stack.push(SnippetState {
9332 active_index: 0,
9333 ranges,
9334 choices,
9335 });
9336 }
9337
9338 // Check whether the just-entered snippet ends with an auto-closable bracket.
9339 if self.autoclose_regions.is_empty() {
9340 let snapshot = self.buffer.read(cx).snapshot(cx);
9341 for selection in &mut self.selections.all::<Point>(cx) {
9342 let selection_head = selection.head();
9343 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9344 continue;
9345 };
9346
9347 let mut bracket_pair = None;
9348 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9349 let prev_chars = snapshot
9350 .reversed_chars_at(selection_head)
9351 .collect::<String>();
9352 for (pair, enabled) in scope.brackets() {
9353 if enabled
9354 && pair.close
9355 && prev_chars.starts_with(pair.start.as_str())
9356 && next_chars.starts_with(pair.end.as_str())
9357 {
9358 bracket_pair = Some(pair.clone());
9359 break;
9360 }
9361 }
9362 if let Some(pair) = bracket_pair {
9363 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9364 let autoclose_enabled =
9365 self.use_autoclose && snapshot_settings.use_autoclose;
9366 if autoclose_enabled {
9367 let start = snapshot.anchor_after(selection_head);
9368 let end = snapshot.anchor_after(selection_head);
9369 self.autoclose_regions.push(AutocloseRegion {
9370 selection_id: selection.id,
9371 range: start..end,
9372 pair,
9373 });
9374 }
9375 }
9376 }
9377 }
9378 }
9379 Ok(())
9380 }
9381
9382 pub fn move_to_next_snippet_tabstop(
9383 &mut self,
9384 window: &mut Window,
9385 cx: &mut Context<Self>,
9386 ) -> bool {
9387 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9388 }
9389
9390 pub fn move_to_prev_snippet_tabstop(
9391 &mut self,
9392 window: &mut Window,
9393 cx: &mut Context<Self>,
9394 ) -> bool {
9395 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9396 }
9397
9398 pub fn move_to_snippet_tabstop(
9399 &mut self,
9400 bias: Bias,
9401 window: &mut Window,
9402 cx: &mut Context<Self>,
9403 ) -> bool {
9404 if let Some(mut snippet) = self.snippet_stack.pop() {
9405 match bias {
9406 Bias::Left => {
9407 if snippet.active_index > 0 {
9408 snippet.active_index -= 1;
9409 } else {
9410 self.snippet_stack.push(snippet);
9411 return false;
9412 }
9413 }
9414 Bias::Right => {
9415 if snippet.active_index + 1 < snippet.ranges.len() {
9416 snippet.active_index += 1;
9417 } else {
9418 self.snippet_stack.push(snippet);
9419 return false;
9420 }
9421 }
9422 }
9423 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9424 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9425 // Reverse order so that the first range is the newest created selection.
9426 // Completions will use it and autoscroll will prioritize it.
9427 s.select_ranges(current_ranges.iter().rev().cloned())
9428 });
9429
9430 if let Some(choices) = &snippet.choices[snippet.active_index] {
9431 if let Some(selection) = current_ranges.first() {
9432 self.show_snippet_choices(&choices, selection.clone(), cx);
9433 }
9434 }
9435
9436 // If snippet state is not at the last tabstop, push it back on the stack
9437 if snippet.active_index + 1 < snippet.ranges.len() {
9438 self.snippet_stack.push(snippet);
9439 }
9440 return true;
9441 }
9442 }
9443
9444 false
9445 }
9446
9447 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9448 self.transact(window, cx, |this, window, cx| {
9449 this.select_all(&SelectAll, window, cx);
9450 this.insert("", window, cx);
9451 });
9452 }
9453
9454 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9455 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9456 self.transact(window, cx, |this, window, cx| {
9457 this.select_autoclose_pair(window, cx);
9458 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9459 if !this.linked_edit_ranges.is_empty() {
9460 let selections = this.selections.all::<MultiBufferPoint>(cx);
9461 let snapshot = this.buffer.read(cx).snapshot(cx);
9462
9463 for selection in selections.iter() {
9464 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9465 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9466 if selection_start.buffer_id != selection_end.buffer_id {
9467 continue;
9468 }
9469 if let Some(ranges) =
9470 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9471 {
9472 for (buffer, entries) in ranges {
9473 linked_ranges.entry(buffer).or_default().extend(entries);
9474 }
9475 }
9476 }
9477 }
9478
9479 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9480 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9481 for selection in &mut selections {
9482 if selection.is_empty() {
9483 let old_head = selection.head();
9484 let mut new_head =
9485 movement::left(&display_map, old_head.to_display_point(&display_map))
9486 .to_point(&display_map);
9487 if let Some((buffer, line_buffer_range)) = display_map
9488 .buffer_snapshot
9489 .buffer_line_for_row(MultiBufferRow(old_head.row))
9490 {
9491 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9492 let indent_len = match indent_size.kind {
9493 IndentKind::Space => {
9494 buffer.settings_at(line_buffer_range.start, cx).tab_size
9495 }
9496 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9497 };
9498 if old_head.column <= indent_size.len && old_head.column > 0 {
9499 let indent_len = indent_len.get();
9500 new_head = cmp::min(
9501 new_head,
9502 MultiBufferPoint::new(
9503 old_head.row,
9504 ((old_head.column - 1) / indent_len) * indent_len,
9505 ),
9506 );
9507 }
9508 }
9509
9510 selection.set_head(new_head, SelectionGoal::None);
9511 }
9512 }
9513
9514 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9515 s.select(selections)
9516 });
9517 this.insert("", window, cx);
9518 let empty_str: Arc<str> = Arc::from("");
9519 for (buffer, edits) in linked_ranges {
9520 let snapshot = buffer.read(cx).snapshot();
9521 use text::ToPoint as TP;
9522
9523 let edits = edits
9524 .into_iter()
9525 .map(|range| {
9526 let end_point = TP::to_point(&range.end, &snapshot);
9527 let mut start_point = TP::to_point(&range.start, &snapshot);
9528
9529 if end_point == start_point {
9530 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9531 .saturating_sub(1);
9532 start_point =
9533 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9534 };
9535
9536 (start_point..end_point, empty_str.clone())
9537 })
9538 .sorted_by_key(|(range, _)| range.start)
9539 .collect::<Vec<_>>();
9540 buffer.update(cx, |this, cx| {
9541 this.edit(edits, None, cx);
9542 })
9543 }
9544 this.refresh_inline_completion(true, false, window, cx);
9545 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9546 });
9547 }
9548
9549 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9550 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9551 self.transact(window, cx, |this, window, cx| {
9552 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9553 s.move_with(|map, selection| {
9554 if selection.is_empty() {
9555 let cursor = movement::right(map, selection.head());
9556 selection.end = cursor;
9557 selection.reversed = true;
9558 selection.goal = SelectionGoal::None;
9559 }
9560 })
9561 });
9562 this.insert("", window, cx);
9563 this.refresh_inline_completion(true, false, window, cx);
9564 });
9565 }
9566
9567 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9568 if self.mode.is_single_line() {
9569 cx.propagate();
9570 return;
9571 }
9572
9573 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9574 if self.move_to_prev_snippet_tabstop(window, cx) {
9575 return;
9576 }
9577 self.outdent(&Outdent, window, cx);
9578 }
9579
9580 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9581 if self.mode.is_single_line() {
9582 cx.propagate();
9583 return;
9584 }
9585
9586 if self.move_to_next_snippet_tabstop(window, cx) {
9587 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9588 return;
9589 }
9590 if self.read_only(cx) {
9591 return;
9592 }
9593 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9594 let mut selections = self.selections.all_adjusted(cx);
9595 let buffer = self.buffer.read(cx);
9596 let snapshot = buffer.snapshot(cx);
9597 let rows_iter = selections.iter().map(|s| s.head().row);
9598 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9599
9600 let has_some_cursor_in_whitespace = selections
9601 .iter()
9602 .filter(|selection| selection.is_empty())
9603 .any(|selection| {
9604 let cursor = selection.head();
9605 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9606 cursor.column < current_indent.len
9607 });
9608
9609 let mut edits = Vec::new();
9610 let mut prev_edited_row = 0;
9611 let mut row_delta = 0;
9612 for selection in &mut selections {
9613 if selection.start.row != prev_edited_row {
9614 row_delta = 0;
9615 }
9616 prev_edited_row = selection.end.row;
9617
9618 // If the selection is non-empty, then increase the indentation of the selected lines.
9619 if !selection.is_empty() {
9620 row_delta =
9621 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9622 continue;
9623 }
9624
9625 let cursor = selection.head();
9626 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9627 if let Some(suggested_indent) =
9628 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9629 {
9630 // Don't do anything if already at suggested indent
9631 // and there is any other cursor which is not
9632 if has_some_cursor_in_whitespace
9633 && cursor.column == current_indent.len
9634 && current_indent.len == suggested_indent.len
9635 {
9636 continue;
9637 }
9638
9639 // Adjust line and move cursor to suggested indent
9640 // if cursor is not at suggested indent
9641 if cursor.column < suggested_indent.len
9642 && cursor.column <= current_indent.len
9643 && current_indent.len <= suggested_indent.len
9644 {
9645 selection.start = Point::new(cursor.row, suggested_indent.len);
9646 selection.end = selection.start;
9647 if row_delta == 0 {
9648 edits.extend(Buffer::edit_for_indent_size_adjustment(
9649 cursor.row,
9650 current_indent,
9651 suggested_indent,
9652 ));
9653 row_delta = suggested_indent.len - current_indent.len;
9654 }
9655 continue;
9656 }
9657
9658 // If current indent is more than suggested indent
9659 // only move cursor to current indent and skip indent
9660 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9661 selection.start = Point::new(cursor.row, current_indent.len);
9662 selection.end = selection.start;
9663 continue;
9664 }
9665 }
9666
9667 // Otherwise, insert a hard or soft tab.
9668 let settings = buffer.language_settings_at(cursor, cx);
9669 let tab_size = if settings.hard_tabs {
9670 IndentSize::tab()
9671 } else {
9672 let tab_size = settings.tab_size.get();
9673 let indent_remainder = snapshot
9674 .text_for_range(Point::new(cursor.row, 0)..cursor)
9675 .flat_map(str::chars)
9676 .fold(row_delta % tab_size, |counter: u32, c| {
9677 if c == '\t' {
9678 0
9679 } else {
9680 (counter + 1) % tab_size
9681 }
9682 });
9683
9684 let chars_to_next_tab_stop = tab_size - indent_remainder;
9685 IndentSize::spaces(chars_to_next_tab_stop)
9686 };
9687 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9688 selection.end = selection.start;
9689 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9690 row_delta += tab_size.len;
9691 }
9692
9693 self.transact(window, cx, |this, window, cx| {
9694 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9695 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9696 s.select(selections)
9697 });
9698 this.refresh_inline_completion(true, false, window, cx);
9699 });
9700 }
9701
9702 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9703 if self.read_only(cx) {
9704 return;
9705 }
9706 if self.mode.is_single_line() {
9707 cx.propagate();
9708 return;
9709 }
9710
9711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9712 let mut selections = self.selections.all::<Point>(cx);
9713 let mut prev_edited_row = 0;
9714 let mut row_delta = 0;
9715 let mut edits = Vec::new();
9716 let buffer = self.buffer.read(cx);
9717 let snapshot = buffer.snapshot(cx);
9718 for selection in &mut selections {
9719 if selection.start.row != prev_edited_row {
9720 row_delta = 0;
9721 }
9722 prev_edited_row = selection.end.row;
9723
9724 row_delta =
9725 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9726 }
9727
9728 self.transact(window, cx, |this, window, cx| {
9729 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9730 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9731 s.select(selections)
9732 });
9733 });
9734 }
9735
9736 fn indent_selection(
9737 buffer: &MultiBuffer,
9738 snapshot: &MultiBufferSnapshot,
9739 selection: &mut Selection<Point>,
9740 edits: &mut Vec<(Range<Point>, String)>,
9741 delta_for_start_row: u32,
9742 cx: &App,
9743 ) -> u32 {
9744 let settings = buffer.language_settings_at(selection.start, cx);
9745 let tab_size = settings.tab_size.get();
9746 let indent_kind = if settings.hard_tabs {
9747 IndentKind::Tab
9748 } else {
9749 IndentKind::Space
9750 };
9751 let mut start_row = selection.start.row;
9752 let mut end_row = selection.end.row + 1;
9753
9754 // If a selection ends at the beginning of a line, don't indent
9755 // that last line.
9756 if selection.end.column == 0 && selection.end.row > selection.start.row {
9757 end_row -= 1;
9758 }
9759
9760 // Avoid re-indenting a row that has already been indented by a
9761 // previous selection, but still update this selection's column
9762 // to reflect that indentation.
9763 if delta_for_start_row > 0 {
9764 start_row += 1;
9765 selection.start.column += delta_for_start_row;
9766 if selection.end.row == selection.start.row {
9767 selection.end.column += delta_for_start_row;
9768 }
9769 }
9770
9771 let mut delta_for_end_row = 0;
9772 let has_multiple_rows = start_row + 1 != end_row;
9773 for row in start_row..end_row {
9774 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9775 let indent_delta = match (current_indent.kind, indent_kind) {
9776 (IndentKind::Space, IndentKind::Space) => {
9777 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9778 IndentSize::spaces(columns_to_next_tab_stop)
9779 }
9780 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9781 (_, IndentKind::Tab) => IndentSize::tab(),
9782 };
9783
9784 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9785 0
9786 } else {
9787 selection.start.column
9788 };
9789 let row_start = Point::new(row, start);
9790 edits.push((
9791 row_start..row_start,
9792 indent_delta.chars().collect::<String>(),
9793 ));
9794
9795 // Update this selection's endpoints to reflect the indentation.
9796 if row == selection.start.row {
9797 selection.start.column += indent_delta.len;
9798 }
9799 if row == selection.end.row {
9800 selection.end.column += indent_delta.len;
9801 delta_for_end_row = indent_delta.len;
9802 }
9803 }
9804
9805 if selection.start.row == selection.end.row {
9806 delta_for_start_row + delta_for_end_row
9807 } else {
9808 delta_for_end_row
9809 }
9810 }
9811
9812 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9813 if self.read_only(cx) {
9814 return;
9815 }
9816 if self.mode.is_single_line() {
9817 cx.propagate();
9818 return;
9819 }
9820
9821 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9822 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9823 let selections = self.selections.all::<Point>(cx);
9824 let mut deletion_ranges = Vec::new();
9825 let mut last_outdent = None;
9826 {
9827 let buffer = self.buffer.read(cx);
9828 let snapshot = buffer.snapshot(cx);
9829 for selection in &selections {
9830 let settings = buffer.language_settings_at(selection.start, cx);
9831 let tab_size = settings.tab_size.get();
9832 let mut rows = selection.spanned_rows(false, &display_map);
9833
9834 // Avoid re-outdenting a row that has already been outdented by a
9835 // previous selection.
9836 if let Some(last_row) = last_outdent {
9837 if last_row == rows.start {
9838 rows.start = rows.start.next_row();
9839 }
9840 }
9841 let has_multiple_rows = rows.len() > 1;
9842 for row in rows.iter_rows() {
9843 let indent_size = snapshot.indent_size_for_line(row);
9844 if indent_size.len > 0 {
9845 let deletion_len = match indent_size.kind {
9846 IndentKind::Space => {
9847 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9848 if columns_to_prev_tab_stop == 0 {
9849 tab_size
9850 } else {
9851 columns_to_prev_tab_stop
9852 }
9853 }
9854 IndentKind::Tab => 1,
9855 };
9856 let start = if has_multiple_rows
9857 || deletion_len > selection.start.column
9858 || indent_size.len < selection.start.column
9859 {
9860 0
9861 } else {
9862 selection.start.column - deletion_len
9863 };
9864 deletion_ranges.push(
9865 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9866 );
9867 last_outdent = Some(row);
9868 }
9869 }
9870 }
9871 }
9872
9873 self.transact(window, cx, |this, window, cx| {
9874 this.buffer.update(cx, |buffer, cx| {
9875 let empty_str: Arc<str> = Arc::default();
9876 buffer.edit(
9877 deletion_ranges
9878 .into_iter()
9879 .map(|range| (range, empty_str.clone())),
9880 None,
9881 cx,
9882 );
9883 });
9884 let selections = this.selections.all::<usize>(cx);
9885 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9886 s.select(selections)
9887 });
9888 });
9889 }
9890
9891 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9892 if self.read_only(cx) {
9893 return;
9894 }
9895 if self.mode.is_single_line() {
9896 cx.propagate();
9897 return;
9898 }
9899
9900 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9901 let selections = self
9902 .selections
9903 .all::<usize>(cx)
9904 .into_iter()
9905 .map(|s| s.range());
9906
9907 self.transact(window, cx, |this, window, cx| {
9908 this.buffer.update(cx, |buffer, cx| {
9909 buffer.autoindent_ranges(selections, cx);
9910 });
9911 let selections = this.selections.all::<usize>(cx);
9912 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9913 s.select(selections)
9914 });
9915 });
9916 }
9917
9918 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9919 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9920 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9921 let selections = self.selections.all::<Point>(cx);
9922
9923 let mut new_cursors = Vec::new();
9924 let mut edit_ranges = Vec::new();
9925 let mut selections = selections.iter().peekable();
9926 while let Some(selection) = selections.next() {
9927 let mut rows = selection.spanned_rows(false, &display_map);
9928 let goal_display_column = selection.head().to_display_point(&display_map).column();
9929
9930 // Accumulate contiguous regions of rows that we want to delete.
9931 while let Some(next_selection) = selections.peek() {
9932 let next_rows = next_selection.spanned_rows(false, &display_map);
9933 if next_rows.start <= rows.end {
9934 rows.end = next_rows.end;
9935 selections.next().unwrap();
9936 } else {
9937 break;
9938 }
9939 }
9940
9941 let buffer = &display_map.buffer_snapshot;
9942 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9943 let edit_end;
9944 let cursor_buffer_row;
9945 if buffer.max_point().row >= rows.end.0 {
9946 // If there's a line after the range, delete the \n from the end of the row range
9947 // and position the cursor on the next line.
9948 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9949 cursor_buffer_row = rows.end;
9950 } else {
9951 // If there isn't a line after the range, delete the \n from the line before the
9952 // start of the row range and position the cursor there.
9953 edit_start = edit_start.saturating_sub(1);
9954 edit_end = buffer.len();
9955 cursor_buffer_row = rows.start.previous_row();
9956 }
9957
9958 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9959 *cursor.column_mut() =
9960 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9961
9962 new_cursors.push((
9963 selection.id,
9964 buffer.anchor_after(cursor.to_point(&display_map)),
9965 ));
9966 edit_ranges.push(edit_start..edit_end);
9967 }
9968
9969 self.transact(window, cx, |this, window, cx| {
9970 let buffer = this.buffer.update(cx, |buffer, cx| {
9971 let empty_str: Arc<str> = Arc::default();
9972 buffer.edit(
9973 edit_ranges
9974 .into_iter()
9975 .map(|range| (range, empty_str.clone())),
9976 None,
9977 cx,
9978 );
9979 buffer.snapshot(cx)
9980 });
9981 let new_selections = new_cursors
9982 .into_iter()
9983 .map(|(id, cursor)| {
9984 let cursor = cursor.to_point(&buffer);
9985 Selection {
9986 id,
9987 start: cursor,
9988 end: cursor,
9989 reversed: false,
9990 goal: SelectionGoal::None,
9991 }
9992 })
9993 .collect();
9994
9995 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9996 s.select(new_selections);
9997 });
9998 });
9999 }
10000
10001 pub fn join_lines_impl(
10002 &mut self,
10003 insert_whitespace: bool,
10004 window: &mut Window,
10005 cx: &mut Context<Self>,
10006 ) {
10007 if self.read_only(cx) {
10008 return;
10009 }
10010 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10011 for selection in self.selections.all::<Point>(cx) {
10012 let start = MultiBufferRow(selection.start.row);
10013 // Treat single line selections as if they include the next line. Otherwise this action
10014 // would do nothing for single line selections individual cursors.
10015 let end = if selection.start.row == selection.end.row {
10016 MultiBufferRow(selection.start.row + 1)
10017 } else {
10018 MultiBufferRow(selection.end.row)
10019 };
10020
10021 if let Some(last_row_range) = row_ranges.last_mut() {
10022 if start <= last_row_range.end {
10023 last_row_range.end = end;
10024 continue;
10025 }
10026 }
10027 row_ranges.push(start..end);
10028 }
10029
10030 let snapshot = self.buffer.read(cx).snapshot(cx);
10031 let mut cursor_positions = Vec::new();
10032 for row_range in &row_ranges {
10033 let anchor = snapshot.anchor_before(Point::new(
10034 row_range.end.previous_row().0,
10035 snapshot.line_len(row_range.end.previous_row()),
10036 ));
10037 cursor_positions.push(anchor..anchor);
10038 }
10039
10040 self.transact(window, cx, |this, window, cx| {
10041 for row_range in row_ranges.into_iter().rev() {
10042 for row in row_range.iter_rows().rev() {
10043 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10044 let next_line_row = row.next_row();
10045 let indent = snapshot.indent_size_for_line(next_line_row);
10046 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10047
10048 let replace =
10049 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10050 " "
10051 } else {
10052 ""
10053 };
10054
10055 this.buffer.update(cx, |buffer, cx| {
10056 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10057 });
10058 }
10059 }
10060
10061 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10062 s.select_anchor_ranges(cursor_positions)
10063 });
10064 });
10065 }
10066
10067 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10068 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10069 self.join_lines_impl(true, window, cx);
10070 }
10071
10072 pub fn sort_lines_case_sensitive(
10073 &mut self,
10074 _: &SortLinesCaseSensitive,
10075 window: &mut Window,
10076 cx: &mut Context<Self>,
10077 ) {
10078 self.manipulate_lines(window, cx, |lines| lines.sort())
10079 }
10080
10081 pub fn sort_lines_case_insensitive(
10082 &mut self,
10083 _: &SortLinesCaseInsensitive,
10084 window: &mut Window,
10085 cx: &mut Context<Self>,
10086 ) {
10087 self.manipulate_lines(window, cx, |lines| {
10088 lines.sort_by_key(|line| line.to_lowercase())
10089 })
10090 }
10091
10092 pub fn unique_lines_case_insensitive(
10093 &mut self,
10094 _: &UniqueLinesCaseInsensitive,
10095 window: &mut Window,
10096 cx: &mut Context<Self>,
10097 ) {
10098 self.manipulate_lines(window, cx, |lines| {
10099 let mut seen = HashSet::default();
10100 lines.retain(|line| seen.insert(line.to_lowercase()));
10101 })
10102 }
10103
10104 pub fn unique_lines_case_sensitive(
10105 &mut self,
10106 _: &UniqueLinesCaseSensitive,
10107 window: &mut Window,
10108 cx: &mut Context<Self>,
10109 ) {
10110 self.manipulate_lines(window, cx, |lines| {
10111 let mut seen = HashSet::default();
10112 lines.retain(|line| seen.insert(*line));
10113 })
10114 }
10115
10116 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10117 let Some(project) = self.project.clone() else {
10118 return;
10119 };
10120 self.reload(project, window, cx)
10121 .detach_and_notify_err(window, cx);
10122 }
10123
10124 pub fn restore_file(
10125 &mut self,
10126 _: &::git::RestoreFile,
10127 window: &mut Window,
10128 cx: &mut Context<Self>,
10129 ) {
10130 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10131 let mut buffer_ids = HashSet::default();
10132 let snapshot = self.buffer().read(cx).snapshot(cx);
10133 for selection in self.selections.all::<usize>(cx) {
10134 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10135 }
10136
10137 let buffer = self.buffer().read(cx);
10138 let ranges = buffer_ids
10139 .into_iter()
10140 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10141 .collect::<Vec<_>>();
10142
10143 self.restore_hunks_in_ranges(ranges, window, cx);
10144 }
10145
10146 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10147 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10148 let selections = self
10149 .selections
10150 .all(cx)
10151 .into_iter()
10152 .map(|s| s.range())
10153 .collect();
10154 self.restore_hunks_in_ranges(selections, window, cx);
10155 }
10156
10157 pub fn restore_hunks_in_ranges(
10158 &mut self,
10159 ranges: Vec<Range<Point>>,
10160 window: &mut Window,
10161 cx: &mut Context<Editor>,
10162 ) {
10163 let mut revert_changes = HashMap::default();
10164 let chunk_by = self
10165 .snapshot(window, cx)
10166 .hunks_for_ranges(ranges)
10167 .into_iter()
10168 .chunk_by(|hunk| hunk.buffer_id);
10169 for (buffer_id, hunks) in &chunk_by {
10170 let hunks = hunks.collect::<Vec<_>>();
10171 for hunk in &hunks {
10172 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10173 }
10174 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10175 }
10176 drop(chunk_by);
10177 if !revert_changes.is_empty() {
10178 self.transact(window, cx, |editor, window, cx| {
10179 editor.restore(revert_changes, window, cx);
10180 });
10181 }
10182 }
10183
10184 pub fn open_active_item_in_terminal(
10185 &mut self,
10186 _: &OpenInTerminal,
10187 window: &mut Window,
10188 cx: &mut Context<Self>,
10189 ) {
10190 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10191 let project_path = buffer.read(cx).project_path(cx)?;
10192 let project = self.project.as_ref()?.read(cx);
10193 let entry = project.entry_for_path(&project_path, cx)?;
10194 let parent = match &entry.canonical_path {
10195 Some(canonical_path) => canonical_path.to_path_buf(),
10196 None => project.absolute_path(&project_path, cx)?,
10197 }
10198 .parent()?
10199 .to_path_buf();
10200 Some(parent)
10201 }) {
10202 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10203 }
10204 }
10205
10206 fn set_breakpoint_context_menu(
10207 &mut self,
10208 display_row: DisplayRow,
10209 position: Option<Anchor>,
10210 clicked_point: gpui::Point<Pixels>,
10211 window: &mut Window,
10212 cx: &mut Context<Self>,
10213 ) {
10214 let source = self
10215 .buffer
10216 .read(cx)
10217 .snapshot(cx)
10218 .anchor_before(Point::new(display_row.0, 0u32));
10219
10220 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10221
10222 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10223 self,
10224 source,
10225 clicked_point,
10226 context_menu,
10227 window,
10228 cx,
10229 );
10230 }
10231
10232 fn add_edit_breakpoint_block(
10233 &mut self,
10234 anchor: Anchor,
10235 breakpoint: &Breakpoint,
10236 edit_action: BreakpointPromptEditAction,
10237 window: &mut Window,
10238 cx: &mut Context<Self>,
10239 ) {
10240 let weak_editor = cx.weak_entity();
10241 let bp_prompt = cx.new(|cx| {
10242 BreakpointPromptEditor::new(
10243 weak_editor,
10244 anchor,
10245 breakpoint.clone(),
10246 edit_action,
10247 window,
10248 cx,
10249 )
10250 });
10251
10252 let height = bp_prompt.update(cx, |this, cx| {
10253 this.prompt
10254 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10255 });
10256 let cloned_prompt = bp_prompt.clone();
10257 let blocks = vec![BlockProperties {
10258 style: BlockStyle::Sticky,
10259 placement: BlockPlacement::Above(anchor),
10260 height: Some(height),
10261 render: Arc::new(move |cx| {
10262 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10263 cloned_prompt.clone().into_any_element()
10264 }),
10265 priority: 0,
10266 render_in_minimap: true,
10267 }];
10268
10269 let focus_handle = bp_prompt.focus_handle(cx);
10270 window.focus(&focus_handle);
10271
10272 let block_ids = self.insert_blocks(blocks, None, cx);
10273 bp_prompt.update(cx, |prompt, _| {
10274 prompt.add_block_ids(block_ids);
10275 });
10276 }
10277
10278 pub(crate) fn breakpoint_at_row(
10279 &self,
10280 row: u32,
10281 window: &mut Window,
10282 cx: &mut Context<Self>,
10283 ) -> Option<(Anchor, Breakpoint)> {
10284 let snapshot = self.snapshot(window, cx);
10285 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10286
10287 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10288 }
10289
10290 pub(crate) fn breakpoint_at_anchor(
10291 &self,
10292 breakpoint_position: Anchor,
10293 snapshot: &EditorSnapshot,
10294 cx: &mut Context<Self>,
10295 ) -> Option<(Anchor, Breakpoint)> {
10296 let project = self.project.clone()?;
10297
10298 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10299 snapshot
10300 .buffer_snapshot
10301 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10302 })?;
10303
10304 let enclosing_excerpt = breakpoint_position.excerpt_id;
10305 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10306 let buffer_snapshot = buffer.read(cx).snapshot();
10307
10308 let row = buffer_snapshot
10309 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10310 .row;
10311
10312 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10313 let anchor_end = snapshot
10314 .buffer_snapshot
10315 .anchor_after(Point::new(row, line_len));
10316
10317 let bp = self
10318 .breakpoint_store
10319 .as_ref()?
10320 .read_with(cx, |breakpoint_store, cx| {
10321 breakpoint_store
10322 .breakpoints(
10323 &buffer,
10324 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10325 &buffer_snapshot,
10326 cx,
10327 )
10328 .next()
10329 .and_then(|(bp, _)| {
10330 let breakpoint_row = buffer_snapshot
10331 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10332 .row;
10333
10334 if breakpoint_row == row {
10335 snapshot
10336 .buffer_snapshot
10337 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10338 .map(|position| (position, bp.bp.clone()))
10339 } else {
10340 None
10341 }
10342 })
10343 });
10344 bp
10345 }
10346
10347 pub fn edit_log_breakpoint(
10348 &mut self,
10349 _: &EditLogBreakpoint,
10350 window: &mut Window,
10351 cx: &mut Context<Self>,
10352 ) {
10353 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10354 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10355 message: None,
10356 state: BreakpointState::Enabled,
10357 condition: None,
10358 hit_condition: None,
10359 });
10360
10361 self.add_edit_breakpoint_block(
10362 anchor,
10363 &breakpoint,
10364 BreakpointPromptEditAction::Log,
10365 window,
10366 cx,
10367 );
10368 }
10369 }
10370
10371 fn breakpoints_at_cursors(
10372 &self,
10373 window: &mut Window,
10374 cx: &mut Context<Self>,
10375 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10376 let snapshot = self.snapshot(window, cx);
10377 let cursors = self
10378 .selections
10379 .disjoint_anchors()
10380 .into_iter()
10381 .map(|selection| {
10382 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10383
10384 let breakpoint_position = self
10385 .breakpoint_at_row(cursor_position.row, window, cx)
10386 .map(|bp| bp.0)
10387 .unwrap_or_else(|| {
10388 snapshot
10389 .display_snapshot
10390 .buffer_snapshot
10391 .anchor_after(Point::new(cursor_position.row, 0))
10392 });
10393
10394 let breakpoint = self
10395 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10396 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10397
10398 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10399 })
10400 // 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.
10401 .collect::<HashMap<Anchor, _>>();
10402
10403 cursors.into_iter().collect()
10404 }
10405
10406 pub fn enable_breakpoint(
10407 &mut self,
10408 _: &crate::actions::EnableBreakpoint,
10409 window: &mut Window,
10410 cx: &mut Context<Self>,
10411 ) {
10412 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10413 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10414 continue;
10415 };
10416 self.edit_breakpoint_at_anchor(
10417 anchor,
10418 breakpoint,
10419 BreakpointEditAction::InvertState,
10420 cx,
10421 );
10422 }
10423 }
10424
10425 pub fn disable_breakpoint(
10426 &mut self,
10427 _: &crate::actions::DisableBreakpoint,
10428 window: &mut Window,
10429 cx: &mut Context<Self>,
10430 ) {
10431 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10432 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10433 continue;
10434 };
10435 self.edit_breakpoint_at_anchor(
10436 anchor,
10437 breakpoint,
10438 BreakpointEditAction::InvertState,
10439 cx,
10440 );
10441 }
10442 }
10443
10444 pub fn toggle_breakpoint(
10445 &mut self,
10446 _: &crate::actions::ToggleBreakpoint,
10447 window: &mut Window,
10448 cx: &mut Context<Self>,
10449 ) {
10450 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10451 if let Some(breakpoint) = breakpoint {
10452 self.edit_breakpoint_at_anchor(
10453 anchor,
10454 breakpoint,
10455 BreakpointEditAction::Toggle,
10456 cx,
10457 );
10458 } else {
10459 self.edit_breakpoint_at_anchor(
10460 anchor,
10461 Breakpoint::new_standard(),
10462 BreakpointEditAction::Toggle,
10463 cx,
10464 );
10465 }
10466 }
10467 }
10468
10469 pub fn edit_breakpoint_at_anchor(
10470 &mut self,
10471 breakpoint_position: Anchor,
10472 breakpoint: Breakpoint,
10473 edit_action: BreakpointEditAction,
10474 cx: &mut Context<Self>,
10475 ) {
10476 let Some(breakpoint_store) = &self.breakpoint_store else {
10477 return;
10478 };
10479
10480 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10481 if breakpoint_position == Anchor::min() {
10482 self.buffer()
10483 .read(cx)
10484 .excerpt_buffer_ids()
10485 .into_iter()
10486 .next()
10487 } else {
10488 None
10489 }
10490 }) else {
10491 return;
10492 };
10493
10494 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10495 return;
10496 };
10497
10498 breakpoint_store.update(cx, |breakpoint_store, cx| {
10499 breakpoint_store.toggle_breakpoint(
10500 buffer,
10501 BreakpointWithPosition {
10502 position: breakpoint_position.text_anchor,
10503 bp: breakpoint,
10504 },
10505 edit_action,
10506 cx,
10507 );
10508 });
10509
10510 cx.notify();
10511 }
10512
10513 #[cfg(any(test, feature = "test-support"))]
10514 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10515 self.breakpoint_store.clone()
10516 }
10517
10518 pub fn prepare_restore_change(
10519 &self,
10520 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10521 hunk: &MultiBufferDiffHunk,
10522 cx: &mut App,
10523 ) -> Option<()> {
10524 if hunk.is_created_file() {
10525 return None;
10526 }
10527 let buffer = self.buffer.read(cx);
10528 let diff = buffer.diff_for(hunk.buffer_id)?;
10529 let buffer = buffer.buffer(hunk.buffer_id)?;
10530 let buffer = buffer.read(cx);
10531 let original_text = diff
10532 .read(cx)
10533 .base_text()
10534 .as_rope()
10535 .slice(hunk.diff_base_byte_range.clone());
10536 let buffer_snapshot = buffer.snapshot();
10537 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10538 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10539 probe
10540 .0
10541 .start
10542 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10543 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10544 }) {
10545 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10546 Some(())
10547 } else {
10548 None
10549 }
10550 }
10551
10552 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10553 self.manipulate_lines(window, cx, |lines| lines.reverse())
10554 }
10555
10556 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10557 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10558 }
10559
10560 fn manipulate_lines<Fn>(
10561 &mut self,
10562 window: &mut Window,
10563 cx: &mut Context<Self>,
10564 mut callback: Fn,
10565 ) where
10566 Fn: FnMut(&mut Vec<&str>),
10567 {
10568 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10569
10570 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10571 let buffer = self.buffer.read(cx).snapshot(cx);
10572
10573 let mut edits = Vec::new();
10574
10575 let selections = self.selections.all::<Point>(cx);
10576 let mut selections = selections.iter().peekable();
10577 let mut contiguous_row_selections = Vec::new();
10578 let mut new_selections = Vec::new();
10579 let mut added_lines = 0;
10580 let mut removed_lines = 0;
10581
10582 while let Some(selection) = selections.next() {
10583 let (start_row, end_row) = consume_contiguous_rows(
10584 &mut contiguous_row_selections,
10585 selection,
10586 &display_map,
10587 &mut selections,
10588 );
10589
10590 let start_point = Point::new(start_row.0, 0);
10591 let end_point = Point::new(
10592 end_row.previous_row().0,
10593 buffer.line_len(end_row.previous_row()),
10594 );
10595 let text = buffer
10596 .text_for_range(start_point..end_point)
10597 .collect::<String>();
10598
10599 let mut lines = text.split('\n').collect_vec();
10600
10601 let lines_before = lines.len();
10602 callback(&mut lines);
10603 let lines_after = lines.len();
10604
10605 edits.push((start_point..end_point, lines.join("\n")));
10606
10607 // Selections must change based on added and removed line count
10608 let start_row =
10609 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10610 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10611 new_selections.push(Selection {
10612 id: selection.id,
10613 start: start_row,
10614 end: end_row,
10615 goal: SelectionGoal::None,
10616 reversed: selection.reversed,
10617 });
10618
10619 if lines_after > lines_before {
10620 added_lines += lines_after - lines_before;
10621 } else if lines_before > lines_after {
10622 removed_lines += lines_before - lines_after;
10623 }
10624 }
10625
10626 self.transact(window, cx, |this, window, cx| {
10627 let buffer = this.buffer.update(cx, |buffer, cx| {
10628 buffer.edit(edits, None, cx);
10629 buffer.snapshot(cx)
10630 });
10631
10632 // Recalculate offsets on newly edited buffer
10633 let new_selections = new_selections
10634 .iter()
10635 .map(|s| {
10636 let start_point = Point::new(s.start.0, 0);
10637 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10638 Selection {
10639 id: s.id,
10640 start: buffer.point_to_offset(start_point),
10641 end: buffer.point_to_offset(end_point),
10642 goal: s.goal,
10643 reversed: s.reversed,
10644 }
10645 })
10646 .collect();
10647
10648 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10649 s.select(new_selections);
10650 });
10651
10652 this.request_autoscroll(Autoscroll::fit(), cx);
10653 });
10654 }
10655
10656 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10657 self.manipulate_text(window, cx, |text| {
10658 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10659 if has_upper_case_characters {
10660 text.to_lowercase()
10661 } else {
10662 text.to_uppercase()
10663 }
10664 })
10665 }
10666
10667 pub fn convert_to_upper_case(
10668 &mut self,
10669 _: &ConvertToUpperCase,
10670 window: &mut Window,
10671 cx: &mut Context<Self>,
10672 ) {
10673 self.manipulate_text(window, cx, |text| text.to_uppercase())
10674 }
10675
10676 pub fn convert_to_lower_case(
10677 &mut self,
10678 _: &ConvertToLowerCase,
10679 window: &mut Window,
10680 cx: &mut Context<Self>,
10681 ) {
10682 self.manipulate_text(window, cx, |text| text.to_lowercase())
10683 }
10684
10685 pub fn convert_to_title_case(
10686 &mut self,
10687 _: &ConvertToTitleCase,
10688 window: &mut Window,
10689 cx: &mut Context<Self>,
10690 ) {
10691 self.manipulate_text(window, cx, |text| {
10692 text.split('\n')
10693 .map(|line| line.to_case(Case::Title))
10694 .join("\n")
10695 })
10696 }
10697
10698 pub fn convert_to_snake_case(
10699 &mut self,
10700 _: &ConvertToSnakeCase,
10701 window: &mut Window,
10702 cx: &mut Context<Self>,
10703 ) {
10704 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10705 }
10706
10707 pub fn convert_to_kebab_case(
10708 &mut self,
10709 _: &ConvertToKebabCase,
10710 window: &mut Window,
10711 cx: &mut Context<Self>,
10712 ) {
10713 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10714 }
10715
10716 pub fn convert_to_upper_camel_case(
10717 &mut self,
10718 _: &ConvertToUpperCamelCase,
10719 window: &mut Window,
10720 cx: &mut Context<Self>,
10721 ) {
10722 self.manipulate_text(window, cx, |text| {
10723 text.split('\n')
10724 .map(|line| line.to_case(Case::UpperCamel))
10725 .join("\n")
10726 })
10727 }
10728
10729 pub fn convert_to_lower_camel_case(
10730 &mut self,
10731 _: &ConvertToLowerCamelCase,
10732 window: &mut Window,
10733 cx: &mut Context<Self>,
10734 ) {
10735 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10736 }
10737
10738 pub fn convert_to_opposite_case(
10739 &mut self,
10740 _: &ConvertToOppositeCase,
10741 window: &mut Window,
10742 cx: &mut Context<Self>,
10743 ) {
10744 self.manipulate_text(window, cx, |text| {
10745 text.chars()
10746 .fold(String::with_capacity(text.len()), |mut t, c| {
10747 if c.is_uppercase() {
10748 t.extend(c.to_lowercase());
10749 } else {
10750 t.extend(c.to_uppercase());
10751 }
10752 t
10753 })
10754 })
10755 }
10756
10757 pub fn convert_to_rot13(
10758 &mut self,
10759 _: &ConvertToRot13,
10760 window: &mut Window,
10761 cx: &mut Context<Self>,
10762 ) {
10763 self.manipulate_text(window, cx, |text| {
10764 text.chars()
10765 .map(|c| match c {
10766 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10767 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10768 _ => c,
10769 })
10770 .collect()
10771 })
10772 }
10773
10774 pub fn convert_to_rot47(
10775 &mut self,
10776 _: &ConvertToRot47,
10777 window: &mut Window,
10778 cx: &mut Context<Self>,
10779 ) {
10780 self.manipulate_text(window, cx, |text| {
10781 text.chars()
10782 .map(|c| {
10783 let code_point = c as u32;
10784 if code_point >= 33 && code_point <= 126 {
10785 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10786 }
10787 c
10788 })
10789 .collect()
10790 })
10791 }
10792
10793 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10794 where
10795 Fn: FnMut(&str) -> String,
10796 {
10797 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10798 let buffer = self.buffer.read(cx).snapshot(cx);
10799
10800 let mut new_selections = Vec::new();
10801 let mut edits = Vec::new();
10802 let mut selection_adjustment = 0i32;
10803
10804 for selection in self.selections.all::<usize>(cx) {
10805 let selection_is_empty = selection.is_empty();
10806
10807 let (start, end) = if selection_is_empty {
10808 let word_range = movement::surrounding_word(
10809 &display_map,
10810 selection.start.to_display_point(&display_map),
10811 );
10812 let start = word_range.start.to_offset(&display_map, Bias::Left);
10813 let end = word_range.end.to_offset(&display_map, Bias::Left);
10814 (start, end)
10815 } else {
10816 (selection.start, selection.end)
10817 };
10818
10819 let text = buffer.text_for_range(start..end).collect::<String>();
10820 let old_length = text.len() as i32;
10821 let text = callback(&text);
10822
10823 new_selections.push(Selection {
10824 start: (start as i32 - selection_adjustment) as usize,
10825 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10826 goal: SelectionGoal::None,
10827 ..selection
10828 });
10829
10830 selection_adjustment += old_length - text.len() as i32;
10831
10832 edits.push((start..end, text));
10833 }
10834
10835 self.transact(window, cx, |this, window, cx| {
10836 this.buffer.update(cx, |buffer, cx| {
10837 buffer.edit(edits, None, cx);
10838 });
10839
10840 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10841 s.select(new_selections);
10842 });
10843
10844 this.request_autoscroll(Autoscroll::fit(), cx);
10845 });
10846 }
10847
10848 pub fn move_selection_on_drop(
10849 &mut self,
10850 selection: &Selection<Anchor>,
10851 target: DisplayPoint,
10852 is_cut: bool,
10853 window: &mut Window,
10854 cx: &mut Context<Self>,
10855 ) {
10856 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10857 let buffer = &display_map.buffer_snapshot;
10858 let mut edits = Vec::new();
10859 let insert_point = display_map
10860 .clip_point(target, Bias::Left)
10861 .to_point(&display_map);
10862 let text = buffer
10863 .text_for_range(selection.start..selection.end)
10864 .collect::<String>();
10865 if is_cut {
10866 edits.push(((selection.start..selection.end), String::new()));
10867 }
10868 let insert_anchor = buffer.anchor_before(insert_point);
10869 edits.push(((insert_anchor..insert_anchor), text));
10870 let last_edit_start = insert_anchor.bias_left(buffer);
10871 let last_edit_end = insert_anchor.bias_right(buffer);
10872 self.transact(window, cx, |this, window, cx| {
10873 this.buffer.update(cx, |buffer, cx| {
10874 buffer.edit(edits, None, cx);
10875 });
10876 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10877 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10878 });
10879 });
10880 }
10881
10882 pub fn clear_selection_drag_state(&mut self) {
10883 self.selection_drag_state = SelectionDragState::None;
10884 }
10885
10886 pub fn duplicate(
10887 &mut self,
10888 upwards: bool,
10889 whole_lines: bool,
10890 window: &mut Window,
10891 cx: &mut Context<Self>,
10892 ) {
10893 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10894
10895 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10896 let buffer = &display_map.buffer_snapshot;
10897 let selections = self.selections.all::<Point>(cx);
10898
10899 let mut edits = Vec::new();
10900 let mut selections_iter = selections.iter().peekable();
10901 while let Some(selection) = selections_iter.next() {
10902 let mut rows = selection.spanned_rows(false, &display_map);
10903 // duplicate line-wise
10904 if whole_lines || selection.start == selection.end {
10905 // Avoid duplicating the same lines twice.
10906 while let Some(next_selection) = selections_iter.peek() {
10907 let next_rows = next_selection.spanned_rows(false, &display_map);
10908 if next_rows.start < rows.end {
10909 rows.end = next_rows.end;
10910 selections_iter.next().unwrap();
10911 } else {
10912 break;
10913 }
10914 }
10915
10916 // Copy the text from the selected row region and splice it either at the start
10917 // or end of the region.
10918 let start = Point::new(rows.start.0, 0);
10919 let end = Point::new(
10920 rows.end.previous_row().0,
10921 buffer.line_len(rows.end.previous_row()),
10922 );
10923 let text = buffer
10924 .text_for_range(start..end)
10925 .chain(Some("\n"))
10926 .collect::<String>();
10927 let insert_location = if upwards {
10928 Point::new(rows.end.0, 0)
10929 } else {
10930 start
10931 };
10932 edits.push((insert_location..insert_location, text));
10933 } else {
10934 // duplicate character-wise
10935 let start = selection.start;
10936 let end = selection.end;
10937 let text = buffer.text_for_range(start..end).collect::<String>();
10938 edits.push((selection.end..selection.end, text));
10939 }
10940 }
10941
10942 self.transact(window, cx, |this, _, cx| {
10943 this.buffer.update(cx, |buffer, cx| {
10944 buffer.edit(edits, None, cx);
10945 });
10946
10947 this.request_autoscroll(Autoscroll::fit(), cx);
10948 });
10949 }
10950
10951 pub fn duplicate_line_up(
10952 &mut self,
10953 _: &DuplicateLineUp,
10954 window: &mut Window,
10955 cx: &mut Context<Self>,
10956 ) {
10957 self.duplicate(true, true, window, cx);
10958 }
10959
10960 pub fn duplicate_line_down(
10961 &mut self,
10962 _: &DuplicateLineDown,
10963 window: &mut Window,
10964 cx: &mut Context<Self>,
10965 ) {
10966 self.duplicate(false, true, window, cx);
10967 }
10968
10969 pub fn duplicate_selection(
10970 &mut self,
10971 _: &DuplicateSelection,
10972 window: &mut Window,
10973 cx: &mut Context<Self>,
10974 ) {
10975 self.duplicate(false, false, window, cx);
10976 }
10977
10978 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10979 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10980 if self.mode.is_single_line() {
10981 cx.propagate();
10982 return;
10983 }
10984
10985 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10986 let buffer = self.buffer.read(cx).snapshot(cx);
10987
10988 let mut edits = Vec::new();
10989 let mut unfold_ranges = Vec::new();
10990 let mut refold_creases = Vec::new();
10991
10992 let selections = self.selections.all::<Point>(cx);
10993 let mut selections = selections.iter().peekable();
10994 let mut contiguous_row_selections = Vec::new();
10995 let mut new_selections = Vec::new();
10996
10997 while let Some(selection) = selections.next() {
10998 // Find all the selections that span a contiguous row range
10999 let (start_row, end_row) = consume_contiguous_rows(
11000 &mut contiguous_row_selections,
11001 selection,
11002 &display_map,
11003 &mut selections,
11004 );
11005
11006 // Move the text spanned by the row range to be before the line preceding the row range
11007 if start_row.0 > 0 {
11008 let range_to_move = Point::new(
11009 start_row.previous_row().0,
11010 buffer.line_len(start_row.previous_row()),
11011 )
11012 ..Point::new(
11013 end_row.previous_row().0,
11014 buffer.line_len(end_row.previous_row()),
11015 );
11016 let insertion_point = display_map
11017 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11018 .0;
11019
11020 // Don't move lines across excerpts
11021 if buffer
11022 .excerpt_containing(insertion_point..range_to_move.end)
11023 .is_some()
11024 {
11025 let text = buffer
11026 .text_for_range(range_to_move.clone())
11027 .flat_map(|s| s.chars())
11028 .skip(1)
11029 .chain(['\n'])
11030 .collect::<String>();
11031
11032 edits.push((
11033 buffer.anchor_after(range_to_move.start)
11034 ..buffer.anchor_before(range_to_move.end),
11035 String::new(),
11036 ));
11037 let insertion_anchor = buffer.anchor_after(insertion_point);
11038 edits.push((insertion_anchor..insertion_anchor, text));
11039
11040 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11041
11042 // Move selections up
11043 new_selections.extend(contiguous_row_selections.drain(..).map(
11044 |mut selection| {
11045 selection.start.row -= row_delta;
11046 selection.end.row -= row_delta;
11047 selection
11048 },
11049 ));
11050
11051 // Move folds up
11052 unfold_ranges.push(range_to_move.clone());
11053 for fold in display_map.folds_in_range(
11054 buffer.anchor_before(range_to_move.start)
11055 ..buffer.anchor_after(range_to_move.end),
11056 ) {
11057 let mut start = fold.range.start.to_point(&buffer);
11058 let mut end = fold.range.end.to_point(&buffer);
11059 start.row -= row_delta;
11060 end.row -= row_delta;
11061 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11062 }
11063 }
11064 }
11065
11066 // If we didn't move line(s), preserve the existing selections
11067 new_selections.append(&mut contiguous_row_selections);
11068 }
11069
11070 self.transact(window, cx, |this, window, cx| {
11071 this.unfold_ranges(&unfold_ranges, true, true, cx);
11072 this.buffer.update(cx, |buffer, cx| {
11073 for (range, text) in edits {
11074 buffer.edit([(range, text)], None, cx);
11075 }
11076 });
11077 this.fold_creases(refold_creases, true, window, cx);
11078 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11079 s.select(new_selections);
11080 })
11081 });
11082 }
11083
11084 pub fn move_line_down(
11085 &mut self,
11086 _: &MoveLineDown,
11087 window: &mut Window,
11088 cx: &mut Context<Self>,
11089 ) {
11090 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11091 if self.mode.is_single_line() {
11092 cx.propagate();
11093 return;
11094 }
11095
11096 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11097 let buffer = self.buffer.read(cx).snapshot(cx);
11098
11099 let mut edits = Vec::new();
11100 let mut unfold_ranges = Vec::new();
11101 let mut refold_creases = Vec::new();
11102
11103 let selections = self.selections.all::<Point>(cx);
11104 let mut selections = selections.iter().peekable();
11105 let mut contiguous_row_selections = Vec::new();
11106 let mut new_selections = Vec::new();
11107
11108 while let Some(selection) = selections.next() {
11109 // Find all the selections that span a contiguous row range
11110 let (start_row, end_row) = consume_contiguous_rows(
11111 &mut contiguous_row_selections,
11112 selection,
11113 &display_map,
11114 &mut selections,
11115 );
11116
11117 // Move the text spanned by the row range to be after the last line of the row range
11118 if end_row.0 <= buffer.max_point().row {
11119 let range_to_move =
11120 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11121 let insertion_point = display_map
11122 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11123 .0;
11124
11125 // Don't move lines across excerpt boundaries
11126 if buffer
11127 .excerpt_containing(range_to_move.start..insertion_point)
11128 .is_some()
11129 {
11130 let mut text = String::from("\n");
11131 text.extend(buffer.text_for_range(range_to_move.clone()));
11132 text.pop(); // Drop trailing newline
11133 edits.push((
11134 buffer.anchor_after(range_to_move.start)
11135 ..buffer.anchor_before(range_to_move.end),
11136 String::new(),
11137 ));
11138 let insertion_anchor = buffer.anchor_after(insertion_point);
11139 edits.push((insertion_anchor..insertion_anchor, text));
11140
11141 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11142
11143 // Move selections down
11144 new_selections.extend(contiguous_row_selections.drain(..).map(
11145 |mut selection| {
11146 selection.start.row += row_delta;
11147 selection.end.row += row_delta;
11148 selection
11149 },
11150 ));
11151
11152 // Move folds down
11153 unfold_ranges.push(range_to_move.clone());
11154 for fold in display_map.folds_in_range(
11155 buffer.anchor_before(range_to_move.start)
11156 ..buffer.anchor_after(range_to_move.end),
11157 ) {
11158 let mut start = fold.range.start.to_point(&buffer);
11159 let mut end = fold.range.end.to_point(&buffer);
11160 start.row += row_delta;
11161 end.row += row_delta;
11162 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11163 }
11164 }
11165 }
11166
11167 // If we didn't move line(s), preserve the existing selections
11168 new_selections.append(&mut contiguous_row_selections);
11169 }
11170
11171 self.transact(window, cx, |this, window, cx| {
11172 this.unfold_ranges(&unfold_ranges, true, true, cx);
11173 this.buffer.update(cx, |buffer, cx| {
11174 for (range, text) in edits {
11175 buffer.edit([(range, text)], None, cx);
11176 }
11177 });
11178 this.fold_creases(refold_creases, true, window, cx);
11179 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11180 s.select(new_selections)
11181 });
11182 });
11183 }
11184
11185 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11186 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11187 let text_layout_details = &self.text_layout_details(window);
11188 self.transact(window, cx, |this, window, cx| {
11189 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11190 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11191 s.move_with(|display_map, selection| {
11192 if !selection.is_empty() {
11193 return;
11194 }
11195
11196 let mut head = selection.head();
11197 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11198 if head.column() == display_map.line_len(head.row()) {
11199 transpose_offset = display_map
11200 .buffer_snapshot
11201 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11202 }
11203
11204 if transpose_offset == 0 {
11205 return;
11206 }
11207
11208 *head.column_mut() += 1;
11209 head = display_map.clip_point(head, Bias::Right);
11210 let goal = SelectionGoal::HorizontalPosition(
11211 display_map
11212 .x_for_display_point(head, text_layout_details)
11213 .into(),
11214 );
11215 selection.collapse_to(head, goal);
11216
11217 let transpose_start = display_map
11218 .buffer_snapshot
11219 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11220 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11221 let transpose_end = display_map
11222 .buffer_snapshot
11223 .clip_offset(transpose_offset + 1, Bias::Right);
11224 if let Some(ch) =
11225 display_map.buffer_snapshot.chars_at(transpose_start).next()
11226 {
11227 edits.push((transpose_start..transpose_offset, String::new()));
11228 edits.push((transpose_end..transpose_end, ch.to_string()));
11229 }
11230 }
11231 });
11232 edits
11233 });
11234 this.buffer
11235 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11236 let selections = this.selections.all::<usize>(cx);
11237 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11238 s.select(selections);
11239 });
11240 });
11241 }
11242
11243 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11244 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11245 if self.mode.is_single_line() {
11246 cx.propagate();
11247 return;
11248 }
11249
11250 self.rewrap_impl(RewrapOptions::default(), cx)
11251 }
11252
11253 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11254 let buffer = self.buffer.read(cx).snapshot(cx);
11255 let selections = self.selections.all::<Point>(cx);
11256
11257 // Shrink and split selections to respect paragraph boundaries.
11258 let ranges = selections.into_iter().flat_map(|selection| {
11259 let language_settings = buffer.language_settings_at(selection.head(), cx);
11260 let language_scope = buffer.language_scope_at(selection.head());
11261
11262 let Some(start_row) = (selection.start.row..=selection.end.row)
11263 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11264 else {
11265 return vec![];
11266 };
11267 let Some(end_row) = (selection.start.row..=selection.end.row)
11268 .rev()
11269 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11270 else {
11271 return vec![];
11272 };
11273
11274 let mut row = start_row;
11275 let mut ranges = Vec::new();
11276 while let Some(blank_row) =
11277 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11278 {
11279 let next_paragraph_start = (blank_row + 1..=end_row)
11280 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11281 .unwrap();
11282 ranges.push((
11283 language_settings.clone(),
11284 language_scope.clone(),
11285 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11286 ));
11287 row = next_paragraph_start;
11288 }
11289 ranges.push((
11290 language_settings.clone(),
11291 language_scope.clone(),
11292 Point::new(row, 0)..Point::new(end_row, 0),
11293 ));
11294
11295 ranges
11296 });
11297
11298 let mut edits = Vec::new();
11299 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11300
11301 for (language_settings, language_scope, range) in ranges {
11302 let mut start_row = range.start.row;
11303 let mut end_row = range.end.row;
11304
11305 // Skip selections that overlap with a range that has already been rewrapped.
11306 let selection_range = start_row..end_row;
11307 if rewrapped_row_ranges
11308 .iter()
11309 .any(|range| range.overlaps(&selection_range))
11310 {
11311 continue;
11312 }
11313
11314 let tab_size = language_settings.tab_size;
11315
11316 // Since not all lines in the selection may be at the same indent
11317 // level, choose the indent size that is the most common between all
11318 // of the lines.
11319 //
11320 // If there is a tie, we use the deepest indent.
11321 let (indent_size, indent_end) = {
11322 let mut indent_size_occurrences = HashMap::default();
11323 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11324
11325 for row in start_row..=end_row {
11326 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11327 rows_by_indent_size.entry(indent).or_default().push(row);
11328 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11329 }
11330
11331 let indent_size = indent_size_occurrences
11332 .into_iter()
11333 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11334 .map(|(indent, _)| indent)
11335 .unwrap_or_default();
11336 let row = rows_by_indent_size[&indent_size][0];
11337 let indent_end = Point::new(row, indent_size.len);
11338
11339 (indent_size, indent_end)
11340 };
11341
11342 let mut line_prefix = indent_size.chars().collect::<String>();
11343
11344 let mut inside_comment = false;
11345 if let Some(comment_prefix) = language_scope.and_then(|language| {
11346 language
11347 .line_comment_prefixes()
11348 .iter()
11349 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11350 .cloned()
11351 }) {
11352 line_prefix.push_str(&comment_prefix);
11353 inside_comment = true;
11354 }
11355
11356 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11357 RewrapBehavior::InComments => inside_comment,
11358 RewrapBehavior::InSelections => !range.is_empty(),
11359 RewrapBehavior::Anywhere => true,
11360 };
11361
11362 let should_rewrap = options.override_language_settings
11363 || allow_rewrap_based_on_language
11364 || self.hard_wrap.is_some();
11365 if !should_rewrap {
11366 continue;
11367 }
11368
11369 if range.is_empty() {
11370 'expand_upwards: while start_row > 0 {
11371 let prev_row = start_row - 1;
11372 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11373 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11374 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11375 {
11376 start_row = prev_row;
11377 } else {
11378 break 'expand_upwards;
11379 }
11380 }
11381
11382 'expand_downwards: while end_row < buffer.max_point().row {
11383 let next_row = end_row + 1;
11384 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11385 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11386 && !buffer.is_line_blank(MultiBufferRow(next_row))
11387 {
11388 end_row = next_row;
11389 } else {
11390 break 'expand_downwards;
11391 }
11392 }
11393 }
11394
11395 let start = Point::new(start_row, 0);
11396 let start_offset = start.to_offset(&buffer);
11397 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11398 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11399 let Some(lines_without_prefixes) = selection_text
11400 .lines()
11401 .map(|line| {
11402 line.strip_prefix(&line_prefix)
11403 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11404 .with_context(|| {
11405 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11406 })
11407 })
11408 .collect::<Result<Vec<_>, _>>()
11409 .log_err()
11410 else {
11411 continue;
11412 };
11413
11414 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11415 buffer
11416 .language_settings_at(Point::new(start_row, 0), cx)
11417 .preferred_line_length as usize
11418 });
11419 let wrapped_text = wrap_with_prefix(
11420 line_prefix,
11421 lines_without_prefixes.join("\n"),
11422 wrap_column,
11423 tab_size,
11424 options.preserve_existing_whitespace,
11425 );
11426
11427 // TODO: should always use char-based diff while still supporting cursor behavior that
11428 // matches vim.
11429 let mut diff_options = DiffOptions::default();
11430 if options.override_language_settings {
11431 diff_options.max_word_diff_len = 0;
11432 diff_options.max_word_diff_line_count = 0;
11433 } else {
11434 diff_options.max_word_diff_len = usize::MAX;
11435 diff_options.max_word_diff_line_count = usize::MAX;
11436 }
11437
11438 for (old_range, new_text) in
11439 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11440 {
11441 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11442 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11443 edits.push((edit_start..edit_end, new_text));
11444 }
11445
11446 rewrapped_row_ranges.push(start_row..=end_row);
11447 }
11448
11449 self.buffer
11450 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11451 }
11452
11453 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11454 let mut text = String::new();
11455 let buffer = self.buffer.read(cx).snapshot(cx);
11456 let mut selections = self.selections.all::<Point>(cx);
11457 let mut clipboard_selections = Vec::with_capacity(selections.len());
11458 {
11459 let max_point = buffer.max_point();
11460 let mut is_first = true;
11461 for selection in &mut selections {
11462 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11463 if is_entire_line {
11464 selection.start = Point::new(selection.start.row, 0);
11465 if !selection.is_empty() && selection.end.column == 0 {
11466 selection.end = cmp::min(max_point, selection.end);
11467 } else {
11468 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11469 }
11470 selection.goal = SelectionGoal::None;
11471 }
11472 if is_first {
11473 is_first = false;
11474 } else {
11475 text += "\n";
11476 }
11477 let mut len = 0;
11478 for chunk in buffer.text_for_range(selection.start..selection.end) {
11479 text.push_str(chunk);
11480 len += chunk.len();
11481 }
11482 clipboard_selections.push(ClipboardSelection {
11483 len,
11484 is_entire_line,
11485 first_line_indent: buffer
11486 .indent_size_for_line(MultiBufferRow(selection.start.row))
11487 .len,
11488 });
11489 }
11490 }
11491
11492 self.transact(window, cx, |this, window, cx| {
11493 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11494 s.select(selections);
11495 });
11496 this.insert("", window, cx);
11497 });
11498 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11499 }
11500
11501 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11502 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11503 let item = self.cut_common(window, cx);
11504 cx.write_to_clipboard(item);
11505 }
11506
11507 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11508 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11509 self.change_selections(None, window, cx, |s| {
11510 s.move_with(|snapshot, sel| {
11511 if sel.is_empty() {
11512 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11513 }
11514 });
11515 });
11516 let item = self.cut_common(window, cx);
11517 cx.set_global(KillRing(item))
11518 }
11519
11520 pub fn kill_ring_yank(
11521 &mut self,
11522 _: &KillRingYank,
11523 window: &mut Window,
11524 cx: &mut Context<Self>,
11525 ) {
11526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11527 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11528 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11529 (kill_ring.text().to_string(), kill_ring.metadata_json())
11530 } else {
11531 return;
11532 }
11533 } else {
11534 return;
11535 };
11536 self.do_paste(&text, metadata, false, window, cx);
11537 }
11538
11539 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11540 self.do_copy(true, cx);
11541 }
11542
11543 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11544 self.do_copy(false, cx);
11545 }
11546
11547 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11548 let selections = self.selections.all::<Point>(cx);
11549 let buffer = self.buffer.read(cx).read(cx);
11550 let mut text = String::new();
11551
11552 let mut clipboard_selections = Vec::with_capacity(selections.len());
11553 {
11554 let max_point = buffer.max_point();
11555 let mut is_first = true;
11556 for selection in &selections {
11557 let mut start = selection.start;
11558 let mut end = selection.end;
11559 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11560 if is_entire_line {
11561 start = Point::new(start.row, 0);
11562 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11563 }
11564
11565 let mut trimmed_selections = Vec::new();
11566 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11567 let row = MultiBufferRow(start.row);
11568 let first_indent = buffer.indent_size_for_line(row);
11569 if first_indent.len == 0 || start.column > first_indent.len {
11570 trimmed_selections.push(start..end);
11571 } else {
11572 trimmed_selections.push(
11573 Point::new(row.0, first_indent.len)
11574 ..Point::new(row.0, buffer.line_len(row)),
11575 );
11576 for row in start.row + 1..=end.row {
11577 let mut line_len = buffer.line_len(MultiBufferRow(row));
11578 if row == end.row {
11579 line_len = end.column;
11580 }
11581 if line_len == 0 {
11582 trimmed_selections
11583 .push(Point::new(row, 0)..Point::new(row, line_len));
11584 continue;
11585 }
11586 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11587 if row_indent_size.len >= first_indent.len {
11588 trimmed_selections.push(
11589 Point::new(row, first_indent.len)..Point::new(row, line_len),
11590 );
11591 } else {
11592 trimmed_selections.clear();
11593 trimmed_selections.push(start..end);
11594 break;
11595 }
11596 }
11597 }
11598 } else {
11599 trimmed_selections.push(start..end);
11600 }
11601
11602 for trimmed_range in trimmed_selections {
11603 if is_first {
11604 is_first = false;
11605 } else {
11606 text += "\n";
11607 }
11608 let mut len = 0;
11609 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11610 text.push_str(chunk);
11611 len += chunk.len();
11612 }
11613 clipboard_selections.push(ClipboardSelection {
11614 len,
11615 is_entire_line,
11616 first_line_indent: buffer
11617 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11618 .len,
11619 });
11620 }
11621 }
11622 }
11623
11624 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11625 text,
11626 clipboard_selections,
11627 ));
11628 }
11629
11630 pub fn do_paste(
11631 &mut self,
11632 text: &String,
11633 clipboard_selections: Option<Vec<ClipboardSelection>>,
11634 handle_entire_lines: bool,
11635 window: &mut Window,
11636 cx: &mut Context<Self>,
11637 ) {
11638 if self.read_only(cx) {
11639 return;
11640 }
11641
11642 let clipboard_text = Cow::Borrowed(text);
11643
11644 self.transact(window, cx, |this, window, cx| {
11645 if let Some(mut clipboard_selections) = clipboard_selections {
11646 let old_selections = this.selections.all::<usize>(cx);
11647 let all_selections_were_entire_line =
11648 clipboard_selections.iter().all(|s| s.is_entire_line);
11649 let first_selection_indent_column =
11650 clipboard_selections.first().map(|s| s.first_line_indent);
11651 if clipboard_selections.len() != old_selections.len() {
11652 clipboard_selections.drain(..);
11653 }
11654 let cursor_offset = this.selections.last::<usize>(cx).head();
11655 let mut auto_indent_on_paste = true;
11656
11657 this.buffer.update(cx, |buffer, cx| {
11658 let snapshot = buffer.read(cx);
11659 auto_indent_on_paste = snapshot
11660 .language_settings_at(cursor_offset, cx)
11661 .auto_indent_on_paste;
11662
11663 let mut start_offset = 0;
11664 let mut edits = Vec::new();
11665 let mut original_indent_columns = Vec::new();
11666 for (ix, selection) in old_selections.iter().enumerate() {
11667 let to_insert;
11668 let entire_line;
11669 let original_indent_column;
11670 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11671 let end_offset = start_offset + clipboard_selection.len;
11672 to_insert = &clipboard_text[start_offset..end_offset];
11673 entire_line = clipboard_selection.is_entire_line;
11674 start_offset = end_offset + 1;
11675 original_indent_column = Some(clipboard_selection.first_line_indent);
11676 } else {
11677 to_insert = clipboard_text.as_str();
11678 entire_line = all_selections_were_entire_line;
11679 original_indent_column = first_selection_indent_column
11680 }
11681
11682 // If the corresponding selection was empty when this slice of the
11683 // clipboard text was written, then the entire line containing the
11684 // selection was copied. If this selection is also currently empty,
11685 // then paste the line before the current line of the buffer.
11686 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11687 let column = selection.start.to_point(&snapshot).column as usize;
11688 let line_start = selection.start - column;
11689 line_start..line_start
11690 } else {
11691 selection.range()
11692 };
11693
11694 edits.push((range, to_insert));
11695 original_indent_columns.push(original_indent_column);
11696 }
11697 drop(snapshot);
11698
11699 buffer.edit(
11700 edits,
11701 if auto_indent_on_paste {
11702 Some(AutoindentMode::Block {
11703 original_indent_columns,
11704 })
11705 } else {
11706 None
11707 },
11708 cx,
11709 );
11710 });
11711
11712 let selections = this.selections.all::<usize>(cx);
11713 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11714 s.select(selections)
11715 });
11716 } else {
11717 this.insert(&clipboard_text, window, cx);
11718 }
11719 });
11720 }
11721
11722 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11723 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11724 if let Some(item) = cx.read_from_clipboard() {
11725 let entries = item.entries();
11726
11727 match entries.first() {
11728 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11729 // of all the pasted entries.
11730 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11731 .do_paste(
11732 clipboard_string.text(),
11733 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11734 true,
11735 window,
11736 cx,
11737 ),
11738 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11739 }
11740 }
11741 }
11742
11743 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11744 if self.read_only(cx) {
11745 return;
11746 }
11747
11748 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11749
11750 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11751 if let Some((selections, _)) =
11752 self.selection_history.transaction(transaction_id).cloned()
11753 {
11754 self.change_selections(None, window, cx, |s| {
11755 s.select_anchors(selections.to_vec());
11756 });
11757 } else {
11758 log::error!(
11759 "No entry in selection_history found for undo. \
11760 This may correspond to a bug where undo does not update the selection. \
11761 If this is occurring, please add details to \
11762 https://github.com/zed-industries/zed/issues/22692"
11763 );
11764 }
11765 self.request_autoscroll(Autoscroll::fit(), cx);
11766 self.unmark_text(window, cx);
11767 self.refresh_inline_completion(true, false, window, cx);
11768 cx.emit(EditorEvent::Edited { transaction_id });
11769 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11770 }
11771 }
11772
11773 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11774 if self.read_only(cx) {
11775 return;
11776 }
11777
11778 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11779
11780 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11781 if let Some((_, Some(selections))) =
11782 self.selection_history.transaction(transaction_id).cloned()
11783 {
11784 self.change_selections(None, window, cx, |s| {
11785 s.select_anchors(selections.to_vec());
11786 });
11787 } else {
11788 log::error!(
11789 "No entry in selection_history found for redo. \
11790 This may correspond to a bug where undo does not update the selection. \
11791 If this is occurring, please add details to \
11792 https://github.com/zed-industries/zed/issues/22692"
11793 );
11794 }
11795 self.request_autoscroll(Autoscroll::fit(), cx);
11796 self.unmark_text(window, cx);
11797 self.refresh_inline_completion(true, false, window, cx);
11798 cx.emit(EditorEvent::Edited { transaction_id });
11799 }
11800 }
11801
11802 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11803 self.buffer
11804 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11805 }
11806
11807 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11808 self.buffer
11809 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11810 }
11811
11812 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11813 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11814 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11815 s.move_with(|map, selection| {
11816 let cursor = if selection.is_empty() {
11817 movement::left(map, selection.start)
11818 } else {
11819 selection.start
11820 };
11821 selection.collapse_to(cursor, SelectionGoal::None);
11822 });
11823 })
11824 }
11825
11826 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11827 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11828 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11829 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11830 })
11831 }
11832
11833 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11834 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11835 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11836 s.move_with(|map, selection| {
11837 let cursor = if selection.is_empty() {
11838 movement::right(map, selection.end)
11839 } else {
11840 selection.end
11841 };
11842 selection.collapse_to(cursor, SelectionGoal::None)
11843 });
11844 })
11845 }
11846
11847 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11849 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11850 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11851 })
11852 }
11853
11854 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11855 if self.take_rename(true, window, cx).is_some() {
11856 return;
11857 }
11858
11859 if self.mode.is_single_line() {
11860 cx.propagate();
11861 return;
11862 }
11863
11864 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11865
11866 let text_layout_details = &self.text_layout_details(window);
11867 let selection_count = self.selections.count();
11868 let first_selection = self.selections.first_anchor();
11869
11870 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11871 s.move_with(|map, selection| {
11872 if !selection.is_empty() {
11873 selection.goal = SelectionGoal::None;
11874 }
11875 let (cursor, goal) = movement::up(
11876 map,
11877 selection.start,
11878 selection.goal,
11879 false,
11880 text_layout_details,
11881 );
11882 selection.collapse_to(cursor, goal);
11883 });
11884 });
11885
11886 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11887 {
11888 cx.propagate();
11889 }
11890 }
11891
11892 pub fn move_up_by_lines(
11893 &mut self,
11894 action: &MoveUpByLines,
11895 window: &mut Window,
11896 cx: &mut Context<Self>,
11897 ) {
11898 if self.take_rename(true, window, cx).is_some() {
11899 return;
11900 }
11901
11902 if self.mode.is_single_line() {
11903 cx.propagate();
11904 return;
11905 }
11906
11907 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11908
11909 let text_layout_details = &self.text_layout_details(window);
11910
11911 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11912 s.move_with(|map, selection| {
11913 if !selection.is_empty() {
11914 selection.goal = SelectionGoal::None;
11915 }
11916 let (cursor, goal) = movement::up_by_rows(
11917 map,
11918 selection.start,
11919 action.lines,
11920 selection.goal,
11921 false,
11922 text_layout_details,
11923 );
11924 selection.collapse_to(cursor, goal);
11925 });
11926 })
11927 }
11928
11929 pub fn move_down_by_lines(
11930 &mut self,
11931 action: &MoveDownByLines,
11932 window: &mut Window,
11933 cx: &mut Context<Self>,
11934 ) {
11935 if self.take_rename(true, window, cx).is_some() {
11936 return;
11937 }
11938
11939 if self.mode.is_single_line() {
11940 cx.propagate();
11941 return;
11942 }
11943
11944 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11945
11946 let text_layout_details = &self.text_layout_details(window);
11947
11948 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11949 s.move_with(|map, selection| {
11950 if !selection.is_empty() {
11951 selection.goal = SelectionGoal::None;
11952 }
11953 let (cursor, goal) = movement::down_by_rows(
11954 map,
11955 selection.start,
11956 action.lines,
11957 selection.goal,
11958 false,
11959 text_layout_details,
11960 );
11961 selection.collapse_to(cursor, goal);
11962 });
11963 })
11964 }
11965
11966 pub fn select_down_by_lines(
11967 &mut self,
11968 action: &SelectDownByLines,
11969 window: &mut Window,
11970 cx: &mut Context<Self>,
11971 ) {
11972 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11973 let text_layout_details = &self.text_layout_details(window);
11974 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11975 s.move_heads_with(|map, head, goal| {
11976 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11977 })
11978 })
11979 }
11980
11981 pub fn select_up_by_lines(
11982 &mut self,
11983 action: &SelectUpByLines,
11984 window: &mut Window,
11985 cx: &mut Context<Self>,
11986 ) {
11987 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11988 let text_layout_details = &self.text_layout_details(window);
11989 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11990 s.move_heads_with(|map, head, goal| {
11991 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11992 })
11993 })
11994 }
11995
11996 pub fn select_page_up(
11997 &mut self,
11998 _: &SelectPageUp,
11999 window: &mut Window,
12000 cx: &mut Context<Self>,
12001 ) {
12002 let Some(row_count) = self.visible_row_count() else {
12003 return;
12004 };
12005
12006 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12007
12008 let text_layout_details = &self.text_layout_details(window);
12009
12010 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12011 s.move_heads_with(|map, head, goal| {
12012 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12013 })
12014 })
12015 }
12016
12017 pub fn move_page_up(
12018 &mut self,
12019 action: &MovePageUp,
12020 window: &mut Window,
12021 cx: &mut Context<Self>,
12022 ) {
12023 if self.take_rename(true, window, cx).is_some() {
12024 return;
12025 }
12026
12027 if self
12028 .context_menu
12029 .borrow_mut()
12030 .as_mut()
12031 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12032 .unwrap_or(false)
12033 {
12034 return;
12035 }
12036
12037 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12038 cx.propagate();
12039 return;
12040 }
12041
12042 let Some(row_count) = self.visible_row_count() else {
12043 return;
12044 };
12045
12046 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12047
12048 let autoscroll = if action.center_cursor {
12049 Autoscroll::center()
12050 } else {
12051 Autoscroll::fit()
12052 };
12053
12054 let text_layout_details = &self.text_layout_details(window);
12055
12056 self.change_selections(Some(autoscroll), window, cx, |s| {
12057 s.move_with(|map, selection| {
12058 if !selection.is_empty() {
12059 selection.goal = SelectionGoal::None;
12060 }
12061 let (cursor, goal) = movement::up_by_rows(
12062 map,
12063 selection.end,
12064 row_count,
12065 selection.goal,
12066 false,
12067 text_layout_details,
12068 );
12069 selection.collapse_to(cursor, goal);
12070 });
12071 });
12072 }
12073
12074 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12075 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12076 let text_layout_details = &self.text_layout_details(window);
12077 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12078 s.move_heads_with(|map, head, goal| {
12079 movement::up(map, head, goal, false, text_layout_details)
12080 })
12081 })
12082 }
12083
12084 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12085 self.take_rename(true, window, cx);
12086
12087 if self.mode.is_single_line() {
12088 cx.propagate();
12089 return;
12090 }
12091
12092 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12093
12094 let text_layout_details = &self.text_layout_details(window);
12095 let selection_count = self.selections.count();
12096 let first_selection = self.selections.first_anchor();
12097
12098 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12099 s.move_with(|map, selection| {
12100 if !selection.is_empty() {
12101 selection.goal = SelectionGoal::None;
12102 }
12103 let (cursor, goal) = movement::down(
12104 map,
12105 selection.end,
12106 selection.goal,
12107 false,
12108 text_layout_details,
12109 );
12110 selection.collapse_to(cursor, goal);
12111 });
12112 });
12113
12114 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12115 {
12116 cx.propagate();
12117 }
12118 }
12119
12120 pub fn select_page_down(
12121 &mut self,
12122 _: &SelectPageDown,
12123 window: &mut Window,
12124 cx: &mut Context<Self>,
12125 ) {
12126 let Some(row_count) = self.visible_row_count() else {
12127 return;
12128 };
12129
12130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12131
12132 let text_layout_details = &self.text_layout_details(window);
12133
12134 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12135 s.move_heads_with(|map, head, goal| {
12136 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12137 })
12138 })
12139 }
12140
12141 pub fn move_page_down(
12142 &mut self,
12143 action: &MovePageDown,
12144 window: &mut Window,
12145 cx: &mut Context<Self>,
12146 ) {
12147 if self.take_rename(true, window, cx).is_some() {
12148 return;
12149 }
12150
12151 if self
12152 .context_menu
12153 .borrow_mut()
12154 .as_mut()
12155 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12156 .unwrap_or(false)
12157 {
12158 return;
12159 }
12160
12161 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12162 cx.propagate();
12163 return;
12164 }
12165
12166 let Some(row_count) = self.visible_row_count() else {
12167 return;
12168 };
12169
12170 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12171
12172 let autoscroll = if action.center_cursor {
12173 Autoscroll::center()
12174 } else {
12175 Autoscroll::fit()
12176 };
12177
12178 let text_layout_details = &self.text_layout_details(window);
12179 self.change_selections(Some(autoscroll), window, cx, |s| {
12180 s.move_with(|map, selection| {
12181 if !selection.is_empty() {
12182 selection.goal = SelectionGoal::None;
12183 }
12184 let (cursor, goal) = movement::down_by_rows(
12185 map,
12186 selection.end,
12187 row_count,
12188 selection.goal,
12189 false,
12190 text_layout_details,
12191 );
12192 selection.collapse_to(cursor, goal);
12193 });
12194 });
12195 }
12196
12197 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12199 let text_layout_details = &self.text_layout_details(window);
12200 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12201 s.move_heads_with(|map, head, goal| {
12202 movement::down(map, head, goal, false, text_layout_details)
12203 })
12204 });
12205 }
12206
12207 pub fn context_menu_first(
12208 &mut self,
12209 _: &ContextMenuFirst,
12210 window: &mut Window,
12211 cx: &mut Context<Self>,
12212 ) {
12213 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12214 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12215 }
12216 }
12217
12218 pub fn context_menu_prev(
12219 &mut self,
12220 _: &ContextMenuPrevious,
12221 window: &mut Window,
12222 cx: &mut Context<Self>,
12223 ) {
12224 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12225 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12226 }
12227 }
12228
12229 pub fn context_menu_next(
12230 &mut self,
12231 _: &ContextMenuNext,
12232 window: &mut Window,
12233 cx: &mut Context<Self>,
12234 ) {
12235 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12236 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12237 }
12238 }
12239
12240 pub fn context_menu_last(
12241 &mut self,
12242 _: &ContextMenuLast,
12243 window: &mut Window,
12244 cx: &mut Context<Self>,
12245 ) {
12246 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12247 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12248 }
12249 }
12250
12251 pub fn move_to_previous_word_start(
12252 &mut self,
12253 _: &MoveToPreviousWordStart,
12254 window: &mut Window,
12255 cx: &mut Context<Self>,
12256 ) {
12257 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12258 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12259 s.move_cursors_with(|map, head, _| {
12260 (
12261 movement::previous_word_start(map, head),
12262 SelectionGoal::None,
12263 )
12264 });
12265 })
12266 }
12267
12268 pub fn move_to_previous_subword_start(
12269 &mut self,
12270 _: &MoveToPreviousSubwordStart,
12271 window: &mut Window,
12272 cx: &mut Context<Self>,
12273 ) {
12274 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12275 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12276 s.move_cursors_with(|map, head, _| {
12277 (
12278 movement::previous_subword_start(map, head),
12279 SelectionGoal::None,
12280 )
12281 });
12282 })
12283 }
12284
12285 pub fn select_to_previous_word_start(
12286 &mut self,
12287 _: &SelectToPreviousWordStart,
12288 window: &mut Window,
12289 cx: &mut Context<Self>,
12290 ) {
12291 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12292 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12293 s.move_heads_with(|map, head, _| {
12294 (
12295 movement::previous_word_start(map, head),
12296 SelectionGoal::None,
12297 )
12298 });
12299 })
12300 }
12301
12302 pub fn select_to_previous_subword_start(
12303 &mut self,
12304 _: &SelectToPreviousSubwordStart,
12305 window: &mut Window,
12306 cx: &mut Context<Self>,
12307 ) {
12308 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12309 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12310 s.move_heads_with(|map, head, _| {
12311 (
12312 movement::previous_subword_start(map, head),
12313 SelectionGoal::None,
12314 )
12315 });
12316 })
12317 }
12318
12319 pub fn delete_to_previous_word_start(
12320 &mut self,
12321 action: &DeleteToPreviousWordStart,
12322 window: &mut Window,
12323 cx: &mut Context<Self>,
12324 ) {
12325 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12326 self.transact(window, cx, |this, window, cx| {
12327 this.select_autoclose_pair(window, cx);
12328 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12329 s.move_with(|map, selection| {
12330 if selection.is_empty() {
12331 let cursor = if action.ignore_newlines {
12332 movement::previous_word_start(map, selection.head())
12333 } else {
12334 movement::previous_word_start_or_newline(map, selection.head())
12335 };
12336 selection.set_head(cursor, SelectionGoal::None);
12337 }
12338 });
12339 });
12340 this.insert("", window, cx);
12341 });
12342 }
12343
12344 pub fn delete_to_previous_subword_start(
12345 &mut self,
12346 _: &DeleteToPreviousSubwordStart,
12347 window: &mut Window,
12348 cx: &mut Context<Self>,
12349 ) {
12350 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12351 self.transact(window, cx, |this, window, cx| {
12352 this.select_autoclose_pair(window, cx);
12353 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12354 s.move_with(|map, selection| {
12355 if selection.is_empty() {
12356 let cursor = movement::previous_subword_start(map, selection.head());
12357 selection.set_head(cursor, SelectionGoal::None);
12358 }
12359 });
12360 });
12361 this.insert("", window, cx);
12362 });
12363 }
12364
12365 pub fn move_to_next_word_end(
12366 &mut self,
12367 _: &MoveToNextWordEnd,
12368 window: &mut Window,
12369 cx: &mut Context<Self>,
12370 ) {
12371 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12372 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12373 s.move_cursors_with(|map, head, _| {
12374 (movement::next_word_end(map, head), SelectionGoal::None)
12375 });
12376 })
12377 }
12378
12379 pub fn move_to_next_subword_end(
12380 &mut self,
12381 _: &MoveToNextSubwordEnd,
12382 window: &mut Window,
12383 cx: &mut Context<Self>,
12384 ) {
12385 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12386 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12387 s.move_cursors_with(|map, head, _| {
12388 (movement::next_subword_end(map, head), SelectionGoal::None)
12389 });
12390 })
12391 }
12392
12393 pub fn select_to_next_word_end(
12394 &mut self,
12395 _: &SelectToNextWordEnd,
12396 window: &mut Window,
12397 cx: &mut Context<Self>,
12398 ) {
12399 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12400 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12401 s.move_heads_with(|map, head, _| {
12402 (movement::next_word_end(map, head), SelectionGoal::None)
12403 });
12404 })
12405 }
12406
12407 pub fn select_to_next_subword_end(
12408 &mut self,
12409 _: &SelectToNextSubwordEnd,
12410 window: &mut Window,
12411 cx: &mut Context<Self>,
12412 ) {
12413 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12414 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12415 s.move_heads_with(|map, head, _| {
12416 (movement::next_subword_end(map, head), SelectionGoal::None)
12417 });
12418 })
12419 }
12420
12421 pub fn delete_to_next_word_end(
12422 &mut self,
12423 action: &DeleteToNextWordEnd,
12424 window: &mut Window,
12425 cx: &mut Context<Self>,
12426 ) {
12427 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12428 self.transact(window, cx, |this, window, cx| {
12429 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12430 s.move_with(|map, selection| {
12431 if selection.is_empty() {
12432 let cursor = if action.ignore_newlines {
12433 movement::next_word_end(map, selection.head())
12434 } else {
12435 movement::next_word_end_or_newline(map, selection.head())
12436 };
12437 selection.set_head(cursor, SelectionGoal::None);
12438 }
12439 });
12440 });
12441 this.insert("", window, cx);
12442 });
12443 }
12444
12445 pub fn delete_to_next_subword_end(
12446 &mut self,
12447 _: &DeleteToNextSubwordEnd,
12448 window: &mut Window,
12449 cx: &mut Context<Self>,
12450 ) {
12451 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12452 self.transact(window, cx, |this, window, cx| {
12453 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12454 s.move_with(|map, selection| {
12455 if selection.is_empty() {
12456 let cursor = movement::next_subword_end(map, selection.head());
12457 selection.set_head(cursor, SelectionGoal::None);
12458 }
12459 });
12460 });
12461 this.insert("", window, cx);
12462 });
12463 }
12464
12465 pub fn move_to_beginning_of_line(
12466 &mut self,
12467 action: &MoveToBeginningOfLine,
12468 window: &mut Window,
12469 cx: &mut Context<Self>,
12470 ) {
12471 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12472 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12473 s.move_cursors_with(|map, head, _| {
12474 (
12475 movement::indented_line_beginning(
12476 map,
12477 head,
12478 action.stop_at_soft_wraps,
12479 action.stop_at_indent,
12480 ),
12481 SelectionGoal::None,
12482 )
12483 });
12484 })
12485 }
12486
12487 pub fn select_to_beginning_of_line(
12488 &mut self,
12489 action: &SelectToBeginningOfLine,
12490 window: &mut Window,
12491 cx: &mut Context<Self>,
12492 ) {
12493 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12494 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12495 s.move_heads_with(|map, head, _| {
12496 (
12497 movement::indented_line_beginning(
12498 map,
12499 head,
12500 action.stop_at_soft_wraps,
12501 action.stop_at_indent,
12502 ),
12503 SelectionGoal::None,
12504 )
12505 });
12506 });
12507 }
12508
12509 pub fn delete_to_beginning_of_line(
12510 &mut self,
12511 action: &DeleteToBeginningOfLine,
12512 window: &mut Window,
12513 cx: &mut Context<Self>,
12514 ) {
12515 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12516 self.transact(window, cx, |this, window, cx| {
12517 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12518 s.move_with(|_, selection| {
12519 selection.reversed = true;
12520 });
12521 });
12522
12523 this.select_to_beginning_of_line(
12524 &SelectToBeginningOfLine {
12525 stop_at_soft_wraps: false,
12526 stop_at_indent: action.stop_at_indent,
12527 },
12528 window,
12529 cx,
12530 );
12531 this.backspace(&Backspace, window, cx);
12532 });
12533 }
12534
12535 pub fn move_to_end_of_line(
12536 &mut self,
12537 action: &MoveToEndOfLine,
12538 window: &mut Window,
12539 cx: &mut Context<Self>,
12540 ) {
12541 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12542 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12543 s.move_cursors_with(|map, head, _| {
12544 (
12545 movement::line_end(map, head, action.stop_at_soft_wraps),
12546 SelectionGoal::None,
12547 )
12548 });
12549 })
12550 }
12551
12552 pub fn select_to_end_of_line(
12553 &mut self,
12554 action: &SelectToEndOfLine,
12555 window: &mut Window,
12556 cx: &mut Context<Self>,
12557 ) {
12558 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12559 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12560 s.move_heads_with(|map, head, _| {
12561 (
12562 movement::line_end(map, head, action.stop_at_soft_wraps),
12563 SelectionGoal::None,
12564 )
12565 });
12566 })
12567 }
12568
12569 pub fn delete_to_end_of_line(
12570 &mut self,
12571 _: &DeleteToEndOfLine,
12572 window: &mut Window,
12573 cx: &mut Context<Self>,
12574 ) {
12575 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12576 self.transact(window, cx, |this, window, cx| {
12577 this.select_to_end_of_line(
12578 &SelectToEndOfLine {
12579 stop_at_soft_wraps: false,
12580 },
12581 window,
12582 cx,
12583 );
12584 this.delete(&Delete, window, cx);
12585 });
12586 }
12587
12588 pub fn cut_to_end_of_line(
12589 &mut self,
12590 _: &CutToEndOfLine,
12591 window: &mut Window,
12592 cx: &mut Context<Self>,
12593 ) {
12594 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12595 self.transact(window, cx, |this, window, cx| {
12596 this.select_to_end_of_line(
12597 &SelectToEndOfLine {
12598 stop_at_soft_wraps: false,
12599 },
12600 window,
12601 cx,
12602 );
12603 this.cut(&Cut, window, cx);
12604 });
12605 }
12606
12607 pub fn move_to_start_of_paragraph(
12608 &mut self,
12609 _: &MoveToStartOfParagraph,
12610 window: &mut Window,
12611 cx: &mut Context<Self>,
12612 ) {
12613 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12614 cx.propagate();
12615 return;
12616 }
12617 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12618 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12619 s.move_with(|map, selection| {
12620 selection.collapse_to(
12621 movement::start_of_paragraph(map, selection.head(), 1),
12622 SelectionGoal::None,
12623 )
12624 });
12625 })
12626 }
12627
12628 pub fn move_to_end_of_paragraph(
12629 &mut self,
12630 _: &MoveToEndOfParagraph,
12631 window: &mut Window,
12632 cx: &mut Context<Self>,
12633 ) {
12634 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12635 cx.propagate();
12636 return;
12637 }
12638 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12639 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12640 s.move_with(|map, selection| {
12641 selection.collapse_to(
12642 movement::end_of_paragraph(map, selection.head(), 1),
12643 SelectionGoal::None,
12644 )
12645 });
12646 })
12647 }
12648
12649 pub fn select_to_start_of_paragraph(
12650 &mut self,
12651 _: &SelectToStartOfParagraph,
12652 window: &mut Window,
12653 cx: &mut Context<Self>,
12654 ) {
12655 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12656 cx.propagate();
12657 return;
12658 }
12659 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12660 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12661 s.move_heads_with(|map, head, _| {
12662 (
12663 movement::start_of_paragraph(map, head, 1),
12664 SelectionGoal::None,
12665 )
12666 });
12667 })
12668 }
12669
12670 pub fn select_to_end_of_paragraph(
12671 &mut self,
12672 _: &SelectToEndOfParagraph,
12673 window: &mut Window,
12674 cx: &mut Context<Self>,
12675 ) {
12676 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12677 cx.propagate();
12678 return;
12679 }
12680 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12681 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12682 s.move_heads_with(|map, head, _| {
12683 (
12684 movement::end_of_paragraph(map, head, 1),
12685 SelectionGoal::None,
12686 )
12687 });
12688 })
12689 }
12690
12691 pub fn move_to_start_of_excerpt(
12692 &mut self,
12693 _: &MoveToStartOfExcerpt,
12694 window: &mut Window,
12695 cx: &mut Context<Self>,
12696 ) {
12697 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12698 cx.propagate();
12699 return;
12700 }
12701 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12702 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12703 s.move_with(|map, selection| {
12704 selection.collapse_to(
12705 movement::start_of_excerpt(
12706 map,
12707 selection.head(),
12708 workspace::searchable::Direction::Prev,
12709 ),
12710 SelectionGoal::None,
12711 )
12712 });
12713 })
12714 }
12715
12716 pub fn move_to_start_of_next_excerpt(
12717 &mut self,
12718 _: &MoveToStartOfNextExcerpt,
12719 window: &mut Window,
12720 cx: &mut Context<Self>,
12721 ) {
12722 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12723 cx.propagate();
12724 return;
12725 }
12726
12727 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12728 s.move_with(|map, selection| {
12729 selection.collapse_to(
12730 movement::start_of_excerpt(
12731 map,
12732 selection.head(),
12733 workspace::searchable::Direction::Next,
12734 ),
12735 SelectionGoal::None,
12736 )
12737 });
12738 })
12739 }
12740
12741 pub fn move_to_end_of_excerpt(
12742 &mut self,
12743 _: &MoveToEndOfExcerpt,
12744 window: &mut Window,
12745 cx: &mut Context<Self>,
12746 ) {
12747 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12748 cx.propagate();
12749 return;
12750 }
12751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12752 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12753 s.move_with(|map, selection| {
12754 selection.collapse_to(
12755 movement::end_of_excerpt(
12756 map,
12757 selection.head(),
12758 workspace::searchable::Direction::Next,
12759 ),
12760 SelectionGoal::None,
12761 )
12762 });
12763 })
12764 }
12765
12766 pub fn move_to_end_of_previous_excerpt(
12767 &mut self,
12768 _: &MoveToEndOfPreviousExcerpt,
12769 window: &mut Window,
12770 cx: &mut Context<Self>,
12771 ) {
12772 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12773 cx.propagate();
12774 return;
12775 }
12776 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12777 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12778 s.move_with(|map, selection| {
12779 selection.collapse_to(
12780 movement::end_of_excerpt(
12781 map,
12782 selection.head(),
12783 workspace::searchable::Direction::Prev,
12784 ),
12785 SelectionGoal::None,
12786 )
12787 });
12788 })
12789 }
12790
12791 pub fn select_to_start_of_excerpt(
12792 &mut self,
12793 _: &SelectToStartOfExcerpt,
12794 window: &mut Window,
12795 cx: &mut Context<Self>,
12796 ) {
12797 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12798 cx.propagate();
12799 return;
12800 }
12801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12802 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12803 s.move_heads_with(|map, head, _| {
12804 (
12805 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12806 SelectionGoal::None,
12807 )
12808 });
12809 })
12810 }
12811
12812 pub fn select_to_start_of_next_excerpt(
12813 &mut self,
12814 _: &SelectToStartOfNextExcerpt,
12815 window: &mut Window,
12816 cx: &mut Context<Self>,
12817 ) {
12818 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12819 cx.propagate();
12820 return;
12821 }
12822 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12823 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12824 s.move_heads_with(|map, head, _| {
12825 (
12826 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12827 SelectionGoal::None,
12828 )
12829 });
12830 })
12831 }
12832
12833 pub fn select_to_end_of_excerpt(
12834 &mut self,
12835 _: &SelectToEndOfExcerpt,
12836 window: &mut Window,
12837 cx: &mut Context<Self>,
12838 ) {
12839 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12840 cx.propagate();
12841 return;
12842 }
12843 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12844 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12845 s.move_heads_with(|map, head, _| {
12846 (
12847 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12848 SelectionGoal::None,
12849 )
12850 });
12851 })
12852 }
12853
12854 pub fn select_to_end_of_previous_excerpt(
12855 &mut self,
12856 _: &SelectToEndOfPreviousExcerpt,
12857 window: &mut Window,
12858 cx: &mut Context<Self>,
12859 ) {
12860 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12861 cx.propagate();
12862 return;
12863 }
12864 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12865 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12866 s.move_heads_with(|map, head, _| {
12867 (
12868 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12869 SelectionGoal::None,
12870 )
12871 });
12872 })
12873 }
12874
12875 pub fn move_to_beginning(
12876 &mut self,
12877 _: &MoveToBeginning,
12878 window: &mut Window,
12879 cx: &mut Context<Self>,
12880 ) {
12881 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12882 cx.propagate();
12883 return;
12884 }
12885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12886 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12887 s.select_ranges(vec![0..0]);
12888 });
12889 }
12890
12891 pub fn select_to_beginning(
12892 &mut self,
12893 _: &SelectToBeginning,
12894 window: &mut Window,
12895 cx: &mut Context<Self>,
12896 ) {
12897 let mut selection = self.selections.last::<Point>(cx);
12898 selection.set_head(Point::zero(), SelectionGoal::None);
12899 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12900 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12901 s.select(vec![selection]);
12902 });
12903 }
12904
12905 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12906 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12907 cx.propagate();
12908 return;
12909 }
12910 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12911 let cursor = self.buffer.read(cx).read(cx).len();
12912 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12913 s.select_ranges(vec![cursor..cursor])
12914 });
12915 }
12916
12917 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12918 self.nav_history = nav_history;
12919 }
12920
12921 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12922 self.nav_history.as_ref()
12923 }
12924
12925 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12926 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12927 }
12928
12929 fn push_to_nav_history(
12930 &mut self,
12931 cursor_anchor: Anchor,
12932 new_position: Option<Point>,
12933 is_deactivate: bool,
12934 cx: &mut Context<Self>,
12935 ) {
12936 if let Some(nav_history) = self.nav_history.as_mut() {
12937 let buffer = self.buffer.read(cx).read(cx);
12938 let cursor_position = cursor_anchor.to_point(&buffer);
12939 let scroll_state = self.scroll_manager.anchor();
12940 let scroll_top_row = scroll_state.top_row(&buffer);
12941 drop(buffer);
12942
12943 if let Some(new_position) = new_position {
12944 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12945 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12946 return;
12947 }
12948 }
12949
12950 nav_history.push(
12951 Some(NavigationData {
12952 cursor_anchor,
12953 cursor_position,
12954 scroll_anchor: scroll_state,
12955 scroll_top_row,
12956 }),
12957 cx,
12958 );
12959 cx.emit(EditorEvent::PushedToNavHistory {
12960 anchor: cursor_anchor,
12961 is_deactivate,
12962 })
12963 }
12964 }
12965
12966 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12968 let buffer = self.buffer.read(cx).snapshot(cx);
12969 let mut selection = self.selections.first::<usize>(cx);
12970 selection.set_head(buffer.len(), SelectionGoal::None);
12971 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12972 s.select(vec![selection]);
12973 });
12974 }
12975
12976 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12978 let end = self.buffer.read(cx).read(cx).len();
12979 self.change_selections(None, window, cx, |s| {
12980 s.select_ranges(vec![0..end]);
12981 });
12982 }
12983
12984 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12986 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12987 let mut selections = self.selections.all::<Point>(cx);
12988 let max_point = display_map.buffer_snapshot.max_point();
12989 for selection in &mut selections {
12990 let rows = selection.spanned_rows(true, &display_map);
12991 selection.start = Point::new(rows.start.0, 0);
12992 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12993 selection.reversed = false;
12994 }
12995 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12996 s.select(selections);
12997 });
12998 }
12999
13000 pub fn split_selection_into_lines(
13001 &mut self,
13002 _: &SplitSelectionIntoLines,
13003 window: &mut Window,
13004 cx: &mut Context<Self>,
13005 ) {
13006 let selections = self
13007 .selections
13008 .all::<Point>(cx)
13009 .into_iter()
13010 .map(|selection| selection.start..selection.end)
13011 .collect::<Vec<_>>();
13012 self.unfold_ranges(&selections, true, true, cx);
13013
13014 let mut new_selection_ranges = Vec::new();
13015 {
13016 let buffer = self.buffer.read(cx).read(cx);
13017 for selection in selections {
13018 for row in selection.start.row..selection.end.row {
13019 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13020 new_selection_ranges.push(cursor..cursor);
13021 }
13022
13023 let is_multiline_selection = selection.start.row != selection.end.row;
13024 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13025 // so this action feels more ergonomic when paired with other selection operations
13026 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13027 if !should_skip_last {
13028 new_selection_ranges.push(selection.end..selection.end);
13029 }
13030 }
13031 }
13032 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13033 s.select_ranges(new_selection_ranges);
13034 });
13035 }
13036
13037 pub fn add_selection_above(
13038 &mut self,
13039 _: &AddSelectionAbove,
13040 window: &mut Window,
13041 cx: &mut Context<Self>,
13042 ) {
13043 self.add_selection(true, window, cx);
13044 }
13045
13046 pub fn add_selection_below(
13047 &mut self,
13048 _: &AddSelectionBelow,
13049 window: &mut Window,
13050 cx: &mut Context<Self>,
13051 ) {
13052 self.add_selection(false, window, cx);
13053 }
13054
13055 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13056 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13057
13058 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13059 let all_selections = self.selections.all::<Point>(cx);
13060 let text_layout_details = self.text_layout_details(window);
13061
13062 let (mut columnar_selections, new_selections_to_columnarize) = {
13063 if let Some(state) = self.add_selections_state.as_ref() {
13064 let columnar_selection_ids: HashSet<_> = state
13065 .groups
13066 .iter()
13067 .flat_map(|group| group.stack.iter())
13068 .copied()
13069 .collect();
13070
13071 all_selections
13072 .into_iter()
13073 .partition(|s| columnar_selection_ids.contains(&s.id))
13074 } else {
13075 (Vec::new(), all_selections)
13076 }
13077 };
13078
13079 let mut state = self
13080 .add_selections_state
13081 .take()
13082 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13083
13084 for selection in new_selections_to_columnarize {
13085 let range = selection.display_range(&display_map).sorted();
13086 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13087 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13088 let positions = start_x.min(end_x)..start_x.max(end_x);
13089 let mut stack = Vec::new();
13090 for row in range.start.row().0..=range.end.row().0 {
13091 if let Some(selection) = self.selections.build_columnar_selection(
13092 &display_map,
13093 DisplayRow(row),
13094 &positions,
13095 selection.reversed,
13096 &text_layout_details,
13097 ) {
13098 stack.push(selection.id);
13099 columnar_selections.push(selection);
13100 }
13101 }
13102 if !stack.is_empty() {
13103 if above {
13104 stack.reverse();
13105 }
13106 state.groups.push(AddSelectionsGroup { above, stack });
13107 }
13108 }
13109
13110 let mut final_selections = Vec::new();
13111 let end_row = if above {
13112 DisplayRow(0)
13113 } else {
13114 display_map.max_point().row()
13115 };
13116
13117 let mut last_added_item_per_group = HashMap::default();
13118 for group in state.groups.iter_mut() {
13119 if let Some(last_id) = group.stack.last() {
13120 last_added_item_per_group.insert(*last_id, group);
13121 }
13122 }
13123
13124 for selection in columnar_selections {
13125 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13126 if above == group.above {
13127 let range = selection.display_range(&display_map).sorted();
13128 debug_assert_eq!(range.start.row(), range.end.row());
13129 let mut row = range.start.row();
13130 let positions =
13131 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13132 px(start)..px(end)
13133 } else {
13134 let start_x =
13135 display_map.x_for_display_point(range.start, &text_layout_details);
13136 let end_x =
13137 display_map.x_for_display_point(range.end, &text_layout_details);
13138 start_x.min(end_x)..start_x.max(end_x)
13139 };
13140
13141 let mut maybe_new_selection = None;
13142 while row != end_row {
13143 if above {
13144 row.0 -= 1;
13145 } else {
13146 row.0 += 1;
13147 }
13148 if let Some(new_selection) = self.selections.build_columnar_selection(
13149 &display_map,
13150 row,
13151 &positions,
13152 selection.reversed,
13153 &text_layout_details,
13154 ) {
13155 maybe_new_selection = Some(new_selection);
13156 break;
13157 }
13158 }
13159
13160 if let Some(new_selection) = maybe_new_selection {
13161 group.stack.push(new_selection.id);
13162 if above {
13163 final_selections.push(new_selection);
13164 final_selections.push(selection);
13165 } else {
13166 final_selections.push(selection);
13167 final_selections.push(new_selection);
13168 }
13169 } else {
13170 final_selections.push(selection);
13171 }
13172 } else {
13173 group.stack.pop();
13174 }
13175 } else {
13176 final_selections.push(selection);
13177 }
13178 }
13179
13180 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13181 s.select(final_selections);
13182 });
13183
13184 let final_selection_ids: HashSet<_> = self
13185 .selections
13186 .all::<Point>(cx)
13187 .iter()
13188 .map(|s| s.id)
13189 .collect();
13190 state.groups.retain_mut(|group| {
13191 // selections might get merged above so we remove invalid items from stacks
13192 group.stack.retain(|id| final_selection_ids.contains(id));
13193
13194 // single selection in stack can be treated as initial state
13195 group.stack.len() > 1
13196 });
13197
13198 if !state.groups.is_empty() {
13199 self.add_selections_state = Some(state);
13200 }
13201 }
13202
13203 fn select_match_ranges(
13204 &mut self,
13205 range: Range<usize>,
13206 reversed: bool,
13207 replace_newest: bool,
13208 auto_scroll: Option<Autoscroll>,
13209 window: &mut Window,
13210 cx: &mut Context<Editor>,
13211 ) {
13212 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13213 self.change_selections(auto_scroll, window, cx, |s| {
13214 if replace_newest {
13215 s.delete(s.newest_anchor().id);
13216 }
13217 if reversed {
13218 s.insert_range(range.end..range.start);
13219 } else {
13220 s.insert_range(range);
13221 }
13222 });
13223 }
13224
13225 pub fn select_next_match_internal(
13226 &mut self,
13227 display_map: &DisplaySnapshot,
13228 replace_newest: bool,
13229 autoscroll: Option<Autoscroll>,
13230 window: &mut Window,
13231 cx: &mut Context<Self>,
13232 ) -> Result<()> {
13233 let buffer = &display_map.buffer_snapshot;
13234 let mut selections = self.selections.all::<usize>(cx);
13235 if let Some(mut select_next_state) = self.select_next_state.take() {
13236 let query = &select_next_state.query;
13237 if !select_next_state.done {
13238 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13239 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13240 let mut next_selected_range = None;
13241
13242 let bytes_after_last_selection =
13243 buffer.bytes_in_range(last_selection.end..buffer.len());
13244 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13245 let query_matches = query
13246 .stream_find_iter(bytes_after_last_selection)
13247 .map(|result| (last_selection.end, result))
13248 .chain(
13249 query
13250 .stream_find_iter(bytes_before_first_selection)
13251 .map(|result| (0, result)),
13252 );
13253
13254 for (start_offset, query_match) in query_matches {
13255 let query_match = query_match.unwrap(); // can only fail due to I/O
13256 let offset_range =
13257 start_offset + query_match.start()..start_offset + query_match.end();
13258 let display_range = offset_range.start.to_display_point(display_map)
13259 ..offset_range.end.to_display_point(display_map);
13260
13261 if !select_next_state.wordwise
13262 || (!movement::is_inside_word(display_map, display_range.start)
13263 && !movement::is_inside_word(display_map, display_range.end))
13264 {
13265 // TODO: This is n^2, because we might check all the selections
13266 if !selections
13267 .iter()
13268 .any(|selection| selection.range().overlaps(&offset_range))
13269 {
13270 next_selected_range = Some(offset_range);
13271 break;
13272 }
13273 }
13274 }
13275
13276 if let Some(next_selected_range) = next_selected_range {
13277 self.select_match_ranges(
13278 next_selected_range,
13279 last_selection.reversed,
13280 replace_newest,
13281 autoscroll,
13282 window,
13283 cx,
13284 );
13285 } else {
13286 select_next_state.done = true;
13287 }
13288 }
13289
13290 self.select_next_state = Some(select_next_state);
13291 } else {
13292 let mut only_carets = true;
13293 let mut same_text_selected = true;
13294 let mut selected_text = None;
13295
13296 let mut selections_iter = selections.iter().peekable();
13297 while let Some(selection) = selections_iter.next() {
13298 if selection.start != selection.end {
13299 only_carets = false;
13300 }
13301
13302 if same_text_selected {
13303 if selected_text.is_none() {
13304 selected_text =
13305 Some(buffer.text_for_range(selection.range()).collect::<String>());
13306 }
13307
13308 if let Some(next_selection) = selections_iter.peek() {
13309 if next_selection.range().len() == selection.range().len() {
13310 let next_selected_text = buffer
13311 .text_for_range(next_selection.range())
13312 .collect::<String>();
13313 if Some(next_selected_text) != selected_text {
13314 same_text_selected = false;
13315 selected_text = None;
13316 }
13317 } else {
13318 same_text_selected = false;
13319 selected_text = None;
13320 }
13321 }
13322 }
13323 }
13324
13325 if only_carets {
13326 for selection in &mut selections {
13327 let word_range = movement::surrounding_word(
13328 display_map,
13329 selection.start.to_display_point(display_map),
13330 );
13331 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13332 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13333 selection.goal = SelectionGoal::None;
13334 selection.reversed = false;
13335 self.select_match_ranges(
13336 selection.start..selection.end,
13337 selection.reversed,
13338 replace_newest,
13339 autoscroll,
13340 window,
13341 cx,
13342 );
13343 }
13344
13345 if selections.len() == 1 {
13346 let selection = selections
13347 .last()
13348 .expect("ensured that there's only one selection");
13349 let query = buffer
13350 .text_for_range(selection.start..selection.end)
13351 .collect::<String>();
13352 let is_empty = query.is_empty();
13353 let select_state = SelectNextState {
13354 query: AhoCorasick::new(&[query])?,
13355 wordwise: true,
13356 done: is_empty,
13357 };
13358 self.select_next_state = Some(select_state);
13359 } else {
13360 self.select_next_state = None;
13361 }
13362 } else if let Some(selected_text) = selected_text {
13363 self.select_next_state = Some(SelectNextState {
13364 query: AhoCorasick::new(&[selected_text])?,
13365 wordwise: false,
13366 done: false,
13367 });
13368 self.select_next_match_internal(
13369 display_map,
13370 replace_newest,
13371 autoscroll,
13372 window,
13373 cx,
13374 )?;
13375 }
13376 }
13377 Ok(())
13378 }
13379
13380 pub fn select_all_matches(
13381 &mut self,
13382 _action: &SelectAllMatches,
13383 window: &mut Window,
13384 cx: &mut Context<Self>,
13385 ) -> Result<()> {
13386 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13387
13388 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13389
13390 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13391 let Some(select_next_state) = self.select_next_state.as_mut() else {
13392 return Ok(());
13393 };
13394 if select_next_state.done {
13395 return Ok(());
13396 }
13397
13398 let mut new_selections = Vec::new();
13399
13400 let reversed = self.selections.oldest::<usize>(cx).reversed;
13401 let buffer = &display_map.buffer_snapshot;
13402 let query_matches = select_next_state
13403 .query
13404 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13405
13406 for query_match in query_matches.into_iter() {
13407 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13408 let offset_range = if reversed {
13409 query_match.end()..query_match.start()
13410 } else {
13411 query_match.start()..query_match.end()
13412 };
13413 let display_range = offset_range.start.to_display_point(&display_map)
13414 ..offset_range.end.to_display_point(&display_map);
13415
13416 if !select_next_state.wordwise
13417 || (!movement::is_inside_word(&display_map, display_range.start)
13418 && !movement::is_inside_word(&display_map, display_range.end))
13419 {
13420 new_selections.push(offset_range.start..offset_range.end);
13421 }
13422 }
13423
13424 select_next_state.done = true;
13425 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13426 self.change_selections(None, window, cx, |selections| {
13427 selections.select_ranges(new_selections)
13428 });
13429
13430 Ok(())
13431 }
13432
13433 pub fn select_next(
13434 &mut self,
13435 action: &SelectNext,
13436 window: &mut Window,
13437 cx: &mut Context<Self>,
13438 ) -> Result<()> {
13439 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13440 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13441 self.select_next_match_internal(
13442 &display_map,
13443 action.replace_newest,
13444 Some(Autoscroll::newest()),
13445 window,
13446 cx,
13447 )?;
13448 Ok(())
13449 }
13450
13451 pub fn select_previous(
13452 &mut self,
13453 action: &SelectPrevious,
13454 window: &mut Window,
13455 cx: &mut Context<Self>,
13456 ) -> Result<()> {
13457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13458 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13459 let buffer = &display_map.buffer_snapshot;
13460 let mut selections = self.selections.all::<usize>(cx);
13461 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13462 let query = &select_prev_state.query;
13463 if !select_prev_state.done {
13464 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13465 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13466 let mut next_selected_range = None;
13467 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13468 let bytes_before_last_selection =
13469 buffer.reversed_bytes_in_range(0..last_selection.start);
13470 let bytes_after_first_selection =
13471 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13472 let query_matches = query
13473 .stream_find_iter(bytes_before_last_selection)
13474 .map(|result| (last_selection.start, result))
13475 .chain(
13476 query
13477 .stream_find_iter(bytes_after_first_selection)
13478 .map(|result| (buffer.len(), result)),
13479 );
13480 for (end_offset, query_match) in query_matches {
13481 let query_match = query_match.unwrap(); // can only fail due to I/O
13482 let offset_range =
13483 end_offset - query_match.end()..end_offset - query_match.start();
13484 let display_range = offset_range.start.to_display_point(&display_map)
13485 ..offset_range.end.to_display_point(&display_map);
13486
13487 if !select_prev_state.wordwise
13488 || (!movement::is_inside_word(&display_map, display_range.start)
13489 && !movement::is_inside_word(&display_map, display_range.end))
13490 {
13491 next_selected_range = Some(offset_range);
13492 break;
13493 }
13494 }
13495
13496 if let Some(next_selected_range) = next_selected_range {
13497 self.select_match_ranges(
13498 next_selected_range,
13499 last_selection.reversed,
13500 action.replace_newest,
13501 Some(Autoscroll::newest()),
13502 window,
13503 cx,
13504 );
13505 } else {
13506 select_prev_state.done = true;
13507 }
13508 }
13509
13510 self.select_prev_state = Some(select_prev_state);
13511 } else {
13512 let mut only_carets = true;
13513 let mut same_text_selected = true;
13514 let mut selected_text = None;
13515
13516 let mut selections_iter = selections.iter().peekable();
13517 while let Some(selection) = selections_iter.next() {
13518 if selection.start != selection.end {
13519 only_carets = false;
13520 }
13521
13522 if same_text_selected {
13523 if selected_text.is_none() {
13524 selected_text =
13525 Some(buffer.text_for_range(selection.range()).collect::<String>());
13526 }
13527
13528 if let Some(next_selection) = selections_iter.peek() {
13529 if next_selection.range().len() == selection.range().len() {
13530 let next_selected_text = buffer
13531 .text_for_range(next_selection.range())
13532 .collect::<String>();
13533 if Some(next_selected_text) != selected_text {
13534 same_text_selected = false;
13535 selected_text = None;
13536 }
13537 } else {
13538 same_text_selected = false;
13539 selected_text = None;
13540 }
13541 }
13542 }
13543 }
13544
13545 if only_carets {
13546 for selection in &mut selections {
13547 let word_range = movement::surrounding_word(
13548 &display_map,
13549 selection.start.to_display_point(&display_map),
13550 );
13551 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13552 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13553 selection.goal = SelectionGoal::None;
13554 selection.reversed = false;
13555 self.select_match_ranges(
13556 selection.start..selection.end,
13557 selection.reversed,
13558 action.replace_newest,
13559 Some(Autoscroll::newest()),
13560 window,
13561 cx,
13562 );
13563 }
13564 if selections.len() == 1 {
13565 let selection = selections
13566 .last()
13567 .expect("ensured that there's only one selection");
13568 let query = buffer
13569 .text_for_range(selection.start..selection.end)
13570 .collect::<String>();
13571 let is_empty = query.is_empty();
13572 let select_state = SelectNextState {
13573 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13574 wordwise: true,
13575 done: is_empty,
13576 };
13577 self.select_prev_state = Some(select_state);
13578 } else {
13579 self.select_prev_state = None;
13580 }
13581 } else if let Some(selected_text) = selected_text {
13582 self.select_prev_state = Some(SelectNextState {
13583 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13584 wordwise: false,
13585 done: false,
13586 });
13587 self.select_previous(action, window, cx)?;
13588 }
13589 }
13590 Ok(())
13591 }
13592
13593 pub fn find_next_match(
13594 &mut self,
13595 _: &FindNextMatch,
13596 window: &mut Window,
13597 cx: &mut Context<Self>,
13598 ) -> Result<()> {
13599 let selections = self.selections.disjoint_anchors();
13600 match selections.first() {
13601 Some(first) if selections.len() >= 2 => {
13602 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13603 s.select_ranges([first.range()]);
13604 });
13605 }
13606 _ => self.select_next(
13607 &SelectNext {
13608 replace_newest: true,
13609 },
13610 window,
13611 cx,
13612 )?,
13613 }
13614 Ok(())
13615 }
13616
13617 pub fn find_previous_match(
13618 &mut self,
13619 _: &FindPreviousMatch,
13620 window: &mut Window,
13621 cx: &mut Context<Self>,
13622 ) -> Result<()> {
13623 let selections = self.selections.disjoint_anchors();
13624 match selections.last() {
13625 Some(last) if selections.len() >= 2 => {
13626 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13627 s.select_ranges([last.range()]);
13628 });
13629 }
13630 _ => self.select_previous(
13631 &SelectPrevious {
13632 replace_newest: true,
13633 },
13634 window,
13635 cx,
13636 )?,
13637 }
13638 Ok(())
13639 }
13640
13641 pub fn toggle_comments(
13642 &mut self,
13643 action: &ToggleComments,
13644 window: &mut Window,
13645 cx: &mut Context<Self>,
13646 ) {
13647 if self.read_only(cx) {
13648 return;
13649 }
13650 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13651 let text_layout_details = &self.text_layout_details(window);
13652 self.transact(window, cx, |this, window, cx| {
13653 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13654 let mut edits = Vec::new();
13655 let mut selection_edit_ranges = Vec::new();
13656 let mut last_toggled_row = None;
13657 let snapshot = this.buffer.read(cx).read(cx);
13658 let empty_str: Arc<str> = Arc::default();
13659 let mut suffixes_inserted = Vec::new();
13660 let ignore_indent = action.ignore_indent;
13661
13662 fn comment_prefix_range(
13663 snapshot: &MultiBufferSnapshot,
13664 row: MultiBufferRow,
13665 comment_prefix: &str,
13666 comment_prefix_whitespace: &str,
13667 ignore_indent: bool,
13668 ) -> Range<Point> {
13669 let indent_size = if ignore_indent {
13670 0
13671 } else {
13672 snapshot.indent_size_for_line(row).len
13673 };
13674
13675 let start = Point::new(row.0, indent_size);
13676
13677 let mut line_bytes = snapshot
13678 .bytes_in_range(start..snapshot.max_point())
13679 .flatten()
13680 .copied();
13681
13682 // If this line currently begins with the line comment prefix, then record
13683 // the range containing the prefix.
13684 if line_bytes
13685 .by_ref()
13686 .take(comment_prefix.len())
13687 .eq(comment_prefix.bytes())
13688 {
13689 // Include any whitespace that matches the comment prefix.
13690 let matching_whitespace_len = line_bytes
13691 .zip(comment_prefix_whitespace.bytes())
13692 .take_while(|(a, b)| a == b)
13693 .count() as u32;
13694 let end = Point::new(
13695 start.row,
13696 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13697 );
13698 start..end
13699 } else {
13700 start..start
13701 }
13702 }
13703
13704 fn comment_suffix_range(
13705 snapshot: &MultiBufferSnapshot,
13706 row: MultiBufferRow,
13707 comment_suffix: &str,
13708 comment_suffix_has_leading_space: bool,
13709 ) -> Range<Point> {
13710 let end = Point::new(row.0, snapshot.line_len(row));
13711 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13712
13713 let mut line_end_bytes = snapshot
13714 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13715 .flatten()
13716 .copied();
13717
13718 let leading_space_len = if suffix_start_column > 0
13719 && line_end_bytes.next() == Some(b' ')
13720 && comment_suffix_has_leading_space
13721 {
13722 1
13723 } else {
13724 0
13725 };
13726
13727 // If this line currently begins with the line comment prefix, then record
13728 // the range containing the prefix.
13729 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13730 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13731 start..end
13732 } else {
13733 end..end
13734 }
13735 }
13736
13737 // TODO: Handle selections that cross excerpts
13738 for selection in &mut selections {
13739 let start_column = snapshot
13740 .indent_size_for_line(MultiBufferRow(selection.start.row))
13741 .len;
13742 let language = if let Some(language) =
13743 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13744 {
13745 language
13746 } else {
13747 continue;
13748 };
13749
13750 selection_edit_ranges.clear();
13751
13752 // If multiple selections contain a given row, avoid processing that
13753 // row more than once.
13754 let mut start_row = MultiBufferRow(selection.start.row);
13755 if last_toggled_row == Some(start_row) {
13756 start_row = start_row.next_row();
13757 }
13758 let end_row =
13759 if selection.end.row > selection.start.row && selection.end.column == 0 {
13760 MultiBufferRow(selection.end.row - 1)
13761 } else {
13762 MultiBufferRow(selection.end.row)
13763 };
13764 last_toggled_row = Some(end_row);
13765
13766 if start_row > end_row {
13767 continue;
13768 }
13769
13770 // If the language has line comments, toggle those.
13771 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13772
13773 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13774 if ignore_indent {
13775 full_comment_prefixes = full_comment_prefixes
13776 .into_iter()
13777 .map(|s| Arc::from(s.trim_end()))
13778 .collect();
13779 }
13780
13781 if !full_comment_prefixes.is_empty() {
13782 let first_prefix = full_comment_prefixes
13783 .first()
13784 .expect("prefixes is non-empty");
13785 let prefix_trimmed_lengths = full_comment_prefixes
13786 .iter()
13787 .map(|p| p.trim_end_matches(' ').len())
13788 .collect::<SmallVec<[usize; 4]>>();
13789
13790 let mut all_selection_lines_are_comments = true;
13791
13792 for row in start_row.0..=end_row.0 {
13793 let row = MultiBufferRow(row);
13794 if start_row < end_row && snapshot.is_line_blank(row) {
13795 continue;
13796 }
13797
13798 let prefix_range = full_comment_prefixes
13799 .iter()
13800 .zip(prefix_trimmed_lengths.iter().copied())
13801 .map(|(prefix, trimmed_prefix_len)| {
13802 comment_prefix_range(
13803 snapshot.deref(),
13804 row,
13805 &prefix[..trimmed_prefix_len],
13806 &prefix[trimmed_prefix_len..],
13807 ignore_indent,
13808 )
13809 })
13810 .max_by_key(|range| range.end.column - range.start.column)
13811 .expect("prefixes is non-empty");
13812
13813 if prefix_range.is_empty() {
13814 all_selection_lines_are_comments = false;
13815 }
13816
13817 selection_edit_ranges.push(prefix_range);
13818 }
13819
13820 if all_selection_lines_are_comments {
13821 edits.extend(
13822 selection_edit_ranges
13823 .iter()
13824 .cloned()
13825 .map(|range| (range, empty_str.clone())),
13826 );
13827 } else {
13828 let min_column = selection_edit_ranges
13829 .iter()
13830 .map(|range| range.start.column)
13831 .min()
13832 .unwrap_or(0);
13833 edits.extend(selection_edit_ranges.iter().map(|range| {
13834 let position = Point::new(range.start.row, min_column);
13835 (position..position, first_prefix.clone())
13836 }));
13837 }
13838 } else if let Some((full_comment_prefix, comment_suffix)) =
13839 language.block_comment_delimiters()
13840 {
13841 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13842 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13843 let prefix_range = comment_prefix_range(
13844 snapshot.deref(),
13845 start_row,
13846 comment_prefix,
13847 comment_prefix_whitespace,
13848 ignore_indent,
13849 );
13850 let suffix_range = comment_suffix_range(
13851 snapshot.deref(),
13852 end_row,
13853 comment_suffix.trim_start_matches(' '),
13854 comment_suffix.starts_with(' '),
13855 );
13856
13857 if prefix_range.is_empty() || suffix_range.is_empty() {
13858 edits.push((
13859 prefix_range.start..prefix_range.start,
13860 full_comment_prefix.clone(),
13861 ));
13862 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13863 suffixes_inserted.push((end_row, comment_suffix.len()));
13864 } else {
13865 edits.push((prefix_range, empty_str.clone()));
13866 edits.push((suffix_range, empty_str.clone()));
13867 }
13868 } else {
13869 continue;
13870 }
13871 }
13872
13873 drop(snapshot);
13874 this.buffer.update(cx, |buffer, cx| {
13875 buffer.edit(edits, None, cx);
13876 });
13877
13878 // Adjust selections so that they end before any comment suffixes that
13879 // were inserted.
13880 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13881 let mut selections = this.selections.all::<Point>(cx);
13882 let snapshot = this.buffer.read(cx).read(cx);
13883 for selection in &mut selections {
13884 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13885 match row.cmp(&MultiBufferRow(selection.end.row)) {
13886 Ordering::Less => {
13887 suffixes_inserted.next();
13888 continue;
13889 }
13890 Ordering::Greater => break,
13891 Ordering::Equal => {
13892 if selection.end.column == snapshot.line_len(row) {
13893 if selection.is_empty() {
13894 selection.start.column -= suffix_len as u32;
13895 }
13896 selection.end.column -= suffix_len as u32;
13897 }
13898 break;
13899 }
13900 }
13901 }
13902 }
13903
13904 drop(snapshot);
13905 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13906 s.select(selections)
13907 });
13908
13909 let selections = this.selections.all::<Point>(cx);
13910 let selections_on_single_row = selections.windows(2).all(|selections| {
13911 selections[0].start.row == selections[1].start.row
13912 && selections[0].end.row == selections[1].end.row
13913 && selections[0].start.row == selections[0].end.row
13914 });
13915 let selections_selecting = selections
13916 .iter()
13917 .any(|selection| selection.start != selection.end);
13918 let advance_downwards = action.advance_downwards
13919 && selections_on_single_row
13920 && !selections_selecting
13921 && !matches!(this.mode, EditorMode::SingleLine { .. });
13922
13923 if advance_downwards {
13924 let snapshot = this.buffer.read(cx).snapshot(cx);
13925
13926 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13927 s.move_cursors_with(|display_snapshot, display_point, _| {
13928 let mut point = display_point.to_point(display_snapshot);
13929 point.row += 1;
13930 point = snapshot.clip_point(point, Bias::Left);
13931 let display_point = point.to_display_point(display_snapshot);
13932 let goal = SelectionGoal::HorizontalPosition(
13933 display_snapshot
13934 .x_for_display_point(display_point, text_layout_details)
13935 .into(),
13936 );
13937 (display_point, goal)
13938 })
13939 });
13940 }
13941 });
13942 }
13943
13944 pub fn select_enclosing_symbol(
13945 &mut self,
13946 _: &SelectEnclosingSymbol,
13947 window: &mut Window,
13948 cx: &mut Context<Self>,
13949 ) {
13950 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13951
13952 let buffer = self.buffer.read(cx).snapshot(cx);
13953 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13954
13955 fn update_selection(
13956 selection: &Selection<usize>,
13957 buffer_snap: &MultiBufferSnapshot,
13958 ) -> Option<Selection<usize>> {
13959 let cursor = selection.head();
13960 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13961 for symbol in symbols.iter().rev() {
13962 let start = symbol.range.start.to_offset(buffer_snap);
13963 let end = symbol.range.end.to_offset(buffer_snap);
13964 let new_range = start..end;
13965 if start < selection.start || end > selection.end {
13966 return Some(Selection {
13967 id: selection.id,
13968 start: new_range.start,
13969 end: new_range.end,
13970 goal: SelectionGoal::None,
13971 reversed: selection.reversed,
13972 });
13973 }
13974 }
13975 None
13976 }
13977
13978 let mut selected_larger_symbol = false;
13979 let new_selections = old_selections
13980 .iter()
13981 .map(|selection| match update_selection(selection, &buffer) {
13982 Some(new_selection) => {
13983 if new_selection.range() != selection.range() {
13984 selected_larger_symbol = true;
13985 }
13986 new_selection
13987 }
13988 None => selection.clone(),
13989 })
13990 .collect::<Vec<_>>();
13991
13992 if selected_larger_symbol {
13993 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13994 s.select(new_selections);
13995 });
13996 }
13997 }
13998
13999 pub fn select_larger_syntax_node(
14000 &mut self,
14001 _: &SelectLargerSyntaxNode,
14002 window: &mut Window,
14003 cx: &mut Context<Self>,
14004 ) {
14005 let Some(visible_row_count) = self.visible_row_count() else {
14006 return;
14007 };
14008 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14009 if old_selections.is_empty() {
14010 return;
14011 }
14012
14013 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14014
14015 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14016 let buffer = self.buffer.read(cx).snapshot(cx);
14017
14018 let mut selected_larger_node = false;
14019 let mut new_selections = old_selections
14020 .iter()
14021 .map(|selection| {
14022 let old_range = selection.start..selection.end;
14023
14024 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14025 // manually select word at selection
14026 if ["string_content", "inline"].contains(&node.kind()) {
14027 let word_range = {
14028 let display_point = buffer
14029 .offset_to_point(old_range.start)
14030 .to_display_point(&display_map);
14031 let Range { start, end } =
14032 movement::surrounding_word(&display_map, display_point);
14033 start.to_point(&display_map).to_offset(&buffer)
14034 ..end.to_point(&display_map).to_offset(&buffer)
14035 };
14036 // ignore if word is already selected
14037 if !word_range.is_empty() && old_range != word_range {
14038 let last_word_range = {
14039 let display_point = buffer
14040 .offset_to_point(old_range.end)
14041 .to_display_point(&display_map);
14042 let Range { start, end } =
14043 movement::surrounding_word(&display_map, display_point);
14044 start.to_point(&display_map).to_offset(&buffer)
14045 ..end.to_point(&display_map).to_offset(&buffer)
14046 };
14047 // only select word if start and end point belongs to same word
14048 if word_range == last_word_range {
14049 selected_larger_node = true;
14050 return Selection {
14051 id: selection.id,
14052 start: word_range.start,
14053 end: word_range.end,
14054 goal: SelectionGoal::None,
14055 reversed: selection.reversed,
14056 };
14057 }
14058 }
14059 }
14060 }
14061
14062 let mut new_range = old_range.clone();
14063 while let Some((_node, containing_range)) =
14064 buffer.syntax_ancestor(new_range.clone())
14065 {
14066 new_range = match containing_range {
14067 MultiOrSingleBufferOffsetRange::Single(_) => break,
14068 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14069 };
14070 if !display_map.intersects_fold(new_range.start)
14071 && !display_map.intersects_fold(new_range.end)
14072 {
14073 break;
14074 }
14075 }
14076
14077 selected_larger_node |= new_range != old_range;
14078 Selection {
14079 id: selection.id,
14080 start: new_range.start,
14081 end: new_range.end,
14082 goal: SelectionGoal::None,
14083 reversed: selection.reversed,
14084 }
14085 })
14086 .collect::<Vec<_>>();
14087
14088 if !selected_larger_node {
14089 return; // don't put this call in the history
14090 }
14091
14092 // scroll based on transformation done to the last selection created by the user
14093 let (last_old, last_new) = old_selections
14094 .last()
14095 .zip(new_selections.last().cloned())
14096 .expect("old_selections isn't empty");
14097
14098 // revert selection
14099 let is_selection_reversed = {
14100 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14101 new_selections.last_mut().expect("checked above").reversed =
14102 should_newest_selection_be_reversed;
14103 should_newest_selection_be_reversed
14104 };
14105
14106 if selected_larger_node {
14107 self.select_syntax_node_history.disable_clearing = true;
14108 self.change_selections(None, window, cx, |s| {
14109 s.select(new_selections.clone());
14110 });
14111 self.select_syntax_node_history.disable_clearing = false;
14112 }
14113
14114 let start_row = last_new.start.to_display_point(&display_map).row().0;
14115 let end_row = last_new.end.to_display_point(&display_map).row().0;
14116 let selection_height = end_row - start_row + 1;
14117 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14118
14119 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14120 let scroll_behavior = if fits_on_the_screen {
14121 self.request_autoscroll(Autoscroll::fit(), cx);
14122 SelectSyntaxNodeScrollBehavior::FitSelection
14123 } else if is_selection_reversed {
14124 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14125 SelectSyntaxNodeScrollBehavior::CursorTop
14126 } else {
14127 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14128 SelectSyntaxNodeScrollBehavior::CursorBottom
14129 };
14130
14131 self.select_syntax_node_history.push((
14132 old_selections,
14133 scroll_behavior,
14134 is_selection_reversed,
14135 ));
14136 }
14137
14138 pub fn select_smaller_syntax_node(
14139 &mut self,
14140 _: &SelectSmallerSyntaxNode,
14141 window: &mut Window,
14142 cx: &mut Context<Self>,
14143 ) {
14144 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14145
14146 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14147 self.select_syntax_node_history.pop()
14148 {
14149 if let Some(selection) = selections.last_mut() {
14150 selection.reversed = is_selection_reversed;
14151 }
14152
14153 self.select_syntax_node_history.disable_clearing = true;
14154 self.change_selections(None, window, cx, |s| {
14155 s.select(selections.to_vec());
14156 });
14157 self.select_syntax_node_history.disable_clearing = false;
14158
14159 match scroll_behavior {
14160 SelectSyntaxNodeScrollBehavior::CursorTop => {
14161 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14162 }
14163 SelectSyntaxNodeScrollBehavior::FitSelection => {
14164 self.request_autoscroll(Autoscroll::fit(), cx);
14165 }
14166 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14167 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14168 }
14169 }
14170 }
14171 }
14172
14173 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14174 if !EditorSettings::get_global(cx).gutter.runnables {
14175 self.clear_tasks();
14176 return Task::ready(());
14177 }
14178 let project = self.project.as_ref().map(Entity::downgrade);
14179 let task_sources = self.lsp_task_sources(cx);
14180 let multi_buffer = self.buffer.downgrade();
14181 cx.spawn_in(window, async move |editor, cx| {
14182 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14183 let Some(project) = project.and_then(|p| p.upgrade()) else {
14184 return;
14185 };
14186 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14187 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14188 }) else {
14189 return;
14190 };
14191
14192 let hide_runnables = project
14193 .update(cx, |project, cx| {
14194 // Do not display any test indicators in non-dev server remote projects.
14195 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14196 })
14197 .unwrap_or(true);
14198 if hide_runnables {
14199 return;
14200 }
14201 let new_rows =
14202 cx.background_spawn({
14203 let snapshot = display_snapshot.clone();
14204 async move {
14205 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14206 }
14207 })
14208 .await;
14209 let Ok(lsp_tasks) =
14210 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14211 else {
14212 return;
14213 };
14214 let lsp_tasks = lsp_tasks.await;
14215
14216 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14217 lsp_tasks
14218 .into_iter()
14219 .flat_map(|(kind, tasks)| {
14220 tasks.into_iter().filter_map(move |(location, task)| {
14221 Some((kind.clone(), location?, task))
14222 })
14223 })
14224 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14225 let buffer = location.target.buffer;
14226 let buffer_snapshot = buffer.read(cx).snapshot();
14227 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14228 |(excerpt_id, snapshot, _)| {
14229 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14230 display_snapshot
14231 .buffer_snapshot
14232 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14233 } else {
14234 None
14235 }
14236 },
14237 );
14238 if let Some(offset) = offset {
14239 let task_buffer_range =
14240 location.target.range.to_point(&buffer_snapshot);
14241 let context_buffer_range =
14242 task_buffer_range.to_offset(&buffer_snapshot);
14243 let context_range = BufferOffset(context_buffer_range.start)
14244 ..BufferOffset(context_buffer_range.end);
14245
14246 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14247 .or_insert_with(|| RunnableTasks {
14248 templates: Vec::new(),
14249 offset,
14250 column: task_buffer_range.start.column,
14251 extra_variables: HashMap::default(),
14252 context_range,
14253 })
14254 .templates
14255 .push((kind, task.original_task().clone()));
14256 }
14257
14258 acc
14259 })
14260 }) else {
14261 return;
14262 };
14263
14264 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14265 buffer.language_settings(cx).tasks.prefer_lsp
14266 }) else {
14267 return;
14268 };
14269
14270 let rows = Self::runnable_rows(
14271 project,
14272 display_snapshot,
14273 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14274 new_rows,
14275 cx.clone(),
14276 )
14277 .await;
14278 editor
14279 .update(cx, |editor, _| {
14280 editor.clear_tasks();
14281 for (key, mut value) in rows {
14282 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14283 value.templates.extend(lsp_tasks.templates);
14284 }
14285
14286 editor.insert_tasks(key, value);
14287 }
14288 for (key, value) in lsp_tasks_by_rows {
14289 editor.insert_tasks(key, value);
14290 }
14291 })
14292 .ok();
14293 })
14294 }
14295 fn fetch_runnable_ranges(
14296 snapshot: &DisplaySnapshot,
14297 range: Range<Anchor>,
14298 ) -> Vec<language::RunnableRange> {
14299 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14300 }
14301
14302 fn runnable_rows(
14303 project: Entity<Project>,
14304 snapshot: DisplaySnapshot,
14305 prefer_lsp: bool,
14306 runnable_ranges: Vec<RunnableRange>,
14307 cx: AsyncWindowContext,
14308 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14309 cx.spawn(async move |cx| {
14310 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14311 for mut runnable in runnable_ranges {
14312 let Some(tasks) = cx
14313 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14314 .ok()
14315 else {
14316 continue;
14317 };
14318 let mut tasks = tasks.await;
14319
14320 if prefer_lsp {
14321 tasks.retain(|(task_kind, _)| {
14322 !matches!(task_kind, TaskSourceKind::Language { .. })
14323 });
14324 }
14325 if tasks.is_empty() {
14326 continue;
14327 }
14328
14329 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14330 let Some(row) = snapshot
14331 .buffer_snapshot
14332 .buffer_line_for_row(MultiBufferRow(point.row))
14333 .map(|(_, range)| range.start.row)
14334 else {
14335 continue;
14336 };
14337
14338 let context_range =
14339 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14340 runnable_rows.push((
14341 (runnable.buffer_id, row),
14342 RunnableTasks {
14343 templates: tasks,
14344 offset: snapshot
14345 .buffer_snapshot
14346 .anchor_before(runnable.run_range.start),
14347 context_range,
14348 column: point.column,
14349 extra_variables: runnable.extra_captures,
14350 },
14351 ));
14352 }
14353 runnable_rows
14354 })
14355 }
14356
14357 fn templates_with_tags(
14358 project: &Entity<Project>,
14359 runnable: &mut Runnable,
14360 cx: &mut App,
14361 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14362 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14363 let (worktree_id, file) = project
14364 .buffer_for_id(runnable.buffer, cx)
14365 .and_then(|buffer| buffer.read(cx).file())
14366 .map(|file| (file.worktree_id(cx), file.clone()))
14367 .unzip();
14368
14369 (
14370 project.task_store().read(cx).task_inventory().cloned(),
14371 worktree_id,
14372 file,
14373 )
14374 });
14375
14376 let tags = mem::take(&mut runnable.tags);
14377 let language = runnable.language.clone();
14378 cx.spawn(async move |cx| {
14379 let mut templates_with_tags = Vec::new();
14380 if let Some(inventory) = inventory {
14381 for RunnableTag(tag) in tags {
14382 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14383 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14384 }) else {
14385 return templates_with_tags;
14386 };
14387 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14388 move |(_, template)| {
14389 template.tags.iter().any(|source_tag| source_tag == &tag)
14390 },
14391 ));
14392 }
14393 }
14394 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14395
14396 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14397 // Strongest source wins; if we have worktree tag binding, prefer that to
14398 // global and language bindings;
14399 // if we have a global binding, prefer that to language binding.
14400 let first_mismatch = templates_with_tags
14401 .iter()
14402 .position(|(tag_source, _)| tag_source != leading_tag_source);
14403 if let Some(index) = first_mismatch {
14404 templates_with_tags.truncate(index);
14405 }
14406 }
14407
14408 templates_with_tags
14409 })
14410 }
14411
14412 pub fn move_to_enclosing_bracket(
14413 &mut self,
14414 _: &MoveToEnclosingBracket,
14415 window: &mut Window,
14416 cx: &mut Context<Self>,
14417 ) {
14418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14419 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14420 s.move_offsets_with(|snapshot, selection| {
14421 let Some(enclosing_bracket_ranges) =
14422 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14423 else {
14424 return;
14425 };
14426
14427 let mut best_length = usize::MAX;
14428 let mut best_inside = false;
14429 let mut best_in_bracket_range = false;
14430 let mut best_destination = None;
14431 for (open, close) in enclosing_bracket_ranges {
14432 let close = close.to_inclusive();
14433 let length = close.end() - open.start;
14434 let inside = selection.start >= open.end && selection.end <= *close.start();
14435 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14436 || close.contains(&selection.head());
14437
14438 // If best is next to a bracket and current isn't, skip
14439 if !in_bracket_range && best_in_bracket_range {
14440 continue;
14441 }
14442
14443 // Prefer smaller lengths unless best is inside and current isn't
14444 if length > best_length && (best_inside || !inside) {
14445 continue;
14446 }
14447
14448 best_length = length;
14449 best_inside = inside;
14450 best_in_bracket_range = in_bracket_range;
14451 best_destination = Some(
14452 if close.contains(&selection.start) && close.contains(&selection.end) {
14453 if inside { open.end } else { open.start }
14454 } else if inside {
14455 *close.start()
14456 } else {
14457 *close.end()
14458 },
14459 );
14460 }
14461
14462 if let Some(destination) = best_destination {
14463 selection.collapse_to(destination, SelectionGoal::None);
14464 }
14465 })
14466 });
14467 }
14468
14469 pub fn undo_selection(
14470 &mut self,
14471 _: &UndoSelection,
14472 window: &mut Window,
14473 cx: &mut Context<Self>,
14474 ) {
14475 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14476 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14477 self.selection_history.mode = SelectionHistoryMode::Undoing;
14478 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14479 this.end_selection(window, cx);
14480 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14481 s.select_anchors(entry.selections.to_vec())
14482 });
14483 });
14484 self.selection_history.mode = SelectionHistoryMode::Normal;
14485
14486 self.select_next_state = entry.select_next_state;
14487 self.select_prev_state = entry.select_prev_state;
14488 self.add_selections_state = entry.add_selections_state;
14489 }
14490 }
14491
14492 pub fn redo_selection(
14493 &mut self,
14494 _: &RedoSelection,
14495 window: &mut Window,
14496 cx: &mut Context<Self>,
14497 ) {
14498 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14499 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14500 self.selection_history.mode = SelectionHistoryMode::Redoing;
14501 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14502 this.end_selection(window, cx);
14503 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14504 s.select_anchors(entry.selections.to_vec())
14505 });
14506 });
14507 self.selection_history.mode = SelectionHistoryMode::Normal;
14508
14509 self.select_next_state = entry.select_next_state;
14510 self.select_prev_state = entry.select_prev_state;
14511 self.add_selections_state = entry.add_selections_state;
14512 }
14513 }
14514
14515 pub fn expand_excerpts(
14516 &mut self,
14517 action: &ExpandExcerpts,
14518 _: &mut Window,
14519 cx: &mut Context<Self>,
14520 ) {
14521 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14522 }
14523
14524 pub fn expand_excerpts_down(
14525 &mut self,
14526 action: &ExpandExcerptsDown,
14527 _: &mut Window,
14528 cx: &mut Context<Self>,
14529 ) {
14530 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14531 }
14532
14533 pub fn expand_excerpts_up(
14534 &mut self,
14535 action: &ExpandExcerptsUp,
14536 _: &mut Window,
14537 cx: &mut Context<Self>,
14538 ) {
14539 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14540 }
14541
14542 pub fn expand_excerpts_for_direction(
14543 &mut self,
14544 lines: u32,
14545 direction: ExpandExcerptDirection,
14546
14547 cx: &mut Context<Self>,
14548 ) {
14549 let selections = self.selections.disjoint_anchors();
14550
14551 let lines = if lines == 0 {
14552 EditorSettings::get_global(cx).expand_excerpt_lines
14553 } else {
14554 lines
14555 };
14556
14557 self.buffer.update(cx, |buffer, cx| {
14558 let snapshot = buffer.snapshot(cx);
14559 let mut excerpt_ids = selections
14560 .iter()
14561 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14562 .collect::<Vec<_>>();
14563 excerpt_ids.sort();
14564 excerpt_ids.dedup();
14565 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14566 })
14567 }
14568
14569 pub fn expand_excerpt(
14570 &mut self,
14571 excerpt: ExcerptId,
14572 direction: ExpandExcerptDirection,
14573 window: &mut Window,
14574 cx: &mut Context<Self>,
14575 ) {
14576 let current_scroll_position = self.scroll_position(cx);
14577 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14578 let mut should_scroll_up = false;
14579
14580 if direction == ExpandExcerptDirection::Down {
14581 let multi_buffer = self.buffer.read(cx);
14582 let snapshot = multi_buffer.snapshot(cx);
14583 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14584 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14585 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14586 let buffer_snapshot = buffer.read(cx).snapshot();
14587 let excerpt_end_row =
14588 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14589 let last_row = buffer_snapshot.max_point().row;
14590 let lines_below = last_row.saturating_sub(excerpt_end_row);
14591 should_scroll_up = lines_below >= lines_to_expand;
14592 }
14593 }
14594 }
14595 }
14596
14597 self.buffer.update(cx, |buffer, cx| {
14598 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14599 });
14600
14601 if should_scroll_up {
14602 let new_scroll_position =
14603 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14604 self.set_scroll_position(new_scroll_position, window, cx);
14605 }
14606 }
14607
14608 pub fn go_to_singleton_buffer_point(
14609 &mut self,
14610 point: Point,
14611 window: &mut Window,
14612 cx: &mut Context<Self>,
14613 ) {
14614 self.go_to_singleton_buffer_range(point..point, window, cx);
14615 }
14616
14617 pub fn go_to_singleton_buffer_range(
14618 &mut self,
14619 range: Range<Point>,
14620 window: &mut Window,
14621 cx: &mut Context<Self>,
14622 ) {
14623 let multibuffer = self.buffer().read(cx);
14624 let Some(buffer) = multibuffer.as_singleton() else {
14625 return;
14626 };
14627 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14628 return;
14629 };
14630 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14631 return;
14632 };
14633 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14634 s.select_anchor_ranges([start..end])
14635 });
14636 }
14637
14638 pub fn go_to_diagnostic(
14639 &mut self,
14640 _: &GoToDiagnostic,
14641 window: &mut Window,
14642 cx: &mut Context<Self>,
14643 ) {
14644 if !self.diagnostics_enabled() {
14645 return;
14646 }
14647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14648 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14649 }
14650
14651 pub fn go_to_prev_diagnostic(
14652 &mut self,
14653 _: &GoToPreviousDiagnostic,
14654 window: &mut Window,
14655 cx: &mut Context<Self>,
14656 ) {
14657 if !self.diagnostics_enabled() {
14658 return;
14659 }
14660 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14661 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14662 }
14663
14664 pub fn go_to_diagnostic_impl(
14665 &mut self,
14666 direction: Direction,
14667 window: &mut Window,
14668 cx: &mut Context<Self>,
14669 ) {
14670 let buffer = self.buffer.read(cx).snapshot(cx);
14671 let selection = self.selections.newest::<usize>(cx);
14672
14673 let mut active_group_id = None;
14674 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14675 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14676 active_group_id = Some(active_group.group_id);
14677 }
14678 }
14679
14680 fn filtered(
14681 snapshot: EditorSnapshot,
14682 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14683 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14684 diagnostics
14685 .filter(|entry| entry.range.start != entry.range.end)
14686 .filter(|entry| !entry.diagnostic.is_unnecessary)
14687 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14688 }
14689
14690 let snapshot = self.snapshot(window, cx);
14691 let before = filtered(
14692 snapshot.clone(),
14693 buffer
14694 .diagnostics_in_range(0..selection.start)
14695 .filter(|entry| entry.range.start <= selection.start),
14696 );
14697 let after = filtered(
14698 snapshot,
14699 buffer
14700 .diagnostics_in_range(selection.start..buffer.len())
14701 .filter(|entry| entry.range.start >= selection.start),
14702 );
14703
14704 let mut found: Option<DiagnosticEntry<usize>> = None;
14705 if direction == Direction::Prev {
14706 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14707 {
14708 for diagnostic in prev_diagnostics.into_iter().rev() {
14709 if diagnostic.range.start != selection.start
14710 || active_group_id
14711 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14712 {
14713 found = Some(diagnostic);
14714 break 'outer;
14715 }
14716 }
14717 }
14718 } else {
14719 for diagnostic in after.chain(before) {
14720 if diagnostic.range.start != selection.start
14721 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14722 {
14723 found = Some(diagnostic);
14724 break;
14725 }
14726 }
14727 }
14728 let Some(next_diagnostic) = found else {
14729 return;
14730 };
14731
14732 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14733 return;
14734 };
14735 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14736 s.select_ranges(vec![
14737 next_diagnostic.range.start..next_diagnostic.range.start,
14738 ])
14739 });
14740 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14741 self.refresh_inline_completion(false, true, window, cx);
14742 }
14743
14744 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14745 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14746 let snapshot = self.snapshot(window, cx);
14747 let selection = self.selections.newest::<Point>(cx);
14748 self.go_to_hunk_before_or_after_position(
14749 &snapshot,
14750 selection.head(),
14751 Direction::Next,
14752 window,
14753 cx,
14754 );
14755 }
14756
14757 pub fn go_to_hunk_before_or_after_position(
14758 &mut self,
14759 snapshot: &EditorSnapshot,
14760 position: Point,
14761 direction: Direction,
14762 window: &mut Window,
14763 cx: &mut Context<Editor>,
14764 ) {
14765 let row = if direction == Direction::Next {
14766 self.hunk_after_position(snapshot, position)
14767 .map(|hunk| hunk.row_range.start)
14768 } else {
14769 self.hunk_before_position(snapshot, position)
14770 };
14771
14772 if let Some(row) = row {
14773 let destination = Point::new(row.0, 0);
14774 let autoscroll = Autoscroll::center();
14775
14776 self.unfold_ranges(&[destination..destination], false, false, cx);
14777 self.change_selections(Some(autoscroll), window, cx, |s| {
14778 s.select_ranges([destination..destination]);
14779 });
14780 }
14781 }
14782
14783 fn hunk_after_position(
14784 &mut self,
14785 snapshot: &EditorSnapshot,
14786 position: Point,
14787 ) -> Option<MultiBufferDiffHunk> {
14788 snapshot
14789 .buffer_snapshot
14790 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14791 .find(|hunk| hunk.row_range.start.0 > position.row)
14792 .or_else(|| {
14793 snapshot
14794 .buffer_snapshot
14795 .diff_hunks_in_range(Point::zero()..position)
14796 .find(|hunk| hunk.row_range.end.0 < position.row)
14797 })
14798 }
14799
14800 fn go_to_prev_hunk(
14801 &mut self,
14802 _: &GoToPreviousHunk,
14803 window: &mut Window,
14804 cx: &mut Context<Self>,
14805 ) {
14806 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14807 let snapshot = self.snapshot(window, cx);
14808 let selection = self.selections.newest::<Point>(cx);
14809 self.go_to_hunk_before_or_after_position(
14810 &snapshot,
14811 selection.head(),
14812 Direction::Prev,
14813 window,
14814 cx,
14815 );
14816 }
14817
14818 fn hunk_before_position(
14819 &mut self,
14820 snapshot: &EditorSnapshot,
14821 position: Point,
14822 ) -> Option<MultiBufferRow> {
14823 snapshot
14824 .buffer_snapshot
14825 .diff_hunk_before(position)
14826 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14827 }
14828
14829 fn go_to_next_change(
14830 &mut self,
14831 _: &GoToNextChange,
14832 window: &mut Window,
14833 cx: &mut Context<Self>,
14834 ) {
14835 if let Some(selections) = self
14836 .change_list
14837 .next_change(1, Direction::Next)
14838 .map(|s| s.to_vec())
14839 {
14840 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14841 let map = s.display_map();
14842 s.select_display_ranges(selections.iter().map(|a| {
14843 let point = a.to_display_point(&map);
14844 point..point
14845 }))
14846 })
14847 }
14848 }
14849
14850 fn go_to_previous_change(
14851 &mut self,
14852 _: &GoToPreviousChange,
14853 window: &mut Window,
14854 cx: &mut Context<Self>,
14855 ) {
14856 if let Some(selections) = self
14857 .change_list
14858 .next_change(1, Direction::Prev)
14859 .map(|s| s.to_vec())
14860 {
14861 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14862 let map = s.display_map();
14863 s.select_display_ranges(selections.iter().map(|a| {
14864 let point = a.to_display_point(&map);
14865 point..point
14866 }))
14867 })
14868 }
14869 }
14870
14871 fn go_to_line<T: 'static>(
14872 &mut self,
14873 position: Anchor,
14874 highlight_color: Option<Hsla>,
14875 window: &mut Window,
14876 cx: &mut Context<Self>,
14877 ) {
14878 let snapshot = self.snapshot(window, cx).display_snapshot;
14879 let position = position.to_point(&snapshot.buffer_snapshot);
14880 let start = snapshot
14881 .buffer_snapshot
14882 .clip_point(Point::new(position.row, 0), Bias::Left);
14883 let end = start + Point::new(1, 0);
14884 let start = snapshot.buffer_snapshot.anchor_before(start);
14885 let end = snapshot.buffer_snapshot.anchor_before(end);
14886
14887 self.highlight_rows::<T>(
14888 start..end,
14889 highlight_color
14890 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14891 Default::default(),
14892 cx,
14893 );
14894
14895 if self.buffer.read(cx).is_singleton() {
14896 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14897 }
14898 }
14899
14900 pub fn go_to_definition(
14901 &mut self,
14902 _: &GoToDefinition,
14903 window: &mut Window,
14904 cx: &mut Context<Self>,
14905 ) -> Task<Result<Navigated>> {
14906 let definition =
14907 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14908 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14909 cx.spawn_in(window, async move |editor, cx| {
14910 if definition.await? == Navigated::Yes {
14911 return Ok(Navigated::Yes);
14912 }
14913 match fallback_strategy {
14914 GoToDefinitionFallback::None => Ok(Navigated::No),
14915 GoToDefinitionFallback::FindAllReferences => {
14916 match editor.update_in(cx, |editor, window, cx| {
14917 editor.find_all_references(&FindAllReferences, window, cx)
14918 })? {
14919 Some(references) => references.await,
14920 None => Ok(Navigated::No),
14921 }
14922 }
14923 }
14924 })
14925 }
14926
14927 pub fn go_to_declaration(
14928 &mut self,
14929 _: &GoToDeclaration,
14930 window: &mut Window,
14931 cx: &mut Context<Self>,
14932 ) -> Task<Result<Navigated>> {
14933 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14934 }
14935
14936 pub fn go_to_declaration_split(
14937 &mut self,
14938 _: &GoToDeclaration,
14939 window: &mut Window,
14940 cx: &mut Context<Self>,
14941 ) -> Task<Result<Navigated>> {
14942 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14943 }
14944
14945 pub fn go_to_implementation(
14946 &mut self,
14947 _: &GoToImplementation,
14948 window: &mut Window,
14949 cx: &mut Context<Self>,
14950 ) -> Task<Result<Navigated>> {
14951 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14952 }
14953
14954 pub fn go_to_implementation_split(
14955 &mut self,
14956 _: &GoToImplementationSplit,
14957 window: &mut Window,
14958 cx: &mut Context<Self>,
14959 ) -> Task<Result<Navigated>> {
14960 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14961 }
14962
14963 pub fn go_to_type_definition(
14964 &mut self,
14965 _: &GoToTypeDefinition,
14966 window: &mut Window,
14967 cx: &mut Context<Self>,
14968 ) -> Task<Result<Navigated>> {
14969 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14970 }
14971
14972 pub fn go_to_definition_split(
14973 &mut self,
14974 _: &GoToDefinitionSplit,
14975 window: &mut Window,
14976 cx: &mut Context<Self>,
14977 ) -> Task<Result<Navigated>> {
14978 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14979 }
14980
14981 pub fn go_to_type_definition_split(
14982 &mut self,
14983 _: &GoToTypeDefinitionSplit,
14984 window: &mut Window,
14985 cx: &mut Context<Self>,
14986 ) -> Task<Result<Navigated>> {
14987 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14988 }
14989
14990 fn go_to_definition_of_kind(
14991 &mut self,
14992 kind: GotoDefinitionKind,
14993 split: bool,
14994 window: &mut Window,
14995 cx: &mut Context<Self>,
14996 ) -> Task<Result<Navigated>> {
14997 let Some(provider) = self.semantics_provider.clone() else {
14998 return Task::ready(Ok(Navigated::No));
14999 };
15000 let head = self.selections.newest::<usize>(cx).head();
15001 let buffer = self.buffer.read(cx);
15002 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15003 text_anchor
15004 } else {
15005 return Task::ready(Ok(Navigated::No));
15006 };
15007
15008 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15009 return Task::ready(Ok(Navigated::No));
15010 };
15011
15012 cx.spawn_in(window, async move |editor, cx| {
15013 let definitions = definitions.await?;
15014 let navigated = editor
15015 .update_in(cx, |editor, window, cx| {
15016 editor.navigate_to_hover_links(
15017 Some(kind),
15018 definitions
15019 .into_iter()
15020 .filter(|location| {
15021 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15022 })
15023 .map(HoverLink::Text)
15024 .collect::<Vec<_>>(),
15025 split,
15026 window,
15027 cx,
15028 )
15029 })?
15030 .await?;
15031 anyhow::Ok(navigated)
15032 })
15033 }
15034
15035 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15036 let selection = self.selections.newest_anchor();
15037 let head = selection.head();
15038 let tail = selection.tail();
15039
15040 let Some((buffer, start_position)) =
15041 self.buffer.read(cx).text_anchor_for_position(head, cx)
15042 else {
15043 return;
15044 };
15045
15046 let end_position = if head != tail {
15047 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15048 return;
15049 };
15050 Some(pos)
15051 } else {
15052 None
15053 };
15054
15055 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15056 let url = if let Some(end_pos) = end_position {
15057 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15058 } else {
15059 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15060 };
15061
15062 if let Some(url) = url {
15063 editor.update(cx, |_, cx| {
15064 cx.open_url(&url);
15065 })
15066 } else {
15067 Ok(())
15068 }
15069 });
15070
15071 url_finder.detach();
15072 }
15073
15074 pub fn open_selected_filename(
15075 &mut self,
15076 _: &OpenSelectedFilename,
15077 window: &mut Window,
15078 cx: &mut Context<Self>,
15079 ) {
15080 let Some(workspace) = self.workspace() else {
15081 return;
15082 };
15083
15084 let position = self.selections.newest_anchor().head();
15085
15086 let Some((buffer, buffer_position)) =
15087 self.buffer.read(cx).text_anchor_for_position(position, cx)
15088 else {
15089 return;
15090 };
15091
15092 let project = self.project.clone();
15093
15094 cx.spawn_in(window, async move |_, cx| {
15095 let result = find_file(&buffer, project, buffer_position, cx).await;
15096
15097 if let Some((_, path)) = result {
15098 workspace
15099 .update_in(cx, |workspace, window, cx| {
15100 workspace.open_resolved_path(path, window, cx)
15101 })?
15102 .await?;
15103 }
15104 anyhow::Ok(())
15105 })
15106 .detach();
15107 }
15108
15109 pub(crate) fn navigate_to_hover_links(
15110 &mut self,
15111 kind: Option<GotoDefinitionKind>,
15112 mut definitions: Vec<HoverLink>,
15113 split: bool,
15114 window: &mut Window,
15115 cx: &mut Context<Editor>,
15116 ) -> Task<Result<Navigated>> {
15117 // If there is one definition, just open it directly
15118 if definitions.len() == 1 {
15119 let definition = definitions.pop().unwrap();
15120
15121 enum TargetTaskResult {
15122 Location(Option<Location>),
15123 AlreadyNavigated,
15124 }
15125
15126 let target_task = match definition {
15127 HoverLink::Text(link) => {
15128 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15129 }
15130 HoverLink::InlayHint(lsp_location, server_id) => {
15131 let computation =
15132 self.compute_target_location(lsp_location, server_id, window, cx);
15133 cx.background_spawn(async move {
15134 let location = computation.await?;
15135 Ok(TargetTaskResult::Location(location))
15136 })
15137 }
15138 HoverLink::Url(url) => {
15139 cx.open_url(&url);
15140 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15141 }
15142 HoverLink::File(path) => {
15143 if let Some(workspace) = self.workspace() {
15144 cx.spawn_in(window, async move |_, cx| {
15145 workspace
15146 .update_in(cx, |workspace, window, cx| {
15147 workspace.open_resolved_path(path, window, cx)
15148 })?
15149 .await
15150 .map(|_| TargetTaskResult::AlreadyNavigated)
15151 })
15152 } else {
15153 Task::ready(Ok(TargetTaskResult::Location(None)))
15154 }
15155 }
15156 };
15157 cx.spawn_in(window, async move |editor, cx| {
15158 let target = match target_task.await.context("target resolution task")? {
15159 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15160 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15161 TargetTaskResult::Location(Some(target)) => target,
15162 };
15163
15164 editor.update_in(cx, |editor, window, cx| {
15165 let Some(workspace) = editor.workspace() else {
15166 return Navigated::No;
15167 };
15168 let pane = workspace.read(cx).active_pane().clone();
15169
15170 let range = target.range.to_point(target.buffer.read(cx));
15171 let range = editor.range_for_match(&range);
15172 let range = collapse_multiline_range(range);
15173
15174 if !split
15175 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15176 {
15177 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15178 } else {
15179 window.defer(cx, move |window, cx| {
15180 let target_editor: Entity<Self> =
15181 workspace.update(cx, |workspace, cx| {
15182 let pane = if split {
15183 workspace.adjacent_pane(window, cx)
15184 } else {
15185 workspace.active_pane().clone()
15186 };
15187
15188 workspace.open_project_item(
15189 pane,
15190 target.buffer.clone(),
15191 true,
15192 true,
15193 window,
15194 cx,
15195 )
15196 });
15197 target_editor.update(cx, |target_editor, cx| {
15198 // When selecting a definition in a different buffer, disable the nav history
15199 // to avoid creating a history entry at the previous cursor location.
15200 pane.update(cx, |pane, _| pane.disable_history());
15201 target_editor.go_to_singleton_buffer_range(range, window, cx);
15202 pane.update(cx, |pane, _| pane.enable_history());
15203 });
15204 });
15205 }
15206 Navigated::Yes
15207 })
15208 })
15209 } else if !definitions.is_empty() {
15210 cx.spawn_in(window, async move |editor, cx| {
15211 let (title, location_tasks, workspace) = editor
15212 .update_in(cx, |editor, window, cx| {
15213 let tab_kind = match kind {
15214 Some(GotoDefinitionKind::Implementation) => "Implementations",
15215 _ => "Definitions",
15216 };
15217 let title = definitions
15218 .iter()
15219 .find_map(|definition| match definition {
15220 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15221 let buffer = origin.buffer.read(cx);
15222 format!(
15223 "{} for {}",
15224 tab_kind,
15225 buffer
15226 .text_for_range(origin.range.clone())
15227 .collect::<String>()
15228 )
15229 }),
15230 HoverLink::InlayHint(_, _) => None,
15231 HoverLink::Url(_) => None,
15232 HoverLink::File(_) => None,
15233 })
15234 .unwrap_or(tab_kind.to_string());
15235 let location_tasks = definitions
15236 .into_iter()
15237 .map(|definition| match definition {
15238 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15239 HoverLink::InlayHint(lsp_location, server_id) => editor
15240 .compute_target_location(lsp_location, server_id, window, cx),
15241 HoverLink::Url(_) => Task::ready(Ok(None)),
15242 HoverLink::File(_) => Task::ready(Ok(None)),
15243 })
15244 .collect::<Vec<_>>();
15245 (title, location_tasks, editor.workspace().clone())
15246 })
15247 .context("location tasks preparation")?;
15248
15249 let locations: Vec<Location> = future::join_all(location_tasks)
15250 .await
15251 .into_iter()
15252 .filter_map(|location| location.transpose())
15253 .collect::<Result<_>>()
15254 .context("location tasks")?;
15255
15256 if locations.is_empty() {
15257 return Ok(Navigated::No);
15258 }
15259
15260 let Some(workspace) = workspace else {
15261 return Ok(Navigated::No);
15262 };
15263
15264 let opened = workspace
15265 .update_in(cx, |workspace, window, cx| {
15266 Self::open_locations_in_multibuffer(
15267 workspace,
15268 locations,
15269 title,
15270 split,
15271 MultibufferSelectionMode::First,
15272 window,
15273 cx,
15274 )
15275 })
15276 .ok();
15277
15278 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15279 })
15280 } else {
15281 Task::ready(Ok(Navigated::No))
15282 }
15283 }
15284
15285 fn compute_target_location(
15286 &self,
15287 lsp_location: lsp::Location,
15288 server_id: LanguageServerId,
15289 window: &mut Window,
15290 cx: &mut Context<Self>,
15291 ) -> Task<anyhow::Result<Option<Location>>> {
15292 let Some(project) = self.project.clone() else {
15293 return Task::ready(Ok(None));
15294 };
15295
15296 cx.spawn_in(window, async move |editor, cx| {
15297 let location_task = editor.update(cx, |_, cx| {
15298 project.update(cx, |project, cx| {
15299 let language_server_name = project
15300 .language_server_statuses(cx)
15301 .find(|(id, _)| server_id == *id)
15302 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15303 language_server_name.map(|language_server_name| {
15304 project.open_local_buffer_via_lsp(
15305 lsp_location.uri.clone(),
15306 server_id,
15307 language_server_name,
15308 cx,
15309 )
15310 })
15311 })
15312 })?;
15313 let location = match location_task {
15314 Some(task) => Some({
15315 let target_buffer_handle = task.await.context("open local buffer")?;
15316 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15317 let target_start = target_buffer
15318 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15319 let target_end = target_buffer
15320 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15321 target_buffer.anchor_after(target_start)
15322 ..target_buffer.anchor_before(target_end)
15323 })?;
15324 Location {
15325 buffer: target_buffer_handle,
15326 range,
15327 }
15328 }),
15329 None => None,
15330 };
15331 Ok(location)
15332 })
15333 }
15334
15335 pub fn find_all_references(
15336 &mut self,
15337 _: &FindAllReferences,
15338 window: &mut Window,
15339 cx: &mut Context<Self>,
15340 ) -> Option<Task<Result<Navigated>>> {
15341 let selection = self.selections.newest::<usize>(cx);
15342 let multi_buffer = self.buffer.read(cx);
15343 let head = selection.head();
15344
15345 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15346 let head_anchor = multi_buffer_snapshot.anchor_at(
15347 head,
15348 if head < selection.tail() {
15349 Bias::Right
15350 } else {
15351 Bias::Left
15352 },
15353 );
15354
15355 match self
15356 .find_all_references_task_sources
15357 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15358 {
15359 Ok(_) => {
15360 log::info!(
15361 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15362 );
15363 return None;
15364 }
15365 Err(i) => {
15366 self.find_all_references_task_sources.insert(i, head_anchor);
15367 }
15368 }
15369
15370 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15371 let workspace = self.workspace()?;
15372 let project = workspace.read(cx).project().clone();
15373 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15374 Some(cx.spawn_in(window, async move |editor, cx| {
15375 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15376 if let Ok(i) = editor
15377 .find_all_references_task_sources
15378 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15379 {
15380 editor.find_all_references_task_sources.remove(i);
15381 }
15382 });
15383
15384 let locations = references.await?;
15385 if locations.is_empty() {
15386 return anyhow::Ok(Navigated::No);
15387 }
15388
15389 workspace.update_in(cx, |workspace, window, cx| {
15390 let title = locations
15391 .first()
15392 .as_ref()
15393 .map(|location| {
15394 let buffer = location.buffer.read(cx);
15395 format!(
15396 "References to `{}`",
15397 buffer
15398 .text_for_range(location.range.clone())
15399 .collect::<String>()
15400 )
15401 })
15402 .unwrap();
15403 Self::open_locations_in_multibuffer(
15404 workspace,
15405 locations,
15406 title,
15407 false,
15408 MultibufferSelectionMode::First,
15409 window,
15410 cx,
15411 );
15412 Navigated::Yes
15413 })
15414 }))
15415 }
15416
15417 /// Opens a multibuffer with the given project locations in it
15418 pub fn open_locations_in_multibuffer(
15419 workspace: &mut Workspace,
15420 mut locations: Vec<Location>,
15421 title: String,
15422 split: bool,
15423 multibuffer_selection_mode: MultibufferSelectionMode,
15424 window: &mut Window,
15425 cx: &mut Context<Workspace>,
15426 ) {
15427 if locations.is_empty() {
15428 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15429 return;
15430 }
15431
15432 // If there are multiple definitions, open them in a multibuffer
15433 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15434 let mut locations = locations.into_iter().peekable();
15435 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15436 let capability = workspace.project().read(cx).capability();
15437
15438 let excerpt_buffer = cx.new(|cx| {
15439 let mut multibuffer = MultiBuffer::new(capability);
15440 while let Some(location) = locations.next() {
15441 let buffer = location.buffer.read(cx);
15442 let mut ranges_for_buffer = Vec::new();
15443 let range = location.range.to_point(buffer);
15444 ranges_for_buffer.push(range.clone());
15445
15446 while let Some(next_location) = locations.peek() {
15447 if next_location.buffer == location.buffer {
15448 ranges_for_buffer.push(next_location.range.to_point(buffer));
15449 locations.next();
15450 } else {
15451 break;
15452 }
15453 }
15454
15455 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15456 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15457 PathKey::for_buffer(&location.buffer, cx),
15458 location.buffer.clone(),
15459 ranges_for_buffer,
15460 DEFAULT_MULTIBUFFER_CONTEXT,
15461 cx,
15462 );
15463 ranges.extend(new_ranges)
15464 }
15465
15466 multibuffer.with_title(title)
15467 });
15468
15469 let editor = cx.new(|cx| {
15470 Editor::for_multibuffer(
15471 excerpt_buffer,
15472 Some(workspace.project().clone()),
15473 window,
15474 cx,
15475 )
15476 });
15477 editor.update(cx, |editor, cx| {
15478 match multibuffer_selection_mode {
15479 MultibufferSelectionMode::First => {
15480 if let Some(first_range) = ranges.first() {
15481 editor.change_selections(None, window, cx, |selections| {
15482 selections.clear_disjoint();
15483 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15484 });
15485 }
15486 editor.highlight_background::<Self>(
15487 &ranges,
15488 |theme| theme.colors().editor_highlighted_line_background,
15489 cx,
15490 );
15491 }
15492 MultibufferSelectionMode::All => {
15493 editor.change_selections(None, window, cx, |selections| {
15494 selections.clear_disjoint();
15495 selections.select_anchor_ranges(ranges);
15496 });
15497 }
15498 }
15499 editor.register_buffers_with_language_servers(cx);
15500 });
15501
15502 let item = Box::new(editor);
15503 let item_id = item.item_id();
15504
15505 if split {
15506 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15507 } else {
15508 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15509 let (preview_item_id, preview_item_idx) =
15510 workspace.active_pane().read_with(cx, |pane, _| {
15511 (pane.preview_item_id(), pane.preview_item_idx())
15512 });
15513
15514 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15515
15516 if let Some(preview_item_id) = preview_item_id {
15517 workspace.active_pane().update(cx, |pane, cx| {
15518 pane.remove_item(preview_item_id, false, false, window, cx);
15519 });
15520 }
15521 } else {
15522 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15523 }
15524 }
15525 workspace.active_pane().update(cx, |pane, cx| {
15526 pane.set_preview_item_id(Some(item_id), cx);
15527 });
15528 }
15529
15530 pub fn rename(
15531 &mut self,
15532 _: &Rename,
15533 window: &mut Window,
15534 cx: &mut Context<Self>,
15535 ) -> Option<Task<Result<()>>> {
15536 use language::ToOffset as _;
15537
15538 let provider = self.semantics_provider.clone()?;
15539 let selection = self.selections.newest_anchor().clone();
15540 let (cursor_buffer, cursor_buffer_position) = self
15541 .buffer
15542 .read(cx)
15543 .text_anchor_for_position(selection.head(), cx)?;
15544 let (tail_buffer, cursor_buffer_position_end) = self
15545 .buffer
15546 .read(cx)
15547 .text_anchor_for_position(selection.tail(), cx)?;
15548 if tail_buffer != cursor_buffer {
15549 return None;
15550 }
15551
15552 let snapshot = cursor_buffer.read(cx).snapshot();
15553 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15554 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15555 let prepare_rename = provider
15556 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15557 .unwrap_or_else(|| Task::ready(Ok(None)));
15558 drop(snapshot);
15559
15560 Some(cx.spawn_in(window, async move |this, cx| {
15561 let rename_range = if let Some(range) = prepare_rename.await? {
15562 Some(range)
15563 } else {
15564 this.update(cx, |this, cx| {
15565 let buffer = this.buffer.read(cx).snapshot(cx);
15566 let mut buffer_highlights = this
15567 .document_highlights_for_position(selection.head(), &buffer)
15568 .filter(|highlight| {
15569 highlight.start.excerpt_id == selection.head().excerpt_id
15570 && highlight.end.excerpt_id == selection.head().excerpt_id
15571 });
15572 buffer_highlights
15573 .next()
15574 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15575 })?
15576 };
15577 if let Some(rename_range) = rename_range {
15578 this.update_in(cx, |this, window, cx| {
15579 let snapshot = cursor_buffer.read(cx).snapshot();
15580 let rename_buffer_range = rename_range.to_offset(&snapshot);
15581 let cursor_offset_in_rename_range =
15582 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15583 let cursor_offset_in_rename_range_end =
15584 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15585
15586 this.take_rename(false, window, cx);
15587 let buffer = this.buffer.read(cx).read(cx);
15588 let cursor_offset = selection.head().to_offset(&buffer);
15589 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15590 let rename_end = rename_start + rename_buffer_range.len();
15591 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15592 let mut old_highlight_id = None;
15593 let old_name: Arc<str> = buffer
15594 .chunks(rename_start..rename_end, true)
15595 .map(|chunk| {
15596 if old_highlight_id.is_none() {
15597 old_highlight_id = chunk.syntax_highlight_id;
15598 }
15599 chunk.text
15600 })
15601 .collect::<String>()
15602 .into();
15603
15604 drop(buffer);
15605
15606 // Position the selection in the rename editor so that it matches the current selection.
15607 this.show_local_selections = false;
15608 let rename_editor = cx.new(|cx| {
15609 let mut editor = Editor::single_line(window, cx);
15610 editor.buffer.update(cx, |buffer, cx| {
15611 buffer.edit([(0..0, old_name.clone())], None, cx)
15612 });
15613 let rename_selection_range = match cursor_offset_in_rename_range
15614 .cmp(&cursor_offset_in_rename_range_end)
15615 {
15616 Ordering::Equal => {
15617 editor.select_all(&SelectAll, window, cx);
15618 return editor;
15619 }
15620 Ordering::Less => {
15621 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15622 }
15623 Ordering::Greater => {
15624 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15625 }
15626 };
15627 if rename_selection_range.end > old_name.len() {
15628 editor.select_all(&SelectAll, window, cx);
15629 } else {
15630 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15631 s.select_ranges([rename_selection_range]);
15632 });
15633 }
15634 editor
15635 });
15636 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15637 if e == &EditorEvent::Focused {
15638 cx.emit(EditorEvent::FocusedIn)
15639 }
15640 })
15641 .detach();
15642
15643 let write_highlights =
15644 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15645 let read_highlights =
15646 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15647 let ranges = write_highlights
15648 .iter()
15649 .flat_map(|(_, ranges)| ranges.iter())
15650 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15651 .cloned()
15652 .collect();
15653
15654 this.highlight_text::<Rename>(
15655 ranges,
15656 HighlightStyle {
15657 fade_out: Some(0.6),
15658 ..Default::default()
15659 },
15660 cx,
15661 );
15662 let rename_focus_handle = rename_editor.focus_handle(cx);
15663 window.focus(&rename_focus_handle);
15664 let block_id = this.insert_blocks(
15665 [BlockProperties {
15666 style: BlockStyle::Flex,
15667 placement: BlockPlacement::Below(range.start),
15668 height: Some(1),
15669 render: Arc::new({
15670 let rename_editor = rename_editor.clone();
15671 move |cx: &mut BlockContext| {
15672 let mut text_style = cx.editor_style.text.clone();
15673 if let Some(highlight_style) = old_highlight_id
15674 .and_then(|h| h.style(&cx.editor_style.syntax))
15675 {
15676 text_style = text_style.highlight(highlight_style);
15677 }
15678 div()
15679 .block_mouse_except_scroll()
15680 .pl(cx.anchor_x)
15681 .child(EditorElement::new(
15682 &rename_editor,
15683 EditorStyle {
15684 background: cx.theme().system().transparent,
15685 local_player: cx.editor_style.local_player,
15686 text: text_style,
15687 scrollbar_width: cx.editor_style.scrollbar_width,
15688 syntax: cx.editor_style.syntax.clone(),
15689 status: cx.editor_style.status.clone(),
15690 inlay_hints_style: HighlightStyle {
15691 font_weight: Some(FontWeight::BOLD),
15692 ..make_inlay_hints_style(cx.app)
15693 },
15694 inline_completion_styles: make_suggestion_styles(
15695 cx.app,
15696 ),
15697 ..EditorStyle::default()
15698 },
15699 ))
15700 .into_any_element()
15701 }
15702 }),
15703 priority: 0,
15704 render_in_minimap: true,
15705 }],
15706 Some(Autoscroll::fit()),
15707 cx,
15708 )[0];
15709 this.pending_rename = Some(RenameState {
15710 range,
15711 old_name,
15712 editor: rename_editor,
15713 block_id,
15714 });
15715 })?;
15716 }
15717
15718 Ok(())
15719 }))
15720 }
15721
15722 pub fn confirm_rename(
15723 &mut self,
15724 _: &ConfirmRename,
15725 window: &mut Window,
15726 cx: &mut Context<Self>,
15727 ) -> Option<Task<Result<()>>> {
15728 let rename = self.take_rename(false, window, cx)?;
15729 let workspace = self.workspace()?.downgrade();
15730 let (buffer, start) = self
15731 .buffer
15732 .read(cx)
15733 .text_anchor_for_position(rename.range.start, cx)?;
15734 let (end_buffer, _) = self
15735 .buffer
15736 .read(cx)
15737 .text_anchor_for_position(rename.range.end, cx)?;
15738 if buffer != end_buffer {
15739 return None;
15740 }
15741
15742 let old_name = rename.old_name;
15743 let new_name = rename.editor.read(cx).text(cx);
15744
15745 let rename = self.semantics_provider.as_ref()?.perform_rename(
15746 &buffer,
15747 start,
15748 new_name.clone(),
15749 cx,
15750 )?;
15751
15752 Some(cx.spawn_in(window, async move |editor, cx| {
15753 let project_transaction = rename.await?;
15754 Self::open_project_transaction(
15755 &editor,
15756 workspace,
15757 project_transaction,
15758 format!("Rename: {} → {}", old_name, new_name),
15759 cx,
15760 )
15761 .await?;
15762
15763 editor.update(cx, |editor, cx| {
15764 editor.refresh_document_highlights(cx);
15765 })?;
15766 Ok(())
15767 }))
15768 }
15769
15770 fn take_rename(
15771 &mut self,
15772 moving_cursor: bool,
15773 window: &mut Window,
15774 cx: &mut Context<Self>,
15775 ) -> Option<RenameState> {
15776 let rename = self.pending_rename.take()?;
15777 if rename.editor.focus_handle(cx).is_focused(window) {
15778 window.focus(&self.focus_handle);
15779 }
15780
15781 self.remove_blocks(
15782 [rename.block_id].into_iter().collect(),
15783 Some(Autoscroll::fit()),
15784 cx,
15785 );
15786 self.clear_highlights::<Rename>(cx);
15787 self.show_local_selections = true;
15788
15789 if moving_cursor {
15790 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15791 editor.selections.newest::<usize>(cx).head()
15792 });
15793
15794 // Update the selection to match the position of the selection inside
15795 // the rename editor.
15796 let snapshot = self.buffer.read(cx).read(cx);
15797 let rename_range = rename.range.to_offset(&snapshot);
15798 let cursor_in_editor = snapshot
15799 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15800 .min(rename_range.end);
15801 drop(snapshot);
15802
15803 self.change_selections(None, window, cx, |s| {
15804 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15805 });
15806 } else {
15807 self.refresh_document_highlights(cx);
15808 }
15809
15810 Some(rename)
15811 }
15812
15813 pub fn pending_rename(&self) -> Option<&RenameState> {
15814 self.pending_rename.as_ref()
15815 }
15816
15817 fn format(
15818 &mut self,
15819 _: &Format,
15820 window: &mut Window,
15821 cx: &mut Context<Self>,
15822 ) -> Option<Task<Result<()>>> {
15823 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15824
15825 let project = match &self.project {
15826 Some(project) => project.clone(),
15827 None => return None,
15828 };
15829
15830 Some(self.perform_format(
15831 project,
15832 FormatTrigger::Manual,
15833 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15834 window,
15835 cx,
15836 ))
15837 }
15838
15839 fn format_selections(
15840 &mut self,
15841 _: &FormatSelections,
15842 window: &mut Window,
15843 cx: &mut Context<Self>,
15844 ) -> Option<Task<Result<()>>> {
15845 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15846
15847 let project = match &self.project {
15848 Some(project) => project.clone(),
15849 None => return None,
15850 };
15851
15852 let ranges = self
15853 .selections
15854 .all_adjusted(cx)
15855 .into_iter()
15856 .map(|selection| selection.range())
15857 .collect_vec();
15858
15859 Some(self.perform_format(
15860 project,
15861 FormatTrigger::Manual,
15862 FormatTarget::Ranges(ranges),
15863 window,
15864 cx,
15865 ))
15866 }
15867
15868 fn perform_format(
15869 &mut self,
15870 project: Entity<Project>,
15871 trigger: FormatTrigger,
15872 target: FormatTarget,
15873 window: &mut Window,
15874 cx: &mut Context<Self>,
15875 ) -> Task<Result<()>> {
15876 let buffer = self.buffer.clone();
15877 let (buffers, target) = match target {
15878 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15879 FormatTarget::Ranges(selection_ranges) => {
15880 let multi_buffer = buffer.read(cx);
15881 let snapshot = multi_buffer.read(cx);
15882 let mut buffers = HashSet::default();
15883 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15884 BTreeMap::new();
15885 for selection_range in selection_ranges {
15886 for (buffer, buffer_range, _) in
15887 snapshot.range_to_buffer_ranges(selection_range)
15888 {
15889 let buffer_id = buffer.remote_id();
15890 let start = buffer.anchor_before(buffer_range.start);
15891 let end = buffer.anchor_after(buffer_range.end);
15892 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15893 buffer_id_to_ranges
15894 .entry(buffer_id)
15895 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15896 .or_insert_with(|| vec![start..end]);
15897 }
15898 }
15899 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15900 }
15901 };
15902
15903 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15904 let selections_prev = transaction_id_prev
15905 .and_then(|transaction_id_prev| {
15906 // default to selections as they were after the last edit, if we have them,
15907 // instead of how they are now.
15908 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15909 // will take you back to where you made the last edit, instead of staying where you scrolled
15910 self.selection_history
15911 .transaction(transaction_id_prev)
15912 .map(|t| t.0.clone())
15913 })
15914 .unwrap_or_else(|| {
15915 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15916 self.selections.disjoint_anchors()
15917 });
15918
15919 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15920 let format = project.update(cx, |project, cx| {
15921 project.format(buffers, target, true, trigger, cx)
15922 });
15923
15924 cx.spawn_in(window, async move |editor, cx| {
15925 let transaction = futures::select_biased! {
15926 transaction = format.log_err().fuse() => transaction,
15927 () = timeout => {
15928 log::warn!("timed out waiting for formatting");
15929 None
15930 }
15931 };
15932
15933 buffer
15934 .update(cx, |buffer, cx| {
15935 if let Some(transaction) = transaction {
15936 if !buffer.is_singleton() {
15937 buffer.push_transaction(&transaction.0, cx);
15938 }
15939 }
15940 cx.notify();
15941 })
15942 .ok();
15943
15944 if let Some(transaction_id_now) =
15945 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15946 {
15947 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15948 if has_new_transaction {
15949 _ = editor.update(cx, |editor, _| {
15950 editor
15951 .selection_history
15952 .insert_transaction(transaction_id_now, selections_prev);
15953 });
15954 }
15955 }
15956
15957 Ok(())
15958 })
15959 }
15960
15961 fn organize_imports(
15962 &mut self,
15963 _: &OrganizeImports,
15964 window: &mut Window,
15965 cx: &mut Context<Self>,
15966 ) -> Option<Task<Result<()>>> {
15967 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15968 let project = match &self.project {
15969 Some(project) => project.clone(),
15970 None => return None,
15971 };
15972 Some(self.perform_code_action_kind(
15973 project,
15974 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15975 window,
15976 cx,
15977 ))
15978 }
15979
15980 fn perform_code_action_kind(
15981 &mut self,
15982 project: Entity<Project>,
15983 kind: CodeActionKind,
15984 window: &mut Window,
15985 cx: &mut Context<Self>,
15986 ) -> Task<Result<()>> {
15987 let buffer = self.buffer.clone();
15988 let buffers = buffer.read(cx).all_buffers();
15989 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15990 let apply_action = project.update(cx, |project, cx| {
15991 project.apply_code_action_kind(buffers, kind, true, cx)
15992 });
15993 cx.spawn_in(window, async move |_, cx| {
15994 let transaction = futures::select_biased! {
15995 () = timeout => {
15996 log::warn!("timed out waiting for executing code action");
15997 None
15998 }
15999 transaction = apply_action.log_err().fuse() => transaction,
16000 };
16001 buffer
16002 .update(cx, |buffer, cx| {
16003 // check if we need this
16004 if let Some(transaction) = transaction {
16005 if !buffer.is_singleton() {
16006 buffer.push_transaction(&transaction.0, cx);
16007 }
16008 }
16009 cx.notify();
16010 })
16011 .ok();
16012 Ok(())
16013 })
16014 }
16015
16016 fn restart_language_server(
16017 &mut self,
16018 _: &RestartLanguageServer,
16019 _: &mut Window,
16020 cx: &mut Context<Self>,
16021 ) {
16022 if let Some(project) = self.project.clone() {
16023 self.buffer.update(cx, |multi_buffer, cx| {
16024 project.update(cx, |project, cx| {
16025 project.restart_language_servers_for_buffers(
16026 multi_buffer.all_buffers().into_iter().collect(),
16027 cx,
16028 );
16029 });
16030 })
16031 }
16032 }
16033
16034 fn stop_language_server(
16035 &mut self,
16036 _: &StopLanguageServer,
16037 _: &mut Window,
16038 cx: &mut Context<Self>,
16039 ) {
16040 if let Some(project) = self.project.clone() {
16041 self.buffer.update(cx, |multi_buffer, cx| {
16042 project.update(cx, |project, cx| {
16043 project.stop_language_servers_for_buffers(
16044 multi_buffer.all_buffers().into_iter().collect(),
16045 cx,
16046 );
16047 cx.emit(project::Event::RefreshInlayHints);
16048 });
16049 });
16050 }
16051 }
16052
16053 fn cancel_language_server_work(
16054 workspace: &mut Workspace,
16055 _: &actions::CancelLanguageServerWork,
16056 _: &mut Window,
16057 cx: &mut Context<Workspace>,
16058 ) {
16059 let project = workspace.project();
16060 let buffers = workspace
16061 .active_item(cx)
16062 .and_then(|item| item.act_as::<Editor>(cx))
16063 .map_or(HashSet::default(), |editor| {
16064 editor.read(cx).buffer.read(cx).all_buffers()
16065 });
16066 project.update(cx, |project, cx| {
16067 project.cancel_language_server_work_for_buffers(buffers, cx);
16068 });
16069 }
16070
16071 fn show_character_palette(
16072 &mut self,
16073 _: &ShowCharacterPalette,
16074 window: &mut Window,
16075 _: &mut Context<Self>,
16076 ) {
16077 window.show_character_palette();
16078 }
16079
16080 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16081 if !self.diagnostics_enabled() {
16082 return;
16083 }
16084
16085 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16086 let buffer = self.buffer.read(cx).snapshot(cx);
16087 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16088 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16089 let is_valid = buffer
16090 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16091 .any(|entry| {
16092 entry.diagnostic.is_primary
16093 && !entry.range.is_empty()
16094 && entry.range.start == primary_range_start
16095 && entry.diagnostic.message == active_diagnostics.active_message
16096 });
16097
16098 if !is_valid {
16099 self.dismiss_diagnostics(cx);
16100 }
16101 }
16102 }
16103
16104 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16105 match &self.active_diagnostics {
16106 ActiveDiagnostic::Group(group) => Some(group),
16107 _ => None,
16108 }
16109 }
16110
16111 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16112 if !self.diagnostics_enabled() {
16113 return;
16114 }
16115 self.dismiss_diagnostics(cx);
16116 self.active_diagnostics = ActiveDiagnostic::All;
16117 }
16118
16119 fn activate_diagnostics(
16120 &mut self,
16121 buffer_id: BufferId,
16122 diagnostic: DiagnosticEntry<usize>,
16123 window: &mut Window,
16124 cx: &mut Context<Self>,
16125 ) {
16126 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16127 return;
16128 }
16129 self.dismiss_diagnostics(cx);
16130 let snapshot = self.snapshot(window, cx);
16131 let buffer = self.buffer.read(cx).snapshot(cx);
16132 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16133 return;
16134 };
16135
16136 let diagnostic_group = buffer
16137 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16138 .collect::<Vec<_>>();
16139
16140 let blocks =
16141 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16142
16143 let blocks = self.display_map.update(cx, |display_map, cx| {
16144 display_map.insert_blocks(blocks, cx).into_iter().collect()
16145 });
16146 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16147 active_range: buffer.anchor_before(diagnostic.range.start)
16148 ..buffer.anchor_after(diagnostic.range.end),
16149 active_message: diagnostic.diagnostic.message.clone(),
16150 group_id: diagnostic.diagnostic.group_id,
16151 blocks,
16152 });
16153 cx.notify();
16154 }
16155
16156 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16157 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16158 return;
16159 };
16160
16161 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16162 if let ActiveDiagnostic::Group(group) = prev {
16163 self.display_map.update(cx, |display_map, cx| {
16164 display_map.remove_blocks(group.blocks, cx);
16165 });
16166 cx.notify();
16167 }
16168 }
16169
16170 /// Disable inline diagnostics rendering for this editor.
16171 pub fn disable_inline_diagnostics(&mut self) {
16172 self.inline_diagnostics_enabled = false;
16173 self.inline_diagnostics_update = Task::ready(());
16174 self.inline_diagnostics.clear();
16175 }
16176
16177 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16178 self.diagnostics_enabled = false;
16179 self.dismiss_diagnostics(cx);
16180 self.inline_diagnostics_update = Task::ready(());
16181 self.inline_diagnostics.clear();
16182 }
16183
16184 pub fn diagnostics_enabled(&self) -> bool {
16185 self.diagnostics_enabled && self.mode.is_full()
16186 }
16187
16188 pub fn inline_diagnostics_enabled(&self) -> bool {
16189 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16190 }
16191
16192 pub fn show_inline_diagnostics(&self) -> bool {
16193 self.show_inline_diagnostics
16194 }
16195
16196 pub fn toggle_inline_diagnostics(
16197 &mut self,
16198 _: &ToggleInlineDiagnostics,
16199 window: &mut Window,
16200 cx: &mut Context<Editor>,
16201 ) {
16202 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16203 self.refresh_inline_diagnostics(false, window, cx);
16204 }
16205
16206 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16207 self.diagnostics_max_severity = severity;
16208 self.display_map.update(cx, |display_map, _| {
16209 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16210 });
16211 }
16212
16213 pub fn toggle_diagnostics(
16214 &mut self,
16215 _: &ToggleDiagnostics,
16216 window: &mut Window,
16217 cx: &mut Context<Editor>,
16218 ) {
16219 if !self.diagnostics_enabled() {
16220 return;
16221 }
16222
16223 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16224 EditorSettings::get_global(cx)
16225 .diagnostics_max_severity
16226 .filter(|severity| severity != &DiagnosticSeverity::Off)
16227 .unwrap_or(DiagnosticSeverity::Hint)
16228 } else {
16229 DiagnosticSeverity::Off
16230 };
16231 self.set_max_diagnostics_severity(new_severity, cx);
16232 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16233 self.active_diagnostics = ActiveDiagnostic::None;
16234 self.inline_diagnostics_update = Task::ready(());
16235 self.inline_diagnostics.clear();
16236 } else {
16237 self.refresh_inline_diagnostics(false, window, cx);
16238 }
16239
16240 cx.notify();
16241 }
16242
16243 pub fn toggle_minimap(
16244 &mut self,
16245 _: &ToggleMinimap,
16246 window: &mut Window,
16247 cx: &mut Context<Editor>,
16248 ) {
16249 if self.supports_minimap(cx) {
16250 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16251 }
16252 }
16253
16254 fn refresh_inline_diagnostics(
16255 &mut self,
16256 debounce: bool,
16257 window: &mut Window,
16258 cx: &mut Context<Self>,
16259 ) {
16260 let max_severity = ProjectSettings::get_global(cx)
16261 .diagnostics
16262 .inline
16263 .max_severity
16264 .unwrap_or(self.diagnostics_max_severity);
16265
16266 if !self.inline_diagnostics_enabled()
16267 || !self.show_inline_diagnostics
16268 || max_severity == DiagnosticSeverity::Off
16269 {
16270 self.inline_diagnostics_update = Task::ready(());
16271 self.inline_diagnostics.clear();
16272 return;
16273 }
16274
16275 let debounce_ms = ProjectSettings::get_global(cx)
16276 .diagnostics
16277 .inline
16278 .update_debounce_ms;
16279 let debounce = if debounce && debounce_ms > 0 {
16280 Some(Duration::from_millis(debounce_ms))
16281 } else {
16282 None
16283 };
16284 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16285 if let Some(debounce) = debounce {
16286 cx.background_executor().timer(debounce).await;
16287 }
16288 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16289 editor
16290 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16291 .ok()
16292 }) else {
16293 return;
16294 };
16295
16296 let new_inline_diagnostics = cx
16297 .background_spawn(async move {
16298 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16299 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16300 let message = diagnostic_entry
16301 .diagnostic
16302 .message
16303 .split_once('\n')
16304 .map(|(line, _)| line)
16305 .map(SharedString::new)
16306 .unwrap_or_else(|| {
16307 SharedString::from(diagnostic_entry.diagnostic.message)
16308 });
16309 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16310 let (Ok(i) | Err(i)) = inline_diagnostics
16311 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16312 inline_diagnostics.insert(
16313 i,
16314 (
16315 start_anchor,
16316 InlineDiagnostic {
16317 message,
16318 group_id: diagnostic_entry.diagnostic.group_id,
16319 start: diagnostic_entry.range.start.to_point(&snapshot),
16320 is_primary: diagnostic_entry.diagnostic.is_primary,
16321 severity: diagnostic_entry.diagnostic.severity,
16322 },
16323 ),
16324 );
16325 }
16326 inline_diagnostics
16327 })
16328 .await;
16329
16330 editor
16331 .update(cx, |editor, cx| {
16332 editor.inline_diagnostics = new_inline_diagnostics;
16333 cx.notify();
16334 })
16335 .ok();
16336 });
16337 }
16338
16339 fn pull_diagnostics(
16340 &mut self,
16341 buffer_id: Option<BufferId>,
16342 window: &Window,
16343 cx: &mut Context<Self>,
16344 ) -> Option<()> {
16345 if !self.mode().is_full() {
16346 return None;
16347 }
16348 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16349 .diagnostics
16350 .lsp_pull_diagnostics;
16351 if !pull_diagnostics_settings.enabled {
16352 return None;
16353 }
16354 let project = self.project.as_ref()?.downgrade();
16355 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16356 let mut buffers = self.buffer.read(cx).all_buffers();
16357 if let Some(buffer_id) = buffer_id {
16358 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16359 }
16360
16361 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16362 cx.background_executor().timer(debounce).await;
16363
16364 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16365 buffers
16366 .into_iter()
16367 .filter_map(|buffer| {
16368 project
16369 .update(cx, |project, cx| {
16370 project.lsp_store().update(cx, |lsp_store, cx| {
16371 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16372 })
16373 })
16374 .ok()
16375 })
16376 .collect::<FuturesUnordered<_>>()
16377 }) else {
16378 return;
16379 };
16380
16381 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16382 match pull_task {
16383 Ok(()) => {
16384 if editor
16385 .update_in(cx, |editor, window, cx| {
16386 editor.update_diagnostics_state(window, cx);
16387 })
16388 .is_err()
16389 {
16390 return;
16391 }
16392 }
16393 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16394 }
16395 }
16396 });
16397
16398 Some(())
16399 }
16400
16401 pub fn set_selections_from_remote(
16402 &mut self,
16403 selections: Vec<Selection<Anchor>>,
16404 pending_selection: Option<Selection<Anchor>>,
16405 window: &mut Window,
16406 cx: &mut Context<Self>,
16407 ) {
16408 let old_cursor_position = self.selections.newest_anchor().head();
16409 self.selections.change_with(cx, |s| {
16410 s.select_anchors(selections);
16411 if let Some(pending_selection) = pending_selection {
16412 s.set_pending(pending_selection, SelectMode::Character);
16413 } else {
16414 s.clear_pending();
16415 }
16416 });
16417 self.selections_did_change(
16418 false,
16419 &old_cursor_position,
16420 SelectionEffects::default(),
16421 window,
16422 cx,
16423 );
16424 }
16425
16426 pub fn transact(
16427 &mut self,
16428 window: &mut Window,
16429 cx: &mut Context<Self>,
16430 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16431 ) -> Option<TransactionId> {
16432 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16433 this.start_transaction_at(Instant::now(), window, cx);
16434 update(this, window, cx);
16435 this.end_transaction_at(Instant::now(), cx)
16436 })
16437 }
16438
16439 pub fn start_transaction_at(
16440 &mut self,
16441 now: Instant,
16442 window: &mut Window,
16443 cx: &mut Context<Self>,
16444 ) {
16445 self.end_selection(window, cx);
16446 if let Some(tx_id) = self
16447 .buffer
16448 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16449 {
16450 self.selection_history
16451 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16452 cx.emit(EditorEvent::TransactionBegun {
16453 transaction_id: tx_id,
16454 })
16455 }
16456 }
16457
16458 pub fn end_transaction_at(
16459 &mut self,
16460 now: Instant,
16461 cx: &mut Context<Self>,
16462 ) -> Option<TransactionId> {
16463 if let Some(transaction_id) = self
16464 .buffer
16465 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16466 {
16467 if let Some((_, end_selections)) =
16468 self.selection_history.transaction_mut(transaction_id)
16469 {
16470 *end_selections = Some(self.selections.disjoint_anchors());
16471 } else {
16472 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16473 }
16474
16475 cx.emit(EditorEvent::Edited { transaction_id });
16476 Some(transaction_id)
16477 } else {
16478 None
16479 }
16480 }
16481
16482 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16483 if self.selection_mark_mode {
16484 self.change_selections(None, window, cx, |s| {
16485 s.move_with(|_, sel| {
16486 sel.collapse_to(sel.head(), SelectionGoal::None);
16487 });
16488 })
16489 }
16490 self.selection_mark_mode = true;
16491 cx.notify();
16492 }
16493
16494 pub fn swap_selection_ends(
16495 &mut self,
16496 _: &actions::SwapSelectionEnds,
16497 window: &mut Window,
16498 cx: &mut Context<Self>,
16499 ) {
16500 self.change_selections(None, window, cx, |s| {
16501 s.move_with(|_, sel| {
16502 if sel.start != sel.end {
16503 sel.reversed = !sel.reversed
16504 }
16505 });
16506 });
16507 self.request_autoscroll(Autoscroll::newest(), cx);
16508 cx.notify();
16509 }
16510
16511 pub fn toggle_fold(
16512 &mut self,
16513 _: &actions::ToggleFold,
16514 window: &mut Window,
16515 cx: &mut Context<Self>,
16516 ) {
16517 if self.is_singleton(cx) {
16518 let selection = self.selections.newest::<Point>(cx);
16519
16520 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16521 let range = if selection.is_empty() {
16522 let point = selection.head().to_display_point(&display_map);
16523 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16524 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16525 .to_point(&display_map);
16526 start..end
16527 } else {
16528 selection.range()
16529 };
16530 if display_map.folds_in_range(range).next().is_some() {
16531 self.unfold_lines(&Default::default(), window, cx)
16532 } else {
16533 self.fold(&Default::default(), window, cx)
16534 }
16535 } else {
16536 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16537 let buffer_ids: HashSet<_> = self
16538 .selections
16539 .disjoint_anchor_ranges()
16540 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16541 .collect();
16542
16543 let should_unfold = buffer_ids
16544 .iter()
16545 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16546
16547 for buffer_id in buffer_ids {
16548 if should_unfold {
16549 self.unfold_buffer(buffer_id, cx);
16550 } else {
16551 self.fold_buffer(buffer_id, cx);
16552 }
16553 }
16554 }
16555 }
16556
16557 pub fn toggle_fold_recursive(
16558 &mut self,
16559 _: &actions::ToggleFoldRecursive,
16560 window: &mut Window,
16561 cx: &mut Context<Self>,
16562 ) {
16563 let selection = self.selections.newest::<Point>(cx);
16564
16565 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16566 let range = if selection.is_empty() {
16567 let point = selection.head().to_display_point(&display_map);
16568 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16569 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16570 .to_point(&display_map);
16571 start..end
16572 } else {
16573 selection.range()
16574 };
16575 if display_map.folds_in_range(range).next().is_some() {
16576 self.unfold_recursive(&Default::default(), window, cx)
16577 } else {
16578 self.fold_recursive(&Default::default(), window, cx)
16579 }
16580 }
16581
16582 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16583 if self.is_singleton(cx) {
16584 let mut to_fold = Vec::new();
16585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16586 let selections = self.selections.all_adjusted(cx);
16587
16588 for selection in selections {
16589 let range = selection.range().sorted();
16590 let buffer_start_row = range.start.row;
16591
16592 if range.start.row != range.end.row {
16593 let mut found = false;
16594 let mut row = range.start.row;
16595 while row <= range.end.row {
16596 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16597 {
16598 found = true;
16599 row = crease.range().end.row + 1;
16600 to_fold.push(crease);
16601 } else {
16602 row += 1
16603 }
16604 }
16605 if found {
16606 continue;
16607 }
16608 }
16609
16610 for row in (0..=range.start.row).rev() {
16611 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16612 if crease.range().end.row >= buffer_start_row {
16613 to_fold.push(crease);
16614 if row <= range.start.row {
16615 break;
16616 }
16617 }
16618 }
16619 }
16620 }
16621
16622 self.fold_creases(to_fold, true, window, cx);
16623 } else {
16624 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16625 let buffer_ids = self
16626 .selections
16627 .disjoint_anchor_ranges()
16628 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16629 .collect::<HashSet<_>>();
16630 for buffer_id in buffer_ids {
16631 self.fold_buffer(buffer_id, cx);
16632 }
16633 }
16634 }
16635
16636 fn fold_at_level(
16637 &mut self,
16638 fold_at: &FoldAtLevel,
16639 window: &mut Window,
16640 cx: &mut Context<Self>,
16641 ) {
16642 if !self.buffer.read(cx).is_singleton() {
16643 return;
16644 }
16645
16646 let fold_at_level = fold_at.0;
16647 let snapshot = self.buffer.read(cx).snapshot(cx);
16648 let mut to_fold = Vec::new();
16649 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16650
16651 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16652 while start_row < end_row {
16653 match self
16654 .snapshot(window, cx)
16655 .crease_for_buffer_row(MultiBufferRow(start_row))
16656 {
16657 Some(crease) => {
16658 let nested_start_row = crease.range().start.row + 1;
16659 let nested_end_row = crease.range().end.row;
16660
16661 if current_level < fold_at_level {
16662 stack.push((nested_start_row, nested_end_row, current_level + 1));
16663 } else if current_level == fold_at_level {
16664 to_fold.push(crease);
16665 }
16666
16667 start_row = nested_end_row + 1;
16668 }
16669 None => start_row += 1,
16670 }
16671 }
16672 }
16673
16674 self.fold_creases(to_fold, true, window, cx);
16675 }
16676
16677 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16678 if self.buffer.read(cx).is_singleton() {
16679 let mut fold_ranges = Vec::new();
16680 let snapshot = self.buffer.read(cx).snapshot(cx);
16681
16682 for row in 0..snapshot.max_row().0 {
16683 if let Some(foldable_range) = self
16684 .snapshot(window, cx)
16685 .crease_for_buffer_row(MultiBufferRow(row))
16686 {
16687 fold_ranges.push(foldable_range);
16688 }
16689 }
16690
16691 self.fold_creases(fold_ranges, true, window, cx);
16692 } else {
16693 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16694 editor
16695 .update_in(cx, |editor, _, cx| {
16696 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16697 editor.fold_buffer(buffer_id, cx);
16698 }
16699 })
16700 .ok();
16701 });
16702 }
16703 }
16704
16705 pub fn fold_function_bodies(
16706 &mut self,
16707 _: &actions::FoldFunctionBodies,
16708 window: &mut Window,
16709 cx: &mut Context<Self>,
16710 ) {
16711 let snapshot = self.buffer.read(cx).snapshot(cx);
16712
16713 let ranges = snapshot
16714 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16715 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16716 .collect::<Vec<_>>();
16717
16718 let creases = ranges
16719 .into_iter()
16720 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16721 .collect();
16722
16723 self.fold_creases(creases, true, window, cx);
16724 }
16725
16726 pub fn fold_recursive(
16727 &mut self,
16728 _: &actions::FoldRecursive,
16729 window: &mut Window,
16730 cx: &mut Context<Self>,
16731 ) {
16732 let mut to_fold = Vec::new();
16733 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16734 let selections = self.selections.all_adjusted(cx);
16735
16736 for selection in selections {
16737 let range = selection.range().sorted();
16738 let buffer_start_row = range.start.row;
16739
16740 if range.start.row != range.end.row {
16741 let mut found = false;
16742 for row in range.start.row..=range.end.row {
16743 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16744 found = true;
16745 to_fold.push(crease);
16746 }
16747 }
16748 if found {
16749 continue;
16750 }
16751 }
16752
16753 for row in (0..=range.start.row).rev() {
16754 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16755 if crease.range().end.row >= buffer_start_row {
16756 to_fold.push(crease);
16757 } else {
16758 break;
16759 }
16760 }
16761 }
16762 }
16763
16764 self.fold_creases(to_fold, true, window, cx);
16765 }
16766
16767 pub fn fold_at(
16768 &mut self,
16769 buffer_row: MultiBufferRow,
16770 window: &mut Window,
16771 cx: &mut Context<Self>,
16772 ) {
16773 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16774
16775 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16776 let autoscroll = self
16777 .selections
16778 .all::<Point>(cx)
16779 .iter()
16780 .any(|selection| crease.range().overlaps(&selection.range()));
16781
16782 self.fold_creases(vec![crease], autoscroll, window, cx);
16783 }
16784 }
16785
16786 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16787 if self.is_singleton(cx) {
16788 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16789 let buffer = &display_map.buffer_snapshot;
16790 let selections = self.selections.all::<Point>(cx);
16791 let ranges = selections
16792 .iter()
16793 .map(|s| {
16794 let range = s.display_range(&display_map).sorted();
16795 let mut start = range.start.to_point(&display_map);
16796 let mut end = range.end.to_point(&display_map);
16797 start.column = 0;
16798 end.column = buffer.line_len(MultiBufferRow(end.row));
16799 start..end
16800 })
16801 .collect::<Vec<_>>();
16802
16803 self.unfold_ranges(&ranges, true, true, cx);
16804 } else {
16805 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16806 let buffer_ids = self
16807 .selections
16808 .disjoint_anchor_ranges()
16809 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16810 .collect::<HashSet<_>>();
16811 for buffer_id in buffer_ids {
16812 self.unfold_buffer(buffer_id, cx);
16813 }
16814 }
16815 }
16816
16817 pub fn unfold_recursive(
16818 &mut self,
16819 _: &UnfoldRecursive,
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 selections = self.selections.all::<Point>(cx);
16825 let ranges = selections
16826 .iter()
16827 .map(|s| {
16828 let mut range = s.display_range(&display_map).sorted();
16829 *range.start.column_mut() = 0;
16830 *range.end.column_mut() = display_map.line_len(range.end.row());
16831 let start = range.start.to_point(&display_map);
16832 let end = range.end.to_point(&display_map);
16833 start..end
16834 })
16835 .collect::<Vec<_>>();
16836
16837 self.unfold_ranges(&ranges, true, true, cx);
16838 }
16839
16840 pub fn unfold_at(
16841 &mut self,
16842 buffer_row: MultiBufferRow,
16843 _window: &mut Window,
16844 cx: &mut Context<Self>,
16845 ) {
16846 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16847
16848 let intersection_range = Point::new(buffer_row.0, 0)
16849 ..Point::new(
16850 buffer_row.0,
16851 display_map.buffer_snapshot.line_len(buffer_row),
16852 );
16853
16854 let autoscroll = self
16855 .selections
16856 .all::<Point>(cx)
16857 .iter()
16858 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16859
16860 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16861 }
16862
16863 pub fn unfold_all(
16864 &mut self,
16865 _: &actions::UnfoldAll,
16866 _window: &mut Window,
16867 cx: &mut Context<Self>,
16868 ) {
16869 if self.buffer.read(cx).is_singleton() {
16870 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16871 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16872 } else {
16873 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16874 editor
16875 .update(cx, |editor, cx| {
16876 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16877 editor.unfold_buffer(buffer_id, cx);
16878 }
16879 })
16880 .ok();
16881 });
16882 }
16883 }
16884
16885 pub fn fold_selected_ranges(
16886 &mut self,
16887 _: &FoldSelectedRanges,
16888 window: &mut Window,
16889 cx: &mut Context<Self>,
16890 ) {
16891 let selections = self.selections.all_adjusted(cx);
16892 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16893 let ranges = selections
16894 .into_iter()
16895 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16896 .collect::<Vec<_>>();
16897 self.fold_creases(ranges, true, window, cx);
16898 }
16899
16900 pub fn fold_ranges<T: ToOffset + Clone>(
16901 &mut self,
16902 ranges: Vec<Range<T>>,
16903 auto_scroll: bool,
16904 window: &mut Window,
16905 cx: &mut Context<Self>,
16906 ) {
16907 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16908 let ranges = ranges
16909 .into_iter()
16910 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16911 .collect::<Vec<_>>();
16912 self.fold_creases(ranges, auto_scroll, window, cx);
16913 }
16914
16915 pub fn fold_creases<T: ToOffset + Clone>(
16916 &mut self,
16917 creases: Vec<Crease<T>>,
16918 auto_scroll: bool,
16919 _window: &mut Window,
16920 cx: &mut Context<Self>,
16921 ) {
16922 if creases.is_empty() {
16923 return;
16924 }
16925
16926 let mut buffers_affected = HashSet::default();
16927 let multi_buffer = self.buffer().read(cx);
16928 for crease in &creases {
16929 if let Some((_, buffer, _)) =
16930 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16931 {
16932 buffers_affected.insert(buffer.read(cx).remote_id());
16933 };
16934 }
16935
16936 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16937
16938 if auto_scroll {
16939 self.request_autoscroll(Autoscroll::fit(), cx);
16940 }
16941
16942 cx.notify();
16943
16944 self.scrollbar_marker_state.dirty = true;
16945 self.folds_did_change(cx);
16946 }
16947
16948 /// Removes any folds whose ranges intersect any of the given ranges.
16949 pub fn unfold_ranges<T: ToOffset + Clone>(
16950 &mut self,
16951 ranges: &[Range<T>],
16952 inclusive: bool,
16953 auto_scroll: bool,
16954 cx: &mut Context<Self>,
16955 ) {
16956 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16957 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16958 });
16959 self.folds_did_change(cx);
16960 }
16961
16962 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16963 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16964 return;
16965 }
16966 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16967 self.display_map.update(cx, |display_map, cx| {
16968 display_map.fold_buffers([buffer_id], cx)
16969 });
16970 cx.emit(EditorEvent::BufferFoldToggled {
16971 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16972 folded: true,
16973 });
16974 cx.notify();
16975 }
16976
16977 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16978 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16979 return;
16980 }
16981 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16982 self.display_map.update(cx, |display_map, cx| {
16983 display_map.unfold_buffers([buffer_id], cx);
16984 });
16985 cx.emit(EditorEvent::BufferFoldToggled {
16986 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16987 folded: false,
16988 });
16989 cx.notify();
16990 }
16991
16992 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16993 self.display_map.read(cx).is_buffer_folded(buffer)
16994 }
16995
16996 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16997 self.display_map.read(cx).folded_buffers()
16998 }
16999
17000 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17001 self.display_map.update(cx, |display_map, cx| {
17002 display_map.disable_header_for_buffer(buffer_id, cx);
17003 });
17004 cx.notify();
17005 }
17006
17007 /// Removes any folds with the given ranges.
17008 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17009 &mut self,
17010 ranges: &[Range<T>],
17011 type_id: TypeId,
17012 auto_scroll: bool,
17013 cx: &mut Context<Self>,
17014 ) {
17015 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17016 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17017 });
17018 self.folds_did_change(cx);
17019 }
17020
17021 fn remove_folds_with<T: ToOffset + Clone>(
17022 &mut self,
17023 ranges: &[Range<T>],
17024 auto_scroll: bool,
17025 cx: &mut Context<Self>,
17026 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17027 ) {
17028 if ranges.is_empty() {
17029 return;
17030 }
17031
17032 let mut buffers_affected = HashSet::default();
17033 let multi_buffer = self.buffer().read(cx);
17034 for range in ranges {
17035 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17036 buffers_affected.insert(buffer.read(cx).remote_id());
17037 };
17038 }
17039
17040 self.display_map.update(cx, update);
17041
17042 if auto_scroll {
17043 self.request_autoscroll(Autoscroll::fit(), cx);
17044 }
17045
17046 cx.notify();
17047 self.scrollbar_marker_state.dirty = true;
17048 self.active_indent_guides_state.dirty = true;
17049 }
17050
17051 pub fn update_fold_widths(
17052 &mut self,
17053 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
17054 cx: &mut Context<Self>,
17055 ) -> bool {
17056 self.display_map
17057 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17058 }
17059
17060 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17061 self.display_map.read(cx).fold_placeholder.clone()
17062 }
17063
17064 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17065 self.buffer.update(cx, |buffer, cx| {
17066 buffer.set_all_diff_hunks_expanded(cx);
17067 });
17068 }
17069
17070 pub fn expand_all_diff_hunks(
17071 &mut self,
17072 _: &ExpandAllDiffHunks,
17073 _window: &mut Window,
17074 cx: &mut Context<Self>,
17075 ) {
17076 self.buffer.update(cx, |buffer, cx| {
17077 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17078 });
17079 }
17080
17081 pub fn toggle_selected_diff_hunks(
17082 &mut self,
17083 _: &ToggleSelectedDiffHunks,
17084 _window: &mut Window,
17085 cx: &mut Context<Self>,
17086 ) {
17087 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17088 self.toggle_diff_hunks_in_ranges(ranges, cx);
17089 }
17090
17091 pub fn diff_hunks_in_ranges<'a>(
17092 &'a self,
17093 ranges: &'a [Range<Anchor>],
17094 buffer: &'a MultiBufferSnapshot,
17095 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17096 ranges.iter().flat_map(move |range| {
17097 let end_excerpt_id = range.end.excerpt_id;
17098 let range = range.to_point(buffer);
17099 let mut peek_end = range.end;
17100 if range.end.row < buffer.max_row().0 {
17101 peek_end = Point::new(range.end.row + 1, 0);
17102 }
17103 buffer
17104 .diff_hunks_in_range(range.start..peek_end)
17105 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17106 })
17107 }
17108
17109 pub fn has_stageable_diff_hunks_in_ranges(
17110 &self,
17111 ranges: &[Range<Anchor>],
17112 snapshot: &MultiBufferSnapshot,
17113 ) -> bool {
17114 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17115 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17116 }
17117
17118 pub fn toggle_staged_selected_diff_hunks(
17119 &mut self,
17120 _: &::git::ToggleStaged,
17121 _: &mut Window,
17122 cx: &mut Context<Self>,
17123 ) {
17124 let snapshot = self.buffer.read(cx).snapshot(cx);
17125 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17126 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17127 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17128 }
17129
17130 pub fn set_render_diff_hunk_controls(
17131 &mut self,
17132 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17133 cx: &mut Context<Self>,
17134 ) {
17135 self.render_diff_hunk_controls = render_diff_hunk_controls;
17136 cx.notify();
17137 }
17138
17139 pub fn stage_and_next(
17140 &mut self,
17141 _: &::git::StageAndNext,
17142 window: &mut Window,
17143 cx: &mut Context<Self>,
17144 ) {
17145 self.do_stage_or_unstage_and_next(true, window, cx);
17146 }
17147
17148 pub fn unstage_and_next(
17149 &mut self,
17150 _: &::git::UnstageAndNext,
17151 window: &mut Window,
17152 cx: &mut Context<Self>,
17153 ) {
17154 self.do_stage_or_unstage_and_next(false, window, cx);
17155 }
17156
17157 pub fn stage_or_unstage_diff_hunks(
17158 &mut self,
17159 stage: bool,
17160 ranges: Vec<Range<Anchor>>,
17161 cx: &mut Context<Self>,
17162 ) {
17163 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17164 cx.spawn(async move |this, cx| {
17165 task.await?;
17166 this.update(cx, |this, cx| {
17167 let snapshot = this.buffer.read(cx).snapshot(cx);
17168 let chunk_by = this
17169 .diff_hunks_in_ranges(&ranges, &snapshot)
17170 .chunk_by(|hunk| hunk.buffer_id);
17171 for (buffer_id, hunks) in &chunk_by {
17172 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17173 }
17174 })
17175 })
17176 .detach_and_log_err(cx);
17177 }
17178
17179 fn save_buffers_for_ranges_if_needed(
17180 &mut self,
17181 ranges: &[Range<Anchor>],
17182 cx: &mut Context<Editor>,
17183 ) -> Task<Result<()>> {
17184 let multibuffer = self.buffer.read(cx);
17185 let snapshot = multibuffer.read(cx);
17186 let buffer_ids: HashSet<_> = ranges
17187 .iter()
17188 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17189 .collect();
17190 drop(snapshot);
17191
17192 let mut buffers = HashSet::default();
17193 for buffer_id in buffer_ids {
17194 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17195 let buffer = buffer_entity.read(cx);
17196 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17197 {
17198 buffers.insert(buffer_entity);
17199 }
17200 }
17201 }
17202
17203 if let Some(project) = &self.project {
17204 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17205 } else {
17206 Task::ready(Ok(()))
17207 }
17208 }
17209
17210 fn do_stage_or_unstage_and_next(
17211 &mut self,
17212 stage: bool,
17213 window: &mut Window,
17214 cx: &mut Context<Self>,
17215 ) {
17216 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17217
17218 if ranges.iter().any(|range| range.start != range.end) {
17219 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17220 return;
17221 }
17222
17223 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17224 let snapshot = self.snapshot(window, cx);
17225 let position = self.selections.newest::<Point>(cx).head();
17226 let mut row = snapshot
17227 .buffer_snapshot
17228 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17229 .find(|hunk| hunk.row_range.start.0 > position.row)
17230 .map(|hunk| hunk.row_range.start);
17231
17232 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17233 // Outside of the project diff editor, wrap around to the beginning.
17234 if !all_diff_hunks_expanded {
17235 row = row.or_else(|| {
17236 snapshot
17237 .buffer_snapshot
17238 .diff_hunks_in_range(Point::zero()..position)
17239 .find(|hunk| hunk.row_range.end.0 < position.row)
17240 .map(|hunk| hunk.row_range.start)
17241 });
17242 }
17243
17244 if let Some(row) = row {
17245 let destination = Point::new(row.0, 0);
17246 let autoscroll = Autoscroll::center();
17247
17248 self.unfold_ranges(&[destination..destination], false, false, cx);
17249 self.change_selections(Some(autoscroll), window, cx, |s| {
17250 s.select_ranges([destination..destination]);
17251 });
17252 }
17253 }
17254
17255 fn do_stage_or_unstage(
17256 &self,
17257 stage: bool,
17258 buffer_id: BufferId,
17259 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17260 cx: &mut App,
17261 ) -> Option<()> {
17262 let project = self.project.as_ref()?;
17263 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17264 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17265 let buffer_snapshot = buffer.read(cx).snapshot();
17266 let file_exists = buffer_snapshot
17267 .file()
17268 .is_some_and(|file| file.disk_state().exists());
17269 diff.update(cx, |diff, cx| {
17270 diff.stage_or_unstage_hunks(
17271 stage,
17272 &hunks
17273 .map(|hunk| buffer_diff::DiffHunk {
17274 buffer_range: hunk.buffer_range,
17275 diff_base_byte_range: hunk.diff_base_byte_range,
17276 secondary_status: hunk.secondary_status,
17277 range: Point::zero()..Point::zero(), // unused
17278 })
17279 .collect::<Vec<_>>(),
17280 &buffer_snapshot,
17281 file_exists,
17282 cx,
17283 )
17284 });
17285 None
17286 }
17287
17288 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17289 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17290 self.buffer
17291 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17292 }
17293
17294 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17295 self.buffer.update(cx, |buffer, cx| {
17296 let ranges = vec![Anchor::min()..Anchor::max()];
17297 if !buffer.all_diff_hunks_expanded()
17298 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17299 {
17300 buffer.collapse_diff_hunks(ranges, cx);
17301 true
17302 } else {
17303 false
17304 }
17305 })
17306 }
17307
17308 fn toggle_diff_hunks_in_ranges(
17309 &mut self,
17310 ranges: Vec<Range<Anchor>>,
17311 cx: &mut Context<Editor>,
17312 ) {
17313 self.buffer.update(cx, |buffer, cx| {
17314 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17315 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17316 })
17317 }
17318
17319 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17320 self.buffer.update(cx, |buffer, cx| {
17321 let snapshot = buffer.snapshot(cx);
17322 let excerpt_id = range.end.excerpt_id;
17323 let point_range = range.to_point(&snapshot);
17324 let expand = !buffer.single_hunk_is_expanded(range, cx);
17325 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17326 })
17327 }
17328
17329 pub(crate) fn apply_all_diff_hunks(
17330 &mut self,
17331 _: &ApplyAllDiffHunks,
17332 window: &mut Window,
17333 cx: &mut Context<Self>,
17334 ) {
17335 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17336
17337 let buffers = self.buffer.read(cx).all_buffers();
17338 for branch_buffer in buffers {
17339 branch_buffer.update(cx, |branch_buffer, cx| {
17340 branch_buffer.merge_into_base(Vec::new(), cx);
17341 });
17342 }
17343
17344 if let Some(project) = self.project.clone() {
17345 self.save(
17346 SaveOptions {
17347 format: true,
17348 autosave: false,
17349 },
17350 project,
17351 window,
17352 cx,
17353 )
17354 .detach_and_log_err(cx);
17355 }
17356 }
17357
17358 pub(crate) fn apply_selected_diff_hunks(
17359 &mut self,
17360 _: &ApplyDiffHunk,
17361 window: &mut Window,
17362 cx: &mut Context<Self>,
17363 ) {
17364 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17365 let snapshot = self.snapshot(window, cx);
17366 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17367 let mut ranges_by_buffer = HashMap::default();
17368 self.transact(window, cx, |editor, _window, cx| {
17369 for hunk in hunks {
17370 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17371 ranges_by_buffer
17372 .entry(buffer.clone())
17373 .or_insert_with(Vec::new)
17374 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17375 }
17376 }
17377
17378 for (buffer, ranges) in ranges_by_buffer {
17379 buffer.update(cx, |buffer, cx| {
17380 buffer.merge_into_base(ranges, cx);
17381 });
17382 }
17383 });
17384
17385 if let Some(project) = self.project.clone() {
17386 self.save(
17387 SaveOptions {
17388 format: true,
17389 autosave: false,
17390 },
17391 project,
17392 window,
17393 cx,
17394 )
17395 .detach_and_log_err(cx);
17396 }
17397 }
17398
17399 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17400 if hovered != self.gutter_hovered {
17401 self.gutter_hovered = hovered;
17402 cx.notify();
17403 }
17404 }
17405
17406 pub fn insert_blocks(
17407 &mut self,
17408 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17409 autoscroll: Option<Autoscroll>,
17410 cx: &mut Context<Self>,
17411 ) -> Vec<CustomBlockId> {
17412 let blocks = self
17413 .display_map
17414 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17415 if let Some(autoscroll) = autoscroll {
17416 self.request_autoscroll(autoscroll, cx);
17417 }
17418 cx.notify();
17419 blocks
17420 }
17421
17422 pub fn resize_blocks(
17423 &mut self,
17424 heights: HashMap<CustomBlockId, u32>,
17425 autoscroll: Option<Autoscroll>,
17426 cx: &mut Context<Self>,
17427 ) {
17428 self.display_map
17429 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17430 if let Some(autoscroll) = autoscroll {
17431 self.request_autoscroll(autoscroll, cx);
17432 }
17433 cx.notify();
17434 }
17435
17436 pub fn replace_blocks(
17437 &mut self,
17438 renderers: HashMap<CustomBlockId, RenderBlock>,
17439 autoscroll: Option<Autoscroll>,
17440 cx: &mut Context<Self>,
17441 ) {
17442 self.display_map
17443 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17444 if let Some(autoscroll) = autoscroll {
17445 self.request_autoscroll(autoscroll, cx);
17446 }
17447 cx.notify();
17448 }
17449
17450 pub fn remove_blocks(
17451 &mut self,
17452 block_ids: HashSet<CustomBlockId>,
17453 autoscroll: Option<Autoscroll>,
17454 cx: &mut Context<Self>,
17455 ) {
17456 self.display_map.update(cx, |display_map, cx| {
17457 display_map.remove_blocks(block_ids, cx)
17458 });
17459 if let Some(autoscroll) = autoscroll {
17460 self.request_autoscroll(autoscroll, cx);
17461 }
17462 cx.notify();
17463 }
17464
17465 pub fn row_for_block(
17466 &self,
17467 block_id: CustomBlockId,
17468 cx: &mut Context<Self>,
17469 ) -> Option<DisplayRow> {
17470 self.display_map
17471 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17472 }
17473
17474 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17475 self.focused_block = Some(focused_block);
17476 }
17477
17478 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17479 self.focused_block.take()
17480 }
17481
17482 pub fn insert_creases(
17483 &mut self,
17484 creases: impl IntoIterator<Item = Crease<Anchor>>,
17485 cx: &mut Context<Self>,
17486 ) -> Vec<CreaseId> {
17487 self.display_map
17488 .update(cx, |map, cx| map.insert_creases(creases, cx))
17489 }
17490
17491 pub fn remove_creases(
17492 &mut self,
17493 ids: impl IntoIterator<Item = CreaseId>,
17494 cx: &mut Context<Self>,
17495 ) -> Vec<(CreaseId, Range<Anchor>)> {
17496 self.display_map
17497 .update(cx, |map, cx| map.remove_creases(ids, cx))
17498 }
17499
17500 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17501 self.display_map
17502 .update(cx, |map, cx| map.snapshot(cx))
17503 .longest_row()
17504 }
17505
17506 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17507 self.display_map
17508 .update(cx, |map, cx| map.snapshot(cx))
17509 .max_point()
17510 }
17511
17512 pub fn text(&self, cx: &App) -> String {
17513 self.buffer.read(cx).read(cx).text()
17514 }
17515
17516 pub fn is_empty(&self, cx: &App) -> bool {
17517 self.buffer.read(cx).read(cx).is_empty()
17518 }
17519
17520 pub fn text_option(&self, cx: &App) -> Option<String> {
17521 let text = self.text(cx);
17522 let text = text.trim();
17523
17524 if text.is_empty() {
17525 return None;
17526 }
17527
17528 Some(text.to_string())
17529 }
17530
17531 pub fn set_text(
17532 &mut self,
17533 text: impl Into<Arc<str>>,
17534 window: &mut Window,
17535 cx: &mut Context<Self>,
17536 ) {
17537 self.transact(window, cx, |this, _, cx| {
17538 this.buffer
17539 .read(cx)
17540 .as_singleton()
17541 .expect("you can only call set_text on editors for singleton buffers")
17542 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17543 });
17544 }
17545
17546 pub fn display_text(&self, cx: &mut App) -> String {
17547 self.display_map
17548 .update(cx, |map, cx| map.snapshot(cx))
17549 .text()
17550 }
17551
17552 fn create_minimap(
17553 &self,
17554 minimap_settings: MinimapSettings,
17555 window: &mut Window,
17556 cx: &mut Context<Self>,
17557 ) -> Option<Entity<Self>> {
17558 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17559 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17560 }
17561
17562 fn initialize_new_minimap(
17563 &self,
17564 minimap_settings: MinimapSettings,
17565 window: &mut Window,
17566 cx: &mut Context<Self>,
17567 ) -> Entity<Self> {
17568 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17569
17570 let mut minimap = Editor::new_internal(
17571 EditorMode::Minimap {
17572 parent: cx.weak_entity(),
17573 },
17574 self.buffer.clone(),
17575 self.project.clone(),
17576 Some(self.display_map.clone()),
17577 window,
17578 cx,
17579 );
17580 minimap.scroll_manager.clone_state(&self.scroll_manager);
17581 minimap.set_text_style_refinement(TextStyleRefinement {
17582 font_size: Some(MINIMAP_FONT_SIZE),
17583 font_weight: Some(MINIMAP_FONT_WEIGHT),
17584 ..Default::default()
17585 });
17586 minimap.update_minimap_configuration(minimap_settings, cx);
17587 cx.new(|_| minimap)
17588 }
17589
17590 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17591 let current_line_highlight = minimap_settings
17592 .current_line_highlight
17593 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17594 self.set_current_line_highlight(Some(current_line_highlight));
17595 }
17596
17597 pub fn minimap(&self) -> Option<&Entity<Self>> {
17598 self.minimap
17599 .as_ref()
17600 .filter(|_| self.minimap_visibility.visible())
17601 }
17602
17603 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17604 let mut wrap_guides = smallvec![];
17605
17606 if self.show_wrap_guides == Some(false) {
17607 return wrap_guides;
17608 }
17609
17610 let settings = self.buffer.read(cx).language_settings(cx);
17611 if settings.show_wrap_guides {
17612 match self.soft_wrap_mode(cx) {
17613 SoftWrap::Column(soft_wrap) => {
17614 wrap_guides.push((soft_wrap as usize, true));
17615 }
17616 SoftWrap::Bounded(soft_wrap) => {
17617 wrap_guides.push((soft_wrap as usize, true));
17618 }
17619 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17620 }
17621 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17622 }
17623
17624 wrap_guides
17625 }
17626
17627 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17628 let settings = self.buffer.read(cx).language_settings(cx);
17629 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17630 match mode {
17631 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17632 SoftWrap::None
17633 }
17634 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17635 language_settings::SoftWrap::PreferredLineLength => {
17636 SoftWrap::Column(settings.preferred_line_length)
17637 }
17638 language_settings::SoftWrap::Bounded => {
17639 SoftWrap::Bounded(settings.preferred_line_length)
17640 }
17641 }
17642 }
17643
17644 pub fn set_soft_wrap_mode(
17645 &mut self,
17646 mode: language_settings::SoftWrap,
17647
17648 cx: &mut Context<Self>,
17649 ) {
17650 self.soft_wrap_mode_override = Some(mode);
17651 cx.notify();
17652 }
17653
17654 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17655 self.hard_wrap = hard_wrap;
17656 cx.notify();
17657 }
17658
17659 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17660 self.text_style_refinement = Some(style);
17661 }
17662
17663 /// called by the Element so we know what style we were most recently rendered with.
17664 pub(crate) fn set_style(
17665 &mut self,
17666 style: EditorStyle,
17667 window: &mut Window,
17668 cx: &mut Context<Self>,
17669 ) {
17670 // We intentionally do not inform the display map about the minimap style
17671 // so that wrapping is not recalculated and stays consistent for the editor
17672 // and its linked minimap.
17673 if !self.mode.is_minimap() {
17674 let rem_size = window.rem_size();
17675 self.display_map.update(cx, |map, cx| {
17676 map.set_font(
17677 style.text.font(),
17678 style.text.font_size.to_pixels(rem_size),
17679 cx,
17680 )
17681 });
17682 }
17683 self.style = Some(style);
17684 }
17685
17686 pub fn style(&self) -> Option<&EditorStyle> {
17687 self.style.as_ref()
17688 }
17689
17690 // Called by the element. This method is not designed to be called outside of the editor
17691 // element's layout code because it does not notify when rewrapping is computed synchronously.
17692 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17693 self.display_map
17694 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17695 }
17696
17697 pub fn set_soft_wrap(&mut self) {
17698 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17699 }
17700
17701 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17702 if self.soft_wrap_mode_override.is_some() {
17703 self.soft_wrap_mode_override.take();
17704 } else {
17705 let soft_wrap = match self.soft_wrap_mode(cx) {
17706 SoftWrap::GitDiff => return,
17707 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17708 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17709 language_settings::SoftWrap::None
17710 }
17711 };
17712 self.soft_wrap_mode_override = Some(soft_wrap);
17713 }
17714 cx.notify();
17715 }
17716
17717 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17718 let Some(workspace) = self.workspace() else {
17719 return;
17720 };
17721 let fs = workspace.read(cx).app_state().fs.clone();
17722 let current_show = TabBarSettings::get_global(cx).show;
17723 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17724 setting.show = Some(!current_show);
17725 });
17726 }
17727
17728 pub fn toggle_indent_guides(
17729 &mut self,
17730 _: &ToggleIndentGuides,
17731 _: &mut Window,
17732 cx: &mut Context<Self>,
17733 ) {
17734 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17735 self.buffer
17736 .read(cx)
17737 .language_settings(cx)
17738 .indent_guides
17739 .enabled
17740 });
17741 self.show_indent_guides = Some(!currently_enabled);
17742 cx.notify();
17743 }
17744
17745 fn should_show_indent_guides(&self) -> Option<bool> {
17746 self.show_indent_guides
17747 }
17748
17749 pub fn toggle_line_numbers(
17750 &mut self,
17751 _: &ToggleLineNumbers,
17752 _: &mut Window,
17753 cx: &mut Context<Self>,
17754 ) {
17755 let mut editor_settings = EditorSettings::get_global(cx).clone();
17756 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17757 EditorSettings::override_global(editor_settings, cx);
17758 }
17759
17760 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17761 if let Some(show_line_numbers) = self.show_line_numbers {
17762 return show_line_numbers;
17763 }
17764 EditorSettings::get_global(cx).gutter.line_numbers
17765 }
17766
17767 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17768 self.use_relative_line_numbers
17769 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17770 }
17771
17772 pub fn toggle_relative_line_numbers(
17773 &mut self,
17774 _: &ToggleRelativeLineNumbers,
17775 _: &mut Window,
17776 cx: &mut Context<Self>,
17777 ) {
17778 let is_relative = self.should_use_relative_line_numbers(cx);
17779 self.set_relative_line_number(Some(!is_relative), cx)
17780 }
17781
17782 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17783 self.use_relative_line_numbers = is_relative;
17784 cx.notify();
17785 }
17786
17787 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17788 self.show_gutter = show_gutter;
17789 cx.notify();
17790 }
17791
17792 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17793 self.show_scrollbars = ScrollbarAxes {
17794 horizontal: show,
17795 vertical: show,
17796 };
17797 cx.notify();
17798 }
17799
17800 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17801 self.show_scrollbars.vertical = show;
17802 cx.notify();
17803 }
17804
17805 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17806 self.show_scrollbars.horizontal = show;
17807 cx.notify();
17808 }
17809
17810 pub fn set_minimap_visibility(
17811 &mut self,
17812 minimap_visibility: MinimapVisibility,
17813 window: &mut Window,
17814 cx: &mut Context<Self>,
17815 ) {
17816 if self.minimap_visibility != minimap_visibility {
17817 if minimap_visibility.visible() && self.minimap.is_none() {
17818 let minimap_settings = EditorSettings::get_global(cx).minimap;
17819 self.minimap =
17820 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17821 }
17822 self.minimap_visibility = minimap_visibility;
17823 cx.notify();
17824 }
17825 }
17826
17827 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17828 self.set_show_scrollbars(false, cx);
17829 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17830 }
17831
17832 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17833 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17834 }
17835
17836 /// Normally the text in full mode and auto height editors is padded on the
17837 /// left side by roughly half a character width for improved hit testing.
17838 ///
17839 /// Use this method to disable this for cases where this is not wanted (e.g.
17840 /// if you want to align the editor text with some other text above or below)
17841 /// or if you want to add this padding to single-line editors.
17842 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17843 self.offset_content = offset_content;
17844 cx.notify();
17845 }
17846
17847 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17848 self.show_line_numbers = Some(show_line_numbers);
17849 cx.notify();
17850 }
17851
17852 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17853 self.disable_expand_excerpt_buttons = true;
17854 cx.notify();
17855 }
17856
17857 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17858 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17859 cx.notify();
17860 }
17861
17862 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17863 self.show_code_actions = Some(show_code_actions);
17864 cx.notify();
17865 }
17866
17867 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17868 self.show_runnables = Some(show_runnables);
17869 cx.notify();
17870 }
17871
17872 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17873 self.show_breakpoints = Some(show_breakpoints);
17874 cx.notify();
17875 }
17876
17877 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17878 if self.display_map.read(cx).masked != masked {
17879 self.display_map.update(cx, |map, _| map.masked = masked);
17880 }
17881 cx.notify()
17882 }
17883
17884 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17885 self.show_wrap_guides = Some(show_wrap_guides);
17886 cx.notify();
17887 }
17888
17889 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17890 self.show_indent_guides = Some(show_indent_guides);
17891 cx.notify();
17892 }
17893
17894 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17895 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17896 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17897 if let Some(dir) = file.abs_path(cx).parent() {
17898 return Some(dir.to_owned());
17899 }
17900 }
17901
17902 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17903 return Some(project_path.path.to_path_buf());
17904 }
17905 }
17906
17907 None
17908 }
17909
17910 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17911 self.active_excerpt(cx)?
17912 .1
17913 .read(cx)
17914 .file()
17915 .and_then(|f| f.as_local())
17916 }
17917
17918 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17919 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17920 let buffer = buffer.read(cx);
17921 if let Some(project_path) = buffer.project_path(cx) {
17922 let project = self.project.as_ref()?.read(cx);
17923 project.absolute_path(&project_path, cx)
17924 } else {
17925 buffer
17926 .file()
17927 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17928 }
17929 })
17930 }
17931
17932 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17933 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17934 let project_path = buffer.read(cx).project_path(cx)?;
17935 let project = self.project.as_ref()?.read(cx);
17936 let entry = project.entry_for_path(&project_path, cx)?;
17937 let path = entry.path.to_path_buf();
17938 Some(path)
17939 })
17940 }
17941
17942 pub fn reveal_in_finder(
17943 &mut self,
17944 _: &RevealInFileManager,
17945 _window: &mut Window,
17946 cx: &mut Context<Self>,
17947 ) {
17948 if let Some(target) = self.target_file(cx) {
17949 cx.reveal_path(&target.abs_path(cx));
17950 }
17951 }
17952
17953 pub fn copy_path(
17954 &mut self,
17955 _: &zed_actions::workspace::CopyPath,
17956 _window: &mut Window,
17957 cx: &mut Context<Self>,
17958 ) {
17959 if let Some(path) = self.target_file_abs_path(cx) {
17960 if let Some(path) = path.to_str() {
17961 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17962 }
17963 }
17964 }
17965
17966 pub fn copy_relative_path(
17967 &mut self,
17968 _: &zed_actions::workspace::CopyRelativePath,
17969 _window: &mut Window,
17970 cx: &mut Context<Self>,
17971 ) {
17972 if let Some(path) = self.target_file_path(cx) {
17973 if let Some(path) = path.to_str() {
17974 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17975 }
17976 }
17977 }
17978
17979 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17980 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17981 buffer.read(cx).project_path(cx)
17982 } else {
17983 None
17984 }
17985 }
17986
17987 // Returns true if the editor handled a go-to-line request
17988 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17989 maybe!({
17990 let breakpoint_store = self.breakpoint_store.as_ref()?;
17991
17992 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17993 else {
17994 self.clear_row_highlights::<ActiveDebugLine>();
17995 return None;
17996 };
17997
17998 let position = active_stack_frame.position;
17999 let buffer_id = position.buffer_id?;
18000 let snapshot = self
18001 .project
18002 .as_ref()?
18003 .read(cx)
18004 .buffer_for_id(buffer_id, cx)?
18005 .read(cx)
18006 .snapshot();
18007
18008 let mut handled = false;
18009 for (id, ExcerptRange { context, .. }) in
18010 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18011 {
18012 if context.start.cmp(&position, &snapshot).is_ge()
18013 || context.end.cmp(&position, &snapshot).is_lt()
18014 {
18015 continue;
18016 }
18017 let snapshot = self.buffer.read(cx).snapshot(cx);
18018 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18019
18020 handled = true;
18021 self.clear_row_highlights::<ActiveDebugLine>();
18022
18023 self.go_to_line::<ActiveDebugLine>(
18024 multibuffer_anchor,
18025 Some(cx.theme().colors().editor_debugger_active_line_background),
18026 window,
18027 cx,
18028 );
18029
18030 cx.notify();
18031 }
18032
18033 handled.then_some(())
18034 })
18035 .is_some()
18036 }
18037
18038 pub fn copy_file_name_without_extension(
18039 &mut self,
18040 _: &CopyFileNameWithoutExtension,
18041 _: &mut Window,
18042 cx: &mut Context<Self>,
18043 ) {
18044 if let Some(file) = self.target_file(cx) {
18045 if let Some(file_stem) = file.path().file_stem() {
18046 if let Some(name) = file_stem.to_str() {
18047 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18048 }
18049 }
18050 }
18051 }
18052
18053 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18054 if let Some(file) = self.target_file(cx) {
18055 if let Some(file_name) = file.path().file_name() {
18056 if let Some(name) = file_name.to_str() {
18057 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18058 }
18059 }
18060 }
18061 }
18062
18063 pub fn toggle_git_blame(
18064 &mut self,
18065 _: &::git::Blame,
18066 window: &mut Window,
18067 cx: &mut Context<Self>,
18068 ) {
18069 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18070
18071 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18072 self.start_git_blame(true, window, cx);
18073 }
18074
18075 cx.notify();
18076 }
18077
18078 pub fn toggle_git_blame_inline(
18079 &mut self,
18080 _: &ToggleGitBlameInline,
18081 window: &mut Window,
18082 cx: &mut Context<Self>,
18083 ) {
18084 self.toggle_git_blame_inline_internal(true, window, cx);
18085 cx.notify();
18086 }
18087
18088 pub fn open_git_blame_commit(
18089 &mut self,
18090 _: &OpenGitBlameCommit,
18091 window: &mut Window,
18092 cx: &mut Context<Self>,
18093 ) {
18094 self.open_git_blame_commit_internal(window, cx);
18095 }
18096
18097 fn open_git_blame_commit_internal(
18098 &mut self,
18099 window: &mut Window,
18100 cx: &mut Context<Self>,
18101 ) -> Option<()> {
18102 let blame = self.blame.as_ref()?;
18103 let snapshot = self.snapshot(window, cx);
18104 let cursor = self.selections.newest::<Point>(cx).head();
18105 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18106 let blame_entry = blame
18107 .update(cx, |blame, cx| {
18108 blame
18109 .blame_for_rows(
18110 &[RowInfo {
18111 buffer_id: Some(buffer.remote_id()),
18112 buffer_row: Some(point.row),
18113 ..Default::default()
18114 }],
18115 cx,
18116 )
18117 .next()
18118 })
18119 .flatten()?;
18120 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18121 let repo = blame.read(cx).repository(cx)?;
18122 let workspace = self.workspace()?.downgrade();
18123 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18124 None
18125 }
18126
18127 pub fn git_blame_inline_enabled(&self) -> bool {
18128 self.git_blame_inline_enabled
18129 }
18130
18131 pub fn toggle_selection_menu(
18132 &mut self,
18133 _: &ToggleSelectionMenu,
18134 _: &mut Window,
18135 cx: &mut Context<Self>,
18136 ) {
18137 self.show_selection_menu = self
18138 .show_selection_menu
18139 .map(|show_selections_menu| !show_selections_menu)
18140 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18141
18142 cx.notify();
18143 }
18144
18145 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18146 self.show_selection_menu
18147 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18148 }
18149
18150 fn start_git_blame(
18151 &mut self,
18152 user_triggered: bool,
18153 window: &mut Window,
18154 cx: &mut Context<Self>,
18155 ) {
18156 if let Some(project) = self.project.as_ref() {
18157 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18158 return;
18159 };
18160
18161 if buffer.read(cx).file().is_none() {
18162 return;
18163 }
18164
18165 let focused = self.focus_handle(cx).contains_focused(window, cx);
18166
18167 let project = project.clone();
18168 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18169 self.blame_subscription =
18170 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18171 self.blame = Some(blame);
18172 }
18173 }
18174
18175 fn toggle_git_blame_inline_internal(
18176 &mut self,
18177 user_triggered: bool,
18178 window: &mut Window,
18179 cx: &mut Context<Self>,
18180 ) {
18181 if self.git_blame_inline_enabled {
18182 self.git_blame_inline_enabled = false;
18183 self.show_git_blame_inline = false;
18184 self.show_git_blame_inline_delay_task.take();
18185 } else {
18186 self.git_blame_inline_enabled = true;
18187 self.start_git_blame_inline(user_triggered, window, cx);
18188 }
18189
18190 cx.notify();
18191 }
18192
18193 fn start_git_blame_inline(
18194 &mut self,
18195 user_triggered: bool,
18196 window: &mut Window,
18197 cx: &mut Context<Self>,
18198 ) {
18199 self.start_git_blame(user_triggered, window, cx);
18200
18201 if ProjectSettings::get_global(cx)
18202 .git
18203 .inline_blame_delay()
18204 .is_some()
18205 {
18206 self.start_inline_blame_timer(window, cx);
18207 } else {
18208 self.show_git_blame_inline = true
18209 }
18210 }
18211
18212 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18213 self.blame.as_ref()
18214 }
18215
18216 pub fn show_git_blame_gutter(&self) -> bool {
18217 self.show_git_blame_gutter
18218 }
18219
18220 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18221 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18222 }
18223
18224 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18225 self.show_git_blame_inline
18226 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18227 && !self.newest_selection_head_on_empty_line(cx)
18228 && self.has_blame_entries(cx)
18229 }
18230
18231 fn has_blame_entries(&self, cx: &App) -> bool {
18232 self.blame()
18233 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18234 }
18235
18236 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18237 let cursor_anchor = self.selections.newest_anchor().head();
18238
18239 let snapshot = self.buffer.read(cx).snapshot(cx);
18240 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18241
18242 snapshot.line_len(buffer_row) == 0
18243 }
18244
18245 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18246 let buffer_and_selection = maybe!({
18247 let selection = self.selections.newest::<Point>(cx);
18248 let selection_range = selection.range();
18249
18250 let multi_buffer = self.buffer().read(cx);
18251 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18252 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18253
18254 let (buffer, range, _) = if selection.reversed {
18255 buffer_ranges.first()
18256 } else {
18257 buffer_ranges.last()
18258 }?;
18259
18260 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18261 ..text::ToPoint::to_point(&range.end, &buffer).row;
18262 Some((
18263 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18264 selection,
18265 ))
18266 });
18267
18268 let Some((buffer, selection)) = buffer_and_selection else {
18269 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18270 };
18271
18272 let Some(project) = self.project.as_ref() else {
18273 return Task::ready(Err(anyhow!("editor does not have project")));
18274 };
18275
18276 project.update(cx, |project, cx| {
18277 project.get_permalink_to_line(&buffer, selection, cx)
18278 })
18279 }
18280
18281 pub fn copy_permalink_to_line(
18282 &mut self,
18283 _: &CopyPermalinkToLine,
18284 window: &mut Window,
18285 cx: &mut Context<Self>,
18286 ) {
18287 let permalink_task = self.get_permalink_to_line(cx);
18288 let workspace = self.workspace();
18289
18290 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18291 Ok(permalink) => {
18292 cx.update(|_, cx| {
18293 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18294 })
18295 .ok();
18296 }
18297 Err(err) => {
18298 let message = format!("Failed to copy permalink: {err}");
18299
18300 anyhow::Result::<()>::Err(err).log_err();
18301
18302 if let Some(workspace) = workspace {
18303 workspace
18304 .update_in(cx, |workspace, _, cx| {
18305 struct CopyPermalinkToLine;
18306
18307 workspace.show_toast(
18308 Toast::new(
18309 NotificationId::unique::<CopyPermalinkToLine>(),
18310 message,
18311 ),
18312 cx,
18313 )
18314 })
18315 .ok();
18316 }
18317 }
18318 })
18319 .detach();
18320 }
18321
18322 pub fn copy_file_location(
18323 &mut self,
18324 _: &CopyFileLocation,
18325 _: &mut Window,
18326 cx: &mut Context<Self>,
18327 ) {
18328 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18329 if let Some(file) = self.target_file(cx) {
18330 if let Some(path) = file.path().to_str() {
18331 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18332 }
18333 }
18334 }
18335
18336 pub fn open_permalink_to_line(
18337 &mut self,
18338 _: &OpenPermalinkToLine,
18339 window: &mut Window,
18340 cx: &mut Context<Self>,
18341 ) {
18342 let permalink_task = self.get_permalink_to_line(cx);
18343 let workspace = self.workspace();
18344
18345 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18346 Ok(permalink) => {
18347 cx.update(|_, cx| {
18348 cx.open_url(permalink.as_ref());
18349 })
18350 .ok();
18351 }
18352 Err(err) => {
18353 let message = format!("Failed to open permalink: {err}");
18354
18355 anyhow::Result::<()>::Err(err).log_err();
18356
18357 if let Some(workspace) = workspace {
18358 workspace
18359 .update(cx, |workspace, cx| {
18360 struct OpenPermalinkToLine;
18361
18362 workspace.show_toast(
18363 Toast::new(
18364 NotificationId::unique::<OpenPermalinkToLine>(),
18365 message,
18366 ),
18367 cx,
18368 )
18369 })
18370 .ok();
18371 }
18372 }
18373 })
18374 .detach();
18375 }
18376
18377 pub fn insert_uuid_v4(
18378 &mut self,
18379 _: &InsertUuidV4,
18380 window: &mut Window,
18381 cx: &mut Context<Self>,
18382 ) {
18383 self.insert_uuid(UuidVersion::V4, window, cx);
18384 }
18385
18386 pub fn insert_uuid_v7(
18387 &mut self,
18388 _: &InsertUuidV7,
18389 window: &mut Window,
18390 cx: &mut Context<Self>,
18391 ) {
18392 self.insert_uuid(UuidVersion::V7, window, cx);
18393 }
18394
18395 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18396 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18397 self.transact(window, cx, |this, window, cx| {
18398 let edits = this
18399 .selections
18400 .all::<Point>(cx)
18401 .into_iter()
18402 .map(|selection| {
18403 let uuid = match version {
18404 UuidVersion::V4 => uuid::Uuid::new_v4(),
18405 UuidVersion::V7 => uuid::Uuid::now_v7(),
18406 };
18407
18408 (selection.range(), uuid.to_string())
18409 });
18410 this.edit(edits, cx);
18411 this.refresh_inline_completion(true, false, window, cx);
18412 });
18413 }
18414
18415 pub fn open_selections_in_multibuffer(
18416 &mut self,
18417 _: &OpenSelectionsInMultibuffer,
18418 window: &mut Window,
18419 cx: &mut Context<Self>,
18420 ) {
18421 let multibuffer = self.buffer.read(cx);
18422
18423 let Some(buffer) = multibuffer.as_singleton() else {
18424 return;
18425 };
18426
18427 let Some(workspace) = self.workspace() else {
18428 return;
18429 };
18430
18431 let title = multibuffer.title(cx).to_string();
18432
18433 let locations = self
18434 .selections
18435 .all_anchors(cx)
18436 .into_iter()
18437 .map(|selection| Location {
18438 buffer: buffer.clone(),
18439 range: selection.start.text_anchor..selection.end.text_anchor,
18440 })
18441 .collect::<Vec<_>>();
18442
18443 cx.spawn_in(window, async move |_, cx| {
18444 workspace.update_in(cx, |workspace, window, cx| {
18445 Self::open_locations_in_multibuffer(
18446 workspace,
18447 locations,
18448 format!("Selections for '{title}'"),
18449 false,
18450 MultibufferSelectionMode::All,
18451 window,
18452 cx,
18453 );
18454 })
18455 })
18456 .detach();
18457 }
18458
18459 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18460 /// last highlight added will be used.
18461 ///
18462 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18463 pub fn highlight_rows<T: 'static>(
18464 &mut self,
18465 range: Range<Anchor>,
18466 color: Hsla,
18467 options: RowHighlightOptions,
18468 cx: &mut Context<Self>,
18469 ) {
18470 let snapshot = self.buffer().read(cx).snapshot(cx);
18471 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18472 let ix = row_highlights.binary_search_by(|highlight| {
18473 Ordering::Equal
18474 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18475 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18476 });
18477
18478 if let Err(mut ix) = ix {
18479 let index = post_inc(&mut self.highlight_order);
18480
18481 // If this range intersects with the preceding highlight, then merge it with
18482 // the preceding highlight. Otherwise insert a new highlight.
18483 let mut merged = false;
18484 if ix > 0 {
18485 let prev_highlight = &mut row_highlights[ix - 1];
18486 if prev_highlight
18487 .range
18488 .end
18489 .cmp(&range.start, &snapshot)
18490 .is_ge()
18491 {
18492 ix -= 1;
18493 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18494 prev_highlight.range.end = range.end;
18495 }
18496 merged = true;
18497 prev_highlight.index = index;
18498 prev_highlight.color = color;
18499 prev_highlight.options = options;
18500 }
18501 }
18502
18503 if !merged {
18504 row_highlights.insert(
18505 ix,
18506 RowHighlight {
18507 range: range.clone(),
18508 index,
18509 color,
18510 options,
18511 type_id: TypeId::of::<T>(),
18512 },
18513 );
18514 }
18515
18516 // If any of the following highlights intersect with this one, merge them.
18517 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18518 let highlight = &row_highlights[ix];
18519 if next_highlight
18520 .range
18521 .start
18522 .cmp(&highlight.range.end, &snapshot)
18523 .is_le()
18524 {
18525 if next_highlight
18526 .range
18527 .end
18528 .cmp(&highlight.range.end, &snapshot)
18529 .is_gt()
18530 {
18531 row_highlights[ix].range.end = next_highlight.range.end;
18532 }
18533 row_highlights.remove(ix + 1);
18534 } else {
18535 break;
18536 }
18537 }
18538 }
18539 }
18540
18541 /// Remove any highlighted row ranges of the given type that intersect the
18542 /// given ranges.
18543 pub fn remove_highlighted_rows<T: 'static>(
18544 &mut self,
18545 ranges_to_remove: Vec<Range<Anchor>>,
18546 cx: &mut Context<Self>,
18547 ) {
18548 let snapshot = self.buffer().read(cx).snapshot(cx);
18549 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18550 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18551 row_highlights.retain(|highlight| {
18552 while let Some(range_to_remove) = ranges_to_remove.peek() {
18553 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18554 Ordering::Less | Ordering::Equal => {
18555 ranges_to_remove.next();
18556 }
18557 Ordering::Greater => {
18558 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18559 Ordering::Less | Ordering::Equal => {
18560 return false;
18561 }
18562 Ordering::Greater => break,
18563 }
18564 }
18565 }
18566 }
18567
18568 true
18569 })
18570 }
18571
18572 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18573 pub fn clear_row_highlights<T: 'static>(&mut self) {
18574 self.highlighted_rows.remove(&TypeId::of::<T>());
18575 }
18576
18577 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18578 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18579 self.highlighted_rows
18580 .get(&TypeId::of::<T>())
18581 .map_or(&[] as &[_], |vec| vec.as_slice())
18582 .iter()
18583 .map(|highlight| (highlight.range.clone(), highlight.color))
18584 }
18585
18586 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18587 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18588 /// Allows to ignore certain kinds of highlights.
18589 pub fn highlighted_display_rows(
18590 &self,
18591 window: &mut Window,
18592 cx: &mut App,
18593 ) -> BTreeMap<DisplayRow, LineHighlight> {
18594 let snapshot = self.snapshot(window, cx);
18595 let mut used_highlight_orders = HashMap::default();
18596 self.highlighted_rows
18597 .iter()
18598 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18599 .fold(
18600 BTreeMap::<DisplayRow, LineHighlight>::new(),
18601 |mut unique_rows, highlight| {
18602 let start = highlight.range.start.to_display_point(&snapshot);
18603 let end = highlight.range.end.to_display_point(&snapshot);
18604 let start_row = start.row().0;
18605 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18606 && end.column() == 0
18607 {
18608 end.row().0.saturating_sub(1)
18609 } else {
18610 end.row().0
18611 };
18612 for row in start_row..=end_row {
18613 let used_index =
18614 used_highlight_orders.entry(row).or_insert(highlight.index);
18615 if highlight.index >= *used_index {
18616 *used_index = highlight.index;
18617 unique_rows.insert(
18618 DisplayRow(row),
18619 LineHighlight {
18620 include_gutter: highlight.options.include_gutter,
18621 border: None,
18622 background: highlight.color.into(),
18623 type_id: Some(highlight.type_id),
18624 },
18625 );
18626 }
18627 }
18628 unique_rows
18629 },
18630 )
18631 }
18632
18633 pub fn highlighted_display_row_for_autoscroll(
18634 &self,
18635 snapshot: &DisplaySnapshot,
18636 ) -> Option<DisplayRow> {
18637 self.highlighted_rows
18638 .values()
18639 .flat_map(|highlighted_rows| highlighted_rows.iter())
18640 .filter_map(|highlight| {
18641 if highlight.options.autoscroll {
18642 Some(highlight.range.start.to_display_point(snapshot).row())
18643 } else {
18644 None
18645 }
18646 })
18647 .min()
18648 }
18649
18650 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18651 self.highlight_background::<SearchWithinRange>(
18652 ranges,
18653 |colors| colors.colors().editor_document_highlight_read_background,
18654 cx,
18655 )
18656 }
18657
18658 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18659 self.breadcrumb_header = Some(new_header);
18660 }
18661
18662 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18663 self.clear_background_highlights::<SearchWithinRange>(cx);
18664 }
18665
18666 pub fn highlight_background<T: 'static>(
18667 &mut self,
18668 ranges: &[Range<Anchor>],
18669 color_fetcher: fn(&Theme) -> Hsla,
18670 cx: &mut Context<Self>,
18671 ) {
18672 self.background_highlights.insert(
18673 HighlightKey::Type(TypeId::of::<T>()),
18674 (color_fetcher, Arc::from(ranges)),
18675 );
18676 self.scrollbar_marker_state.dirty = true;
18677 cx.notify();
18678 }
18679
18680 pub fn highlight_background_key<T: 'static>(
18681 &mut self,
18682 key: usize,
18683 ranges: &[Range<Anchor>],
18684 color_fetcher: fn(&Theme) -> Hsla,
18685 cx: &mut Context<Self>,
18686 ) {
18687 self.background_highlights.insert(
18688 HighlightKey::TypePlus(TypeId::of::<T>(), key),
18689 (color_fetcher, Arc::from(ranges)),
18690 );
18691 self.scrollbar_marker_state.dirty = true;
18692 cx.notify();
18693 }
18694
18695 pub fn clear_background_highlights<T: 'static>(
18696 &mut self,
18697 cx: &mut Context<Self>,
18698 ) -> Option<BackgroundHighlight> {
18699 let text_highlights = self
18700 .background_highlights
18701 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
18702 if !text_highlights.1.is_empty() {
18703 self.scrollbar_marker_state.dirty = true;
18704 cx.notify();
18705 }
18706 Some(text_highlights)
18707 }
18708
18709 pub fn highlight_gutter<T: 'static>(
18710 &mut self,
18711 ranges: impl Into<Vec<Range<Anchor>>>,
18712 color_fetcher: fn(&App) -> Hsla,
18713 cx: &mut Context<Self>,
18714 ) {
18715 self.gutter_highlights
18716 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18717 cx.notify();
18718 }
18719
18720 pub fn clear_gutter_highlights<T: 'static>(
18721 &mut self,
18722 cx: &mut Context<Self>,
18723 ) -> Option<GutterHighlight> {
18724 cx.notify();
18725 self.gutter_highlights.remove(&TypeId::of::<T>())
18726 }
18727
18728 pub fn insert_gutter_highlight<T: 'static>(
18729 &mut self,
18730 range: Range<Anchor>,
18731 color_fetcher: fn(&App) -> Hsla,
18732 cx: &mut Context<Self>,
18733 ) {
18734 let snapshot = self.buffer().read(cx).snapshot(cx);
18735 let mut highlights = self
18736 .gutter_highlights
18737 .remove(&TypeId::of::<T>())
18738 .map(|(_, highlights)| highlights)
18739 .unwrap_or_default();
18740 let ix = highlights.binary_search_by(|highlight| {
18741 Ordering::Equal
18742 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18743 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18744 });
18745 if let Err(ix) = ix {
18746 highlights.insert(ix, range);
18747 }
18748 self.gutter_highlights
18749 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18750 }
18751
18752 pub fn remove_gutter_highlights<T: 'static>(
18753 &mut self,
18754 ranges_to_remove: Vec<Range<Anchor>>,
18755 cx: &mut Context<Self>,
18756 ) {
18757 let snapshot = self.buffer().read(cx).snapshot(cx);
18758 let Some((color_fetcher, mut gutter_highlights)) =
18759 self.gutter_highlights.remove(&TypeId::of::<T>())
18760 else {
18761 return;
18762 };
18763 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18764 gutter_highlights.retain(|highlight| {
18765 while let Some(range_to_remove) = ranges_to_remove.peek() {
18766 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18767 Ordering::Less | Ordering::Equal => {
18768 ranges_to_remove.next();
18769 }
18770 Ordering::Greater => {
18771 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18772 Ordering::Less | Ordering::Equal => {
18773 return false;
18774 }
18775 Ordering::Greater => break,
18776 }
18777 }
18778 }
18779 }
18780
18781 true
18782 });
18783 self.gutter_highlights
18784 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18785 }
18786
18787 #[cfg(feature = "test-support")]
18788 pub fn all_text_highlights(
18789 &self,
18790 window: &mut Window,
18791 cx: &mut Context<Self>,
18792 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
18793 let snapshot = self.snapshot(window, cx);
18794 self.display_map.update(cx, |display_map, _| {
18795 display_map
18796 .all_text_highlights()
18797 .map(|highlight| {
18798 let (style, ranges) = highlight.as_ref();
18799 (
18800 *style,
18801 ranges
18802 .iter()
18803 .map(|range| range.clone().to_display_points(&snapshot))
18804 .collect(),
18805 )
18806 })
18807 .collect()
18808 })
18809 }
18810
18811 #[cfg(feature = "test-support")]
18812 pub fn all_text_background_highlights(
18813 &self,
18814 window: &mut Window,
18815 cx: &mut Context<Self>,
18816 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18817 let snapshot = self.snapshot(window, cx);
18818 let buffer = &snapshot.buffer_snapshot;
18819 let start = buffer.anchor_before(0);
18820 let end = buffer.anchor_after(buffer.len());
18821 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
18822 }
18823
18824 #[cfg(feature = "test-support")]
18825 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18826 let snapshot = self.buffer().read(cx).snapshot(cx);
18827
18828 let highlights = self
18829 .background_highlights
18830 .get(&HighlightKey::Type(TypeId::of::<
18831 items::BufferSearchHighlights,
18832 >()));
18833
18834 if let Some((_color, ranges)) = highlights {
18835 ranges
18836 .iter()
18837 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18838 .collect_vec()
18839 } else {
18840 vec![]
18841 }
18842 }
18843
18844 fn document_highlights_for_position<'a>(
18845 &'a self,
18846 position: Anchor,
18847 buffer: &'a MultiBufferSnapshot,
18848 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18849 let read_highlights = self
18850 .background_highlights
18851 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
18852 .map(|h| &h.1);
18853 let write_highlights = self
18854 .background_highlights
18855 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
18856 .map(|h| &h.1);
18857 let left_position = position.bias_left(buffer);
18858 let right_position = position.bias_right(buffer);
18859 read_highlights
18860 .into_iter()
18861 .chain(write_highlights)
18862 .flat_map(move |ranges| {
18863 let start_ix = match ranges.binary_search_by(|probe| {
18864 let cmp = probe.end.cmp(&left_position, buffer);
18865 if cmp.is_ge() {
18866 Ordering::Greater
18867 } else {
18868 Ordering::Less
18869 }
18870 }) {
18871 Ok(i) | Err(i) => i,
18872 };
18873
18874 ranges[start_ix..]
18875 .iter()
18876 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18877 })
18878 }
18879
18880 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18881 self.background_highlights
18882 .get(&HighlightKey::Type(TypeId::of::<T>()))
18883 .map_or(false, |(_, highlights)| !highlights.is_empty())
18884 }
18885
18886 pub fn background_highlights_in_range(
18887 &self,
18888 search_range: Range<Anchor>,
18889 display_snapshot: &DisplaySnapshot,
18890 theme: &Theme,
18891 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18892 let mut results = Vec::new();
18893 for (color_fetcher, ranges) in self.background_highlights.values() {
18894 let color = color_fetcher(theme);
18895 let start_ix = match ranges.binary_search_by(|probe| {
18896 let cmp = probe
18897 .end
18898 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18899 if cmp.is_gt() {
18900 Ordering::Greater
18901 } else {
18902 Ordering::Less
18903 }
18904 }) {
18905 Ok(i) | Err(i) => i,
18906 };
18907 for range in &ranges[start_ix..] {
18908 if range
18909 .start
18910 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18911 .is_ge()
18912 {
18913 break;
18914 }
18915
18916 let start = range.start.to_display_point(display_snapshot);
18917 let end = range.end.to_display_point(display_snapshot);
18918 results.push((start..end, color))
18919 }
18920 }
18921 results
18922 }
18923
18924 pub fn background_highlight_row_ranges<T: 'static>(
18925 &self,
18926 search_range: Range<Anchor>,
18927 display_snapshot: &DisplaySnapshot,
18928 count: usize,
18929 ) -> Vec<RangeInclusive<DisplayPoint>> {
18930 let mut results = Vec::new();
18931 let Some((_, ranges)) = self
18932 .background_highlights
18933 .get(&HighlightKey::Type(TypeId::of::<T>()))
18934 else {
18935 return vec![];
18936 };
18937
18938 let start_ix = match ranges.binary_search_by(|probe| {
18939 let cmp = probe
18940 .end
18941 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18942 if cmp.is_gt() {
18943 Ordering::Greater
18944 } else {
18945 Ordering::Less
18946 }
18947 }) {
18948 Ok(i) | Err(i) => i,
18949 };
18950 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18951 if let (Some(start_display), Some(end_display)) = (start, end) {
18952 results.push(
18953 start_display.to_display_point(display_snapshot)
18954 ..=end_display.to_display_point(display_snapshot),
18955 );
18956 }
18957 };
18958 let mut start_row: Option<Point> = None;
18959 let mut end_row: Option<Point> = None;
18960 if ranges.len() > count {
18961 return Vec::new();
18962 }
18963 for range in &ranges[start_ix..] {
18964 if range
18965 .start
18966 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18967 .is_ge()
18968 {
18969 break;
18970 }
18971 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18972 if let Some(current_row) = &end_row {
18973 if end.row == current_row.row {
18974 continue;
18975 }
18976 }
18977 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18978 if start_row.is_none() {
18979 assert_eq!(end_row, None);
18980 start_row = Some(start);
18981 end_row = Some(end);
18982 continue;
18983 }
18984 if let Some(current_end) = end_row.as_mut() {
18985 if start.row > current_end.row + 1 {
18986 push_region(start_row, end_row);
18987 start_row = Some(start);
18988 end_row = Some(end);
18989 } else {
18990 // Merge two hunks.
18991 *current_end = end;
18992 }
18993 } else {
18994 unreachable!();
18995 }
18996 }
18997 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18998 push_region(start_row, end_row);
18999 results
19000 }
19001
19002 pub fn gutter_highlights_in_range(
19003 &self,
19004 search_range: Range<Anchor>,
19005 display_snapshot: &DisplaySnapshot,
19006 cx: &App,
19007 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19008 let mut results = Vec::new();
19009 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19010 let color = color_fetcher(cx);
19011 let start_ix = match ranges.binary_search_by(|probe| {
19012 let cmp = probe
19013 .end
19014 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19015 if cmp.is_gt() {
19016 Ordering::Greater
19017 } else {
19018 Ordering::Less
19019 }
19020 }) {
19021 Ok(i) | Err(i) => i,
19022 };
19023 for range in &ranges[start_ix..] {
19024 if range
19025 .start
19026 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19027 .is_ge()
19028 {
19029 break;
19030 }
19031
19032 let start = range.start.to_display_point(display_snapshot);
19033 let end = range.end.to_display_point(display_snapshot);
19034 results.push((start..end, color))
19035 }
19036 }
19037 results
19038 }
19039
19040 /// Get the text ranges corresponding to the redaction query
19041 pub fn redacted_ranges(
19042 &self,
19043 search_range: Range<Anchor>,
19044 display_snapshot: &DisplaySnapshot,
19045 cx: &App,
19046 ) -> Vec<Range<DisplayPoint>> {
19047 display_snapshot
19048 .buffer_snapshot
19049 .redacted_ranges(search_range, |file| {
19050 if let Some(file) = file {
19051 file.is_private()
19052 && EditorSettings::get(
19053 Some(SettingsLocation {
19054 worktree_id: file.worktree_id(cx),
19055 path: file.path().as_ref(),
19056 }),
19057 cx,
19058 )
19059 .redact_private_values
19060 } else {
19061 false
19062 }
19063 })
19064 .map(|range| {
19065 range.start.to_display_point(display_snapshot)
19066 ..range.end.to_display_point(display_snapshot)
19067 })
19068 .collect()
19069 }
19070
19071 pub fn highlight_text_key<T: 'static>(
19072 &mut self,
19073 key: usize,
19074 ranges: Vec<Range<Anchor>>,
19075 style: HighlightStyle,
19076 cx: &mut Context<Self>,
19077 ) {
19078 self.display_map.update(cx, |map, _| {
19079 map.highlight_text(
19080 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19081 ranges,
19082 style,
19083 );
19084 });
19085 cx.notify();
19086 }
19087
19088 pub fn highlight_text<T: 'static>(
19089 &mut self,
19090 ranges: Vec<Range<Anchor>>,
19091 style: HighlightStyle,
19092 cx: &mut Context<Self>,
19093 ) {
19094 self.display_map.update(cx, |map, _| {
19095 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19096 });
19097 cx.notify();
19098 }
19099
19100 pub(crate) fn highlight_inlays<T: 'static>(
19101 &mut self,
19102 highlights: Vec<InlayHighlight>,
19103 style: HighlightStyle,
19104 cx: &mut Context<Self>,
19105 ) {
19106 self.display_map.update(cx, |map, _| {
19107 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19108 });
19109 cx.notify();
19110 }
19111
19112 pub fn text_highlights<'a, T: 'static>(
19113 &'a self,
19114 cx: &'a App,
19115 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19116 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19117 }
19118
19119 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19120 let cleared = self
19121 .display_map
19122 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19123 if cleared {
19124 cx.notify();
19125 }
19126 }
19127
19128 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19129 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19130 && self.focus_handle.is_focused(window)
19131 }
19132
19133 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19134 self.show_cursor_when_unfocused = is_enabled;
19135 cx.notify();
19136 }
19137
19138 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19139 cx.notify();
19140 }
19141
19142 fn on_debug_session_event(
19143 &mut self,
19144 _session: Entity<Session>,
19145 event: &SessionEvent,
19146 cx: &mut Context<Self>,
19147 ) {
19148 match event {
19149 SessionEvent::InvalidateInlineValue => {
19150 self.refresh_inline_values(cx);
19151 }
19152 _ => {}
19153 }
19154 }
19155
19156 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19157 let Some(project) = self.project.clone() else {
19158 return;
19159 };
19160
19161 if !self.inline_value_cache.enabled {
19162 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19163 self.splice_inlays(&inlays, Vec::new(), cx);
19164 return;
19165 }
19166
19167 let current_execution_position = self
19168 .highlighted_rows
19169 .get(&TypeId::of::<ActiveDebugLine>())
19170 .and_then(|lines| lines.last().map(|line| line.range.end));
19171
19172 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19173 let inline_values = editor
19174 .update(cx, |editor, cx| {
19175 let Some(current_execution_position) = current_execution_position else {
19176 return Some(Task::ready(Ok(Vec::new())));
19177 };
19178
19179 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19180 let snapshot = buffer.snapshot(cx);
19181
19182 let excerpt = snapshot.excerpt_containing(
19183 current_execution_position..current_execution_position,
19184 )?;
19185
19186 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19187 })?;
19188
19189 let range =
19190 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19191
19192 project.inline_values(buffer, range, cx)
19193 })
19194 .ok()
19195 .flatten()?
19196 .await
19197 .context("refreshing debugger inlays")
19198 .log_err()?;
19199
19200 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19201
19202 for (buffer_id, inline_value) in inline_values
19203 .into_iter()
19204 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19205 {
19206 buffer_inline_values
19207 .entry(buffer_id)
19208 .or_default()
19209 .push(inline_value);
19210 }
19211
19212 editor
19213 .update(cx, |editor, cx| {
19214 let snapshot = editor.buffer.read(cx).snapshot(cx);
19215 let mut new_inlays = Vec::default();
19216
19217 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19218 let buffer_id = buffer_snapshot.remote_id();
19219 buffer_inline_values
19220 .get(&buffer_id)
19221 .into_iter()
19222 .flatten()
19223 .for_each(|hint| {
19224 let inlay = Inlay::debugger(
19225 post_inc(&mut editor.next_inlay_id),
19226 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19227 hint.text(),
19228 );
19229
19230 new_inlays.push(inlay);
19231 });
19232 }
19233
19234 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19235 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19236
19237 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19238 })
19239 .ok()?;
19240 Some(())
19241 });
19242 }
19243
19244 fn on_buffer_event(
19245 &mut self,
19246 multibuffer: &Entity<MultiBuffer>,
19247 event: &multi_buffer::Event,
19248 window: &mut Window,
19249 cx: &mut Context<Self>,
19250 ) {
19251 match event {
19252 multi_buffer::Event::Edited {
19253 singleton_buffer_edited,
19254 edited_buffer,
19255 } => {
19256 self.scrollbar_marker_state.dirty = true;
19257 self.active_indent_guides_state.dirty = true;
19258 self.refresh_active_diagnostics(cx);
19259 self.refresh_code_actions(window, cx);
19260 self.refresh_selected_text_highlights(true, window, cx);
19261 refresh_matching_bracket_highlights(self, window, cx);
19262 if self.has_active_inline_completion() {
19263 self.update_visible_inline_completion(window, cx);
19264 }
19265 if let Some(project) = self.project.as_ref() {
19266 if let Some(edited_buffer) = edited_buffer {
19267 project.update(cx, |project, cx| {
19268 self.registered_buffers
19269 .entry(edited_buffer.read(cx).remote_id())
19270 .or_insert_with(|| {
19271 project
19272 .register_buffer_with_language_servers(&edited_buffer, cx)
19273 });
19274 });
19275 }
19276 }
19277 cx.emit(EditorEvent::BufferEdited);
19278 cx.emit(SearchEvent::MatchesInvalidated);
19279
19280 if let Some(buffer) = edited_buffer {
19281 self.update_lsp_data(None, Some(buffer.read(cx).remote_id()), window, cx);
19282 }
19283
19284 if *singleton_buffer_edited {
19285 if let Some(buffer) = edited_buffer {
19286 if buffer.read(cx).file().is_none() {
19287 cx.emit(EditorEvent::TitleChanged);
19288 }
19289 }
19290 if let Some(project) = &self.project {
19291 #[allow(clippy::mutable_key_type)]
19292 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19293 multibuffer
19294 .all_buffers()
19295 .into_iter()
19296 .filter_map(|buffer| {
19297 buffer.update(cx, |buffer, cx| {
19298 let language = buffer.language()?;
19299 let should_discard = project.update(cx, |project, cx| {
19300 project.is_local()
19301 && !project.has_language_servers_for(buffer, cx)
19302 });
19303 should_discard.not().then_some(language.clone())
19304 })
19305 })
19306 .collect::<HashSet<_>>()
19307 });
19308 if !languages_affected.is_empty() {
19309 self.refresh_inlay_hints(
19310 InlayHintRefreshReason::BufferEdited(languages_affected),
19311 cx,
19312 );
19313 }
19314 }
19315 }
19316
19317 let Some(project) = &self.project else { return };
19318 let (telemetry, is_via_ssh) = {
19319 let project = project.read(cx);
19320 let telemetry = project.client().telemetry().clone();
19321 let is_via_ssh = project.is_via_ssh();
19322 (telemetry, is_via_ssh)
19323 };
19324 refresh_linked_ranges(self, window, cx);
19325 telemetry.log_edit_event("editor", is_via_ssh);
19326 }
19327 multi_buffer::Event::ExcerptsAdded {
19328 buffer,
19329 predecessor,
19330 excerpts,
19331 } => {
19332 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19333 let buffer_id = buffer.read(cx).remote_id();
19334 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19335 if let Some(project) = &self.project {
19336 update_uncommitted_diff_for_buffer(
19337 cx.entity(),
19338 project,
19339 [buffer.clone()],
19340 self.buffer.clone(),
19341 cx,
19342 )
19343 .detach();
19344 }
19345 }
19346 self.update_lsp_data(None, Some(buffer_id), window, cx);
19347 cx.emit(EditorEvent::ExcerptsAdded {
19348 buffer: buffer.clone(),
19349 predecessor: *predecessor,
19350 excerpts: excerpts.clone(),
19351 });
19352 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19353 }
19354 multi_buffer::Event::ExcerptsRemoved {
19355 ids,
19356 removed_buffer_ids,
19357 } => {
19358 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19359 let buffer = self.buffer.read(cx);
19360 self.registered_buffers
19361 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19362 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19363 cx.emit(EditorEvent::ExcerptsRemoved {
19364 ids: ids.clone(),
19365 removed_buffer_ids: removed_buffer_ids.clone(),
19366 });
19367 }
19368 multi_buffer::Event::ExcerptsEdited {
19369 excerpt_ids,
19370 buffer_ids,
19371 } => {
19372 self.display_map.update(cx, |map, cx| {
19373 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19374 });
19375 cx.emit(EditorEvent::ExcerptsEdited {
19376 ids: excerpt_ids.clone(),
19377 });
19378 }
19379 multi_buffer::Event::ExcerptsExpanded { ids } => {
19380 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19381 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19382 }
19383 multi_buffer::Event::Reparsed(buffer_id) => {
19384 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19385 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19386
19387 cx.emit(EditorEvent::Reparsed(*buffer_id));
19388 }
19389 multi_buffer::Event::DiffHunksToggled => {
19390 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19391 }
19392 multi_buffer::Event::LanguageChanged(buffer_id) => {
19393 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19394 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19395 cx.emit(EditorEvent::Reparsed(*buffer_id));
19396 cx.notify();
19397 }
19398 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19399 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19400 multi_buffer::Event::FileHandleChanged
19401 | multi_buffer::Event::Reloaded
19402 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19403 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19404 multi_buffer::Event::DiagnosticsUpdated => {
19405 self.update_diagnostics_state(window, cx);
19406 }
19407 _ => {}
19408 };
19409 }
19410
19411 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19412 if !self.diagnostics_enabled() {
19413 return;
19414 }
19415 self.refresh_active_diagnostics(cx);
19416 self.refresh_inline_diagnostics(true, window, cx);
19417 self.scrollbar_marker_state.dirty = true;
19418 cx.notify();
19419 }
19420
19421 pub fn start_temporary_diff_override(&mut self) {
19422 self.load_diff_task.take();
19423 self.temporary_diff_override = true;
19424 }
19425
19426 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19427 self.temporary_diff_override = false;
19428 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19429 self.buffer.update(cx, |buffer, cx| {
19430 buffer.set_all_diff_hunks_collapsed(cx);
19431 });
19432
19433 if let Some(project) = self.project.clone() {
19434 self.load_diff_task = Some(
19435 update_uncommitted_diff_for_buffer(
19436 cx.entity(),
19437 &project,
19438 self.buffer.read(cx).all_buffers(),
19439 self.buffer.clone(),
19440 cx,
19441 )
19442 .shared(),
19443 );
19444 }
19445 }
19446
19447 fn on_display_map_changed(
19448 &mut self,
19449 _: Entity<DisplayMap>,
19450 _: &mut Window,
19451 cx: &mut Context<Self>,
19452 ) {
19453 cx.notify();
19454 }
19455
19456 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19457 let new_severity = if self.diagnostics_enabled() {
19458 EditorSettings::get_global(cx)
19459 .diagnostics_max_severity
19460 .unwrap_or(DiagnosticSeverity::Hint)
19461 } else {
19462 DiagnosticSeverity::Off
19463 };
19464 self.set_max_diagnostics_severity(new_severity, cx);
19465 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19466 self.update_edit_prediction_settings(cx);
19467 self.refresh_inline_completion(true, false, window, cx);
19468 self.refresh_inlay_hints(
19469 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19470 self.selections.newest_anchor().head(),
19471 &self.buffer.read(cx).snapshot(cx),
19472 cx,
19473 )),
19474 cx,
19475 );
19476
19477 let old_cursor_shape = self.cursor_shape;
19478
19479 {
19480 let editor_settings = EditorSettings::get_global(cx);
19481 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19482 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19483 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19484 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19485 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19486 }
19487
19488 if old_cursor_shape != self.cursor_shape {
19489 cx.emit(EditorEvent::CursorShapeChanged);
19490 }
19491
19492 let project_settings = ProjectSettings::get_global(cx);
19493 self.serialize_dirty_buffers =
19494 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19495
19496 if self.mode.is_full() {
19497 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19498 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19499 if self.show_inline_diagnostics != show_inline_diagnostics {
19500 self.show_inline_diagnostics = show_inline_diagnostics;
19501 self.refresh_inline_diagnostics(false, window, cx);
19502 }
19503
19504 if self.git_blame_inline_enabled != inline_blame_enabled {
19505 self.toggle_git_blame_inline_internal(false, window, cx);
19506 }
19507
19508 let minimap_settings = EditorSettings::get_global(cx).minimap;
19509 if self.minimap_visibility != MinimapVisibility::Disabled {
19510 if self.minimap_visibility.settings_visibility()
19511 != minimap_settings.minimap_enabled()
19512 {
19513 self.set_minimap_visibility(
19514 MinimapVisibility::for_mode(self.mode(), cx),
19515 window,
19516 cx,
19517 );
19518 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19519 minimap_entity.update(cx, |minimap_editor, cx| {
19520 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19521 })
19522 }
19523 }
19524 }
19525
19526 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19527 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19528 }) {
19529 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19530 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19531 }
19532 self.refresh_colors(None, None, window, cx);
19533 }
19534
19535 cx.notify();
19536 }
19537
19538 pub fn set_searchable(&mut self, searchable: bool) {
19539 self.searchable = searchable;
19540 }
19541
19542 pub fn searchable(&self) -> bool {
19543 self.searchable
19544 }
19545
19546 fn open_proposed_changes_editor(
19547 &mut self,
19548 _: &OpenProposedChangesEditor,
19549 window: &mut Window,
19550 cx: &mut Context<Self>,
19551 ) {
19552 let Some(workspace) = self.workspace() else {
19553 cx.propagate();
19554 return;
19555 };
19556
19557 let selections = self.selections.all::<usize>(cx);
19558 let multi_buffer = self.buffer.read(cx);
19559 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19560 let mut new_selections_by_buffer = HashMap::default();
19561 for selection in selections {
19562 for (buffer, range, _) in
19563 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19564 {
19565 let mut range = range.to_point(buffer);
19566 range.start.column = 0;
19567 range.end.column = buffer.line_len(range.end.row);
19568 new_selections_by_buffer
19569 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19570 .or_insert(Vec::new())
19571 .push(range)
19572 }
19573 }
19574
19575 let proposed_changes_buffers = new_selections_by_buffer
19576 .into_iter()
19577 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19578 .collect::<Vec<_>>();
19579 let proposed_changes_editor = cx.new(|cx| {
19580 ProposedChangesEditor::new(
19581 "Proposed changes",
19582 proposed_changes_buffers,
19583 self.project.clone(),
19584 window,
19585 cx,
19586 )
19587 });
19588
19589 window.defer(cx, move |window, cx| {
19590 workspace.update(cx, |workspace, cx| {
19591 workspace.active_pane().update(cx, |pane, cx| {
19592 pane.add_item(
19593 Box::new(proposed_changes_editor),
19594 true,
19595 true,
19596 None,
19597 window,
19598 cx,
19599 );
19600 });
19601 });
19602 });
19603 }
19604
19605 pub fn open_excerpts_in_split(
19606 &mut self,
19607 _: &OpenExcerptsSplit,
19608 window: &mut Window,
19609 cx: &mut Context<Self>,
19610 ) {
19611 self.open_excerpts_common(None, true, window, cx)
19612 }
19613
19614 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19615 self.open_excerpts_common(None, false, window, cx)
19616 }
19617
19618 fn open_excerpts_common(
19619 &mut self,
19620 jump_data: Option<JumpData>,
19621 split: bool,
19622 window: &mut Window,
19623 cx: &mut Context<Self>,
19624 ) {
19625 let Some(workspace) = self.workspace() else {
19626 cx.propagate();
19627 return;
19628 };
19629
19630 if self.buffer.read(cx).is_singleton() {
19631 cx.propagate();
19632 return;
19633 }
19634
19635 let mut new_selections_by_buffer = HashMap::default();
19636 match &jump_data {
19637 Some(JumpData::MultiBufferPoint {
19638 excerpt_id,
19639 position,
19640 anchor,
19641 line_offset_from_top,
19642 }) => {
19643 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19644 if let Some(buffer) = multi_buffer_snapshot
19645 .buffer_id_for_excerpt(*excerpt_id)
19646 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19647 {
19648 let buffer_snapshot = buffer.read(cx).snapshot();
19649 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19650 language::ToPoint::to_point(anchor, &buffer_snapshot)
19651 } else {
19652 buffer_snapshot.clip_point(*position, Bias::Left)
19653 };
19654 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19655 new_selections_by_buffer.insert(
19656 buffer,
19657 (
19658 vec![jump_to_offset..jump_to_offset],
19659 Some(*line_offset_from_top),
19660 ),
19661 );
19662 }
19663 }
19664 Some(JumpData::MultiBufferRow {
19665 row,
19666 line_offset_from_top,
19667 }) => {
19668 let point = MultiBufferPoint::new(row.0, 0);
19669 if let Some((buffer, buffer_point, _)) =
19670 self.buffer.read(cx).point_to_buffer_point(point, cx)
19671 {
19672 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19673 new_selections_by_buffer
19674 .entry(buffer)
19675 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19676 .0
19677 .push(buffer_offset..buffer_offset)
19678 }
19679 }
19680 None => {
19681 let selections = self.selections.all::<usize>(cx);
19682 let multi_buffer = self.buffer.read(cx);
19683 for selection in selections {
19684 for (snapshot, range, _, anchor) in multi_buffer
19685 .snapshot(cx)
19686 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19687 {
19688 if let Some(anchor) = anchor {
19689 // selection is in a deleted hunk
19690 let Some(buffer_id) = anchor.buffer_id else {
19691 continue;
19692 };
19693 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19694 continue;
19695 };
19696 let offset = text::ToOffset::to_offset(
19697 &anchor.text_anchor,
19698 &buffer_handle.read(cx).snapshot(),
19699 );
19700 let range = offset..offset;
19701 new_selections_by_buffer
19702 .entry(buffer_handle)
19703 .or_insert((Vec::new(), None))
19704 .0
19705 .push(range)
19706 } else {
19707 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19708 else {
19709 continue;
19710 };
19711 new_selections_by_buffer
19712 .entry(buffer_handle)
19713 .or_insert((Vec::new(), None))
19714 .0
19715 .push(range)
19716 }
19717 }
19718 }
19719 }
19720 }
19721
19722 new_selections_by_buffer
19723 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19724
19725 if new_selections_by_buffer.is_empty() {
19726 return;
19727 }
19728
19729 // We defer the pane interaction because we ourselves are a workspace item
19730 // and activating a new item causes the pane to call a method on us reentrantly,
19731 // which panics if we're on the stack.
19732 window.defer(cx, move |window, cx| {
19733 workspace.update(cx, |workspace, cx| {
19734 let pane = if split {
19735 workspace.adjacent_pane(window, cx)
19736 } else {
19737 workspace.active_pane().clone()
19738 };
19739
19740 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19741 let editor = buffer
19742 .read(cx)
19743 .file()
19744 .is_none()
19745 .then(|| {
19746 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19747 // so `workspace.open_project_item` will never find them, always opening a new editor.
19748 // Instead, we try to activate the existing editor in the pane first.
19749 let (editor, pane_item_index) =
19750 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19751 let editor = item.downcast::<Editor>()?;
19752 let singleton_buffer =
19753 editor.read(cx).buffer().read(cx).as_singleton()?;
19754 if singleton_buffer == buffer {
19755 Some((editor, i))
19756 } else {
19757 None
19758 }
19759 })?;
19760 pane.update(cx, |pane, cx| {
19761 pane.activate_item(pane_item_index, true, true, window, cx)
19762 });
19763 Some(editor)
19764 })
19765 .flatten()
19766 .unwrap_or_else(|| {
19767 workspace.open_project_item::<Self>(
19768 pane.clone(),
19769 buffer,
19770 true,
19771 true,
19772 window,
19773 cx,
19774 )
19775 });
19776
19777 editor.update(cx, |editor, cx| {
19778 let autoscroll = match scroll_offset {
19779 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19780 None => Autoscroll::newest(),
19781 };
19782 let nav_history = editor.nav_history.take();
19783 editor.change_selections(Some(autoscroll), window, cx, |s| {
19784 s.select_ranges(ranges);
19785 });
19786 editor.nav_history = nav_history;
19787 });
19788 }
19789 })
19790 });
19791 }
19792
19793 // For now, don't allow opening excerpts in buffers that aren't backed by
19794 // regular project files.
19795 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19796 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19797 }
19798
19799 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19800 let snapshot = self.buffer.read(cx).read(cx);
19801 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19802 Some(
19803 ranges
19804 .iter()
19805 .map(move |range| {
19806 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19807 })
19808 .collect(),
19809 )
19810 }
19811
19812 fn selection_replacement_ranges(
19813 &self,
19814 range: Range<OffsetUtf16>,
19815 cx: &mut App,
19816 ) -> Vec<Range<OffsetUtf16>> {
19817 let selections = self.selections.all::<OffsetUtf16>(cx);
19818 let newest_selection = selections
19819 .iter()
19820 .max_by_key(|selection| selection.id)
19821 .unwrap();
19822 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19823 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19824 let snapshot = self.buffer.read(cx).read(cx);
19825 selections
19826 .into_iter()
19827 .map(|mut selection| {
19828 selection.start.0 =
19829 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19830 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19831 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19832 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19833 })
19834 .collect()
19835 }
19836
19837 fn report_editor_event(
19838 &self,
19839 event_type: &'static str,
19840 file_extension: Option<String>,
19841 cx: &App,
19842 ) {
19843 if cfg!(any(test, feature = "test-support")) {
19844 return;
19845 }
19846
19847 let Some(project) = &self.project else { return };
19848
19849 // If None, we are in a file without an extension
19850 let file = self
19851 .buffer
19852 .read(cx)
19853 .as_singleton()
19854 .and_then(|b| b.read(cx).file());
19855 let file_extension = file_extension.or(file
19856 .as_ref()
19857 .and_then(|file| Path::new(file.file_name(cx)).extension())
19858 .and_then(|e| e.to_str())
19859 .map(|a| a.to_string()));
19860
19861 let vim_mode = vim_enabled(cx);
19862
19863 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19864 let copilot_enabled = edit_predictions_provider
19865 == language::language_settings::EditPredictionProvider::Copilot;
19866 let copilot_enabled_for_language = self
19867 .buffer
19868 .read(cx)
19869 .language_settings(cx)
19870 .show_edit_predictions;
19871
19872 let project = project.read(cx);
19873 telemetry::event!(
19874 event_type,
19875 file_extension,
19876 vim_mode,
19877 copilot_enabled,
19878 copilot_enabled_for_language,
19879 edit_predictions_provider,
19880 is_via_ssh = project.is_via_ssh(),
19881 );
19882 }
19883
19884 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19885 /// with each line being an array of {text, highlight} objects.
19886 fn copy_highlight_json(
19887 &mut self,
19888 _: &CopyHighlightJson,
19889 window: &mut Window,
19890 cx: &mut Context<Self>,
19891 ) {
19892 #[derive(Serialize)]
19893 struct Chunk<'a> {
19894 text: String,
19895 highlight: Option<&'a str>,
19896 }
19897
19898 let snapshot = self.buffer.read(cx).snapshot(cx);
19899 let range = self
19900 .selected_text_range(false, window, cx)
19901 .and_then(|selection| {
19902 if selection.range.is_empty() {
19903 None
19904 } else {
19905 Some(selection.range)
19906 }
19907 })
19908 .unwrap_or_else(|| 0..snapshot.len());
19909
19910 let chunks = snapshot.chunks(range, true);
19911 let mut lines = Vec::new();
19912 let mut line: VecDeque<Chunk> = VecDeque::new();
19913
19914 let Some(style) = self.style.as_ref() else {
19915 return;
19916 };
19917
19918 for chunk in chunks {
19919 let highlight = chunk
19920 .syntax_highlight_id
19921 .and_then(|id| id.name(&style.syntax));
19922 let mut chunk_lines = chunk.text.split('\n').peekable();
19923 while let Some(text) = chunk_lines.next() {
19924 let mut merged_with_last_token = false;
19925 if let Some(last_token) = line.back_mut() {
19926 if last_token.highlight == highlight {
19927 last_token.text.push_str(text);
19928 merged_with_last_token = true;
19929 }
19930 }
19931
19932 if !merged_with_last_token {
19933 line.push_back(Chunk {
19934 text: text.into(),
19935 highlight,
19936 });
19937 }
19938
19939 if chunk_lines.peek().is_some() {
19940 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19941 line.pop_front();
19942 }
19943 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19944 line.pop_back();
19945 }
19946
19947 lines.push(mem::take(&mut line));
19948 }
19949 }
19950 }
19951
19952 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19953 return;
19954 };
19955 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19956 }
19957
19958 pub fn open_context_menu(
19959 &mut self,
19960 _: &OpenContextMenu,
19961 window: &mut Window,
19962 cx: &mut Context<Self>,
19963 ) {
19964 self.request_autoscroll(Autoscroll::newest(), cx);
19965 let position = self.selections.newest_display(cx).start;
19966 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19967 }
19968
19969 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19970 &self.inlay_hint_cache
19971 }
19972
19973 pub fn replay_insert_event(
19974 &mut self,
19975 text: &str,
19976 relative_utf16_range: Option<Range<isize>>,
19977 window: &mut Window,
19978 cx: &mut Context<Self>,
19979 ) {
19980 if !self.input_enabled {
19981 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19982 return;
19983 }
19984 if let Some(relative_utf16_range) = relative_utf16_range {
19985 let selections = self.selections.all::<OffsetUtf16>(cx);
19986 self.change_selections(None, window, cx, |s| {
19987 let new_ranges = selections.into_iter().map(|range| {
19988 let start = OffsetUtf16(
19989 range
19990 .head()
19991 .0
19992 .saturating_add_signed(relative_utf16_range.start),
19993 );
19994 let end = OffsetUtf16(
19995 range
19996 .head()
19997 .0
19998 .saturating_add_signed(relative_utf16_range.end),
19999 );
20000 start..end
20001 });
20002 s.select_ranges(new_ranges);
20003 });
20004 }
20005
20006 self.handle_input(text, window, cx);
20007 }
20008
20009 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20010 let Some(provider) = self.semantics_provider.as_ref() else {
20011 return false;
20012 };
20013
20014 let mut supports = false;
20015 self.buffer().update(cx, |this, cx| {
20016 this.for_each_buffer(|buffer| {
20017 supports |= provider.supports_inlay_hints(buffer, cx);
20018 });
20019 });
20020
20021 supports
20022 }
20023
20024 pub fn is_focused(&self, window: &Window) -> bool {
20025 self.focus_handle.is_focused(window)
20026 }
20027
20028 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20029 cx.emit(EditorEvent::Focused);
20030
20031 if let Some(descendant) = self
20032 .last_focused_descendant
20033 .take()
20034 .and_then(|descendant| descendant.upgrade())
20035 {
20036 window.focus(&descendant);
20037 } else {
20038 if let Some(blame) = self.blame.as_ref() {
20039 blame.update(cx, GitBlame::focus)
20040 }
20041
20042 self.blink_manager.update(cx, BlinkManager::enable);
20043 self.show_cursor_names(window, cx);
20044 self.buffer.update(cx, |buffer, cx| {
20045 buffer.finalize_last_transaction(cx);
20046 if self.leader_id.is_none() {
20047 buffer.set_active_selections(
20048 &self.selections.disjoint_anchors(),
20049 self.selections.line_mode,
20050 self.cursor_shape,
20051 cx,
20052 );
20053 }
20054 });
20055 }
20056 }
20057
20058 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20059 cx.emit(EditorEvent::FocusedIn)
20060 }
20061
20062 fn handle_focus_out(
20063 &mut self,
20064 event: FocusOutEvent,
20065 _window: &mut Window,
20066 cx: &mut Context<Self>,
20067 ) {
20068 if event.blurred != self.focus_handle {
20069 self.last_focused_descendant = Some(event.blurred);
20070 }
20071 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20072 }
20073
20074 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20075 self.blink_manager.update(cx, BlinkManager::disable);
20076 self.buffer
20077 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20078
20079 if let Some(blame) = self.blame.as_ref() {
20080 blame.update(cx, GitBlame::blur)
20081 }
20082 if !self.hover_state.focused(window, cx) {
20083 hide_hover(self, cx);
20084 }
20085 if !self
20086 .context_menu
20087 .borrow()
20088 .as_ref()
20089 .is_some_and(|context_menu| context_menu.focused(window, cx))
20090 {
20091 self.hide_context_menu(window, cx);
20092 }
20093 self.discard_inline_completion(false, cx);
20094 cx.emit(EditorEvent::Blurred);
20095 cx.notify();
20096 }
20097
20098 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20099 let mut pending: String = window
20100 .pending_input_keystrokes()
20101 .into_iter()
20102 .flatten()
20103 .filter_map(|keystroke| {
20104 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20105 keystroke.key_char.clone()
20106 } else {
20107 None
20108 }
20109 })
20110 .collect();
20111
20112 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20113 pending = "".to_string();
20114 }
20115
20116 let existing_pending = self
20117 .text_highlights::<PendingInput>(cx)
20118 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20119 if existing_pending.is_none() && pending.is_empty() {
20120 return;
20121 }
20122 let transaction =
20123 self.transact(window, cx, |this, window, cx| {
20124 let selections = this.selections.all::<usize>(cx);
20125 let edits = selections
20126 .iter()
20127 .map(|selection| (selection.end..selection.end, pending.clone()));
20128 this.edit(edits, cx);
20129 this.change_selections(None, window, cx, |s| {
20130 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20131 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20132 }));
20133 });
20134 if let Some(existing_ranges) = existing_pending {
20135 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20136 this.edit(edits, cx);
20137 }
20138 });
20139
20140 let snapshot = self.snapshot(window, cx);
20141 let ranges = self
20142 .selections
20143 .all::<usize>(cx)
20144 .into_iter()
20145 .map(|selection| {
20146 snapshot.buffer_snapshot.anchor_after(selection.end)
20147 ..snapshot
20148 .buffer_snapshot
20149 .anchor_before(selection.end + pending.len())
20150 })
20151 .collect();
20152
20153 if pending.is_empty() {
20154 self.clear_highlights::<PendingInput>(cx);
20155 } else {
20156 self.highlight_text::<PendingInput>(
20157 ranges,
20158 HighlightStyle {
20159 underline: Some(UnderlineStyle {
20160 thickness: px(1.),
20161 color: None,
20162 wavy: false,
20163 }),
20164 ..Default::default()
20165 },
20166 cx,
20167 );
20168 }
20169
20170 self.ime_transaction = self.ime_transaction.or(transaction);
20171 if let Some(transaction) = self.ime_transaction {
20172 self.buffer.update(cx, |buffer, cx| {
20173 buffer.group_until_transaction(transaction, cx);
20174 });
20175 }
20176
20177 if self.text_highlights::<PendingInput>(cx).is_none() {
20178 self.ime_transaction.take();
20179 }
20180 }
20181
20182 pub fn register_action_renderer(
20183 &mut self,
20184 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20185 ) -> Subscription {
20186 let id = self.next_editor_action_id.post_inc();
20187 self.editor_actions
20188 .borrow_mut()
20189 .insert(id, Box::new(listener));
20190
20191 let editor_actions = self.editor_actions.clone();
20192 Subscription::new(move || {
20193 editor_actions.borrow_mut().remove(&id);
20194 })
20195 }
20196
20197 pub fn register_action<A: Action>(
20198 &mut self,
20199 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20200 ) -> Subscription {
20201 let id = self.next_editor_action_id.post_inc();
20202 let listener = Arc::new(listener);
20203 self.editor_actions.borrow_mut().insert(
20204 id,
20205 Box::new(move |_, window, _| {
20206 let listener = listener.clone();
20207 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20208 let action = action.downcast_ref().unwrap();
20209 if phase == DispatchPhase::Bubble {
20210 listener(action, window, cx)
20211 }
20212 })
20213 }),
20214 );
20215
20216 let editor_actions = self.editor_actions.clone();
20217 Subscription::new(move || {
20218 editor_actions.borrow_mut().remove(&id);
20219 })
20220 }
20221
20222 pub fn file_header_size(&self) -> u32 {
20223 FILE_HEADER_HEIGHT
20224 }
20225
20226 pub fn restore(
20227 &mut self,
20228 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20229 window: &mut Window,
20230 cx: &mut Context<Self>,
20231 ) {
20232 let workspace = self.workspace();
20233 let project = self.project.as_ref();
20234 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20235 let mut tasks = Vec::new();
20236 for (buffer_id, changes) in revert_changes {
20237 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20238 buffer.update(cx, |buffer, cx| {
20239 buffer.edit(
20240 changes
20241 .into_iter()
20242 .map(|(range, text)| (range, text.to_string())),
20243 None,
20244 cx,
20245 );
20246 });
20247
20248 if let Some(project) =
20249 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20250 {
20251 project.update(cx, |project, cx| {
20252 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20253 })
20254 }
20255 }
20256 }
20257 tasks
20258 });
20259 cx.spawn_in(window, async move |_, cx| {
20260 for (buffer, task) in save_tasks {
20261 let result = task.await;
20262 if result.is_err() {
20263 let Some(path) = buffer
20264 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20265 .ok()
20266 else {
20267 continue;
20268 };
20269 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20270 let Some(task) = cx
20271 .update_window_entity(&workspace, |workspace, window, cx| {
20272 workspace
20273 .open_path_preview(path, None, false, false, false, window, cx)
20274 })
20275 .ok()
20276 else {
20277 continue;
20278 };
20279 task.await.log_err();
20280 }
20281 }
20282 }
20283 })
20284 .detach();
20285 self.change_selections(None, window, cx, |selections| selections.refresh());
20286 }
20287
20288 pub fn to_pixel_point(
20289 &self,
20290 source: multi_buffer::Anchor,
20291 editor_snapshot: &EditorSnapshot,
20292 window: &mut Window,
20293 ) -> Option<gpui::Point<Pixels>> {
20294 let source_point = source.to_display_point(editor_snapshot);
20295 self.display_to_pixel_point(source_point, editor_snapshot, window)
20296 }
20297
20298 pub fn display_to_pixel_point(
20299 &self,
20300 source: DisplayPoint,
20301 editor_snapshot: &EditorSnapshot,
20302 window: &mut Window,
20303 ) -> Option<gpui::Point<Pixels>> {
20304 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20305 let text_layout_details = self.text_layout_details(window);
20306 let scroll_top = text_layout_details
20307 .scroll_anchor
20308 .scroll_position(editor_snapshot)
20309 .y;
20310
20311 if source.row().as_f32() < scroll_top.floor() {
20312 return None;
20313 }
20314 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20315 let source_y = line_height * (source.row().as_f32() - scroll_top);
20316 Some(gpui::Point::new(source_x, source_y))
20317 }
20318
20319 pub fn has_visible_completions_menu(&self) -> bool {
20320 !self.edit_prediction_preview_is_active()
20321 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20322 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20323 })
20324 }
20325
20326 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20327 if self.mode.is_minimap() {
20328 return;
20329 }
20330 self.addons
20331 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20332 }
20333
20334 pub fn unregister_addon<T: Addon>(&mut self) {
20335 self.addons.remove(&std::any::TypeId::of::<T>());
20336 }
20337
20338 pub fn addon<T: Addon>(&self) -> Option<&T> {
20339 let type_id = std::any::TypeId::of::<T>();
20340 self.addons
20341 .get(&type_id)
20342 .and_then(|item| item.to_any().downcast_ref::<T>())
20343 }
20344
20345 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20346 let type_id = std::any::TypeId::of::<T>();
20347 self.addons
20348 .get_mut(&type_id)
20349 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20350 }
20351
20352 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20353 let text_layout_details = self.text_layout_details(window);
20354 let style = &text_layout_details.editor_style;
20355 let font_id = window.text_system().resolve_font(&style.text.font());
20356 let font_size = style.text.font_size.to_pixels(window.rem_size());
20357 let line_height = style.text.line_height_in_pixels(window.rem_size());
20358 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20359
20360 gpui::Size::new(em_width, line_height)
20361 }
20362
20363 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20364 self.load_diff_task.clone()
20365 }
20366
20367 fn read_metadata_from_db(
20368 &mut self,
20369 item_id: u64,
20370 workspace_id: WorkspaceId,
20371 window: &mut Window,
20372 cx: &mut Context<Editor>,
20373 ) {
20374 if self.is_singleton(cx)
20375 && !self.mode.is_minimap()
20376 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20377 {
20378 let buffer_snapshot = OnceCell::new();
20379
20380 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20381 if !folds.is_empty() {
20382 let snapshot =
20383 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20384 self.fold_ranges(
20385 folds
20386 .into_iter()
20387 .map(|(start, end)| {
20388 snapshot.clip_offset(start, Bias::Left)
20389 ..snapshot.clip_offset(end, Bias::Right)
20390 })
20391 .collect(),
20392 false,
20393 window,
20394 cx,
20395 );
20396 }
20397 }
20398
20399 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20400 if !selections.is_empty() {
20401 let snapshot =
20402 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20403 // skip adding the initial selection to selection history
20404 self.selection_history.mode = SelectionHistoryMode::Skipping;
20405 self.change_selections(None, window, cx, |s| {
20406 s.select_ranges(selections.into_iter().map(|(start, end)| {
20407 snapshot.clip_offset(start, Bias::Left)
20408 ..snapshot.clip_offset(end, Bias::Right)
20409 }));
20410 });
20411 self.selection_history.mode = SelectionHistoryMode::Normal;
20412 }
20413 };
20414 }
20415
20416 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20417 }
20418
20419 fn update_lsp_data(
20420 &mut self,
20421 for_server_id: Option<LanguageServerId>,
20422 for_buffer: Option<BufferId>,
20423 window: &mut Window,
20424 cx: &mut Context<'_, Self>,
20425 ) {
20426 self.pull_diagnostics(for_buffer, window, cx);
20427 self.refresh_colors(for_server_id, for_buffer, window, cx);
20428 }
20429}
20430
20431fn vim_enabled(cx: &App) -> bool {
20432 cx.global::<SettingsStore>()
20433 .raw_user_settings()
20434 .get("vim_mode")
20435 == Some(&serde_json::Value::Bool(true))
20436}
20437
20438fn process_completion_for_edit(
20439 completion: &Completion,
20440 intent: CompletionIntent,
20441 buffer: &Entity<Buffer>,
20442 cursor_position: &text::Anchor,
20443 cx: &mut Context<Editor>,
20444) -> CompletionEdit {
20445 let buffer = buffer.read(cx);
20446 let buffer_snapshot = buffer.snapshot();
20447 let (snippet, new_text) = if completion.is_snippet() {
20448 // Workaround for typescript language server issues so that methods don't expand within
20449 // strings and functions with type expressions. The previous point is used because the query
20450 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20451 let mut snippet_source = completion.new_text.clone();
20452 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20453 previous_point.column = previous_point.column.saturating_sub(1);
20454 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20455 if scope.prefers_label_for_snippet_in_completion() {
20456 if let Some(label) = completion.label() {
20457 if matches!(
20458 completion.kind(),
20459 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20460 ) {
20461 snippet_source = label;
20462 }
20463 }
20464 }
20465 }
20466 match Snippet::parse(&snippet_source).log_err() {
20467 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20468 None => (None, completion.new_text.clone()),
20469 }
20470 } else {
20471 (None, completion.new_text.clone())
20472 };
20473
20474 let mut range_to_replace = {
20475 let replace_range = &completion.replace_range;
20476 if let CompletionSource::Lsp {
20477 insert_range: Some(insert_range),
20478 ..
20479 } = &completion.source
20480 {
20481 debug_assert_eq!(
20482 insert_range.start, replace_range.start,
20483 "insert_range and replace_range should start at the same position"
20484 );
20485 debug_assert!(
20486 insert_range
20487 .start
20488 .cmp(&cursor_position, &buffer_snapshot)
20489 .is_le(),
20490 "insert_range should start before or at cursor position"
20491 );
20492 debug_assert!(
20493 replace_range
20494 .start
20495 .cmp(&cursor_position, &buffer_snapshot)
20496 .is_le(),
20497 "replace_range should start before or at cursor position"
20498 );
20499 debug_assert!(
20500 insert_range
20501 .end
20502 .cmp(&cursor_position, &buffer_snapshot)
20503 .is_le(),
20504 "insert_range should end before or at cursor position"
20505 );
20506
20507 let should_replace = match intent {
20508 CompletionIntent::CompleteWithInsert => false,
20509 CompletionIntent::CompleteWithReplace => true,
20510 CompletionIntent::Complete | CompletionIntent::Compose => {
20511 let insert_mode =
20512 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20513 .completions
20514 .lsp_insert_mode;
20515 match insert_mode {
20516 LspInsertMode::Insert => false,
20517 LspInsertMode::Replace => true,
20518 LspInsertMode::ReplaceSubsequence => {
20519 let mut text_to_replace = buffer.chars_for_range(
20520 buffer.anchor_before(replace_range.start)
20521 ..buffer.anchor_after(replace_range.end),
20522 );
20523 let mut current_needle = text_to_replace.next();
20524 for haystack_ch in completion.label.text.chars() {
20525 if let Some(needle_ch) = current_needle {
20526 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20527 current_needle = text_to_replace.next();
20528 }
20529 }
20530 }
20531 current_needle.is_none()
20532 }
20533 LspInsertMode::ReplaceSuffix => {
20534 if replace_range
20535 .end
20536 .cmp(&cursor_position, &buffer_snapshot)
20537 .is_gt()
20538 {
20539 let range_after_cursor = *cursor_position..replace_range.end;
20540 let text_after_cursor = buffer
20541 .text_for_range(
20542 buffer.anchor_before(range_after_cursor.start)
20543 ..buffer.anchor_after(range_after_cursor.end),
20544 )
20545 .collect::<String>()
20546 .to_ascii_lowercase();
20547 completion
20548 .label
20549 .text
20550 .to_ascii_lowercase()
20551 .ends_with(&text_after_cursor)
20552 } else {
20553 true
20554 }
20555 }
20556 }
20557 }
20558 };
20559
20560 if should_replace {
20561 replace_range.clone()
20562 } else {
20563 insert_range.clone()
20564 }
20565 } else {
20566 replace_range.clone()
20567 }
20568 };
20569
20570 if range_to_replace
20571 .end
20572 .cmp(&cursor_position, &buffer_snapshot)
20573 .is_lt()
20574 {
20575 range_to_replace.end = *cursor_position;
20576 }
20577
20578 CompletionEdit {
20579 new_text,
20580 replace_range: range_to_replace.to_offset(&buffer),
20581 snippet,
20582 }
20583}
20584
20585struct CompletionEdit {
20586 new_text: String,
20587 replace_range: Range<usize>,
20588 snippet: Option<Snippet>,
20589}
20590
20591fn insert_extra_newline_brackets(
20592 buffer: &MultiBufferSnapshot,
20593 range: Range<usize>,
20594 language: &language::LanguageScope,
20595) -> bool {
20596 let leading_whitespace_len = buffer
20597 .reversed_chars_at(range.start)
20598 .take_while(|c| c.is_whitespace() && *c != '\n')
20599 .map(|c| c.len_utf8())
20600 .sum::<usize>();
20601 let trailing_whitespace_len = buffer
20602 .chars_at(range.end)
20603 .take_while(|c| c.is_whitespace() && *c != '\n')
20604 .map(|c| c.len_utf8())
20605 .sum::<usize>();
20606 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20607
20608 language.brackets().any(|(pair, enabled)| {
20609 let pair_start = pair.start.trim_end();
20610 let pair_end = pair.end.trim_start();
20611
20612 enabled
20613 && pair.newline
20614 && buffer.contains_str_at(range.end, pair_end)
20615 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20616 })
20617}
20618
20619fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20620 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20621 [(buffer, range, _)] => (*buffer, range.clone()),
20622 _ => return false,
20623 };
20624 let pair = {
20625 let mut result: Option<BracketMatch> = None;
20626
20627 for pair in buffer
20628 .all_bracket_ranges(range.clone())
20629 .filter(move |pair| {
20630 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20631 })
20632 {
20633 let len = pair.close_range.end - pair.open_range.start;
20634
20635 if let Some(existing) = &result {
20636 let existing_len = existing.close_range.end - existing.open_range.start;
20637 if len > existing_len {
20638 continue;
20639 }
20640 }
20641
20642 result = Some(pair);
20643 }
20644
20645 result
20646 };
20647 let Some(pair) = pair else {
20648 return false;
20649 };
20650 pair.newline_only
20651 && buffer
20652 .chars_for_range(pair.open_range.end..range.start)
20653 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20654 .all(|c| c.is_whitespace() && c != '\n')
20655}
20656
20657fn update_uncommitted_diff_for_buffer(
20658 editor: Entity<Editor>,
20659 project: &Entity<Project>,
20660 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20661 buffer: Entity<MultiBuffer>,
20662 cx: &mut App,
20663) -> Task<()> {
20664 let mut tasks = Vec::new();
20665 project.update(cx, |project, cx| {
20666 for buffer in buffers {
20667 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20668 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20669 }
20670 }
20671 });
20672 cx.spawn(async move |cx| {
20673 let diffs = future::join_all(tasks).await;
20674 if editor
20675 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20676 .unwrap_or(false)
20677 {
20678 return;
20679 }
20680
20681 buffer
20682 .update(cx, |buffer, cx| {
20683 for diff in diffs.into_iter().flatten() {
20684 buffer.add_diff(diff, cx);
20685 }
20686 })
20687 .ok();
20688 })
20689}
20690
20691fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20692 let tab_size = tab_size.get() as usize;
20693 let mut width = offset;
20694
20695 for ch in text.chars() {
20696 width += if ch == '\t' {
20697 tab_size - (width % tab_size)
20698 } else {
20699 1
20700 };
20701 }
20702
20703 width - offset
20704}
20705
20706#[cfg(test)]
20707mod tests {
20708 use super::*;
20709
20710 #[test]
20711 fn test_string_size_with_expanded_tabs() {
20712 let nz = |val| NonZeroU32::new(val).unwrap();
20713 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20714 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20715 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20716 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20717 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20718 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20719 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20720 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20721 }
20722}
20723
20724/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20725struct WordBreakingTokenizer<'a> {
20726 input: &'a str,
20727}
20728
20729impl<'a> WordBreakingTokenizer<'a> {
20730 fn new(input: &'a str) -> Self {
20731 Self { input }
20732 }
20733}
20734
20735fn is_char_ideographic(ch: char) -> bool {
20736 use unicode_script::Script::*;
20737 use unicode_script::UnicodeScript;
20738 matches!(ch.script(), Han | Tangut | Yi)
20739}
20740
20741fn is_grapheme_ideographic(text: &str) -> bool {
20742 text.chars().any(is_char_ideographic)
20743}
20744
20745fn is_grapheme_whitespace(text: &str) -> bool {
20746 text.chars().any(|x| x.is_whitespace())
20747}
20748
20749fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20750 text.chars().next().map_or(false, |ch| {
20751 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20752 })
20753}
20754
20755#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20756enum WordBreakToken<'a> {
20757 Word { token: &'a str, grapheme_len: usize },
20758 InlineWhitespace { token: &'a str, grapheme_len: usize },
20759 Newline,
20760}
20761
20762impl<'a> Iterator for WordBreakingTokenizer<'a> {
20763 /// Yields a span, the count of graphemes in the token, and whether it was
20764 /// whitespace. Note that it also breaks at word boundaries.
20765 type Item = WordBreakToken<'a>;
20766
20767 fn next(&mut self) -> Option<Self::Item> {
20768 use unicode_segmentation::UnicodeSegmentation;
20769 if self.input.is_empty() {
20770 return None;
20771 }
20772
20773 let mut iter = self.input.graphemes(true).peekable();
20774 let mut offset = 0;
20775 let mut grapheme_len = 0;
20776 if let Some(first_grapheme) = iter.next() {
20777 let is_newline = first_grapheme == "\n";
20778 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20779 offset += first_grapheme.len();
20780 grapheme_len += 1;
20781 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20782 if let Some(grapheme) = iter.peek().copied() {
20783 if should_stay_with_preceding_ideograph(grapheme) {
20784 offset += grapheme.len();
20785 grapheme_len += 1;
20786 }
20787 }
20788 } else {
20789 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20790 let mut next_word_bound = words.peek().copied();
20791 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20792 next_word_bound = words.next();
20793 }
20794 while let Some(grapheme) = iter.peek().copied() {
20795 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20796 break;
20797 };
20798 if is_grapheme_whitespace(grapheme) != is_whitespace
20799 || (grapheme == "\n") != is_newline
20800 {
20801 break;
20802 };
20803 offset += grapheme.len();
20804 grapheme_len += 1;
20805 iter.next();
20806 }
20807 }
20808 let token = &self.input[..offset];
20809 self.input = &self.input[offset..];
20810 if token == "\n" {
20811 Some(WordBreakToken::Newline)
20812 } else if is_whitespace {
20813 Some(WordBreakToken::InlineWhitespace {
20814 token,
20815 grapheme_len,
20816 })
20817 } else {
20818 Some(WordBreakToken::Word {
20819 token,
20820 grapheme_len,
20821 })
20822 }
20823 } else {
20824 None
20825 }
20826 }
20827}
20828
20829#[test]
20830fn test_word_breaking_tokenizer() {
20831 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20832 ("", &[]),
20833 (" ", &[whitespace(" ", 2)]),
20834 ("Ʒ", &[word("Ʒ", 1)]),
20835 ("Ǽ", &[word("Ǽ", 1)]),
20836 ("⋑", &[word("⋑", 1)]),
20837 ("⋑⋑", &[word("⋑⋑", 2)]),
20838 (
20839 "原理,进而",
20840 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20841 ),
20842 (
20843 "hello world",
20844 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20845 ),
20846 (
20847 "hello, world",
20848 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20849 ),
20850 (
20851 " hello world",
20852 &[
20853 whitespace(" ", 2),
20854 word("hello", 5),
20855 whitespace(" ", 1),
20856 word("world", 5),
20857 ],
20858 ),
20859 (
20860 "这是什么 \n 钢笔",
20861 &[
20862 word("这", 1),
20863 word("是", 1),
20864 word("什", 1),
20865 word("么", 1),
20866 whitespace(" ", 1),
20867 newline(),
20868 whitespace(" ", 1),
20869 word("钢", 1),
20870 word("笔", 1),
20871 ],
20872 ),
20873 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20874 ];
20875
20876 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20877 WordBreakToken::Word {
20878 token,
20879 grapheme_len,
20880 }
20881 }
20882
20883 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20884 WordBreakToken::InlineWhitespace {
20885 token,
20886 grapheme_len,
20887 }
20888 }
20889
20890 fn newline() -> WordBreakToken<'static> {
20891 WordBreakToken::Newline
20892 }
20893
20894 for (input, result) in tests {
20895 assert_eq!(
20896 WordBreakingTokenizer::new(input)
20897 .collect::<Vec<_>>()
20898 .as_slice(),
20899 *result,
20900 );
20901 }
20902}
20903
20904fn wrap_with_prefix(
20905 line_prefix: String,
20906 unwrapped_text: String,
20907 wrap_column: usize,
20908 tab_size: NonZeroU32,
20909 preserve_existing_whitespace: bool,
20910) -> String {
20911 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20912 let mut wrapped_text = String::new();
20913 let mut current_line = line_prefix.clone();
20914
20915 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20916 let mut current_line_len = line_prefix_len;
20917 let mut in_whitespace = false;
20918 for token in tokenizer {
20919 let have_preceding_whitespace = in_whitespace;
20920 match token {
20921 WordBreakToken::Word {
20922 token,
20923 grapheme_len,
20924 } => {
20925 in_whitespace = false;
20926 if current_line_len + grapheme_len > wrap_column
20927 && current_line_len != line_prefix_len
20928 {
20929 wrapped_text.push_str(current_line.trim_end());
20930 wrapped_text.push('\n');
20931 current_line.truncate(line_prefix.len());
20932 current_line_len = line_prefix_len;
20933 }
20934 current_line.push_str(token);
20935 current_line_len += grapheme_len;
20936 }
20937 WordBreakToken::InlineWhitespace {
20938 mut token,
20939 mut grapheme_len,
20940 } => {
20941 in_whitespace = true;
20942 if have_preceding_whitespace && !preserve_existing_whitespace {
20943 continue;
20944 }
20945 if !preserve_existing_whitespace {
20946 token = " ";
20947 grapheme_len = 1;
20948 }
20949 if current_line_len + grapheme_len > wrap_column {
20950 wrapped_text.push_str(current_line.trim_end());
20951 wrapped_text.push('\n');
20952 current_line.truncate(line_prefix.len());
20953 current_line_len = line_prefix_len;
20954 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20955 current_line.push_str(token);
20956 current_line_len += grapheme_len;
20957 }
20958 }
20959 WordBreakToken::Newline => {
20960 in_whitespace = true;
20961 if preserve_existing_whitespace {
20962 wrapped_text.push_str(current_line.trim_end());
20963 wrapped_text.push('\n');
20964 current_line.truncate(line_prefix.len());
20965 current_line_len = line_prefix_len;
20966 } else if have_preceding_whitespace {
20967 continue;
20968 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20969 {
20970 wrapped_text.push_str(current_line.trim_end());
20971 wrapped_text.push('\n');
20972 current_line.truncate(line_prefix.len());
20973 current_line_len = line_prefix_len;
20974 } else if current_line_len != line_prefix_len {
20975 current_line.push(' ');
20976 current_line_len += 1;
20977 }
20978 }
20979 }
20980 }
20981
20982 if !current_line.is_empty() {
20983 wrapped_text.push_str(¤t_line);
20984 }
20985 wrapped_text
20986}
20987
20988#[test]
20989fn test_wrap_with_prefix() {
20990 assert_eq!(
20991 wrap_with_prefix(
20992 "# ".to_string(),
20993 "abcdefg".to_string(),
20994 4,
20995 NonZeroU32::new(4).unwrap(),
20996 false,
20997 ),
20998 "# abcdefg"
20999 );
21000 assert_eq!(
21001 wrap_with_prefix(
21002 "".to_string(),
21003 "\thello world".to_string(),
21004 8,
21005 NonZeroU32::new(4).unwrap(),
21006 false,
21007 ),
21008 "hello\nworld"
21009 );
21010 assert_eq!(
21011 wrap_with_prefix(
21012 "// ".to_string(),
21013 "xx \nyy zz aa bb cc".to_string(),
21014 12,
21015 NonZeroU32::new(4).unwrap(),
21016 false,
21017 ),
21018 "// xx yy zz\n// aa bb cc"
21019 );
21020 assert_eq!(
21021 wrap_with_prefix(
21022 String::new(),
21023 "这是什么 \n 钢笔".to_string(),
21024 3,
21025 NonZeroU32::new(4).unwrap(),
21026 false,
21027 ),
21028 "这是什\n么 钢\n笔"
21029 );
21030}
21031
21032pub trait CollaborationHub {
21033 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21034 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21035 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21036}
21037
21038impl CollaborationHub for Entity<Project> {
21039 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21040 self.read(cx).collaborators()
21041 }
21042
21043 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21044 self.read(cx).user_store().read(cx).participant_indices()
21045 }
21046
21047 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21048 let this = self.read(cx);
21049 let user_ids = this.collaborators().values().map(|c| c.user_id);
21050 this.user_store().read(cx).participant_names(user_ids, cx)
21051 }
21052}
21053
21054pub trait SemanticsProvider {
21055 fn hover(
21056 &self,
21057 buffer: &Entity<Buffer>,
21058 position: text::Anchor,
21059 cx: &mut App,
21060 ) -> Option<Task<Vec<project::Hover>>>;
21061
21062 fn inline_values(
21063 &self,
21064 buffer_handle: Entity<Buffer>,
21065 range: Range<text::Anchor>,
21066 cx: &mut App,
21067 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21068
21069 fn inlay_hints(
21070 &self,
21071 buffer_handle: Entity<Buffer>,
21072 range: Range<text::Anchor>,
21073 cx: &mut App,
21074 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21075
21076 fn resolve_inlay_hint(
21077 &self,
21078 hint: InlayHint,
21079 buffer_handle: Entity<Buffer>,
21080 server_id: LanguageServerId,
21081 cx: &mut App,
21082 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21083
21084 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21085
21086 fn document_highlights(
21087 &self,
21088 buffer: &Entity<Buffer>,
21089 position: text::Anchor,
21090 cx: &mut App,
21091 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21092
21093 fn definitions(
21094 &self,
21095 buffer: &Entity<Buffer>,
21096 position: text::Anchor,
21097 kind: GotoDefinitionKind,
21098 cx: &mut App,
21099 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21100
21101 fn range_for_rename(
21102 &self,
21103 buffer: &Entity<Buffer>,
21104 position: text::Anchor,
21105 cx: &mut App,
21106 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21107
21108 fn perform_rename(
21109 &self,
21110 buffer: &Entity<Buffer>,
21111 position: text::Anchor,
21112 new_name: String,
21113 cx: &mut App,
21114 ) -> Option<Task<Result<ProjectTransaction>>>;
21115}
21116
21117pub trait CompletionProvider {
21118 fn completions(
21119 &self,
21120 excerpt_id: ExcerptId,
21121 buffer: &Entity<Buffer>,
21122 buffer_position: text::Anchor,
21123 trigger: CompletionContext,
21124 window: &mut Window,
21125 cx: &mut Context<Editor>,
21126 ) -> Task<Result<Vec<CompletionResponse>>>;
21127
21128 fn resolve_completions(
21129 &self,
21130 _buffer: Entity<Buffer>,
21131 _completion_indices: Vec<usize>,
21132 _completions: Rc<RefCell<Box<[Completion]>>>,
21133 _cx: &mut Context<Editor>,
21134 ) -> Task<Result<bool>> {
21135 Task::ready(Ok(false))
21136 }
21137
21138 fn apply_additional_edits_for_completion(
21139 &self,
21140 _buffer: Entity<Buffer>,
21141 _completions: Rc<RefCell<Box<[Completion]>>>,
21142 _completion_index: usize,
21143 _push_to_history: bool,
21144 _cx: &mut Context<Editor>,
21145 ) -> Task<Result<Option<language::Transaction>>> {
21146 Task::ready(Ok(None))
21147 }
21148
21149 fn is_completion_trigger(
21150 &self,
21151 buffer: &Entity<Buffer>,
21152 position: language::Anchor,
21153 text: &str,
21154 trigger_in_words: bool,
21155 menu_is_open: bool,
21156 cx: &mut Context<Editor>,
21157 ) -> bool;
21158
21159 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21160
21161 fn sort_completions(&self) -> bool {
21162 true
21163 }
21164
21165 fn filter_completions(&self) -> bool {
21166 true
21167 }
21168}
21169
21170pub trait CodeActionProvider {
21171 fn id(&self) -> Arc<str>;
21172
21173 fn code_actions(
21174 &self,
21175 buffer: &Entity<Buffer>,
21176 range: Range<text::Anchor>,
21177 window: &mut Window,
21178 cx: &mut App,
21179 ) -> Task<Result<Vec<CodeAction>>>;
21180
21181 fn apply_code_action(
21182 &self,
21183 buffer_handle: Entity<Buffer>,
21184 action: CodeAction,
21185 excerpt_id: ExcerptId,
21186 push_to_history: bool,
21187 window: &mut Window,
21188 cx: &mut App,
21189 ) -> Task<Result<ProjectTransaction>>;
21190}
21191
21192impl CodeActionProvider for Entity<Project> {
21193 fn id(&self) -> Arc<str> {
21194 "project".into()
21195 }
21196
21197 fn code_actions(
21198 &self,
21199 buffer: &Entity<Buffer>,
21200 range: Range<text::Anchor>,
21201 _window: &mut Window,
21202 cx: &mut App,
21203 ) -> Task<Result<Vec<CodeAction>>> {
21204 self.update(cx, |project, cx| {
21205 let code_lens = project.code_lens(buffer, range.clone(), cx);
21206 let code_actions = project.code_actions(buffer, range, None, cx);
21207 cx.background_spawn(async move {
21208 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21209 Ok(code_lens
21210 .context("code lens fetch")?
21211 .into_iter()
21212 .chain(code_actions.context("code action fetch")?)
21213 .collect())
21214 })
21215 })
21216 }
21217
21218 fn apply_code_action(
21219 &self,
21220 buffer_handle: Entity<Buffer>,
21221 action: CodeAction,
21222 _excerpt_id: ExcerptId,
21223 push_to_history: bool,
21224 _window: &mut Window,
21225 cx: &mut App,
21226 ) -> Task<Result<ProjectTransaction>> {
21227 self.update(cx, |project, cx| {
21228 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21229 })
21230 }
21231}
21232
21233fn snippet_completions(
21234 project: &Project,
21235 buffer: &Entity<Buffer>,
21236 buffer_position: text::Anchor,
21237 cx: &mut App,
21238) -> Task<Result<CompletionResponse>> {
21239 let languages = buffer.read(cx).languages_at(buffer_position);
21240 let snippet_store = project.snippets().read(cx);
21241
21242 let scopes: Vec<_> = languages
21243 .iter()
21244 .filter_map(|language| {
21245 let language_name = language.lsp_id();
21246 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21247
21248 if snippets.is_empty() {
21249 None
21250 } else {
21251 Some((language.default_scope(), snippets))
21252 }
21253 })
21254 .collect();
21255
21256 if scopes.is_empty() {
21257 return Task::ready(Ok(CompletionResponse {
21258 completions: vec![],
21259 is_incomplete: false,
21260 }));
21261 }
21262
21263 let snapshot = buffer.read(cx).text_snapshot();
21264 let chars: String = snapshot
21265 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21266 .collect();
21267 let executor = cx.background_executor().clone();
21268
21269 cx.background_spawn(async move {
21270 let mut is_incomplete = false;
21271 let mut completions: Vec<Completion> = Vec::new();
21272 for (scope, snippets) in scopes.into_iter() {
21273 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21274 let mut last_word = chars
21275 .chars()
21276 .take_while(|c| classifier.is_word(*c))
21277 .collect::<String>();
21278 last_word = last_word.chars().rev().collect();
21279
21280 if last_word.is_empty() {
21281 return Ok(CompletionResponse {
21282 completions: vec![],
21283 is_incomplete: true,
21284 });
21285 }
21286
21287 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21288 let to_lsp = |point: &text::Anchor| {
21289 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21290 point_to_lsp(end)
21291 };
21292 let lsp_end = to_lsp(&buffer_position);
21293
21294 let candidates = snippets
21295 .iter()
21296 .enumerate()
21297 .flat_map(|(ix, snippet)| {
21298 snippet
21299 .prefix
21300 .iter()
21301 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21302 })
21303 .collect::<Vec<StringMatchCandidate>>();
21304
21305 const MAX_RESULTS: usize = 100;
21306 let mut matches = fuzzy::match_strings(
21307 &candidates,
21308 &last_word,
21309 last_word.chars().any(|c| c.is_uppercase()),
21310 true,
21311 MAX_RESULTS,
21312 &Default::default(),
21313 executor.clone(),
21314 )
21315 .await;
21316
21317 if matches.len() >= MAX_RESULTS {
21318 is_incomplete = true;
21319 }
21320
21321 // Remove all candidates where the query's start does not match the start of any word in the candidate
21322 if let Some(query_start) = last_word.chars().next() {
21323 matches.retain(|string_match| {
21324 split_words(&string_match.string).any(|word| {
21325 // Check that the first codepoint of the word as lowercase matches the first
21326 // codepoint of the query as lowercase
21327 word.chars()
21328 .flat_map(|codepoint| codepoint.to_lowercase())
21329 .zip(query_start.to_lowercase())
21330 .all(|(word_cp, query_cp)| word_cp == query_cp)
21331 })
21332 });
21333 }
21334
21335 let matched_strings = matches
21336 .into_iter()
21337 .map(|m| m.string)
21338 .collect::<HashSet<_>>();
21339
21340 completions.extend(snippets.iter().filter_map(|snippet| {
21341 let matching_prefix = snippet
21342 .prefix
21343 .iter()
21344 .find(|prefix| matched_strings.contains(*prefix))?;
21345 let start = as_offset - last_word.len();
21346 let start = snapshot.anchor_before(start);
21347 let range = start..buffer_position;
21348 let lsp_start = to_lsp(&start);
21349 let lsp_range = lsp::Range {
21350 start: lsp_start,
21351 end: lsp_end,
21352 };
21353 Some(Completion {
21354 replace_range: range,
21355 new_text: snippet.body.clone(),
21356 source: CompletionSource::Lsp {
21357 insert_range: None,
21358 server_id: LanguageServerId(usize::MAX),
21359 resolved: true,
21360 lsp_completion: Box::new(lsp::CompletionItem {
21361 label: snippet.prefix.first().unwrap().clone(),
21362 kind: Some(CompletionItemKind::SNIPPET),
21363 label_details: snippet.description.as_ref().map(|description| {
21364 lsp::CompletionItemLabelDetails {
21365 detail: Some(description.clone()),
21366 description: None,
21367 }
21368 }),
21369 insert_text_format: Some(InsertTextFormat::SNIPPET),
21370 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21371 lsp::InsertReplaceEdit {
21372 new_text: snippet.body.clone(),
21373 insert: lsp_range,
21374 replace: lsp_range,
21375 },
21376 )),
21377 filter_text: Some(snippet.body.clone()),
21378 sort_text: Some(char::MAX.to_string()),
21379 ..lsp::CompletionItem::default()
21380 }),
21381 lsp_defaults: None,
21382 },
21383 label: CodeLabel {
21384 text: matching_prefix.clone(),
21385 runs: Vec::new(),
21386 filter_range: 0..matching_prefix.len(),
21387 },
21388 icon_path: None,
21389 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21390 single_line: snippet.name.clone().into(),
21391 plain_text: snippet
21392 .description
21393 .clone()
21394 .map(|description| description.into()),
21395 }),
21396 insert_text_mode: None,
21397 confirm: None,
21398 })
21399 }))
21400 }
21401
21402 Ok(CompletionResponse {
21403 completions,
21404 is_incomplete,
21405 })
21406 })
21407}
21408
21409impl CompletionProvider for Entity<Project> {
21410 fn completions(
21411 &self,
21412 _excerpt_id: ExcerptId,
21413 buffer: &Entity<Buffer>,
21414 buffer_position: text::Anchor,
21415 options: CompletionContext,
21416 _window: &mut Window,
21417 cx: &mut Context<Editor>,
21418 ) -> Task<Result<Vec<CompletionResponse>>> {
21419 self.update(cx, |project, cx| {
21420 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21421 let project_completions = project.completions(buffer, buffer_position, options, cx);
21422 cx.background_spawn(async move {
21423 let mut responses = project_completions.await?;
21424 let snippets = snippets.await?;
21425 if !snippets.completions.is_empty() {
21426 responses.push(snippets);
21427 }
21428 Ok(responses)
21429 })
21430 })
21431 }
21432
21433 fn resolve_completions(
21434 &self,
21435 buffer: Entity<Buffer>,
21436 completion_indices: Vec<usize>,
21437 completions: Rc<RefCell<Box<[Completion]>>>,
21438 cx: &mut Context<Editor>,
21439 ) -> Task<Result<bool>> {
21440 self.update(cx, |project, cx| {
21441 project.lsp_store().update(cx, |lsp_store, cx| {
21442 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21443 })
21444 })
21445 }
21446
21447 fn apply_additional_edits_for_completion(
21448 &self,
21449 buffer: Entity<Buffer>,
21450 completions: Rc<RefCell<Box<[Completion]>>>,
21451 completion_index: usize,
21452 push_to_history: bool,
21453 cx: &mut Context<Editor>,
21454 ) -> Task<Result<Option<language::Transaction>>> {
21455 self.update(cx, |project, cx| {
21456 project.lsp_store().update(cx, |lsp_store, cx| {
21457 lsp_store.apply_additional_edits_for_completion(
21458 buffer,
21459 completions,
21460 completion_index,
21461 push_to_history,
21462 cx,
21463 )
21464 })
21465 })
21466 }
21467
21468 fn is_completion_trigger(
21469 &self,
21470 buffer: &Entity<Buffer>,
21471 position: language::Anchor,
21472 text: &str,
21473 trigger_in_words: bool,
21474 menu_is_open: bool,
21475 cx: &mut Context<Editor>,
21476 ) -> bool {
21477 let mut chars = text.chars();
21478 let char = if let Some(char) = chars.next() {
21479 char
21480 } else {
21481 return false;
21482 };
21483 if chars.next().is_some() {
21484 return false;
21485 }
21486
21487 let buffer = buffer.read(cx);
21488 let snapshot = buffer.snapshot();
21489 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21490 return false;
21491 }
21492 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21493 if trigger_in_words && classifier.is_word(char) {
21494 return true;
21495 }
21496
21497 buffer.completion_triggers().contains(text)
21498 }
21499}
21500
21501impl SemanticsProvider for Entity<Project> {
21502 fn hover(
21503 &self,
21504 buffer: &Entity<Buffer>,
21505 position: text::Anchor,
21506 cx: &mut App,
21507 ) -> Option<Task<Vec<project::Hover>>> {
21508 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21509 }
21510
21511 fn document_highlights(
21512 &self,
21513 buffer: &Entity<Buffer>,
21514 position: text::Anchor,
21515 cx: &mut App,
21516 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21517 Some(self.update(cx, |project, cx| {
21518 project.document_highlights(buffer, position, cx)
21519 }))
21520 }
21521
21522 fn definitions(
21523 &self,
21524 buffer: &Entity<Buffer>,
21525 position: text::Anchor,
21526 kind: GotoDefinitionKind,
21527 cx: &mut App,
21528 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21529 Some(self.update(cx, |project, cx| match kind {
21530 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21531 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21532 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21533 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21534 }))
21535 }
21536
21537 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21538 // TODO: make this work for remote projects
21539 self.update(cx, |project, cx| {
21540 if project
21541 .active_debug_session(cx)
21542 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21543 {
21544 return true;
21545 }
21546
21547 buffer.update(cx, |buffer, cx| {
21548 project.any_language_server_supports_inlay_hints(buffer, cx)
21549 })
21550 })
21551 }
21552
21553 fn inline_values(
21554 &self,
21555 buffer_handle: Entity<Buffer>,
21556 range: Range<text::Anchor>,
21557 cx: &mut App,
21558 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21559 self.update(cx, |project, cx| {
21560 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21561
21562 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21563 })
21564 }
21565
21566 fn inlay_hints(
21567 &self,
21568 buffer_handle: Entity<Buffer>,
21569 range: Range<text::Anchor>,
21570 cx: &mut App,
21571 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21572 Some(self.update(cx, |project, cx| {
21573 project.inlay_hints(buffer_handle, range, cx)
21574 }))
21575 }
21576
21577 fn resolve_inlay_hint(
21578 &self,
21579 hint: InlayHint,
21580 buffer_handle: Entity<Buffer>,
21581 server_id: LanguageServerId,
21582 cx: &mut App,
21583 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21584 Some(self.update(cx, |project, cx| {
21585 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21586 }))
21587 }
21588
21589 fn range_for_rename(
21590 &self,
21591 buffer: &Entity<Buffer>,
21592 position: text::Anchor,
21593 cx: &mut App,
21594 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21595 Some(self.update(cx, |project, cx| {
21596 let buffer = buffer.clone();
21597 let task = project.prepare_rename(buffer.clone(), position, cx);
21598 cx.spawn(async move |_, cx| {
21599 Ok(match task.await? {
21600 PrepareRenameResponse::Success(range) => Some(range),
21601 PrepareRenameResponse::InvalidPosition => None,
21602 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21603 // Fallback on using TreeSitter info to determine identifier range
21604 buffer.read_with(cx, |buffer, _| {
21605 let snapshot = buffer.snapshot();
21606 let (range, kind) = snapshot.surrounding_word(position);
21607 if kind != Some(CharKind::Word) {
21608 return None;
21609 }
21610 Some(
21611 snapshot.anchor_before(range.start)
21612 ..snapshot.anchor_after(range.end),
21613 )
21614 })?
21615 }
21616 })
21617 })
21618 }))
21619 }
21620
21621 fn perform_rename(
21622 &self,
21623 buffer: &Entity<Buffer>,
21624 position: text::Anchor,
21625 new_name: String,
21626 cx: &mut App,
21627 ) -> Option<Task<Result<ProjectTransaction>>> {
21628 Some(self.update(cx, |project, cx| {
21629 project.perform_rename(buffer.clone(), position, new_name, cx)
21630 }))
21631 }
21632}
21633
21634fn inlay_hint_settings(
21635 location: Anchor,
21636 snapshot: &MultiBufferSnapshot,
21637 cx: &mut Context<Editor>,
21638) -> InlayHintSettings {
21639 let file = snapshot.file_at(location);
21640 let language = snapshot.language_at(location).map(|l| l.name());
21641 language_settings(language, file, cx).inlay_hints
21642}
21643
21644fn consume_contiguous_rows(
21645 contiguous_row_selections: &mut Vec<Selection<Point>>,
21646 selection: &Selection<Point>,
21647 display_map: &DisplaySnapshot,
21648 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21649) -> (MultiBufferRow, MultiBufferRow) {
21650 contiguous_row_selections.push(selection.clone());
21651 let start_row = MultiBufferRow(selection.start.row);
21652 let mut end_row = ending_row(selection, display_map);
21653
21654 while let Some(next_selection) = selections.peek() {
21655 if next_selection.start.row <= end_row.0 {
21656 end_row = ending_row(next_selection, display_map);
21657 contiguous_row_selections.push(selections.next().unwrap().clone());
21658 } else {
21659 break;
21660 }
21661 }
21662 (start_row, end_row)
21663}
21664
21665fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21666 if next_selection.end.column > 0 || next_selection.is_empty() {
21667 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21668 } else {
21669 MultiBufferRow(next_selection.end.row)
21670 }
21671}
21672
21673impl EditorSnapshot {
21674 pub fn remote_selections_in_range<'a>(
21675 &'a self,
21676 range: &'a Range<Anchor>,
21677 collaboration_hub: &dyn CollaborationHub,
21678 cx: &'a App,
21679 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21680 let participant_names = collaboration_hub.user_names(cx);
21681 let participant_indices = collaboration_hub.user_participant_indices(cx);
21682 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21683 let collaborators_by_replica_id = collaborators_by_peer_id
21684 .values()
21685 .map(|collaborator| (collaborator.replica_id, collaborator))
21686 .collect::<HashMap<_, _>>();
21687 self.buffer_snapshot
21688 .selections_in_range(range, false)
21689 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21690 if replica_id == AGENT_REPLICA_ID {
21691 Some(RemoteSelection {
21692 replica_id,
21693 selection,
21694 cursor_shape,
21695 line_mode,
21696 collaborator_id: CollaboratorId::Agent,
21697 user_name: Some("Agent".into()),
21698 color: cx.theme().players().agent(),
21699 })
21700 } else {
21701 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21702 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21703 let user_name = participant_names.get(&collaborator.user_id).cloned();
21704 Some(RemoteSelection {
21705 replica_id,
21706 selection,
21707 cursor_shape,
21708 line_mode,
21709 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21710 user_name,
21711 color: if let Some(index) = participant_index {
21712 cx.theme().players().color_for_participant(index.0)
21713 } else {
21714 cx.theme().players().absent()
21715 },
21716 })
21717 }
21718 })
21719 }
21720
21721 pub fn hunks_for_ranges(
21722 &self,
21723 ranges: impl IntoIterator<Item = Range<Point>>,
21724 ) -> Vec<MultiBufferDiffHunk> {
21725 let mut hunks = Vec::new();
21726 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21727 HashMap::default();
21728 for query_range in ranges {
21729 let query_rows =
21730 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21731 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21732 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21733 ) {
21734 // Include deleted hunks that are adjacent to the query range, because
21735 // otherwise they would be missed.
21736 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21737 if hunk.status().is_deleted() {
21738 intersects_range |= hunk.row_range.start == query_rows.end;
21739 intersects_range |= hunk.row_range.end == query_rows.start;
21740 }
21741 if intersects_range {
21742 if !processed_buffer_rows
21743 .entry(hunk.buffer_id)
21744 .or_default()
21745 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21746 {
21747 continue;
21748 }
21749 hunks.push(hunk);
21750 }
21751 }
21752 }
21753
21754 hunks
21755 }
21756
21757 fn display_diff_hunks_for_rows<'a>(
21758 &'a self,
21759 display_rows: Range<DisplayRow>,
21760 folded_buffers: &'a HashSet<BufferId>,
21761 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21762 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21763 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21764
21765 self.buffer_snapshot
21766 .diff_hunks_in_range(buffer_start..buffer_end)
21767 .filter_map(|hunk| {
21768 if folded_buffers.contains(&hunk.buffer_id) {
21769 return None;
21770 }
21771
21772 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21773 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21774
21775 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21776 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21777
21778 let display_hunk = if hunk_display_start.column() != 0 {
21779 DisplayDiffHunk::Folded {
21780 display_row: hunk_display_start.row(),
21781 }
21782 } else {
21783 let mut end_row = hunk_display_end.row();
21784 if hunk_display_end.column() > 0 {
21785 end_row.0 += 1;
21786 }
21787 let is_created_file = hunk.is_created_file();
21788 DisplayDiffHunk::Unfolded {
21789 status: hunk.status(),
21790 diff_base_byte_range: hunk.diff_base_byte_range,
21791 display_row_range: hunk_display_start.row()..end_row,
21792 multi_buffer_range: Anchor::range_in_buffer(
21793 hunk.excerpt_id,
21794 hunk.buffer_id,
21795 hunk.buffer_range,
21796 ),
21797 is_created_file,
21798 }
21799 };
21800
21801 Some(display_hunk)
21802 })
21803 }
21804
21805 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21806 self.display_snapshot.buffer_snapshot.language_at(position)
21807 }
21808
21809 pub fn is_focused(&self) -> bool {
21810 self.is_focused
21811 }
21812
21813 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21814 self.placeholder_text.as_ref()
21815 }
21816
21817 pub fn scroll_position(&self) -> gpui::Point<f32> {
21818 self.scroll_anchor.scroll_position(&self.display_snapshot)
21819 }
21820
21821 fn gutter_dimensions(
21822 &self,
21823 font_id: FontId,
21824 font_size: Pixels,
21825 max_line_number_width: Pixels,
21826 cx: &App,
21827 ) -> Option<GutterDimensions> {
21828 if !self.show_gutter {
21829 return None;
21830 }
21831
21832 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21833 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21834
21835 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21836 matches!(
21837 ProjectSettings::get_global(cx).git.git_gutter,
21838 Some(GitGutterSetting::TrackedFiles)
21839 )
21840 });
21841 let gutter_settings = EditorSettings::get_global(cx).gutter;
21842 let show_line_numbers = self
21843 .show_line_numbers
21844 .unwrap_or(gutter_settings.line_numbers);
21845 let line_gutter_width = if show_line_numbers {
21846 // Avoid flicker-like gutter resizes when the line number gains another digit by
21847 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21848 let min_width_for_number_on_gutter =
21849 ch_advance * gutter_settings.min_line_number_digits as f32;
21850 max_line_number_width.max(min_width_for_number_on_gutter)
21851 } else {
21852 0.0.into()
21853 };
21854
21855 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21856 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21857
21858 let git_blame_entries_width =
21859 self.git_blame_gutter_max_author_length
21860 .map(|max_author_length| {
21861 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21862 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21863
21864 /// The number of characters to dedicate to gaps and margins.
21865 const SPACING_WIDTH: usize = 4;
21866
21867 let max_char_count = max_author_length.min(renderer.max_author_length())
21868 + ::git::SHORT_SHA_LENGTH
21869 + MAX_RELATIVE_TIMESTAMP.len()
21870 + SPACING_WIDTH;
21871
21872 ch_advance * max_char_count
21873 });
21874
21875 let is_singleton = self.buffer_snapshot.is_singleton();
21876
21877 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21878 left_padding += if !is_singleton {
21879 ch_width * 4.0
21880 } else if show_runnables || show_breakpoints {
21881 ch_width * 3.0
21882 } else if show_git_gutter && show_line_numbers {
21883 ch_width * 2.0
21884 } else if show_git_gutter || show_line_numbers {
21885 ch_width
21886 } else {
21887 px(0.)
21888 };
21889
21890 let shows_folds = is_singleton && gutter_settings.folds;
21891
21892 let right_padding = if shows_folds && show_line_numbers {
21893 ch_width * 4.0
21894 } else if shows_folds || (!is_singleton && show_line_numbers) {
21895 ch_width * 3.0
21896 } else if show_line_numbers {
21897 ch_width
21898 } else {
21899 px(0.)
21900 };
21901
21902 Some(GutterDimensions {
21903 left_padding,
21904 right_padding,
21905 width: line_gutter_width + left_padding + right_padding,
21906 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21907 git_blame_entries_width,
21908 })
21909 }
21910
21911 pub fn render_crease_toggle(
21912 &self,
21913 buffer_row: MultiBufferRow,
21914 row_contains_cursor: bool,
21915 editor: Entity<Editor>,
21916 window: &mut Window,
21917 cx: &mut App,
21918 ) -> Option<AnyElement> {
21919 let folded = self.is_line_folded(buffer_row);
21920 let mut is_foldable = false;
21921
21922 if let Some(crease) = self
21923 .crease_snapshot
21924 .query_row(buffer_row, &self.buffer_snapshot)
21925 {
21926 is_foldable = true;
21927 match crease {
21928 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21929 if let Some(render_toggle) = render_toggle {
21930 let toggle_callback =
21931 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21932 if folded {
21933 editor.update(cx, |editor, cx| {
21934 editor.fold_at(buffer_row, window, cx)
21935 });
21936 } else {
21937 editor.update(cx, |editor, cx| {
21938 editor.unfold_at(buffer_row, window, cx)
21939 });
21940 }
21941 });
21942 return Some((render_toggle)(
21943 buffer_row,
21944 folded,
21945 toggle_callback,
21946 window,
21947 cx,
21948 ));
21949 }
21950 }
21951 }
21952 }
21953
21954 is_foldable |= self.starts_indent(buffer_row);
21955
21956 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21957 Some(
21958 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21959 .toggle_state(folded)
21960 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21961 if folded {
21962 this.unfold_at(buffer_row, window, cx);
21963 } else {
21964 this.fold_at(buffer_row, window, cx);
21965 }
21966 }))
21967 .into_any_element(),
21968 )
21969 } else {
21970 None
21971 }
21972 }
21973
21974 pub fn render_crease_trailer(
21975 &self,
21976 buffer_row: MultiBufferRow,
21977 window: &mut Window,
21978 cx: &mut App,
21979 ) -> Option<AnyElement> {
21980 let folded = self.is_line_folded(buffer_row);
21981 if let Crease::Inline { render_trailer, .. } = self
21982 .crease_snapshot
21983 .query_row(buffer_row, &self.buffer_snapshot)?
21984 {
21985 let render_trailer = render_trailer.as_ref()?;
21986 Some(render_trailer(buffer_row, folded, window, cx))
21987 } else {
21988 None
21989 }
21990 }
21991}
21992
21993impl Deref for EditorSnapshot {
21994 type Target = DisplaySnapshot;
21995
21996 fn deref(&self) -> &Self::Target {
21997 &self.display_snapshot
21998 }
21999}
22000
22001#[derive(Clone, Debug, PartialEq, Eq)]
22002pub enum EditorEvent {
22003 InputIgnored {
22004 text: Arc<str>,
22005 },
22006 InputHandled {
22007 utf16_range_to_replace: Option<Range<isize>>,
22008 text: Arc<str>,
22009 },
22010 ExcerptsAdded {
22011 buffer: Entity<Buffer>,
22012 predecessor: ExcerptId,
22013 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22014 },
22015 ExcerptsRemoved {
22016 ids: Vec<ExcerptId>,
22017 removed_buffer_ids: Vec<BufferId>,
22018 },
22019 BufferFoldToggled {
22020 ids: Vec<ExcerptId>,
22021 folded: bool,
22022 },
22023 ExcerptsEdited {
22024 ids: Vec<ExcerptId>,
22025 },
22026 ExcerptsExpanded {
22027 ids: Vec<ExcerptId>,
22028 },
22029 BufferEdited,
22030 Edited {
22031 transaction_id: clock::Lamport,
22032 },
22033 Reparsed(BufferId),
22034 Focused,
22035 FocusedIn,
22036 Blurred,
22037 DirtyChanged,
22038 Saved,
22039 TitleChanged,
22040 DiffBaseChanged,
22041 SelectionsChanged {
22042 local: bool,
22043 },
22044 ScrollPositionChanged {
22045 local: bool,
22046 autoscroll: bool,
22047 },
22048 Closed,
22049 TransactionUndone {
22050 transaction_id: clock::Lamport,
22051 },
22052 TransactionBegun {
22053 transaction_id: clock::Lamport,
22054 },
22055 Reloaded,
22056 CursorShapeChanged,
22057 PushedToNavHistory {
22058 anchor: Anchor,
22059 is_deactivate: bool,
22060 },
22061}
22062
22063impl EventEmitter<EditorEvent> for Editor {}
22064
22065impl Focusable for Editor {
22066 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22067 self.focus_handle.clone()
22068 }
22069}
22070
22071impl Render for Editor {
22072 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22073 let settings = ThemeSettings::get_global(cx);
22074
22075 let mut text_style = match self.mode {
22076 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22077 color: cx.theme().colors().editor_foreground,
22078 font_family: settings.ui_font.family.clone(),
22079 font_features: settings.ui_font.features.clone(),
22080 font_fallbacks: settings.ui_font.fallbacks.clone(),
22081 font_size: rems(0.875).into(),
22082 font_weight: settings.ui_font.weight,
22083 line_height: relative(settings.buffer_line_height.value()),
22084 ..Default::default()
22085 },
22086 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22087 color: cx.theme().colors().editor_foreground,
22088 font_family: settings.buffer_font.family.clone(),
22089 font_features: settings.buffer_font.features.clone(),
22090 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22091 font_size: settings.buffer_font_size(cx).into(),
22092 font_weight: settings.buffer_font.weight,
22093 line_height: relative(settings.buffer_line_height.value()),
22094 ..Default::default()
22095 },
22096 };
22097 if let Some(text_style_refinement) = &self.text_style_refinement {
22098 text_style.refine(text_style_refinement)
22099 }
22100
22101 let background = match self.mode {
22102 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22103 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22104 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22105 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22106 };
22107
22108 EditorElement::new(
22109 &cx.entity(),
22110 EditorStyle {
22111 background,
22112 local_player: cx.theme().players().local(),
22113 text: text_style,
22114 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22115 syntax: cx.theme().syntax().clone(),
22116 status: cx.theme().status().clone(),
22117 inlay_hints_style: make_inlay_hints_style(cx),
22118 inline_completion_styles: make_suggestion_styles(cx),
22119 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22120 show_underlines: self.diagnostics_enabled(),
22121 },
22122 )
22123 }
22124}
22125
22126impl EntityInputHandler for Editor {
22127 fn text_for_range(
22128 &mut self,
22129 range_utf16: Range<usize>,
22130 adjusted_range: &mut Option<Range<usize>>,
22131 _: &mut Window,
22132 cx: &mut Context<Self>,
22133 ) -> Option<String> {
22134 let snapshot = self.buffer.read(cx).read(cx);
22135 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22136 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22137 if (start.0..end.0) != range_utf16 {
22138 adjusted_range.replace(start.0..end.0);
22139 }
22140 Some(snapshot.text_for_range(start..end).collect())
22141 }
22142
22143 fn selected_text_range(
22144 &mut self,
22145 ignore_disabled_input: bool,
22146 _: &mut Window,
22147 cx: &mut Context<Self>,
22148 ) -> Option<UTF16Selection> {
22149 // Prevent the IME menu from appearing when holding down an alphabetic key
22150 // while input is disabled.
22151 if !ignore_disabled_input && !self.input_enabled {
22152 return None;
22153 }
22154
22155 let selection = self.selections.newest::<OffsetUtf16>(cx);
22156 let range = selection.range();
22157
22158 Some(UTF16Selection {
22159 range: range.start.0..range.end.0,
22160 reversed: selection.reversed,
22161 })
22162 }
22163
22164 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22165 let snapshot = self.buffer.read(cx).read(cx);
22166 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22167 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22168 }
22169
22170 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22171 self.clear_highlights::<InputComposition>(cx);
22172 self.ime_transaction.take();
22173 }
22174
22175 fn replace_text_in_range(
22176 &mut self,
22177 range_utf16: Option<Range<usize>>,
22178 text: &str,
22179 window: &mut Window,
22180 cx: &mut Context<Self>,
22181 ) {
22182 if !self.input_enabled {
22183 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22184 return;
22185 }
22186
22187 self.transact(window, cx, |this, window, cx| {
22188 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22189 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22190 Some(this.selection_replacement_ranges(range_utf16, cx))
22191 } else {
22192 this.marked_text_ranges(cx)
22193 };
22194
22195 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22196 let newest_selection_id = this.selections.newest_anchor().id;
22197 this.selections
22198 .all::<OffsetUtf16>(cx)
22199 .iter()
22200 .zip(ranges_to_replace.iter())
22201 .find_map(|(selection, range)| {
22202 if selection.id == newest_selection_id {
22203 Some(
22204 (range.start.0 as isize - selection.head().0 as isize)
22205 ..(range.end.0 as isize - selection.head().0 as isize),
22206 )
22207 } else {
22208 None
22209 }
22210 })
22211 });
22212
22213 cx.emit(EditorEvent::InputHandled {
22214 utf16_range_to_replace: range_to_replace,
22215 text: text.into(),
22216 });
22217
22218 if let Some(new_selected_ranges) = new_selected_ranges {
22219 this.change_selections(None, window, cx, |selections| {
22220 selections.select_ranges(new_selected_ranges)
22221 });
22222 this.backspace(&Default::default(), window, cx);
22223 }
22224
22225 this.handle_input(text, window, cx);
22226 });
22227
22228 if let Some(transaction) = self.ime_transaction {
22229 self.buffer.update(cx, |buffer, cx| {
22230 buffer.group_until_transaction(transaction, cx);
22231 });
22232 }
22233
22234 self.unmark_text(window, cx);
22235 }
22236
22237 fn replace_and_mark_text_in_range(
22238 &mut self,
22239 range_utf16: Option<Range<usize>>,
22240 text: &str,
22241 new_selected_range_utf16: Option<Range<usize>>,
22242 window: &mut Window,
22243 cx: &mut Context<Self>,
22244 ) {
22245 if !self.input_enabled {
22246 return;
22247 }
22248
22249 let transaction = self.transact(window, cx, |this, window, cx| {
22250 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22251 let snapshot = this.buffer.read(cx).read(cx);
22252 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22253 for marked_range in &mut marked_ranges {
22254 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22255 marked_range.start.0 += relative_range_utf16.start;
22256 marked_range.start =
22257 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22258 marked_range.end =
22259 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22260 }
22261 }
22262 Some(marked_ranges)
22263 } else if let Some(range_utf16) = range_utf16 {
22264 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22265 Some(this.selection_replacement_ranges(range_utf16, cx))
22266 } else {
22267 None
22268 };
22269
22270 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22271 let newest_selection_id = this.selections.newest_anchor().id;
22272 this.selections
22273 .all::<OffsetUtf16>(cx)
22274 .iter()
22275 .zip(ranges_to_replace.iter())
22276 .find_map(|(selection, range)| {
22277 if selection.id == newest_selection_id {
22278 Some(
22279 (range.start.0 as isize - selection.head().0 as isize)
22280 ..(range.end.0 as isize - selection.head().0 as isize),
22281 )
22282 } else {
22283 None
22284 }
22285 })
22286 });
22287
22288 cx.emit(EditorEvent::InputHandled {
22289 utf16_range_to_replace: range_to_replace,
22290 text: text.into(),
22291 });
22292
22293 if let Some(ranges) = ranges_to_replace {
22294 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22295 }
22296
22297 let marked_ranges = {
22298 let snapshot = this.buffer.read(cx).read(cx);
22299 this.selections
22300 .disjoint_anchors()
22301 .iter()
22302 .map(|selection| {
22303 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22304 })
22305 .collect::<Vec<_>>()
22306 };
22307
22308 if text.is_empty() {
22309 this.unmark_text(window, cx);
22310 } else {
22311 this.highlight_text::<InputComposition>(
22312 marked_ranges.clone(),
22313 HighlightStyle {
22314 underline: Some(UnderlineStyle {
22315 thickness: px(1.),
22316 color: None,
22317 wavy: false,
22318 }),
22319 ..Default::default()
22320 },
22321 cx,
22322 );
22323 }
22324
22325 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22326 let use_autoclose = this.use_autoclose;
22327 let use_auto_surround = this.use_auto_surround;
22328 this.set_use_autoclose(false);
22329 this.set_use_auto_surround(false);
22330 this.handle_input(text, window, cx);
22331 this.set_use_autoclose(use_autoclose);
22332 this.set_use_auto_surround(use_auto_surround);
22333
22334 if let Some(new_selected_range) = new_selected_range_utf16 {
22335 let snapshot = this.buffer.read(cx).read(cx);
22336 let new_selected_ranges = marked_ranges
22337 .into_iter()
22338 .map(|marked_range| {
22339 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22340 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22341 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22342 snapshot.clip_offset_utf16(new_start, Bias::Left)
22343 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22344 })
22345 .collect::<Vec<_>>();
22346
22347 drop(snapshot);
22348 this.change_selections(None, window, cx, |selections| {
22349 selections.select_ranges(new_selected_ranges)
22350 });
22351 }
22352 });
22353
22354 self.ime_transaction = self.ime_transaction.or(transaction);
22355 if let Some(transaction) = self.ime_transaction {
22356 self.buffer.update(cx, |buffer, cx| {
22357 buffer.group_until_transaction(transaction, cx);
22358 });
22359 }
22360
22361 if self.text_highlights::<InputComposition>(cx).is_none() {
22362 self.ime_transaction.take();
22363 }
22364 }
22365
22366 fn bounds_for_range(
22367 &mut self,
22368 range_utf16: Range<usize>,
22369 element_bounds: gpui::Bounds<Pixels>,
22370 window: &mut Window,
22371 cx: &mut Context<Self>,
22372 ) -> Option<gpui::Bounds<Pixels>> {
22373 let text_layout_details = self.text_layout_details(window);
22374 let gpui::Size {
22375 width: em_width,
22376 height: line_height,
22377 } = self.character_size(window);
22378
22379 let snapshot = self.snapshot(window, cx);
22380 let scroll_position = snapshot.scroll_position();
22381 let scroll_left = scroll_position.x * em_width;
22382
22383 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22384 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22385 + self.gutter_dimensions.width
22386 + self.gutter_dimensions.margin;
22387 let y = line_height * (start.row().as_f32() - scroll_position.y);
22388
22389 Some(Bounds {
22390 origin: element_bounds.origin + point(x, y),
22391 size: size(em_width, line_height),
22392 })
22393 }
22394
22395 fn character_index_for_point(
22396 &mut self,
22397 point: gpui::Point<Pixels>,
22398 _window: &mut Window,
22399 _cx: &mut Context<Self>,
22400 ) -> Option<usize> {
22401 let position_map = self.last_position_map.as_ref()?;
22402 if !position_map.text_hitbox.contains(&point) {
22403 return None;
22404 }
22405 let display_point = position_map.point_for_position(point).previous_valid;
22406 let anchor = position_map
22407 .snapshot
22408 .display_point_to_anchor(display_point, Bias::Left);
22409 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22410 Some(utf16_offset.0)
22411 }
22412}
22413
22414trait SelectionExt {
22415 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22416 fn spanned_rows(
22417 &self,
22418 include_end_if_at_line_start: bool,
22419 map: &DisplaySnapshot,
22420 ) -> Range<MultiBufferRow>;
22421}
22422
22423impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22424 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22425 let start = self
22426 .start
22427 .to_point(&map.buffer_snapshot)
22428 .to_display_point(map);
22429 let end = self
22430 .end
22431 .to_point(&map.buffer_snapshot)
22432 .to_display_point(map);
22433 if self.reversed {
22434 end..start
22435 } else {
22436 start..end
22437 }
22438 }
22439
22440 fn spanned_rows(
22441 &self,
22442 include_end_if_at_line_start: bool,
22443 map: &DisplaySnapshot,
22444 ) -> Range<MultiBufferRow> {
22445 let start = self.start.to_point(&map.buffer_snapshot);
22446 let mut end = self.end.to_point(&map.buffer_snapshot);
22447 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22448 end.row -= 1;
22449 }
22450
22451 let buffer_start = map.prev_line_boundary(start).0;
22452 let buffer_end = map.next_line_boundary(end).0;
22453 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22454 }
22455}
22456
22457impl<T: InvalidationRegion> InvalidationStack<T> {
22458 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22459 where
22460 S: Clone + ToOffset,
22461 {
22462 while let Some(region) = self.last() {
22463 let all_selections_inside_invalidation_ranges =
22464 if selections.len() == region.ranges().len() {
22465 selections
22466 .iter()
22467 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22468 .all(|(selection, invalidation_range)| {
22469 let head = selection.head().to_offset(buffer);
22470 invalidation_range.start <= head && invalidation_range.end >= head
22471 })
22472 } else {
22473 false
22474 };
22475
22476 if all_selections_inside_invalidation_ranges {
22477 break;
22478 } else {
22479 self.pop();
22480 }
22481 }
22482 }
22483}
22484
22485impl<T> Default for InvalidationStack<T> {
22486 fn default() -> Self {
22487 Self(Default::default())
22488 }
22489}
22490
22491impl<T> Deref for InvalidationStack<T> {
22492 type Target = Vec<T>;
22493
22494 fn deref(&self) -> &Self::Target {
22495 &self.0
22496 }
22497}
22498
22499impl<T> DerefMut for InvalidationStack<T> {
22500 fn deref_mut(&mut self) -> &mut Self::Target {
22501 &mut self.0
22502 }
22503}
22504
22505impl InvalidationRegion for SnippetState {
22506 fn ranges(&self) -> &[Range<Anchor>] {
22507 &self.ranges[self.active_index]
22508 }
22509}
22510
22511fn inline_completion_edit_text(
22512 current_snapshot: &BufferSnapshot,
22513 edits: &[(Range<Anchor>, String)],
22514 edit_preview: &EditPreview,
22515 include_deletions: bool,
22516 cx: &App,
22517) -> HighlightedText {
22518 let edits = edits
22519 .iter()
22520 .map(|(anchor, text)| {
22521 (
22522 anchor.start.text_anchor..anchor.end.text_anchor,
22523 text.clone(),
22524 )
22525 })
22526 .collect::<Vec<_>>();
22527
22528 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22529}
22530
22531pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22532 match severity {
22533 lsp::DiagnosticSeverity::ERROR => colors.error,
22534 lsp::DiagnosticSeverity::WARNING => colors.warning,
22535 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22536 lsp::DiagnosticSeverity::HINT => colors.info,
22537 _ => colors.ignored,
22538 }
22539}
22540
22541pub fn styled_runs_for_code_label<'a>(
22542 label: &'a CodeLabel,
22543 syntax_theme: &'a theme::SyntaxTheme,
22544) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22545 let fade_out = HighlightStyle {
22546 fade_out: Some(0.35),
22547 ..Default::default()
22548 };
22549
22550 let mut prev_end = label.filter_range.end;
22551 label
22552 .runs
22553 .iter()
22554 .enumerate()
22555 .flat_map(move |(ix, (range, highlight_id))| {
22556 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22557 style
22558 } else {
22559 return Default::default();
22560 };
22561 let mut muted_style = style;
22562 muted_style.highlight(fade_out);
22563
22564 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22565 if range.start >= label.filter_range.end {
22566 if range.start > prev_end {
22567 runs.push((prev_end..range.start, fade_out));
22568 }
22569 runs.push((range.clone(), muted_style));
22570 } else if range.end <= label.filter_range.end {
22571 runs.push((range.clone(), style));
22572 } else {
22573 runs.push((range.start..label.filter_range.end, style));
22574 runs.push((label.filter_range.end..range.end, muted_style));
22575 }
22576 prev_end = cmp::max(prev_end, range.end);
22577
22578 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22579 runs.push((prev_end..label.text.len(), fade_out));
22580 }
22581
22582 runs
22583 })
22584}
22585
22586pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22587 let mut prev_index = 0;
22588 let mut prev_codepoint: Option<char> = None;
22589 text.char_indices()
22590 .chain([(text.len(), '\0')])
22591 .filter_map(move |(index, codepoint)| {
22592 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22593 let is_boundary = index == text.len()
22594 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22595 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22596 if is_boundary {
22597 let chunk = &text[prev_index..index];
22598 prev_index = index;
22599 Some(chunk)
22600 } else {
22601 None
22602 }
22603 })
22604}
22605
22606pub trait RangeToAnchorExt: Sized {
22607 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22608
22609 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22610 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22611 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22612 }
22613}
22614
22615impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22616 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22617 let start_offset = self.start.to_offset(snapshot);
22618 let end_offset = self.end.to_offset(snapshot);
22619 if start_offset == end_offset {
22620 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22621 } else {
22622 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22623 }
22624 }
22625}
22626
22627pub trait RowExt {
22628 fn as_f32(&self) -> f32;
22629
22630 fn next_row(&self) -> Self;
22631
22632 fn previous_row(&self) -> Self;
22633
22634 fn minus(&self, other: Self) -> u32;
22635}
22636
22637impl RowExt for DisplayRow {
22638 fn as_f32(&self) -> f32 {
22639 self.0 as f32
22640 }
22641
22642 fn next_row(&self) -> Self {
22643 Self(self.0 + 1)
22644 }
22645
22646 fn previous_row(&self) -> Self {
22647 Self(self.0.saturating_sub(1))
22648 }
22649
22650 fn minus(&self, other: Self) -> u32 {
22651 self.0 - other.0
22652 }
22653}
22654
22655impl RowExt for MultiBufferRow {
22656 fn as_f32(&self) -> f32 {
22657 self.0 as f32
22658 }
22659
22660 fn next_row(&self) -> Self {
22661 Self(self.0 + 1)
22662 }
22663
22664 fn previous_row(&self) -> Self {
22665 Self(self.0.saturating_sub(1))
22666 }
22667
22668 fn minus(&self, other: Self) -> u32 {
22669 self.0 - other.0
22670 }
22671}
22672
22673trait RowRangeExt {
22674 type Row;
22675
22676 fn len(&self) -> usize;
22677
22678 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22679}
22680
22681impl RowRangeExt for Range<MultiBufferRow> {
22682 type Row = MultiBufferRow;
22683
22684 fn len(&self) -> usize {
22685 (self.end.0 - self.start.0) as usize
22686 }
22687
22688 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22689 (self.start.0..self.end.0).map(MultiBufferRow)
22690 }
22691}
22692
22693impl RowRangeExt for Range<DisplayRow> {
22694 type Row = DisplayRow;
22695
22696 fn len(&self) -> usize {
22697 (self.end.0 - self.start.0) as usize
22698 }
22699
22700 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22701 (self.start.0..self.end.0).map(DisplayRow)
22702 }
22703}
22704
22705/// If select range has more than one line, we
22706/// just point the cursor to range.start.
22707fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22708 if range.start.row == range.end.row {
22709 range
22710 } else {
22711 range.start..range.start
22712 }
22713}
22714pub struct KillRing(ClipboardItem);
22715impl Global for KillRing {}
22716
22717const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22718
22719enum BreakpointPromptEditAction {
22720 Log,
22721 Condition,
22722 HitCondition,
22723}
22724
22725struct BreakpointPromptEditor {
22726 pub(crate) prompt: Entity<Editor>,
22727 editor: WeakEntity<Editor>,
22728 breakpoint_anchor: Anchor,
22729 breakpoint: Breakpoint,
22730 edit_action: BreakpointPromptEditAction,
22731 block_ids: HashSet<CustomBlockId>,
22732 editor_margins: Arc<Mutex<EditorMargins>>,
22733 _subscriptions: Vec<Subscription>,
22734}
22735
22736impl BreakpointPromptEditor {
22737 const MAX_LINES: u8 = 4;
22738
22739 fn new(
22740 editor: WeakEntity<Editor>,
22741 breakpoint_anchor: Anchor,
22742 breakpoint: Breakpoint,
22743 edit_action: BreakpointPromptEditAction,
22744 window: &mut Window,
22745 cx: &mut Context<Self>,
22746 ) -> Self {
22747 let base_text = match edit_action {
22748 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22749 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22750 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22751 }
22752 .map(|msg| msg.to_string())
22753 .unwrap_or_default();
22754
22755 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22756 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22757
22758 let prompt = cx.new(|cx| {
22759 let mut prompt = Editor::new(
22760 EditorMode::AutoHeight {
22761 min_lines: 1,
22762 max_lines: Some(Self::MAX_LINES as usize),
22763 },
22764 buffer,
22765 None,
22766 window,
22767 cx,
22768 );
22769 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22770 prompt.set_show_cursor_when_unfocused(false, cx);
22771 prompt.set_placeholder_text(
22772 match edit_action {
22773 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22774 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22775 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22776 },
22777 cx,
22778 );
22779
22780 prompt
22781 });
22782
22783 Self {
22784 prompt,
22785 editor,
22786 breakpoint_anchor,
22787 breakpoint,
22788 edit_action,
22789 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22790 block_ids: Default::default(),
22791 _subscriptions: vec![],
22792 }
22793 }
22794
22795 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22796 self.block_ids.extend(block_ids)
22797 }
22798
22799 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22800 if let Some(editor) = self.editor.upgrade() {
22801 let message = self
22802 .prompt
22803 .read(cx)
22804 .buffer
22805 .read(cx)
22806 .as_singleton()
22807 .expect("A multi buffer in breakpoint prompt isn't possible")
22808 .read(cx)
22809 .as_rope()
22810 .to_string();
22811
22812 editor.update(cx, |editor, cx| {
22813 editor.edit_breakpoint_at_anchor(
22814 self.breakpoint_anchor,
22815 self.breakpoint.clone(),
22816 match self.edit_action {
22817 BreakpointPromptEditAction::Log => {
22818 BreakpointEditAction::EditLogMessage(message.into())
22819 }
22820 BreakpointPromptEditAction::Condition => {
22821 BreakpointEditAction::EditCondition(message.into())
22822 }
22823 BreakpointPromptEditAction::HitCondition => {
22824 BreakpointEditAction::EditHitCondition(message.into())
22825 }
22826 },
22827 cx,
22828 );
22829
22830 editor.remove_blocks(self.block_ids.clone(), None, cx);
22831 cx.focus_self(window);
22832 });
22833 }
22834 }
22835
22836 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22837 self.editor
22838 .update(cx, |editor, cx| {
22839 editor.remove_blocks(self.block_ids.clone(), None, cx);
22840 window.focus(&editor.focus_handle);
22841 })
22842 .log_err();
22843 }
22844
22845 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22846 let settings = ThemeSettings::get_global(cx);
22847 let text_style = TextStyle {
22848 color: if self.prompt.read(cx).read_only(cx) {
22849 cx.theme().colors().text_disabled
22850 } else {
22851 cx.theme().colors().text
22852 },
22853 font_family: settings.buffer_font.family.clone(),
22854 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22855 font_size: settings.buffer_font_size(cx).into(),
22856 font_weight: settings.buffer_font.weight,
22857 line_height: relative(settings.buffer_line_height.value()),
22858 ..Default::default()
22859 };
22860 EditorElement::new(
22861 &self.prompt,
22862 EditorStyle {
22863 background: cx.theme().colors().editor_background,
22864 local_player: cx.theme().players().local(),
22865 text: text_style,
22866 ..Default::default()
22867 },
22868 )
22869 }
22870}
22871
22872impl Render for BreakpointPromptEditor {
22873 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22874 let editor_margins = *self.editor_margins.lock();
22875 let gutter_dimensions = editor_margins.gutter;
22876 h_flex()
22877 .key_context("Editor")
22878 .bg(cx.theme().colors().editor_background)
22879 .border_y_1()
22880 .border_color(cx.theme().status().info_border)
22881 .size_full()
22882 .py(window.line_height() / 2.5)
22883 .on_action(cx.listener(Self::confirm))
22884 .on_action(cx.listener(Self::cancel))
22885 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22886 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22887 }
22888}
22889
22890impl Focusable for BreakpointPromptEditor {
22891 fn focus_handle(&self, cx: &App) -> FocusHandle {
22892 self.prompt.focus_handle(cx)
22893 }
22894}
22895
22896fn all_edits_insertions_or_deletions(
22897 edits: &Vec<(Range<Anchor>, String)>,
22898 snapshot: &MultiBufferSnapshot,
22899) -> bool {
22900 let mut all_insertions = true;
22901 let mut all_deletions = true;
22902
22903 for (range, new_text) in edits.iter() {
22904 let range_is_empty = range.to_offset(&snapshot).is_empty();
22905 let text_is_empty = new_text.is_empty();
22906
22907 if range_is_empty != text_is_empty {
22908 if range_is_empty {
22909 all_deletions = false;
22910 } else {
22911 all_insertions = false;
22912 }
22913 } else {
22914 return false;
22915 }
22916
22917 if !all_insertions && !all_deletions {
22918 return false;
22919 }
22920 }
22921 all_insertions || all_deletions
22922}
22923
22924struct MissingEditPredictionKeybindingTooltip;
22925
22926impl Render for MissingEditPredictionKeybindingTooltip {
22927 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22928 ui::tooltip_container(window, cx, |container, _, cx| {
22929 container
22930 .flex_shrink_0()
22931 .max_w_80()
22932 .min_h(rems_from_px(124.))
22933 .justify_between()
22934 .child(
22935 v_flex()
22936 .flex_1()
22937 .text_ui_sm(cx)
22938 .child(Label::new("Conflict with Accept Keybinding"))
22939 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22940 )
22941 .child(
22942 h_flex()
22943 .pb_1()
22944 .gap_1()
22945 .items_end()
22946 .w_full()
22947 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22948 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22949 }))
22950 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22951 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22952 })),
22953 )
22954 })
22955 }
22956}
22957
22958#[derive(Debug, Clone, Copy, PartialEq)]
22959pub struct LineHighlight {
22960 pub background: Background,
22961 pub border: Option<gpui::Hsla>,
22962 pub include_gutter: bool,
22963 pub type_id: Option<TypeId>,
22964}
22965
22966fn render_diff_hunk_controls(
22967 row: u32,
22968 status: &DiffHunkStatus,
22969 hunk_range: Range<Anchor>,
22970 is_created_file: bool,
22971 line_height: Pixels,
22972 editor: &Entity<Editor>,
22973 _window: &mut Window,
22974 cx: &mut App,
22975) -> AnyElement {
22976 h_flex()
22977 .h(line_height)
22978 .mr_1()
22979 .gap_1()
22980 .px_0p5()
22981 .pb_1()
22982 .border_x_1()
22983 .border_b_1()
22984 .border_color(cx.theme().colors().border_variant)
22985 .rounded_b_lg()
22986 .bg(cx.theme().colors().editor_background)
22987 .gap_1()
22988 .block_mouse_except_scroll()
22989 .shadow_md()
22990 .child(if status.has_secondary_hunk() {
22991 Button::new(("stage", row as u64), "Stage")
22992 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22993 .tooltip({
22994 let focus_handle = editor.focus_handle(cx);
22995 move |window, cx| {
22996 Tooltip::for_action_in(
22997 "Stage Hunk",
22998 &::git::ToggleStaged,
22999 &focus_handle,
23000 window,
23001 cx,
23002 )
23003 }
23004 })
23005 .on_click({
23006 let editor = editor.clone();
23007 move |_event, _window, cx| {
23008 editor.update(cx, |editor, cx| {
23009 editor.stage_or_unstage_diff_hunks(
23010 true,
23011 vec![hunk_range.start..hunk_range.start],
23012 cx,
23013 );
23014 });
23015 }
23016 })
23017 } else {
23018 Button::new(("unstage", row as u64), "Unstage")
23019 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23020 .tooltip({
23021 let focus_handle = editor.focus_handle(cx);
23022 move |window, cx| {
23023 Tooltip::for_action_in(
23024 "Unstage Hunk",
23025 &::git::ToggleStaged,
23026 &focus_handle,
23027 window,
23028 cx,
23029 )
23030 }
23031 })
23032 .on_click({
23033 let editor = editor.clone();
23034 move |_event, _window, cx| {
23035 editor.update(cx, |editor, cx| {
23036 editor.stage_or_unstage_diff_hunks(
23037 false,
23038 vec![hunk_range.start..hunk_range.start],
23039 cx,
23040 );
23041 });
23042 }
23043 })
23044 })
23045 .child(
23046 Button::new(("restore", row as u64), "Restore")
23047 .tooltip({
23048 let focus_handle = editor.focus_handle(cx);
23049 move |window, cx| {
23050 Tooltip::for_action_in(
23051 "Restore Hunk",
23052 &::git::Restore,
23053 &focus_handle,
23054 window,
23055 cx,
23056 )
23057 }
23058 })
23059 .on_click({
23060 let editor = editor.clone();
23061 move |_event, window, cx| {
23062 editor.update(cx, |editor, cx| {
23063 let snapshot = editor.snapshot(window, cx);
23064 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23065 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23066 });
23067 }
23068 })
23069 .disabled(is_created_file),
23070 )
23071 .when(
23072 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23073 |el| {
23074 el.child(
23075 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23076 .shape(IconButtonShape::Square)
23077 .icon_size(IconSize::Small)
23078 // .disabled(!has_multiple_hunks)
23079 .tooltip({
23080 let focus_handle = editor.focus_handle(cx);
23081 move |window, cx| {
23082 Tooltip::for_action_in(
23083 "Next Hunk",
23084 &GoToHunk,
23085 &focus_handle,
23086 window,
23087 cx,
23088 )
23089 }
23090 })
23091 .on_click({
23092 let editor = editor.clone();
23093 move |_event, window, cx| {
23094 editor.update(cx, |editor, cx| {
23095 let snapshot = editor.snapshot(window, cx);
23096 let position =
23097 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23098 editor.go_to_hunk_before_or_after_position(
23099 &snapshot,
23100 position,
23101 Direction::Next,
23102 window,
23103 cx,
23104 );
23105 editor.expand_selected_diff_hunks(cx);
23106 });
23107 }
23108 }),
23109 )
23110 .child(
23111 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23112 .shape(IconButtonShape::Square)
23113 .icon_size(IconSize::Small)
23114 // .disabled(!has_multiple_hunks)
23115 .tooltip({
23116 let focus_handle = editor.focus_handle(cx);
23117 move |window, cx| {
23118 Tooltip::for_action_in(
23119 "Previous Hunk",
23120 &GoToPreviousHunk,
23121 &focus_handle,
23122 window,
23123 cx,
23124 )
23125 }
23126 })
23127 .on_click({
23128 let editor = editor.clone();
23129 move |_event, window, cx| {
23130 editor.update(cx, |editor, cx| {
23131 let snapshot = editor.snapshot(window, cx);
23132 let point =
23133 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23134 editor.go_to_hunk_before_or_after_position(
23135 &snapshot,
23136 point,
23137 Direction::Prev,
23138 window,
23139 cx,
23140 );
23141 editor.expand_selected_diff_hunks(cx);
23142 });
23143 }
23144 }),
23145 )
23146 },
23147 )
23148 .into_any_element()
23149}