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 edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
125 DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
126 Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
127 TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionIntent, CompletionResponse,
151 CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, Location, LocationLink,
152 PrepareRenameResponse, Project, ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
153 debugger::breakpoint_store::Breakpoint,
154 debugger::{
155 breakpoint_store::{
156 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
157 BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
164 project_settings::{GitGutterSetting, ProjectSettings},
165};
166use rand::{seq::SliceRandom, thread_rng};
167use rpc::{ErrorCode, ErrorExt, proto::PeerId};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
169use selections_collection::{
170 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
171};
172use serde::{Deserialize, Serialize};
173use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::TypeId,
178 borrow::Cow,
179 cell::OnceCell,
180 cell::RefCell,
181 cmp::{self, Ordering, Reverse},
182 iter::Peekable,
183 mem,
184 num::NonZeroU32,
185 ops::Not,
186 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 sync::Arc,
190 time::{Duration, Instant},
191};
192use sum_tree::TreeMap;
193use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::{
214 code_context_menus::CompletionsMenuSource,
215 editor_settings::MultiCursorModifier,
216 hover_links::{find_url, find_url_from_range},
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
223const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
224const MAX_LINE_LEN: usize = 1024;
225const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
226const MAX_SELECTION_HISTORY_LEN: usize = 1024;
227pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
228#[doc(hidden)]
229pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
230const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
231
232pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
235
236pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
237pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
238pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
239
240pub type RenderDiffHunkControlsFn = Arc<
241 dyn Fn(
242 u32,
243 &DiffHunkStatus,
244 Range<Anchor>,
245 bool,
246 Pixels,
247 &Entity<Editor>,
248 &mut Window,
249 &mut App,
250 ) -> AnyElement,
251>;
252
253enum ReportEditorEvent {
254 Saved { auto_saved: bool },
255 EditorOpened,
256 ZetaTosClicked,
257 Closed,
258}
259
260impl ReportEditorEvent {
261 pub fn event_type(&self) -> &'static str {
262 match self {
263 Self::Saved { .. } => "Editor Saved",
264 Self::EditorOpened => "Editor Opened",
265 Self::ZetaTosClicked => "Edit Prediction Provider ToS Clicked",
266 Self::Closed => "Editor Closed",
267 }
268 }
269}
270
271struct InlineValueCache {
272 enabled: bool,
273 inlays: Vec<InlayId>,
274 refresh_task: Task<Option<()>>,
275}
276
277impl InlineValueCache {
278 fn new(enabled: bool) -> Self {
279 Self {
280 enabled,
281 inlays: Vec::new(),
282 refresh_task: Task::ready(None),
283 }
284 }
285}
286
287#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
288pub enum InlayId {
289 EditPrediction(usize),
290 DebuggerValue(usize),
291 // LSP
292 Hint(usize),
293 Color(usize),
294}
295
296impl InlayId {
297 fn id(&self) -> usize {
298 match self {
299 Self::EditPrediction(id) => *id,
300 Self::DebuggerValue(id) => *id,
301 Self::Hint(id) => *id,
302 Self::Color(id) => *id,
303 }
304 }
305}
306
307pub enum ActiveDebugLine {}
308pub enum DebugStackFrameLine {}
309enum DocumentHighlightRead {}
310enum DocumentHighlightWrite {}
311enum InputComposition {}
312pub enum PendingInput {}
313enum SelectedTextHighlight {}
314
315pub enum ConflictsOuter {}
316pub enum ConflictsOurs {}
317pub enum ConflictsTheirs {}
318pub enum ConflictsOursMarker {}
319pub enum ConflictsTheirsMarker {}
320
321#[derive(Debug, Copy, Clone, PartialEq, Eq)]
322pub enum Navigated {
323 Yes,
324 No,
325}
326
327impl Navigated {
328 pub fn from_bool(yes: bool) -> Navigated {
329 if yes { Navigated::Yes } else { Navigated::No }
330 }
331}
332
333#[derive(Debug, Clone, PartialEq, Eq)]
334enum DisplayDiffHunk {
335 Folded {
336 display_row: DisplayRow,
337 },
338 Unfolded {
339 is_created_file: bool,
340 diff_base_byte_range: Range<usize>,
341 display_row_range: Range<DisplayRow>,
342 multi_buffer_range: Range<Anchor>,
343 status: DiffHunkStatus,
344 },
345}
346
347pub enum HideMouseCursorOrigin {
348 TypingAction,
349 MovementAction,
350}
351
352pub fn init_settings(cx: &mut App) {
353 EditorSettings::register(cx);
354}
355
356pub fn init(cx: &mut App) {
357 init_settings(cx);
358
359 cx.set_global(GlobalBlameRenderer(Arc::new(())));
360
361 workspace::register_project_item::<Editor>(cx);
362 workspace::FollowableViewRegistry::register::<Editor>(cx);
363 workspace::register_serializable_item::<Editor>(cx);
364
365 cx.observe_new(
366 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
367 workspace.register_action(Editor::new_file);
368 workspace.register_action(Editor::new_file_vertical);
369 workspace.register_action(Editor::new_file_horizontal);
370 workspace.register_action(Editor::cancel_language_server_work);
371 workspace.register_action(Editor::toggle_focus);
372 },
373 )
374 .detach();
375
376 cx.on_action(move |_: &workspace::NewFile, cx| {
377 let app_state = workspace::AppState::global(cx);
378 if let Some(app_state) = app_state.upgrade() {
379 workspace::open_new(
380 Default::default(),
381 app_state,
382 cx,
383 |workspace, window, cx| {
384 Editor::new_file(workspace, &Default::default(), window, cx)
385 },
386 )
387 .detach();
388 }
389 });
390 cx.on_action(move |_: &workspace::NewWindow, cx| {
391 let app_state = workspace::AppState::global(cx);
392 if let Some(app_state) = app_state.upgrade() {
393 workspace::open_new(
394 Default::default(),
395 app_state,
396 cx,
397 |workspace, window, cx| {
398 cx.activate(true);
399 Editor::new_file(workspace, &Default::default(), window, cx)
400 },
401 )
402 .detach();
403 }
404 });
405}
406
407pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
408 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
409}
410
411pub trait DiagnosticRenderer {
412 fn render_group(
413 &self,
414 diagnostic_group: Vec<DiagnosticEntry<Point>>,
415 buffer_id: BufferId,
416 snapshot: EditorSnapshot,
417 editor: WeakEntity<Editor>,
418 cx: &mut App,
419 ) -> Vec<BlockProperties<Anchor>>;
420
421 fn render_hover(
422 &self,
423 diagnostic_group: Vec<DiagnosticEntry<Point>>,
424 range: Range<Point>,
425 buffer_id: BufferId,
426 cx: &mut App,
427 ) -> Option<Entity<markdown::Markdown>>;
428
429 fn open_link(
430 &self,
431 editor: &mut Editor,
432 link: SharedString,
433 window: &mut Window,
434 cx: &mut Context<Editor>,
435 );
436}
437
438pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
439
440impl GlobalDiagnosticRenderer {
441 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
442 cx.try_global::<Self>().map(|g| g.0.clone())
443 }
444}
445
446impl gpui::Global for GlobalDiagnosticRenderer {}
447pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
448 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
449}
450
451pub struct SearchWithinRange;
452
453trait InvalidationRegion {
454 fn ranges(&self) -> &[Range<Anchor>];
455}
456
457#[derive(Clone, Debug, PartialEq)]
458pub enum SelectPhase {
459 Begin {
460 position: DisplayPoint,
461 add: bool,
462 click_count: usize,
463 },
464 BeginColumnar {
465 position: DisplayPoint,
466 reset: bool,
467 mode: ColumnarMode,
468 goal_column: u32,
469 },
470 Extend {
471 position: DisplayPoint,
472 click_count: usize,
473 },
474 Update {
475 position: DisplayPoint,
476 goal_column: u32,
477 scroll_delta: gpui::Point<f32>,
478 },
479 End,
480}
481
482#[derive(Clone, Debug, PartialEq)]
483pub enum ColumnarMode {
484 FromMouse,
485 FromSelection,
486}
487
488#[derive(Clone, Debug)]
489pub enum SelectMode {
490 Character,
491 Word(Range<Anchor>),
492 Line(Range<Anchor>),
493 All,
494}
495
496#[derive(Clone, PartialEq, Eq, Debug)]
497pub enum EditorMode {
498 SingleLine,
499 AutoHeight {
500 min_lines: usize,
501 max_lines: Option<usize>,
502 },
503 Full {
504 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
505 scale_ui_elements_with_buffer_font_size: bool,
506 /// When set to `true`, the editor will render a background for the active line.
507 show_active_line_background: bool,
508 /// When set to `true`, the editor's height will be determined by its content.
509 sized_by_content: bool,
510 },
511 Minimap {
512 parent: WeakEntity<Editor>,
513 },
514}
515
516impl EditorMode {
517 pub fn full() -> Self {
518 Self::Full {
519 scale_ui_elements_with_buffer_font_size: true,
520 show_active_line_background: true,
521 sized_by_content: false,
522 }
523 }
524
525 #[inline]
526 pub fn is_full(&self) -> bool {
527 matches!(self, Self::Full { .. })
528 }
529
530 #[inline]
531 pub fn is_single_line(&self) -> bool {
532 matches!(self, Self::SingleLine { .. })
533 }
534
535 #[inline]
536 fn is_minimap(&self) -> bool {
537 matches!(self, Self::Minimap { .. })
538 }
539}
540
541#[derive(Copy, Clone, Debug)]
542pub enum SoftWrap {
543 /// Prefer not to wrap at all.
544 ///
545 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
546 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
547 GitDiff,
548 /// Prefer a single line generally, unless an overly long line is encountered.
549 None,
550 /// Soft wrap lines that exceed the editor width.
551 EditorWidth,
552 /// Soft wrap lines at the preferred line length.
553 Column(u32),
554 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
555 Bounded(u32),
556}
557
558#[derive(Clone)]
559pub struct EditorStyle {
560 pub background: Hsla,
561 pub border: Hsla,
562 pub local_player: PlayerColor,
563 pub text: TextStyle,
564 pub scrollbar_width: Pixels,
565 pub syntax: Arc<SyntaxTheme>,
566 pub status: StatusColors,
567 pub inlay_hints_style: HighlightStyle,
568 pub edit_prediction_styles: EditPredictionStyles,
569 pub unnecessary_code_fade: f32,
570 pub show_underlines: bool,
571}
572
573impl Default for EditorStyle {
574 fn default() -> Self {
575 Self {
576 background: Hsla::default(),
577 border: Hsla::default(),
578 local_player: PlayerColor::default(),
579 text: TextStyle::default(),
580 scrollbar_width: Pixels::default(),
581 syntax: Default::default(),
582 // HACK: Status colors don't have a real default.
583 // We should look into removing the status colors from the editor
584 // style and retrieve them directly from the theme.
585 status: StatusColors::dark(),
586 inlay_hints_style: HighlightStyle::default(),
587 edit_prediction_styles: EditPredictionStyles {
588 insertion: HighlightStyle::default(),
589 whitespace: HighlightStyle::default(),
590 },
591 unnecessary_code_fade: Default::default(),
592 show_underlines: true,
593 }
594 }
595}
596
597pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
598 let show_background = language_settings::language_settings(None, None, cx)
599 .inlay_hints
600 .show_background;
601
602 HighlightStyle {
603 color: Some(cx.theme().status().hint),
604 background_color: show_background.then(|| cx.theme().status().hint_background),
605 ..HighlightStyle::default()
606 }
607}
608
609pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
610 EditPredictionStyles {
611 insertion: HighlightStyle {
612 color: Some(cx.theme().status().predictive),
613 ..HighlightStyle::default()
614 },
615 whitespace: HighlightStyle {
616 background_color: Some(cx.theme().status().created_background),
617 ..HighlightStyle::default()
618 },
619 }
620}
621
622type CompletionId = usize;
623
624pub(crate) enum EditDisplayMode {
625 TabAccept,
626 DiffPopover,
627 Inline,
628}
629
630enum EditPrediction {
631 Edit {
632 edits: Vec<(Range<Anchor>, String)>,
633 edit_preview: Option<EditPreview>,
634 display_mode: EditDisplayMode,
635 snapshot: BufferSnapshot,
636 },
637 Move {
638 target: Anchor,
639 snapshot: BufferSnapshot,
640 },
641}
642
643struct EditPredictionState {
644 inlay_ids: Vec<InlayId>,
645 completion: EditPrediction,
646 completion_id: Option<SharedString>,
647 invalidation_range: Range<Anchor>,
648}
649
650enum EditPredictionSettings {
651 Disabled,
652 Enabled {
653 show_in_menu: bool,
654 preview_requires_modifier: bool,
655 },
656}
657
658enum EditPredictionHighlight {}
659
660#[derive(Debug, Clone)]
661struct InlineDiagnostic {
662 message: SharedString,
663 group_id: usize,
664 is_primary: bool,
665 start: Point,
666 severity: lsp::DiagnosticSeverity,
667}
668
669pub enum MenuEditPredictionsPolicy {
670 Never,
671 ByProvider,
672}
673
674pub enum EditPredictionPreview {
675 /// Modifier is not pressed
676 Inactive { released_too_fast: bool },
677 /// Modifier pressed
678 Active {
679 since: Instant,
680 previous_scroll_position: Option<ScrollAnchor>,
681 },
682}
683
684impl EditPredictionPreview {
685 pub fn released_too_fast(&self) -> bool {
686 match self {
687 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
688 EditPredictionPreview::Active { .. } => false,
689 }
690 }
691
692 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
693 if let EditPredictionPreview::Active {
694 previous_scroll_position,
695 ..
696 } = self
697 {
698 *previous_scroll_position = scroll_position;
699 }
700 }
701}
702
703pub struct ContextMenuOptions {
704 pub min_entries_visible: usize,
705 pub max_entries_visible: usize,
706 pub placement: Option<ContextMenuPlacement>,
707}
708
709#[derive(Debug, Clone, PartialEq, Eq)]
710pub enum ContextMenuPlacement {
711 Above,
712 Below,
713}
714
715#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
716struct EditorActionId(usize);
717
718impl EditorActionId {
719 pub fn post_inc(&mut self) -> Self {
720 let answer = self.0;
721
722 *self = Self(answer + 1);
723
724 Self(answer)
725 }
726}
727
728// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
729// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
730
731type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
732type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
733
734#[derive(Default)]
735struct ScrollbarMarkerState {
736 scrollbar_size: Size<Pixels>,
737 dirty: bool,
738 markers: Arc<[PaintQuad]>,
739 pending_refresh: Option<Task<Result<()>>>,
740}
741
742impl ScrollbarMarkerState {
743 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
744 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
745 }
746}
747
748#[derive(Clone, Copy, PartialEq, Eq)]
749pub enum MinimapVisibility {
750 Disabled,
751 Enabled {
752 /// The configuration currently present in the users settings.
753 setting_configuration: bool,
754 /// Whether to override the currently set visibility from the users setting.
755 toggle_override: bool,
756 },
757}
758
759impl MinimapVisibility {
760 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
761 if mode.is_full() {
762 Self::Enabled {
763 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
764 toggle_override: false,
765 }
766 } else {
767 Self::Disabled
768 }
769 }
770
771 fn hidden(&self) -> Self {
772 match *self {
773 Self::Enabled {
774 setting_configuration,
775 ..
776 } => Self::Enabled {
777 setting_configuration,
778 toggle_override: setting_configuration,
779 },
780 Self::Disabled => Self::Disabled,
781 }
782 }
783
784 fn disabled(&self) -> bool {
785 match *self {
786 Self::Disabled => true,
787 _ => false,
788 }
789 }
790
791 fn settings_visibility(&self) -> bool {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 ..
796 } => setting_configuration,
797 _ => false,
798 }
799 }
800
801 fn visible(&self) -> bool {
802 match *self {
803 Self::Enabled {
804 setting_configuration,
805 toggle_override,
806 } => setting_configuration ^ toggle_override,
807 _ => false,
808 }
809 }
810
811 fn toggle_visibility(&self) -> Self {
812 match *self {
813 Self::Enabled {
814 toggle_override,
815 setting_configuration,
816 } => Self::Enabled {
817 setting_configuration,
818 toggle_override: !toggle_override,
819 },
820 Self::Disabled => Self::Disabled,
821 }
822 }
823}
824
825#[derive(Clone, Debug)]
826struct RunnableTasks {
827 templates: Vec<(TaskSourceKind, TaskTemplate)>,
828 offset: multi_buffer::Anchor,
829 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
830 column: u32,
831 // Values of all named captures, including those starting with '_'
832 extra_variables: HashMap<String, String>,
833 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
834 context_range: Range<BufferOffset>,
835}
836
837impl RunnableTasks {
838 fn resolve<'a>(
839 &'a self,
840 cx: &'a task::TaskContext,
841 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
842 self.templates.iter().filter_map(|(kind, template)| {
843 template
844 .resolve_task(&kind.to_id_base(), cx)
845 .map(|task| (kind.clone(), task))
846 })
847 }
848}
849
850#[derive(Clone)]
851pub struct ResolvedTasks {
852 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
853 position: Anchor,
854}
855
856#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
857struct BufferOffset(usize);
858
859// Addons allow storing per-editor state in other crates (e.g. Vim)
860pub trait Addon: 'static {
861 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
862
863 fn render_buffer_header_controls(
864 &self,
865 _: &ExcerptInfo,
866 _: &Window,
867 _: &App,
868 ) -> Option<AnyElement> {
869 None
870 }
871
872 fn to_any(&self) -> &dyn std::any::Any;
873
874 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
875 None
876 }
877}
878
879struct ChangeLocation {
880 current: Option<Vec<Anchor>>,
881 original: Vec<Anchor>,
882}
883impl ChangeLocation {
884 fn locations(&self) -> &[Anchor] {
885 self.current.as_ref().unwrap_or(&self.original)
886 }
887}
888
889/// A set of caret positions, registered when the editor was edited.
890pub struct ChangeList {
891 changes: Vec<ChangeLocation>,
892 /// Currently "selected" change.
893 position: Option<usize>,
894}
895
896impl ChangeList {
897 pub fn new() -> Self {
898 Self {
899 changes: Vec::new(),
900 position: None,
901 }
902 }
903
904 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
905 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
906 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
907 if self.changes.is_empty() {
908 return None;
909 }
910
911 let prev = self.position.unwrap_or(self.changes.len());
912 let next = if direction == Direction::Prev {
913 prev.saturating_sub(count)
914 } else {
915 (prev + count).min(self.changes.len() - 1)
916 };
917 self.position = Some(next);
918 self.changes.get(next).map(|change| change.locations())
919 }
920
921 /// Adds a new change to the list, resetting the change list position.
922 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
923 self.position.take();
924 if let Some(last) = self.changes.last_mut()
925 && group
926 {
927 last.current = Some(new_positions)
928 } else {
929 self.changes.push(ChangeLocation {
930 original: new_positions,
931 current: None,
932 });
933 }
934 }
935
936 pub fn last(&self) -> Option<&[Anchor]> {
937 self.changes.last().map(|change| change.locations())
938 }
939
940 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
941 self.changes.last().map(|change| change.original.as_slice())
942 }
943
944 pub fn invert_last_group(&mut self) {
945 if let Some(last) = self.changes.last_mut() {
946 if let Some(current) = last.current.as_mut() {
947 mem::swap(&mut last.original, current);
948 }
949 }
950 }
951}
952
953#[derive(Clone)]
954struct InlineBlamePopoverState {
955 scroll_handle: ScrollHandle,
956 commit_message: Option<ParsedCommitMessage>,
957 markdown: Entity<Markdown>,
958}
959
960struct InlineBlamePopover {
961 position: gpui::Point<Pixels>,
962 hide_task: Option<Task<()>>,
963 popover_bounds: Option<Bounds<Pixels>>,
964 popover_state: InlineBlamePopoverState,
965 keyboard_grace: bool,
966}
967
968enum SelectionDragState {
969 /// State when no drag related activity is detected.
970 None,
971 /// State when the mouse is down on a selection that is about to be dragged.
972 ReadyToDrag {
973 selection: Selection<Anchor>,
974 click_position: gpui::Point<Pixels>,
975 mouse_down_time: Instant,
976 },
977 /// State when the mouse is dragging the selection in the editor.
978 Dragging {
979 selection: Selection<Anchor>,
980 drop_cursor: Selection<Anchor>,
981 hide_drop_cursor: bool,
982 },
983}
984
985enum ColumnarSelectionState {
986 FromMouse {
987 selection_tail: Anchor,
988 display_point: Option<DisplayPoint>,
989 },
990 FromSelection {
991 selection_tail: Anchor,
992 },
993}
994
995/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
996/// a breakpoint on them.
997#[derive(Clone, Copy, Debug, PartialEq, Eq)]
998struct PhantomBreakpointIndicator {
999 display_row: DisplayRow,
1000 /// There's a small debounce between hovering over the line and showing the indicator.
1001 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1002 is_active: bool,
1003 collides_with_existing_breakpoint: bool,
1004}
1005
1006/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1007///
1008/// See the [module level documentation](self) for more information.
1009pub struct Editor {
1010 focus_handle: FocusHandle,
1011 last_focused_descendant: Option<WeakFocusHandle>,
1012 /// The text buffer being edited
1013 buffer: Entity<MultiBuffer>,
1014 /// Map of how text in the buffer should be displayed.
1015 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1016 pub display_map: Entity<DisplayMap>,
1017 pub selections: SelectionsCollection,
1018 pub scroll_manager: ScrollManager,
1019 /// When inline assist editors are linked, they all render cursors because
1020 /// typing enters text into each of them, even the ones that aren't focused.
1021 pub(crate) show_cursor_when_unfocused: bool,
1022 columnar_selection_state: Option<ColumnarSelectionState>,
1023 add_selections_state: Option<AddSelectionsState>,
1024 select_next_state: Option<SelectNextState>,
1025 select_prev_state: Option<SelectNextState>,
1026 selection_history: SelectionHistory,
1027 defer_selection_effects: bool,
1028 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1029 autoclose_regions: Vec<AutocloseRegion>,
1030 snippet_stack: InvalidationStack<SnippetState>,
1031 select_syntax_node_history: SelectSyntaxNodeHistory,
1032 ime_transaction: Option<TransactionId>,
1033 pub diagnostics_max_severity: DiagnosticSeverity,
1034 active_diagnostics: ActiveDiagnostic,
1035 show_inline_diagnostics: bool,
1036 inline_diagnostics_update: Task<()>,
1037 inline_diagnostics_enabled: bool,
1038 diagnostics_enabled: bool,
1039 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1040 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1041 hard_wrap: Option<usize>,
1042
1043 // TODO: make this a access method
1044 pub project: Option<Entity<Project>>,
1045 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1046 completion_provider: Option<Rc<dyn CompletionProvider>>,
1047 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1048 blink_manager: Entity<BlinkManager>,
1049 show_cursor_names: bool,
1050 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1051 pub show_local_selections: bool,
1052 mode: EditorMode,
1053 show_breadcrumbs: bool,
1054 show_gutter: bool,
1055 show_scrollbars: ScrollbarAxes,
1056 minimap_visibility: MinimapVisibility,
1057 offset_content: bool,
1058 disable_expand_excerpt_buttons: bool,
1059 show_line_numbers: Option<bool>,
1060 use_relative_line_numbers: Option<bool>,
1061 show_git_diff_gutter: Option<bool>,
1062 show_code_actions: Option<bool>,
1063 show_runnables: Option<bool>,
1064 show_breakpoints: Option<bool>,
1065 show_wrap_guides: Option<bool>,
1066 show_indent_guides: Option<bool>,
1067 placeholder_text: Option<Arc<str>>,
1068 highlight_order: usize,
1069 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1070 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1071 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1072 scrollbar_marker_state: ScrollbarMarkerState,
1073 active_indent_guides_state: ActiveIndentGuidesState,
1074 nav_history: Option<ItemNavHistory>,
1075 context_menu: RefCell<Option<CodeContextMenu>>,
1076 context_menu_options: Option<ContextMenuOptions>,
1077 mouse_context_menu: Option<MouseContextMenu>,
1078 completion_tasks: Vec<(CompletionId, Task<()>)>,
1079 inline_blame_popover: Option<InlineBlamePopover>,
1080 inline_blame_popover_show_task: Option<Task<()>>,
1081 signature_help_state: SignatureHelpState,
1082 auto_signature_help: Option<bool>,
1083 find_all_references_task_sources: Vec<Anchor>,
1084 next_completion_id: CompletionId,
1085 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1086 code_actions_task: Option<Task<Result<()>>>,
1087 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1088 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1089 document_highlights_task: Option<Task<()>>,
1090 linked_editing_range_task: Option<Task<Option<()>>>,
1091 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1092 pending_rename: Option<RenameState>,
1093 searchable: bool,
1094 cursor_shape: CursorShape,
1095 current_line_highlight: Option<CurrentLineHighlight>,
1096 collapse_matches: bool,
1097 autoindent_mode: Option<AutoindentMode>,
1098 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1099 input_enabled: bool,
1100 use_modal_editing: bool,
1101 read_only: bool,
1102 leader_id: Option<CollaboratorId>,
1103 remote_id: Option<ViewId>,
1104 pub hover_state: HoverState,
1105 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1106 gutter_hovered: bool,
1107 hovered_link_state: Option<HoveredLinkState>,
1108 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1109 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1110 active_edit_prediction: Option<EditPredictionState>,
1111 /// Used to prevent flickering as the user types while the menu is open
1112 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1113 edit_prediction_settings: EditPredictionSettings,
1114 edit_predictions_hidden_for_vim_mode: bool,
1115 show_edit_predictions_override: Option<bool>,
1116 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1117 edit_prediction_preview: EditPredictionPreview,
1118 edit_prediction_indent_conflict: bool,
1119 edit_prediction_requires_modifier_in_indent_conflict: bool,
1120 inlay_hint_cache: InlayHintCache,
1121 next_inlay_id: usize,
1122 _subscriptions: Vec<Subscription>,
1123 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1124 gutter_dimensions: GutterDimensions,
1125 style: Option<EditorStyle>,
1126 text_style_refinement: Option<TextStyleRefinement>,
1127 next_editor_action_id: EditorActionId,
1128 editor_actions: Rc<
1129 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1130 >,
1131 use_autoclose: bool,
1132 use_auto_surround: bool,
1133 auto_replace_emoji_shortcode: bool,
1134 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1135 show_git_blame_gutter: bool,
1136 show_git_blame_inline: bool,
1137 show_git_blame_inline_delay_task: Option<Task<()>>,
1138 git_blame_inline_enabled: bool,
1139 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1140 serialize_dirty_buffers: bool,
1141 show_selection_menu: Option<bool>,
1142 blame: Option<Entity<GitBlame>>,
1143 blame_subscription: Option<Subscription>,
1144 custom_context_menu: Option<
1145 Box<
1146 dyn 'static
1147 + Fn(
1148 &mut Self,
1149 DisplayPoint,
1150 &mut Window,
1151 &mut Context<Self>,
1152 ) -> Option<Entity<ui::ContextMenu>>,
1153 >,
1154 >,
1155 last_bounds: Option<Bounds<Pixels>>,
1156 last_position_map: Option<Rc<PositionMap>>,
1157 expect_bounds_change: Option<Bounds<Pixels>>,
1158 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1159 tasks_update_task: Option<Task<()>>,
1160 breakpoint_store: Option<Entity<BreakpointStore>>,
1161 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1162 hovered_diff_hunk_row: Option<DisplayRow>,
1163 pull_diagnostics_task: Task<()>,
1164 in_project_search: bool,
1165 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1166 breadcrumb_header: Option<String>,
1167 focused_block: Option<FocusedBlock>,
1168 next_scroll_position: NextScrollCursorCenterTopBottom,
1169 addons: HashMap<TypeId, Box<dyn Addon>>,
1170 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1171 load_diff_task: Option<Shared<Task<()>>>,
1172 /// Whether we are temporarily displaying a diff other than git's
1173 temporary_diff_override: bool,
1174 selection_mark_mode: bool,
1175 toggle_fold_multiple_buffers: Task<()>,
1176 _scroll_cursor_center_top_bottom_task: Task<()>,
1177 serialize_selections: Task<()>,
1178 serialize_folds: Task<()>,
1179 mouse_cursor_hidden: bool,
1180 minimap: Option<Entity<Self>>,
1181 hide_mouse_mode: HideMouseMode,
1182 pub change_list: ChangeList,
1183 inline_value_cache: InlineValueCache,
1184 selection_drag_state: SelectionDragState,
1185 next_color_inlay_id: usize,
1186 colors: Option<LspColorData>,
1187 folding_newlines: Task<()>,
1188}
1189
1190#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1191enum NextScrollCursorCenterTopBottom {
1192 #[default]
1193 Center,
1194 Top,
1195 Bottom,
1196}
1197
1198impl NextScrollCursorCenterTopBottom {
1199 fn next(&self) -> Self {
1200 match self {
1201 Self::Center => Self::Top,
1202 Self::Top => Self::Bottom,
1203 Self::Bottom => Self::Center,
1204 }
1205 }
1206}
1207
1208#[derive(Clone)]
1209pub struct EditorSnapshot {
1210 pub mode: EditorMode,
1211 show_gutter: bool,
1212 show_line_numbers: Option<bool>,
1213 show_git_diff_gutter: Option<bool>,
1214 show_code_actions: Option<bool>,
1215 show_runnables: Option<bool>,
1216 show_breakpoints: Option<bool>,
1217 git_blame_gutter_max_author_length: Option<usize>,
1218 pub display_snapshot: DisplaySnapshot,
1219 pub placeholder_text: Option<Arc<str>>,
1220 is_focused: bool,
1221 scroll_anchor: ScrollAnchor,
1222 ongoing_scroll: OngoingScroll,
1223 current_line_highlight: CurrentLineHighlight,
1224 gutter_hovered: bool,
1225}
1226
1227#[derive(Default, Debug, Clone, Copy)]
1228pub struct GutterDimensions {
1229 pub left_padding: Pixels,
1230 pub right_padding: Pixels,
1231 pub width: Pixels,
1232 pub margin: Pixels,
1233 pub git_blame_entries_width: Option<Pixels>,
1234}
1235
1236impl GutterDimensions {
1237 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1238 Self {
1239 margin: Self::default_gutter_margin(font_id, font_size, cx),
1240 ..Default::default()
1241 }
1242 }
1243
1244 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1245 -cx.text_system().descent(font_id, font_size)
1246 }
1247 /// The full width of the space taken up by the gutter.
1248 pub fn full_width(&self) -> Pixels {
1249 self.margin + self.width
1250 }
1251
1252 /// The width of the space reserved for the fold indicators,
1253 /// use alongside 'justify_end' and `gutter_width` to
1254 /// right align content with the line numbers
1255 pub fn fold_area_width(&self) -> Pixels {
1256 self.margin + self.right_padding
1257 }
1258}
1259
1260struct CharacterDimensions {
1261 em_width: Pixels,
1262 em_advance: Pixels,
1263 line_height: Pixels,
1264}
1265
1266#[derive(Debug)]
1267pub struct RemoteSelection {
1268 pub replica_id: ReplicaId,
1269 pub selection: Selection<Anchor>,
1270 pub cursor_shape: CursorShape,
1271 pub collaborator_id: CollaboratorId,
1272 pub line_mode: bool,
1273 pub user_name: Option<SharedString>,
1274 pub color: PlayerColor,
1275}
1276
1277#[derive(Clone, Debug)]
1278struct SelectionHistoryEntry {
1279 selections: Arc<[Selection<Anchor>]>,
1280 select_next_state: Option<SelectNextState>,
1281 select_prev_state: Option<SelectNextState>,
1282 add_selections_state: Option<AddSelectionsState>,
1283}
1284
1285#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1286enum SelectionHistoryMode {
1287 Normal,
1288 Undoing,
1289 Redoing,
1290 Skipping,
1291}
1292
1293#[derive(Clone, PartialEq, Eq, Hash)]
1294struct HoveredCursor {
1295 replica_id: u16,
1296 selection_id: usize,
1297}
1298
1299impl Default for SelectionHistoryMode {
1300 fn default() -> Self {
1301 Self::Normal
1302 }
1303}
1304
1305#[derive(Debug)]
1306/// SelectionEffects controls the side-effects of updating the selection.
1307///
1308/// The default behaviour does "what you mostly want":
1309/// - it pushes to the nav history if the cursor moved by >10 lines
1310/// - it re-triggers completion requests
1311/// - it scrolls to fit
1312///
1313/// You might want to modify these behaviours. For example when doing a "jump"
1314/// like go to definition, we always want to add to nav history; but when scrolling
1315/// in vim mode we never do.
1316///
1317/// Similarly, you might want to disable scrolling if you don't want the viewport to
1318/// move.
1319#[derive(Clone)]
1320pub struct SelectionEffects {
1321 nav_history: Option<bool>,
1322 completions: bool,
1323 scroll: Option<Autoscroll>,
1324}
1325
1326impl Default for SelectionEffects {
1327 fn default() -> Self {
1328 Self {
1329 nav_history: None,
1330 completions: true,
1331 scroll: Some(Autoscroll::fit()),
1332 }
1333 }
1334}
1335impl SelectionEffects {
1336 pub fn scroll(scroll: Autoscroll) -> Self {
1337 Self {
1338 scroll: Some(scroll),
1339 ..Default::default()
1340 }
1341 }
1342
1343 pub fn no_scroll() -> Self {
1344 Self {
1345 scroll: None,
1346 ..Default::default()
1347 }
1348 }
1349
1350 pub fn completions(self, completions: bool) -> Self {
1351 Self {
1352 completions,
1353 ..self
1354 }
1355 }
1356
1357 pub fn nav_history(self, nav_history: bool) -> Self {
1358 Self {
1359 nav_history: Some(nav_history),
1360 ..self
1361 }
1362 }
1363}
1364
1365struct DeferredSelectionEffectsState {
1366 changed: bool,
1367 effects: SelectionEffects,
1368 old_cursor_position: Anchor,
1369 history_entry: SelectionHistoryEntry,
1370}
1371
1372#[derive(Default)]
1373struct SelectionHistory {
1374 #[allow(clippy::type_complexity)]
1375 selections_by_transaction:
1376 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1377 mode: SelectionHistoryMode,
1378 undo_stack: VecDeque<SelectionHistoryEntry>,
1379 redo_stack: VecDeque<SelectionHistoryEntry>,
1380}
1381
1382impl SelectionHistory {
1383 #[track_caller]
1384 fn insert_transaction(
1385 &mut self,
1386 transaction_id: TransactionId,
1387 selections: Arc<[Selection<Anchor>]>,
1388 ) {
1389 if selections.is_empty() {
1390 log::error!(
1391 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1392 std::panic::Location::caller()
1393 );
1394 return;
1395 }
1396 self.selections_by_transaction
1397 .insert(transaction_id, (selections, None));
1398 }
1399
1400 #[allow(clippy::type_complexity)]
1401 fn transaction(
1402 &self,
1403 transaction_id: TransactionId,
1404 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1405 self.selections_by_transaction.get(&transaction_id)
1406 }
1407
1408 #[allow(clippy::type_complexity)]
1409 fn transaction_mut(
1410 &mut self,
1411 transaction_id: TransactionId,
1412 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1413 self.selections_by_transaction.get_mut(&transaction_id)
1414 }
1415
1416 fn push(&mut self, entry: SelectionHistoryEntry) {
1417 if !entry.selections.is_empty() {
1418 match self.mode {
1419 SelectionHistoryMode::Normal => {
1420 self.push_undo(entry);
1421 self.redo_stack.clear();
1422 }
1423 SelectionHistoryMode::Undoing => self.push_redo(entry),
1424 SelectionHistoryMode::Redoing => self.push_undo(entry),
1425 SelectionHistoryMode::Skipping => {}
1426 }
1427 }
1428 }
1429
1430 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1431 if self
1432 .undo_stack
1433 .back()
1434 .map_or(true, |e| e.selections != entry.selections)
1435 {
1436 self.undo_stack.push_back(entry);
1437 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1438 self.undo_stack.pop_front();
1439 }
1440 }
1441 }
1442
1443 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1444 if self
1445 .redo_stack
1446 .back()
1447 .map_or(true, |e| e.selections != entry.selections)
1448 {
1449 self.redo_stack.push_back(entry);
1450 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1451 self.redo_stack.pop_front();
1452 }
1453 }
1454 }
1455}
1456
1457#[derive(Clone, Copy)]
1458pub struct RowHighlightOptions {
1459 pub autoscroll: bool,
1460 pub include_gutter: bool,
1461}
1462
1463impl Default for RowHighlightOptions {
1464 fn default() -> Self {
1465 Self {
1466 autoscroll: Default::default(),
1467 include_gutter: true,
1468 }
1469 }
1470}
1471
1472struct RowHighlight {
1473 index: usize,
1474 range: Range<Anchor>,
1475 color: Hsla,
1476 options: RowHighlightOptions,
1477 type_id: TypeId,
1478}
1479
1480#[derive(Clone, Debug)]
1481struct AddSelectionsState {
1482 groups: Vec<AddSelectionsGroup>,
1483}
1484
1485#[derive(Clone, Debug)]
1486struct AddSelectionsGroup {
1487 above: bool,
1488 stack: Vec<usize>,
1489}
1490
1491#[derive(Clone)]
1492struct SelectNextState {
1493 query: AhoCorasick,
1494 wordwise: bool,
1495 done: bool,
1496}
1497
1498impl std::fmt::Debug for SelectNextState {
1499 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1500 f.debug_struct(std::any::type_name::<Self>())
1501 .field("wordwise", &self.wordwise)
1502 .field("done", &self.done)
1503 .finish()
1504 }
1505}
1506
1507#[derive(Debug)]
1508struct AutocloseRegion {
1509 selection_id: usize,
1510 range: Range<Anchor>,
1511 pair: BracketPair,
1512}
1513
1514#[derive(Debug)]
1515struct SnippetState {
1516 ranges: Vec<Vec<Range<Anchor>>>,
1517 active_index: usize,
1518 choices: Vec<Option<Vec<String>>>,
1519}
1520
1521#[doc(hidden)]
1522pub struct RenameState {
1523 pub range: Range<Anchor>,
1524 pub old_name: Arc<str>,
1525 pub editor: Entity<Editor>,
1526 block_id: CustomBlockId,
1527}
1528
1529struct InvalidationStack<T>(Vec<T>);
1530
1531struct RegisteredEditPredictionProvider {
1532 provider: Arc<dyn EditPredictionProviderHandle>,
1533 _subscription: Subscription,
1534}
1535
1536#[derive(Debug, PartialEq, Eq)]
1537pub struct ActiveDiagnosticGroup {
1538 pub active_range: Range<Anchor>,
1539 pub active_message: String,
1540 pub group_id: usize,
1541 pub blocks: HashSet<CustomBlockId>,
1542}
1543
1544#[derive(Debug, PartialEq, Eq)]
1545
1546pub(crate) enum ActiveDiagnostic {
1547 None,
1548 All,
1549 Group(ActiveDiagnosticGroup),
1550}
1551
1552#[derive(Serialize, Deserialize, Clone, Debug)]
1553pub struct ClipboardSelection {
1554 /// The number of bytes in this selection.
1555 pub len: usize,
1556 /// Whether this was a full-line selection.
1557 pub is_entire_line: bool,
1558 /// The indentation of the first line when this content was originally copied.
1559 pub first_line_indent: u32,
1560}
1561
1562// selections, scroll behavior, was newest selection reversed
1563type SelectSyntaxNodeHistoryState = (
1564 Box<[Selection<usize>]>,
1565 SelectSyntaxNodeScrollBehavior,
1566 bool,
1567);
1568
1569#[derive(Default)]
1570struct SelectSyntaxNodeHistory {
1571 stack: Vec<SelectSyntaxNodeHistoryState>,
1572 // disable temporarily to allow changing selections without losing the stack
1573 pub disable_clearing: bool,
1574}
1575
1576impl SelectSyntaxNodeHistory {
1577 pub fn try_clear(&mut self) {
1578 if !self.disable_clearing {
1579 self.stack.clear();
1580 }
1581 }
1582
1583 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1584 self.stack.push(selection);
1585 }
1586
1587 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1588 self.stack.pop()
1589 }
1590}
1591
1592enum SelectSyntaxNodeScrollBehavior {
1593 CursorTop,
1594 FitSelection,
1595 CursorBottom,
1596}
1597
1598#[derive(Debug)]
1599pub(crate) struct NavigationData {
1600 cursor_anchor: Anchor,
1601 cursor_position: Point,
1602 scroll_anchor: ScrollAnchor,
1603 scroll_top_row: u32,
1604}
1605
1606#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1607pub enum GotoDefinitionKind {
1608 Symbol,
1609 Declaration,
1610 Type,
1611 Implementation,
1612}
1613
1614#[derive(Debug, Clone)]
1615enum InlayHintRefreshReason {
1616 ModifiersChanged(bool),
1617 Toggle(bool),
1618 SettingsChange(InlayHintSettings),
1619 NewLinesShown,
1620 BufferEdited(HashSet<Arc<Language>>),
1621 RefreshRequested,
1622 ExcerptsRemoved(Vec<ExcerptId>),
1623}
1624
1625impl InlayHintRefreshReason {
1626 fn description(&self) -> &'static str {
1627 match self {
1628 Self::ModifiersChanged(_) => "modifiers changed",
1629 Self::Toggle(_) => "toggle",
1630 Self::SettingsChange(_) => "settings change",
1631 Self::NewLinesShown => "new lines shown",
1632 Self::BufferEdited(_) => "buffer edited",
1633 Self::RefreshRequested => "refresh requested",
1634 Self::ExcerptsRemoved(_) => "excerpts removed",
1635 }
1636 }
1637}
1638
1639pub enum FormatTarget {
1640 Buffers(HashSet<Entity<Buffer>>),
1641 Ranges(Vec<Range<MultiBufferPoint>>),
1642}
1643
1644pub(crate) struct FocusedBlock {
1645 id: BlockId,
1646 focus_handle: WeakFocusHandle,
1647}
1648
1649#[derive(Clone)]
1650enum JumpData {
1651 MultiBufferRow {
1652 row: MultiBufferRow,
1653 line_offset_from_top: u32,
1654 },
1655 MultiBufferPoint {
1656 excerpt_id: ExcerptId,
1657 position: Point,
1658 anchor: text::Anchor,
1659 line_offset_from_top: u32,
1660 },
1661}
1662
1663pub enum MultibufferSelectionMode {
1664 First,
1665 All,
1666}
1667
1668#[derive(Clone, Copy, Debug, Default)]
1669pub struct RewrapOptions {
1670 pub override_language_settings: bool,
1671 pub preserve_existing_whitespace: bool,
1672}
1673
1674impl Editor {
1675 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1676 let buffer = cx.new(|cx| Buffer::local("", cx));
1677 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1678 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1679 }
1680
1681 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1682 let buffer = cx.new(|cx| Buffer::local("", cx));
1683 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1684 Self::new(EditorMode::full(), buffer, None, window, cx)
1685 }
1686
1687 pub fn auto_height(
1688 min_lines: usize,
1689 max_lines: usize,
1690 window: &mut Window,
1691 cx: &mut Context<Self>,
1692 ) -> Self {
1693 let buffer = cx.new(|cx| Buffer::local("", cx));
1694 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1695 Self::new(
1696 EditorMode::AutoHeight {
1697 min_lines,
1698 max_lines: Some(max_lines),
1699 },
1700 buffer,
1701 None,
1702 window,
1703 cx,
1704 )
1705 }
1706
1707 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1708 /// The editor grows as tall as needed to fit its content.
1709 pub fn auto_height_unbounded(
1710 min_lines: usize,
1711 window: &mut Window,
1712 cx: &mut Context<Self>,
1713 ) -> Self {
1714 let buffer = cx.new(|cx| Buffer::local("", cx));
1715 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1716 Self::new(
1717 EditorMode::AutoHeight {
1718 min_lines,
1719 max_lines: None,
1720 },
1721 buffer,
1722 None,
1723 window,
1724 cx,
1725 )
1726 }
1727
1728 pub fn for_buffer(
1729 buffer: Entity<Buffer>,
1730 project: Option<Entity<Project>>,
1731 window: &mut Window,
1732 cx: &mut Context<Self>,
1733 ) -> Self {
1734 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1735 Self::new(EditorMode::full(), buffer, project, window, cx)
1736 }
1737
1738 pub fn for_multibuffer(
1739 buffer: Entity<MultiBuffer>,
1740 project: Option<Entity<Project>>,
1741 window: &mut Window,
1742 cx: &mut Context<Self>,
1743 ) -> Self {
1744 Self::new(EditorMode::full(), buffer, project, window, cx)
1745 }
1746
1747 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1748 let mut clone = Self::new(
1749 self.mode.clone(),
1750 self.buffer.clone(),
1751 self.project.clone(),
1752 window,
1753 cx,
1754 );
1755 self.display_map.update(cx, |display_map, cx| {
1756 let snapshot = display_map.snapshot(cx);
1757 clone.display_map.update(cx, |display_map, cx| {
1758 display_map.set_state(&snapshot, cx);
1759 });
1760 });
1761 clone.folds_did_change(cx);
1762 clone.selections.clone_state(&self.selections);
1763 clone.scroll_manager.clone_state(&self.scroll_manager);
1764 clone.searchable = self.searchable;
1765 clone.read_only = self.read_only;
1766 clone
1767 }
1768
1769 pub fn new(
1770 mode: EditorMode,
1771 buffer: Entity<MultiBuffer>,
1772 project: Option<Entity<Project>>,
1773 window: &mut Window,
1774 cx: &mut Context<Self>,
1775 ) -> Self {
1776 Editor::new_internal(mode, buffer, project, None, window, cx)
1777 }
1778
1779 fn new_internal(
1780 mode: EditorMode,
1781 buffer: Entity<MultiBuffer>,
1782 project: Option<Entity<Project>>,
1783 display_map: Option<Entity<DisplayMap>>,
1784 window: &mut Window,
1785 cx: &mut Context<Self>,
1786 ) -> Self {
1787 debug_assert!(
1788 display_map.is_none() || mode.is_minimap(),
1789 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1790 );
1791
1792 let full_mode = mode.is_full();
1793 let is_minimap = mode.is_minimap();
1794 let diagnostics_max_severity = if full_mode {
1795 EditorSettings::get_global(cx)
1796 .diagnostics_max_severity
1797 .unwrap_or(DiagnosticSeverity::Hint)
1798 } else {
1799 DiagnosticSeverity::Off
1800 };
1801 let style = window.text_style();
1802 let font_size = style.font_size.to_pixels(window.rem_size());
1803 let editor = cx.entity().downgrade();
1804 let fold_placeholder = FoldPlaceholder {
1805 constrain_width: true,
1806 render: Arc::new(move |fold_id, fold_range, cx| {
1807 let editor = editor.clone();
1808 div()
1809 .id(fold_id)
1810 .bg(cx.theme().colors().ghost_element_background)
1811 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1812 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1813 .rounded_xs()
1814 .size_full()
1815 .cursor_pointer()
1816 .child("⋯")
1817 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1818 .on_click(move |_, _window, cx| {
1819 editor
1820 .update(cx, |editor, cx| {
1821 editor.unfold_ranges(
1822 &[fold_range.start..fold_range.end],
1823 true,
1824 false,
1825 cx,
1826 );
1827 cx.stop_propagation();
1828 })
1829 .ok();
1830 })
1831 .into_any()
1832 }),
1833 merge_adjacent: true,
1834 ..FoldPlaceholder::default()
1835 };
1836 let display_map = display_map.unwrap_or_else(|| {
1837 cx.new(|cx| {
1838 DisplayMap::new(
1839 buffer.clone(),
1840 style.font(),
1841 font_size,
1842 None,
1843 FILE_HEADER_HEIGHT,
1844 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1845 fold_placeholder,
1846 diagnostics_max_severity,
1847 cx,
1848 )
1849 })
1850 });
1851
1852 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1853
1854 let blink_manager = cx.new(|cx| {
1855 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1856 if is_minimap {
1857 blink_manager.disable(cx);
1858 }
1859 blink_manager
1860 });
1861
1862 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1863 .then(|| language_settings::SoftWrap::None);
1864
1865 let mut project_subscriptions = Vec::new();
1866 if full_mode {
1867 if let Some(project) = project.as_ref() {
1868 project_subscriptions.push(cx.subscribe_in(
1869 project,
1870 window,
1871 |editor, _, event, window, cx| match event {
1872 project::Event::RefreshCodeLens => {
1873 // we always query lens with actions, without storing them, always refreshing them
1874 }
1875 project::Event::RefreshInlayHints => {
1876 editor
1877 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1878 }
1879 project::Event::LanguageServerAdded(..)
1880 | project::Event::LanguageServerRemoved(..) => {
1881 if editor.tasks_update_task.is_none() {
1882 editor.tasks_update_task =
1883 Some(editor.refresh_runnables(window, cx));
1884 }
1885 }
1886 project::Event::SnippetEdit(id, snippet_edits) => {
1887 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1888 let focus_handle = editor.focus_handle(cx);
1889 if focus_handle.is_focused(window) {
1890 let snapshot = buffer.read(cx).snapshot();
1891 for (range, snippet) in snippet_edits {
1892 let editor_range =
1893 language::range_from_lsp(*range).to_offset(&snapshot);
1894 editor
1895 .insert_snippet(
1896 &[editor_range],
1897 snippet.clone(),
1898 window,
1899 cx,
1900 )
1901 .ok();
1902 }
1903 }
1904 }
1905 }
1906 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1907 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1908 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1909 }
1910 }
1911 _ => {}
1912 },
1913 ));
1914 if let Some(task_inventory) = project
1915 .read(cx)
1916 .task_store()
1917 .read(cx)
1918 .task_inventory()
1919 .cloned()
1920 {
1921 project_subscriptions.push(cx.observe_in(
1922 &task_inventory,
1923 window,
1924 |editor, _, window, cx| {
1925 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1926 },
1927 ));
1928 };
1929
1930 project_subscriptions.push(cx.subscribe_in(
1931 &project.read(cx).breakpoint_store(),
1932 window,
1933 |editor, _, event, window, cx| match event {
1934 BreakpointStoreEvent::ClearDebugLines => {
1935 editor.clear_row_highlights::<ActiveDebugLine>();
1936 editor.refresh_inline_values(cx);
1937 }
1938 BreakpointStoreEvent::SetDebugLine => {
1939 if editor.go_to_active_debug_line(window, cx) {
1940 cx.stop_propagation();
1941 }
1942
1943 editor.refresh_inline_values(cx);
1944 }
1945 _ => {}
1946 },
1947 ));
1948 let git_store = project.read(cx).git_store().clone();
1949 let project = project.clone();
1950 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1951 match event {
1952 GitStoreEvent::RepositoryUpdated(
1953 _,
1954 RepositoryEvent::Updated {
1955 new_instance: true, ..
1956 },
1957 _,
1958 ) => {
1959 this.load_diff_task = Some(
1960 update_uncommitted_diff_for_buffer(
1961 cx.entity(),
1962 &project,
1963 this.buffer.read(cx).all_buffers(),
1964 this.buffer.clone(),
1965 cx,
1966 )
1967 .shared(),
1968 );
1969 }
1970 _ => {}
1971 }
1972 }));
1973 }
1974 }
1975
1976 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1977
1978 let inlay_hint_settings =
1979 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1980 let focus_handle = cx.focus_handle();
1981 if !is_minimap {
1982 cx.on_focus(&focus_handle, window, Self::handle_focus)
1983 .detach();
1984 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1985 .detach();
1986 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1987 .detach();
1988 cx.on_blur(&focus_handle, window, Self::handle_blur)
1989 .detach();
1990 cx.observe_pending_input(window, Self::observe_pending_input)
1991 .detach();
1992 }
1993
1994 let show_indent_guides = if matches!(
1995 mode,
1996 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1997 ) {
1998 Some(false)
1999 } else {
2000 None
2001 };
2002
2003 let breakpoint_store = match (&mode, project.as_ref()) {
2004 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2005 _ => None,
2006 };
2007
2008 let mut code_action_providers = Vec::new();
2009 let mut load_uncommitted_diff = None;
2010 if let Some(project) = project.clone() {
2011 load_uncommitted_diff = Some(
2012 update_uncommitted_diff_for_buffer(
2013 cx.entity(),
2014 &project,
2015 buffer.read(cx).all_buffers(),
2016 buffer.clone(),
2017 cx,
2018 )
2019 .shared(),
2020 );
2021 code_action_providers.push(Rc::new(project) as Rc<_>);
2022 }
2023
2024 let mut editor = Self {
2025 focus_handle,
2026 show_cursor_when_unfocused: false,
2027 last_focused_descendant: None,
2028 buffer: buffer.clone(),
2029 display_map: display_map.clone(),
2030 selections,
2031 scroll_manager: ScrollManager::new(cx),
2032 columnar_selection_state: None,
2033 add_selections_state: None,
2034 select_next_state: None,
2035 select_prev_state: None,
2036 selection_history: SelectionHistory::default(),
2037 defer_selection_effects: false,
2038 deferred_selection_effects_state: None,
2039 autoclose_regions: Vec::new(),
2040 snippet_stack: InvalidationStack::default(),
2041 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2042 ime_transaction: None,
2043 active_diagnostics: ActiveDiagnostic::None,
2044 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2045 inline_diagnostics_update: Task::ready(()),
2046 inline_diagnostics: Vec::new(),
2047 soft_wrap_mode_override,
2048 diagnostics_max_severity,
2049 hard_wrap: None,
2050 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2051 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2052 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2053 project,
2054 blink_manager: blink_manager.clone(),
2055 show_local_selections: true,
2056 show_scrollbars: ScrollbarAxes {
2057 horizontal: full_mode,
2058 vertical: full_mode,
2059 },
2060 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2061 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2062 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2063 show_gutter: full_mode,
2064 show_line_numbers: (!full_mode).then_some(false),
2065 use_relative_line_numbers: None,
2066 disable_expand_excerpt_buttons: !full_mode,
2067 show_git_diff_gutter: None,
2068 show_code_actions: None,
2069 show_runnables: None,
2070 show_breakpoints: None,
2071 show_wrap_guides: None,
2072 show_indent_guides,
2073 placeholder_text: None,
2074 highlight_order: 0,
2075 highlighted_rows: HashMap::default(),
2076 background_highlights: TreeMap::default(),
2077 gutter_highlights: TreeMap::default(),
2078 scrollbar_marker_state: ScrollbarMarkerState::default(),
2079 active_indent_guides_state: ActiveIndentGuidesState::default(),
2080 nav_history: None,
2081 context_menu: RefCell::new(None),
2082 context_menu_options: None,
2083 mouse_context_menu: None,
2084 completion_tasks: Vec::new(),
2085 inline_blame_popover: None,
2086 inline_blame_popover_show_task: None,
2087 signature_help_state: SignatureHelpState::default(),
2088 auto_signature_help: None,
2089 find_all_references_task_sources: Vec::new(),
2090 next_completion_id: 0,
2091 next_inlay_id: 0,
2092 code_action_providers,
2093 available_code_actions: None,
2094 code_actions_task: None,
2095 quick_selection_highlight_task: None,
2096 debounced_selection_highlight_task: None,
2097 document_highlights_task: None,
2098 linked_editing_range_task: None,
2099 pending_rename: None,
2100 searchable: !is_minimap,
2101 cursor_shape: EditorSettings::get_global(cx)
2102 .cursor_shape
2103 .unwrap_or_default(),
2104 current_line_highlight: None,
2105 autoindent_mode: Some(AutoindentMode::EachLine),
2106 collapse_matches: false,
2107 workspace: None,
2108 input_enabled: !is_minimap,
2109 use_modal_editing: full_mode,
2110 read_only: is_minimap,
2111 use_autoclose: true,
2112 use_auto_surround: true,
2113 auto_replace_emoji_shortcode: false,
2114 jsx_tag_auto_close_enabled_in_any_buffer: false,
2115 leader_id: None,
2116 remote_id: None,
2117 hover_state: HoverState::default(),
2118 pending_mouse_down: None,
2119 hovered_link_state: None,
2120 edit_prediction_provider: None,
2121 active_edit_prediction: None,
2122 stale_edit_prediction_in_menu: None,
2123 edit_prediction_preview: EditPredictionPreview::Inactive {
2124 released_too_fast: false,
2125 },
2126 inline_diagnostics_enabled: full_mode,
2127 diagnostics_enabled: full_mode,
2128 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2129 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2130 gutter_hovered: false,
2131 pixel_position_of_newest_cursor: None,
2132 last_bounds: None,
2133 last_position_map: None,
2134 expect_bounds_change: None,
2135 gutter_dimensions: GutterDimensions::default(),
2136 style: None,
2137 show_cursor_names: false,
2138 hovered_cursors: HashMap::default(),
2139 next_editor_action_id: EditorActionId::default(),
2140 editor_actions: Rc::default(),
2141 edit_predictions_hidden_for_vim_mode: false,
2142 show_edit_predictions_override: None,
2143 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2144 edit_prediction_settings: EditPredictionSettings::Disabled,
2145 edit_prediction_indent_conflict: false,
2146 edit_prediction_requires_modifier_in_indent_conflict: true,
2147 custom_context_menu: None,
2148 show_git_blame_gutter: false,
2149 show_git_blame_inline: false,
2150 show_selection_menu: None,
2151 show_git_blame_inline_delay_task: None,
2152 git_blame_inline_enabled: full_mode
2153 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2154 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2155 serialize_dirty_buffers: !is_minimap
2156 && ProjectSettings::get_global(cx)
2157 .session
2158 .restore_unsaved_buffers,
2159 blame: None,
2160 blame_subscription: None,
2161 tasks: BTreeMap::default(),
2162
2163 breakpoint_store,
2164 gutter_breakpoint_indicator: (None, None),
2165 hovered_diff_hunk_row: None,
2166 _subscriptions: (!is_minimap)
2167 .then(|| {
2168 vec![
2169 cx.observe(&buffer, Self::on_buffer_changed),
2170 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2171 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2172 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2173 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2174 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2175 cx.observe_window_activation(window, |editor, window, cx| {
2176 let active = window.is_window_active();
2177 editor.blink_manager.update(cx, |blink_manager, cx| {
2178 if active {
2179 blink_manager.enable(cx);
2180 } else {
2181 blink_manager.disable(cx);
2182 }
2183 });
2184 if active {
2185 editor.show_mouse_cursor(cx);
2186 }
2187 }),
2188 ]
2189 })
2190 .unwrap_or_default(),
2191 tasks_update_task: None,
2192 pull_diagnostics_task: Task::ready(()),
2193 colors: None,
2194 next_color_inlay_id: 0,
2195 linked_edit_ranges: Default::default(),
2196 in_project_search: false,
2197 previous_search_ranges: None,
2198 breadcrumb_header: None,
2199 focused_block: None,
2200 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2201 addons: HashMap::default(),
2202 registered_buffers: HashMap::default(),
2203 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2204 selection_mark_mode: false,
2205 toggle_fold_multiple_buffers: Task::ready(()),
2206 serialize_selections: Task::ready(()),
2207 serialize_folds: Task::ready(()),
2208 text_style_refinement: None,
2209 load_diff_task: load_uncommitted_diff,
2210 temporary_diff_override: false,
2211 mouse_cursor_hidden: false,
2212 minimap: None,
2213 hide_mouse_mode: EditorSettings::get_global(cx)
2214 .hide_mouse
2215 .unwrap_or_default(),
2216 change_list: ChangeList::new(),
2217 mode,
2218 selection_drag_state: SelectionDragState::None,
2219 folding_newlines: Task::ready(()),
2220 };
2221
2222 if is_minimap {
2223 return editor;
2224 }
2225
2226 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2227 editor
2228 ._subscriptions
2229 .push(cx.observe(breakpoints, |_, _, cx| {
2230 cx.notify();
2231 }));
2232 }
2233 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2234 editor._subscriptions.extend(project_subscriptions);
2235
2236 editor._subscriptions.push(cx.subscribe_in(
2237 &cx.entity(),
2238 window,
2239 |editor, _, e: &EditorEvent, window, cx| match e {
2240 EditorEvent::ScrollPositionChanged { local, .. } => {
2241 if *local {
2242 let new_anchor = editor.scroll_manager.anchor();
2243 let snapshot = editor.snapshot(window, cx);
2244 editor.update_restoration_data(cx, move |data| {
2245 data.scroll_position = (
2246 new_anchor.top_row(&snapshot.buffer_snapshot),
2247 new_anchor.offset,
2248 );
2249 });
2250 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2251 editor.inline_blame_popover.take();
2252 }
2253 }
2254 EditorEvent::Edited { .. } => {
2255 if !vim_enabled(cx) {
2256 let (map, selections) = editor.selections.all_adjusted_display(cx);
2257 let pop_state = editor
2258 .change_list
2259 .last()
2260 .map(|previous| {
2261 previous.len() == selections.len()
2262 && previous.iter().enumerate().all(|(ix, p)| {
2263 p.to_display_point(&map).row()
2264 == selections[ix].head().row()
2265 })
2266 })
2267 .unwrap_or(false);
2268 let new_positions = selections
2269 .into_iter()
2270 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2271 .collect();
2272 editor
2273 .change_list
2274 .push_to_change_list(pop_state, new_positions);
2275 }
2276 }
2277 _ => (),
2278 },
2279 ));
2280
2281 if let Some(dap_store) = editor
2282 .project
2283 .as_ref()
2284 .map(|project| project.read(cx).dap_store())
2285 {
2286 let weak_editor = cx.weak_entity();
2287
2288 editor
2289 ._subscriptions
2290 .push(
2291 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2292 let session_entity = cx.entity();
2293 weak_editor
2294 .update(cx, |editor, cx| {
2295 editor._subscriptions.push(
2296 cx.subscribe(&session_entity, Self::on_debug_session_event),
2297 );
2298 })
2299 .ok();
2300 }),
2301 );
2302
2303 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2304 editor
2305 ._subscriptions
2306 .push(cx.subscribe(&session, Self::on_debug_session_event));
2307 }
2308 }
2309
2310 // skip adding the initial selection to selection history
2311 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2312 editor.end_selection(window, cx);
2313 editor.selection_history.mode = SelectionHistoryMode::Normal;
2314
2315 editor.scroll_manager.show_scrollbars(window, cx);
2316 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2317
2318 if full_mode {
2319 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2320 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2321
2322 if editor.git_blame_inline_enabled {
2323 editor.start_git_blame_inline(false, window, cx);
2324 }
2325
2326 editor.go_to_active_debug_line(window, cx);
2327
2328 if let Some(buffer) = buffer.read(cx).as_singleton() {
2329 if let Some(project) = editor.project.as_ref() {
2330 let handle = project.update(cx, |project, cx| {
2331 project.register_buffer_with_language_servers(&buffer, cx)
2332 });
2333 editor
2334 .registered_buffers
2335 .insert(buffer.read(cx).remote_id(), handle);
2336 }
2337 }
2338
2339 editor.minimap =
2340 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2341 editor.colors = Some(LspColorData::new(cx));
2342 editor.update_lsp_data(false, None, window, cx);
2343 }
2344
2345 if editor.mode.is_full() {
2346 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2347 }
2348
2349 editor
2350 }
2351
2352 pub fn deploy_mouse_context_menu(
2353 &mut self,
2354 position: gpui::Point<Pixels>,
2355 context_menu: Entity<ContextMenu>,
2356 window: &mut Window,
2357 cx: &mut Context<Self>,
2358 ) {
2359 self.mouse_context_menu = Some(MouseContextMenu::new(
2360 self,
2361 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2362 context_menu,
2363 window,
2364 cx,
2365 ));
2366 }
2367
2368 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2369 self.mouse_context_menu
2370 .as_ref()
2371 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2372 }
2373
2374 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2375 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2376 }
2377
2378 fn key_context_internal(
2379 &self,
2380 has_active_edit_prediction: bool,
2381 window: &Window,
2382 cx: &App,
2383 ) -> KeyContext {
2384 let mut key_context = KeyContext::new_with_defaults();
2385 key_context.add("Editor");
2386 let mode = match self.mode {
2387 EditorMode::SingleLine { .. } => "single_line",
2388 EditorMode::AutoHeight { .. } => "auto_height",
2389 EditorMode::Minimap { .. } => "minimap",
2390 EditorMode::Full { .. } => "full",
2391 };
2392
2393 if EditorSettings::jupyter_enabled(cx) {
2394 key_context.add("jupyter");
2395 }
2396
2397 key_context.set("mode", mode);
2398 if self.pending_rename.is_some() {
2399 key_context.add("renaming");
2400 }
2401
2402 match self.context_menu.borrow().as_ref() {
2403 Some(CodeContextMenu::Completions(menu)) => {
2404 if menu.visible() {
2405 key_context.add("menu");
2406 key_context.add("showing_completions");
2407 }
2408 }
2409 Some(CodeContextMenu::CodeActions(menu)) => {
2410 if menu.visible() {
2411 key_context.add("menu");
2412 key_context.add("showing_code_actions")
2413 }
2414 }
2415 None => {}
2416 }
2417
2418 if self.signature_help_state.has_multiple_signatures() {
2419 key_context.add("showing_signature_help");
2420 }
2421
2422 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2423 if !self.focus_handle(cx).contains_focused(window, cx)
2424 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2425 {
2426 for addon in self.addons.values() {
2427 addon.extend_key_context(&mut key_context, cx)
2428 }
2429 }
2430
2431 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2432 if let Some(extension) = singleton_buffer
2433 .read(cx)
2434 .file()
2435 .and_then(|file| file.path().extension()?.to_str())
2436 {
2437 key_context.set("extension", extension.to_string());
2438 }
2439 } else {
2440 key_context.add("multibuffer");
2441 }
2442
2443 if has_active_edit_prediction {
2444 if self.edit_prediction_in_conflict() {
2445 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2446 } else {
2447 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2448 key_context.add("copilot_suggestion");
2449 }
2450 }
2451
2452 if self.selection_mark_mode {
2453 key_context.add("selection_mode");
2454 }
2455
2456 key_context
2457 }
2458
2459 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2460 if self.mouse_cursor_hidden {
2461 self.mouse_cursor_hidden = false;
2462 cx.notify();
2463 }
2464 }
2465
2466 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2467 let hide_mouse_cursor = match origin {
2468 HideMouseCursorOrigin::TypingAction => {
2469 matches!(
2470 self.hide_mouse_mode,
2471 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2472 )
2473 }
2474 HideMouseCursorOrigin::MovementAction => {
2475 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2476 }
2477 };
2478 if self.mouse_cursor_hidden != hide_mouse_cursor {
2479 self.mouse_cursor_hidden = hide_mouse_cursor;
2480 cx.notify();
2481 }
2482 }
2483
2484 pub fn edit_prediction_in_conflict(&self) -> bool {
2485 if !self.show_edit_predictions_in_menu() {
2486 return false;
2487 }
2488
2489 let showing_completions = self
2490 .context_menu
2491 .borrow()
2492 .as_ref()
2493 .map_or(false, |context| {
2494 matches!(context, CodeContextMenu::Completions(_))
2495 });
2496
2497 showing_completions
2498 || self.edit_prediction_requires_modifier()
2499 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2500 // bindings to insert tab characters.
2501 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2502 }
2503
2504 pub fn accept_edit_prediction_keybind(
2505 &self,
2506 accept_partial: bool,
2507 window: &Window,
2508 cx: &App,
2509 ) -> AcceptEditPredictionBinding {
2510 let key_context = self.key_context_internal(true, window, cx);
2511 let in_conflict = self.edit_prediction_in_conflict();
2512
2513 let bindings = if accept_partial {
2514 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2515 } else {
2516 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2517 };
2518
2519 // TODO: if the binding contains multiple keystrokes, display all of them, not
2520 // just the first one.
2521 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2522 !in_conflict
2523 || binding
2524 .keystrokes()
2525 .first()
2526 .map_or(false, |keystroke| keystroke.modifiers.modified())
2527 }))
2528 }
2529
2530 pub fn new_file(
2531 workspace: &mut Workspace,
2532 _: &workspace::NewFile,
2533 window: &mut Window,
2534 cx: &mut Context<Workspace>,
2535 ) {
2536 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2537 "Failed to create buffer",
2538 window,
2539 cx,
2540 |e, _, _| match e.error_code() {
2541 ErrorCode::RemoteUpgradeRequired => Some(format!(
2542 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2543 e.error_tag("required").unwrap_or("the latest version")
2544 )),
2545 _ => None,
2546 },
2547 );
2548 }
2549
2550 pub fn new_in_workspace(
2551 workspace: &mut Workspace,
2552 window: &mut Window,
2553 cx: &mut Context<Workspace>,
2554 ) -> Task<Result<Entity<Editor>>> {
2555 let project = workspace.project().clone();
2556 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2557
2558 cx.spawn_in(window, async move |workspace, cx| {
2559 let buffer = create.await?;
2560 workspace.update_in(cx, |workspace, window, cx| {
2561 let editor =
2562 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2563 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2564 editor
2565 })
2566 })
2567 }
2568
2569 fn new_file_vertical(
2570 workspace: &mut Workspace,
2571 _: &workspace::NewFileSplitVertical,
2572 window: &mut Window,
2573 cx: &mut Context<Workspace>,
2574 ) {
2575 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2576 }
2577
2578 fn new_file_horizontal(
2579 workspace: &mut Workspace,
2580 _: &workspace::NewFileSplitHorizontal,
2581 window: &mut Window,
2582 cx: &mut Context<Workspace>,
2583 ) {
2584 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2585 }
2586
2587 fn new_file_in_direction(
2588 workspace: &mut Workspace,
2589 direction: SplitDirection,
2590 window: &mut Window,
2591 cx: &mut Context<Workspace>,
2592 ) {
2593 let project = workspace.project().clone();
2594 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2595
2596 cx.spawn_in(window, async move |workspace, cx| {
2597 let buffer = create.await?;
2598 workspace.update_in(cx, move |workspace, window, cx| {
2599 workspace.split_item(
2600 direction,
2601 Box::new(
2602 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2603 ),
2604 window,
2605 cx,
2606 )
2607 })?;
2608 anyhow::Ok(())
2609 })
2610 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2611 match e.error_code() {
2612 ErrorCode::RemoteUpgradeRequired => Some(format!(
2613 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2614 e.error_tag("required").unwrap_or("the latest version")
2615 )),
2616 _ => None,
2617 }
2618 });
2619 }
2620
2621 pub fn leader_id(&self) -> Option<CollaboratorId> {
2622 self.leader_id
2623 }
2624
2625 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2626 &self.buffer
2627 }
2628
2629 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2630 self.workspace.as_ref()?.0.upgrade()
2631 }
2632
2633 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2634 self.buffer().read(cx).title(cx)
2635 }
2636
2637 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2638 let git_blame_gutter_max_author_length = self
2639 .render_git_blame_gutter(cx)
2640 .then(|| {
2641 if let Some(blame) = self.blame.as_ref() {
2642 let max_author_length =
2643 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2644 Some(max_author_length)
2645 } else {
2646 None
2647 }
2648 })
2649 .flatten();
2650
2651 EditorSnapshot {
2652 mode: self.mode.clone(),
2653 show_gutter: self.show_gutter,
2654 show_line_numbers: self.show_line_numbers,
2655 show_git_diff_gutter: self.show_git_diff_gutter,
2656 show_code_actions: self.show_code_actions,
2657 show_runnables: self.show_runnables,
2658 show_breakpoints: self.show_breakpoints,
2659 git_blame_gutter_max_author_length,
2660 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2661 scroll_anchor: self.scroll_manager.anchor(),
2662 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2663 placeholder_text: self.placeholder_text.clone(),
2664 is_focused: self.focus_handle.is_focused(window),
2665 current_line_highlight: self
2666 .current_line_highlight
2667 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2668 gutter_hovered: self.gutter_hovered,
2669 }
2670 }
2671
2672 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2673 self.buffer.read(cx).language_at(point, cx)
2674 }
2675
2676 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2677 self.buffer.read(cx).read(cx).file_at(point).cloned()
2678 }
2679
2680 pub fn active_excerpt(
2681 &self,
2682 cx: &App,
2683 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2684 self.buffer
2685 .read(cx)
2686 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2687 }
2688
2689 pub fn mode(&self) -> &EditorMode {
2690 &self.mode
2691 }
2692
2693 pub fn set_mode(&mut self, mode: EditorMode) {
2694 self.mode = mode;
2695 }
2696
2697 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2698 self.collaboration_hub.as_deref()
2699 }
2700
2701 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2702 self.collaboration_hub = Some(hub);
2703 }
2704
2705 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2706 self.in_project_search = in_project_search;
2707 }
2708
2709 pub fn set_custom_context_menu(
2710 &mut self,
2711 f: impl 'static
2712 + Fn(
2713 &mut Self,
2714 DisplayPoint,
2715 &mut Window,
2716 &mut Context<Self>,
2717 ) -> Option<Entity<ui::ContextMenu>>,
2718 ) {
2719 self.custom_context_menu = Some(Box::new(f))
2720 }
2721
2722 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2723 self.completion_provider = provider;
2724 }
2725
2726 #[cfg(any(test, feature = "test-support"))]
2727 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2728 self.completion_provider.clone()
2729 }
2730
2731 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2732 self.semantics_provider.clone()
2733 }
2734
2735 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2736 self.semantics_provider = provider;
2737 }
2738
2739 pub fn set_edit_prediction_provider<T>(
2740 &mut self,
2741 provider: Option<Entity<T>>,
2742 window: &mut Window,
2743 cx: &mut Context<Self>,
2744 ) where
2745 T: EditPredictionProvider,
2746 {
2747 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2748 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2749 if this.focus_handle.is_focused(window) {
2750 this.update_visible_edit_prediction(window, cx);
2751 }
2752 }),
2753 provider: Arc::new(provider),
2754 });
2755 self.update_edit_prediction_settings(cx);
2756 self.refresh_edit_prediction(false, false, window, cx);
2757 }
2758
2759 pub fn placeholder_text(&self) -> Option<&str> {
2760 self.placeholder_text.as_deref()
2761 }
2762
2763 pub fn set_placeholder_text(
2764 &mut self,
2765 placeholder_text: impl Into<Arc<str>>,
2766 cx: &mut Context<Self>,
2767 ) {
2768 let placeholder_text = Some(placeholder_text.into());
2769 if self.placeholder_text != placeholder_text {
2770 self.placeholder_text = placeholder_text;
2771 cx.notify();
2772 }
2773 }
2774
2775 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2776 self.cursor_shape = cursor_shape;
2777
2778 // Disrupt blink for immediate user feedback that the cursor shape has changed
2779 self.blink_manager.update(cx, BlinkManager::show_cursor);
2780
2781 cx.notify();
2782 }
2783
2784 pub fn set_current_line_highlight(
2785 &mut self,
2786 current_line_highlight: Option<CurrentLineHighlight>,
2787 ) {
2788 self.current_line_highlight = current_line_highlight;
2789 }
2790
2791 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2792 self.collapse_matches = collapse_matches;
2793 }
2794
2795 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2796 let buffers = self.buffer.read(cx).all_buffers();
2797 let Some(project) = self.project.as_ref() else {
2798 return;
2799 };
2800 project.update(cx, |project, cx| {
2801 for buffer in buffers {
2802 self.registered_buffers
2803 .entry(buffer.read(cx).remote_id())
2804 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2805 }
2806 })
2807 }
2808
2809 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2810 if self.collapse_matches {
2811 return range.start..range.start;
2812 }
2813 range.clone()
2814 }
2815
2816 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2817 if self.display_map.read(cx).clip_at_line_ends != clip {
2818 self.display_map
2819 .update(cx, |map, _| map.clip_at_line_ends = clip);
2820 }
2821 }
2822
2823 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2824 self.input_enabled = input_enabled;
2825 }
2826
2827 pub fn set_edit_predictions_hidden_for_vim_mode(
2828 &mut self,
2829 hidden: bool,
2830 window: &mut Window,
2831 cx: &mut Context<Self>,
2832 ) {
2833 if hidden != self.edit_predictions_hidden_for_vim_mode {
2834 self.edit_predictions_hidden_for_vim_mode = hidden;
2835 if hidden {
2836 self.update_visible_edit_prediction(window, cx);
2837 } else {
2838 self.refresh_edit_prediction(true, false, window, cx);
2839 }
2840 }
2841 }
2842
2843 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2844 self.menu_edit_predictions_policy = value;
2845 }
2846
2847 pub fn set_autoindent(&mut self, autoindent: bool) {
2848 if autoindent {
2849 self.autoindent_mode = Some(AutoindentMode::EachLine);
2850 } else {
2851 self.autoindent_mode = None;
2852 }
2853 }
2854
2855 pub fn read_only(&self, cx: &App) -> bool {
2856 self.read_only || self.buffer.read(cx).read_only()
2857 }
2858
2859 pub fn set_read_only(&mut self, read_only: bool) {
2860 self.read_only = read_only;
2861 }
2862
2863 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2864 self.use_autoclose = autoclose;
2865 }
2866
2867 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2868 self.use_auto_surround = auto_surround;
2869 }
2870
2871 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2872 self.auto_replace_emoji_shortcode = auto_replace;
2873 }
2874
2875 pub fn toggle_edit_predictions(
2876 &mut self,
2877 _: &ToggleEditPrediction,
2878 window: &mut Window,
2879 cx: &mut Context<Self>,
2880 ) {
2881 if self.show_edit_predictions_override.is_some() {
2882 self.set_show_edit_predictions(None, window, cx);
2883 } else {
2884 let show_edit_predictions = !self.edit_predictions_enabled();
2885 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2886 }
2887 }
2888
2889 pub fn set_show_edit_predictions(
2890 &mut self,
2891 show_edit_predictions: Option<bool>,
2892 window: &mut Window,
2893 cx: &mut Context<Self>,
2894 ) {
2895 self.show_edit_predictions_override = show_edit_predictions;
2896 self.update_edit_prediction_settings(cx);
2897
2898 if let Some(false) = show_edit_predictions {
2899 self.discard_edit_prediction(false, cx);
2900 } else {
2901 self.refresh_edit_prediction(false, true, window, cx);
2902 }
2903 }
2904
2905 fn edit_predictions_disabled_in_scope(
2906 &self,
2907 buffer: &Entity<Buffer>,
2908 buffer_position: language::Anchor,
2909 cx: &App,
2910 ) -> bool {
2911 let snapshot = buffer.read(cx).snapshot();
2912 let settings = snapshot.settings_at(buffer_position, cx);
2913
2914 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2915 return false;
2916 };
2917
2918 scope.override_name().map_or(false, |scope_name| {
2919 settings
2920 .edit_predictions_disabled_in
2921 .iter()
2922 .any(|s| s == scope_name)
2923 })
2924 }
2925
2926 pub fn set_use_modal_editing(&mut self, to: bool) {
2927 self.use_modal_editing = to;
2928 }
2929
2930 pub fn use_modal_editing(&self) -> bool {
2931 self.use_modal_editing
2932 }
2933
2934 fn selections_did_change(
2935 &mut self,
2936 local: bool,
2937 old_cursor_position: &Anchor,
2938 effects: SelectionEffects,
2939 window: &mut Window,
2940 cx: &mut Context<Self>,
2941 ) {
2942 window.invalidate_character_coordinates();
2943
2944 // Copy selections to primary selection buffer
2945 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2946 if local {
2947 let selections = self.selections.all::<usize>(cx);
2948 let buffer_handle = self.buffer.read(cx).read(cx);
2949
2950 let mut text = String::new();
2951 for (index, selection) in selections.iter().enumerate() {
2952 let text_for_selection = buffer_handle
2953 .text_for_range(selection.start..selection.end)
2954 .collect::<String>();
2955
2956 text.push_str(&text_for_selection);
2957 if index != selections.len() - 1 {
2958 text.push('\n');
2959 }
2960 }
2961
2962 if !text.is_empty() {
2963 cx.write_to_primary(ClipboardItem::new_string(text));
2964 }
2965 }
2966
2967 let selection_anchors = self.selections.disjoint_anchors();
2968
2969 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2970 self.buffer.update(cx, |buffer, cx| {
2971 buffer.set_active_selections(
2972 &selection_anchors,
2973 self.selections.line_mode,
2974 self.cursor_shape,
2975 cx,
2976 )
2977 });
2978 }
2979 let display_map = self
2980 .display_map
2981 .update(cx, |display_map, cx| display_map.snapshot(cx));
2982 let buffer = &display_map.buffer_snapshot;
2983 if self.selections.count() == 1 {
2984 self.add_selections_state = None;
2985 }
2986 self.select_next_state = None;
2987 self.select_prev_state = None;
2988 self.select_syntax_node_history.try_clear();
2989 self.invalidate_autoclose_regions(&selection_anchors, buffer);
2990 self.snippet_stack.invalidate(&selection_anchors, buffer);
2991 self.take_rename(false, window, cx);
2992
2993 let newest_selection = self.selections.newest_anchor();
2994 let new_cursor_position = newest_selection.head();
2995 let selection_start = newest_selection.start;
2996
2997 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2998 self.push_to_nav_history(
2999 *old_cursor_position,
3000 Some(new_cursor_position.to_point(buffer)),
3001 false,
3002 effects.nav_history == Some(true),
3003 cx,
3004 );
3005 }
3006
3007 if local {
3008 if let Some(buffer_id) = new_cursor_position.buffer_id {
3009 if !self.registered_buffers.contains_key(&buffer_id) {
3010 if let Some(project) = self.project.as_ref() {
3011 project.update(cx, |project, cx| {
3012 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3013 return;
3014 };
3015 self.registered_buffers.insert(
3016 buffer_id,
3017 project.register_buffer_with_language_servers(&buffer, cx),
3018 );
3019 })
3020 }
3021 }
3022 }
3023
3024 let mut context_menu = self.context_menu.borrow_mut();
3025 let completion_menu = match context_menu.as_ref() {
3026 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3027 Some(CodeContextMenu::CodeActions(_)) => {
3028 *context_menu = None;
3029 None
3030 }
3031 None => None,
3032 };
3033 let completion_position = completion_menu.map(|menu| menu.initial_position);
3034 drop(context_menu);
3035
3036 if effects.completions {
3037 if let Some(completion_position) = completion_position {
3038 let start_offset = selection_start.to_offset(buffer);
3039 let position_matches = start_offset == completion_position.to_offset(buffer);
3040 let continue_showing = if position_matches {
3041 if self.snippet_stack.is_empty() {
3042 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3043 } else {
3044 // Snippet choices can be shown even when the cursor is in whitespace.
3045 // Dismissing the menu with actions like backspace is handled by
3046 // invalidation regions.
3047 true
3048 }
3049 } else {
3050 false
3051 };
3052
3053 if continue_showing {
3054 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3055 } else {
3056 self.hide_context_menu(window, cx);
3057 }
3058 }
3059 }
3060
3061 hide_hover(self, cx);
3062
3063 if old_cursor_position.to_display_point(&display_map).row()
3064 != new_cursor_position.to_display_point(&display_map).row()
3065 {
3066 self.available_code_actions.take();
3067 }
3068 self.refresh_code_actions(window, cx);
3069 self.refresh_document_highlights(cx);
3070 self.refresh_selected_text_highlights(false, window, cx);
3071 refresh_matching_bracket_highlights(self, window, cx);
3072 self.update_visible_edit_prediction(window, cx);
3073 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3074 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3075 self.inline_blame_popover.take();
3076 if self.git_blame_inline_enabled {
3077 self.start_inline_blame_timer(window, cx);
3078 }
3079 }
3080
3081 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3082 cx.emit(EditorEvent::SelectionsChanged { local });
3083
3084 let selections = &self.selections.disjoint;
3085 if selections.len() == 1 {
3086 cx.emit(SearchEvent::ActiveMatchChanged)
3087 }
3088 if local {
3089 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3090 let inmemory_selections = selections
3091 .iter()
3092 .map(|s| {
3093 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3094 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3095 })
3096 .collect();
3097 self.update_restoration_data(cx, |data| {
3098 data.selections = inmemory_selections;
3099 });
3100
3101 if WorkspaceSettings::get(None, cx).restore_on_startup
3102 != RestoreOnStartupBehavior::None
3103 {
3104 if let Some(workspace_id) =
3105 self.workspace.as_ref().and_then(|workspace| workspace.1)
3106 {
3107 let snapshot = self.buffer().read(cx).snapshot(cx);
3108 let selections = selections.clone();
3109 let background_executor = cx.background_executor().clone();
3110 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3111 self.serialize_selections = cx.background_spawn(async move {
3112 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3113 let db_selections = selections
3114 .iter()
3115 .map(|selection| {
3116 (
3117 selection.start.to_offset(&snapshot),
3118 selection.end.to_offset(&snapshot),
3119 )
3120 })
3121 .collect();
3122
3123 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3124 .await
3125 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3126 .log_err();
3127 });
3128 }
3129 }
3130 }
3131 }
3132
3133 cx.notify();
3134 }
3135
3136 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3137 use text::ToOffset as _;
3138 use text::ToPoint as _;
3139
3140 if self.mode.is_minimap()
3141 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3142 {
3143 return;
3144 }
3145
3146 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3147 return;
3148 };
3149
3150 let snapshot = singleton.read(cx).snapshot();
3151 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3152 let display_snapshot = display_map.snapshot(cx);
3153
3154 display_snapshot
3155 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3156 .map(|fold| {
3157 fold.range.start.text_anchor.to_point(&snapshot)
3158 ..fold.range.end.text_anchor.to_point(&snapshot)
3159 })
3160 .collect()
3161 });
3162 self.update_restoration_data(cx, |data| {
3163 data.folds = inmemory_folds;
3164 });
3165
3166 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3167 return;
3168 };
3169 let background_executor = cx.background_executor().clone();
3170 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3171 let db_folds = self.display_map.update(cx, |display_map, cx| {
3172 display_map
3173 .snapshot(cx)
3174 .folds_in_range(0..snapshot.len())
3175 .map(|fold| {
3176 (
3177 fold.range.start.text_anchor.to_offset(&snapshot),
3178 fold.range.end.text_anchor.to_offset(&snapshot),
3179 )
3180 })
3181 .collect()
3182 });
3183 self.serialize_folds = cx.background_spawn(async move {
3184 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3185 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3186 .await
3187 .with_context(|| {
3188 format!(
3189 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3190 )
3191 })
3192 .log_err();
3193 });
3194 }
3195
3196 pub fn sync_selections(
3197 &mut self,
3198 other: Entity<Editor>,
3199 cx: &mut Context<Self>,
3200 ) -> gpui::Subscription {
3201 let other_selections = other.read(cx).selections.disjoint.to_vec();
3202 self.selections.change_with(cx, |selections| {
3203 selections.select_anchors(other_selections);
3204 });
3205
3206 let other_subscription =
3207 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3208 EditorEvent::SelectionsChanged { local: true } => {
3209 let other_selections = other.read(cx).selections.disjoint.to_vec();
3210 if other_selections.is_empty() {
3211 return;
3212 }
3213 this.selections.change_with(cx, |selections| {
3214 selections.select_anchors(other_selections);
3215 });
3216 }
3217 _ => {}
3218 });
3219
3220 let this_subscription =
3221 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3222 EditorEvent::SelectionsChanged { local: true } => {
3223 let these_selections = this.selections.disjoint.to_vec();
3224 if these_selections.is_empty() {
3225 return;
3226 }
3227 other.update(cx, |other_editor, cx| {
3228 other_editor.selections.change_with(cx, |selections| {
3229 selections.select_anchors(these_selections);
3230 })
3231 });
3232 }
3233 _ => {}
3234 });
3235
3236 Subscription::join(other_subscription, this_subscription)
3237 }
3238
3239 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3240 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3241 /// effects of selection change occur at the end of the transaction.
3242 pub fn change_selections<R>(
3243 &mut self,
3244 effects: SelectionEffects,
3245 window: &mut Window,
3246 cx: &mut Context<Self>,
3247 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3248 ) -> R {
3249 if let Some(state) = &mut self.deferred_selection_effects_state {
3250 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3251 state.effects.completions = effects.completions;
3252 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3253 let (changed, result) = self.selections.change_with(cx, change);
3254 state.changed |= changed;
3255 return result;
3256 }
3257 let mut state = DeferredSelectionEffectsState {
3258 changed: false,
3259 effects,
3260 old_cursor_position: self.selections.newest_anchor().head(),
3261 history_entry: SelectionHistoryEntry {
3262 selections: self.selections.disjoint_anchors(),
3263 select_next_state: self.select_next_state.clone(),
3264 select_prev_state: self.select_prev_state.clone(),
3265 add_selections_state: self.add_selections_state.clone(),
3266 },
3267 };
3268 let (changed, result) = self.selections.change_with(cx, change);
3269 state.changed = state.changed || changed;
3270 if self.defer_selection_effects {
3271 self.deferred_selection_effects_state = Some(state);
3272 } else {
3273 self.apply_selection_effects(state, window, cx);
3274 }
3275 result
3276 }
3277
3278 /// Defers the effects of selection change, so that the effects of multiple calls to
3279 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3280 /// to selection history and the state of popovers based on selection position aren't
3281 /// erroneously updated.
3282 pub fn with_selection_effects_deferred<R>(
3283 &mut self,
3284 window: &mut Window,
3285 cx: &mut Context<Self>,
3286 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3287 ) -> R {
3288 let already_deferred = self.defer_selection_effects;
3289 self.defer_selection_effects = true;
3290 let result = update(self, window, cx);
3291 if !already_deferred {
3292 self.defer_selection_effects = false;
3293 if let Some(state) = self.deferred_selection_effects_state.take() {
3294 self.apply_selection_effects(state, window, cx);
3295 }
3296 }
3297 result
3298 }
3299
3300 fn apply_selection_effects(
3301 &mut self,
3302 state: DeferredSelectionEffectsState,
3303 window: &mut Window,
3304 cx: &mut Context<Self>,
3305 ) {
3306 if state.changed {
3307 self.selection_history.push(state.history_entry);
3308
3309 if let Some(autoscroll) = state.effects.scroll {
3310 self.request_autoscroll(autoscroll, cx);
3311 }
3312
3313 let old_cursor_position = &state.old_cursor_position;
3314
3315 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3316
3317 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3318 self.show_signature_help(&ShowSignatureHelp, window, cx);
3319 }
3320 }
3321 }
3322
3323 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3324 where
3325 I: IntoIterator<Item = (Range<S>, T)>,
3326 S: ToOffset,
3327 T: Into<Arc<str>>,
3328 {
3329 if self.read_only(cx) {
3330 return;
3331 }
3332
3333 self.buffer
3334 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3335 }
3336
3337 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3338 where
3339 I: IntoIterator<Item = (Range<S>, T)>,
3340 S: ToOffset,
3341 T: Into<Arc<str>>,
3342 {
3343 if self.read_only(cx) {
3344 return;
3345 }
3346
3347 self.buffer.update(cx, |buffer, cx| {
3348 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3349 });
3350 }
3351
3352 pub fn edit_with_block_indent<I, S, T>(
3353 &mut self,
3354 edits: I,
3355 original_indent_columns: Vec<Option<u32>>,
3356 cx: &mut Context<Self>,
3357 ) where
3358 I: IntoIterator<Item = (Range<S>, T)>,
3359 S: ToOffset,
3360 T: Into<Arc<str>>,
3361 {
3362 if self.read_only(cx) {
3363 return;
3364 }
3365
3366 self.buffer.update(cx, |buffer, cx| {
3367 buffer.edit(
3368 edits,
3369 Some(AutoindentMode::Block {
3370 original_indent_columns,
3371 }),
3372 cx,
3373 )
3374 });
3375 }
3376
3377 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3378 self.hide_context_menu(window, cx);
3379
3380 match phase {
3381 SelectPhase::Begin {
3382 position,
3383 add,
3384 click_count,
3385 } => self.begin_selection(position, add, click_count, window, cx),
3386 SelectPhase::BeginColumnar {
3387 position,
3388 goal_column,
3389 reset,
3390 mode,
3391 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3392 SelectPhase::Extend {
3393 position,
3394 click_count,
3395 } => self.extend_selection(position, click_count, window, cx),
3396 SelectPhase::Update {
3397 position,
3398 goal_column,
3399 scroll_delta,
3400 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3401 SelectPhase::End => self.end_selection(window, cx),
3402 }
3403 }
3404
3405 fn extend_selection(
3406 &mut self,
3407 position: DisplayPoint,
3408 click_count: usize,
3409 window: &mut Window,
3410 cx: &mut Context<Self>,
3411 ) {
3412 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3413 let tail = self.selections.newest::<usize>(cx).tail();
3414 self.begin_selection(position, false, click_count, window, cx);
3415
3416 let position = position.to_offset(&display_map, Bias::Left);
3417 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3418
3419 let mut pending_selection = self
3420 .selections
3421 .pending_anchor()
3422 .expect("extend_selection not called with pending selection");
3423 if position >= tail {
3424 pending_selection.start = tail_anchor;
3425 } else {
3426 pending_selection.end = tail_anchor;
3427 pending_selection.reversed = true;
3428 }
3429
3430 let mut pending_mode = self.selections.pending_mode().unwrap();
3431 match &mut pending_mode {
3432 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3433 _ => {}
3434 }
3435
3436 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3437 SelectionEffects::scroll(Autoscroll::fit())
3438 } else {
3439 SelectionEffects::no_scroll()
3440 };
3441
3442 self.change_selections(effects, window, cx, |s| {
3443 s.set_pending(pending_selection, pending_mode)
3444 });
3445 }
3446
3447 fn begin_selection(
3448 &mut self,
3449 position: DisplayPoint,
3450 add: bool,
3451 click_count: usize,
3452 window: &mut Window,
3453 cx: &mut Context<Self>,
3454 ) {
3455 if !self.focus_handle.is_focused(window) {
3456 self.last_focused_descendant = None;
3457 window.focus(&self.focus_handle);
3458 }
3459
3460 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3461 let buffer = &display_map.buffer_snapshot;
3462 let position = display_map.clip_point(position, Bias::Left);
3463
3464 let start;
3465 let end;
3466 let mode;
3467 let mut auto_scroll;
3468 match click_count {
3469 1 => {
3470 start = buffer.anchor_before(position.to_point(&display_map));
3471 end = start;
3472 mode = SelectMode::Character;
3473 auto_scroll = true;
3474 }
3475 2 => {
3476 let position = display_map
3477 .clip_point(position, Bias::Left)
3478 .to_offset(&display_map, Bias::Left);
3479 let (range, _) = buffer.surrounding_word(position, false);
3480 start = buffer.anchor_before(range.start);
3481 end = buffer.anchor_before(range.end);
3482 mode = SelectMode::Word(start..end);
3483 auto_scroll = true;
3484 }
3485 3 => {
3486 let position = display_map
3487 .clip_point(position, Bias::Left)
3488 .to_point(&display_map);
3489 let line_start = display_map.prev_line_boundary(position).0;
3490 let next_line_start = buffer.clip_point(
3491 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3492 Bias::Left,
3493 );
3494 start = buffer.anchor_before(line_start);
3495 end = buffer.anchor_before(next_line_start);
3496 mode = SelectMode::Line(start..end);
3497 auto_scroll = true;
3498 }
3499 _ => {
3500 start = buffer.anchor_before(0);
3501 end = buffer.anchor_before(buffer.len());
3502 mode = SelectMode::All;
3503 auto_scroll = false;
3504 }
3505 }
3506 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3507
3508 let point_to_delete: Option<usize> = {
3509 let selected_points: Vec<Selection<Point>> =
3510 self.selections.disjoint_in_range(start..end, cx);
3511
3512 if !add || click_count > 1 {
3513 None
3514 } else if !selected_points.is_empty() {
3515 Some(selected_points[0].id)
3516 } else {
3517 let clicked_point_already_selected =
3518 self.selections.disjoint.iter().find(|selection| {
3519 selection.start.to_point(buffer) == start.to_point(buffer)
3520 || selection.end.to_point(buffer) == end.to_point(buffer)
3521 });
3522
3523 clicked_point_already_selected.map(|selection| selection.id)
3524 }
3525 };
3526
3527 let selections_count = self.selections.count();
3528 let effects = if auto_scroll {
3529 SelectionEffects::default()
3530 } else {
3531 SelectionEffects::no_scroll()
3532 };
3533
3534 self.change_selections(effects, window, cx, |s| {
3535 if let Some(point_to_delete) = point_to_delete {
3536 s.delete(point_to_delete);
3537
3538 if selections_count == 1 {
3539 s.set_pending_anchor_range(start..end, mode);
3540 }
3541 } else {
3542 if !add {
3543 s.clear_disjoint();
3544 }
3545
3546 s.set_pending_anchor_range(start..end, mode);
3547 }
3548 });
3549 }
3550
3551 fn begin_columnar_selection(
3552 &mut self,
3553 position: DisplayPoint,
3554 goal_column: u32,
3555 reset: bool,
3556 mode: ColumnarMode,
3557 window: &mut Window,
3558 cx: &mut Context<Self>,
3559 ) {
3560 if !self.focus_handle.is_focused(window) {
3561 self.last_focused_descendant = None;
3562 window.focus(&self.focus_handle);
3563 }
3564
3565 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3566
3567 if reset {
3568 let pointer_position = display_map
3569 .buffer_snapshot
3570 .anchor_before(position.to_point(&display_map));
3571
3572 self.change_selections(
3573 SelectionEffects::scroll(Autoscroll::newest()),
3574 window,
3575 cx,
3576 |s| {
3577 s.clear_disjoint();
3578 s.set_pending_anchor_range(
3579 pointer_position..pointer_position,
3580 SelectMode::Character,
3581 );
3582 },
3583 );
3584 };
3585
3586 let tail = self.selections.newest::<Point>(cx).tail();
3587 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3588 self.columnar_selection_state = match mode {
3589 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3590 selection_tail: selection_anchor,
3591 display_point: if reset {
3592 if position.column() != goal_column {
3593 Some(DisplayPoint::new(position.row(), goal_column))
3594 } else {
3595 None
3596 }
3597 } else {
3598 None
3599 },
3600 }),
3601 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3602 selection_tail: selection_anchor,
3603 }),
3604 };
3605
3606 if !reset {
3607 self.select_columns(position, goal_column, &display_map, window, cx);
3608 }
3609 }
3610
3611 fn update_selection(
3612 &mut self,
3613 position: DisplayPoint,
3614 goal_column: u32,
3615 scroll_delta: gpui::Point<f32>,
3616 window: &mut Window,
3617 cx: &mut Context<Self>,
3618 ) {
3619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3620
3621 if self.columnar_selection_state.is_some() {
3622 self.select_columns(position, goal_column, &display_map, window, cx);
3623 } else if let Some(mut pending) = self.selections.pending_anchor() {
3624 let buffer = &display_map.buffer_snapshot;
3625 let head;
3626 let tail;
3627 let mode = self.selections.pending_mode().unwrap();
3628 match &mode {
3629 SelectMode::Character => {
3630 head = position.to_point(&display_map);
3631 tail = pending.tail().to_point(buffer);
3632 }
3633 SelectMode::Word(original_range) => {
3634 let offset = display_map
3635 .clip_point(position, Bias::Left)
3636 .to_offset(&display_map, Bias::Left);
3637 let original_range = original_range.to_offset(buffer);
3638
3639 let head_offset = if buffer.is_inside_word(offset, false)
3640 || original_range.contains(&offset)
3641 {
3642 let (word_range, _) = buffer.surrounding_word(offset, false);
3643 if word_range.start < original_range.start {
3644 word_range.start
3645 } else {
3646 word_range.end
3647 }
3648 } else {
3649 offset
3650 };
3651
3652 head = head_offset.to_point(buffer);
3653 if head_offset <= original_range.start {
3654 tail = original_range.end.to_point(buffer);
3655 } else {
3656 tail = original_range.start.to_point(buffer);
3657 }
3658 }
3659 SelectMode::Line(original_range) => {
3660 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3661
3662 let position = display_map
3663 .clip_point(position, Bias::Left)
3664 .to_point(&display_map);
3665 let line_start = display_map.prev_line_boundary(position).0;
3666 let next_line_start = buffer.clip_point(
3667 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3668 Bias::Left,
3669 );
3670
3671 if line_start < original_range.start {
3672 head = line_start
3673 } else {
3674 head = next_line_start
3675 }
3676
3677 if head <= original_range.start {
3678 tail = original_range.end;
3679 } else {
3680 tail = original_range.start;
3681 }
3682 }
3683 SelectMode::All => {
3684 return;
3685 }
3686 };
3687
3688 if head < tail {
3689 pending.start = buffer.anchor_before(head);
3690 pending.end = buffer.anchor_before(tail);
3691 pending.reversed = true;
3692 } else {
3693 pending.start = buffer.anchor_before(tail);
3694 pending.end = buffer.anchor_before(head);
3695 pending.reversed = false;
3696 }
3697
3698 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3699 s.set_pending(pending, mode);
3700 });
3701 } else {
3702 log::error!("update_selection dispatched with no pending selection");
3703 return;
3704 }
3705
3706 self.apply_scroll_delta(scroll_delta, window, cx);
3707 cx.notify();
3708 }
3709
3710 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3711 self.columnar_selection_state.take();
3712 if self.selections.pending_anchor().is_some() {
3713 let selections = self.selections.all::<usize>(cx);
3714 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3715 s.select(selections);
3716 s.clear_pending();
3717 });
3718 }
3719 }
3720
3721 fn select_columns(
3722 &mut self,
3723 head: DisplayPoint,
3724 goal_column: u32,
3725 display_map: &DisplaySnapshot,
3726 window: &mut Window,
3727 cx: &mut Context<Self>,
3728 ) {
3729 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3730 return;
3731 };
3732
3733 let tail = match columnar_state {
3734 ColumnarSelectionState::FromMouse {
3735 selection_tail,
3736 display_point,
3737 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3738 ColumnarSelectionState::FromSelection { selection_tail } => {
3739 selection_tail.to_display_point(&display_map)
3740 }
3741 };
3742
3743 let start_row = cmp::min(tail.row(), head.row());
3744 let end_row = cmp::max(tail.row(), head.row());
3745 let start_column = cmp::min(tail.column(), goal_column);
3746 let end_column = cmp::max(tail.column(), goal_column);
3747 let reversed = start_column < tail.column();
3748
3749 let selection_ranges = (start_row.0..=end_row.0)
3750 .map(DisplayRow)
3751 .filter_map(|row| {
3752 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3753 || start_column <= display_map.line_len(row))
3754 && !display_map.is_block_line(row)
3755 {
3756 let start = display_map
3757 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3758 .to_point(display_map);
3759 let end = display_map
3760 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3761 .to_point(display_map);
3762 if reversed {
3763 Some(end..start)
3764 } else {
3765 Some(start..end)
3766 }
3767 } else {
3768 None
3769 }
3770 })
3771 .collect::<Vec<_>>();
3772
3773 let ranges = match columnar_state {
3774 ColumnarSelectionState::FromMouse { .. } => {
3775 let mut non_empty_ranges = selection_ranges
3776 .iter()
3777 .filter(|selection_range| selection_range.start != selection_range.end)
3778 .peekable();
3779 if non_empty_ranges.peek().is_some() {
3780 non_empty_ranges.cloned().collect()
3781 } else {
3782 selection_ranges
3783 }
3784 }
3785 _ => selection_ranges,
3786 };
3787
3788 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3789 s.select_ranges(ranges);
3790 });
3791 cx.notify();
3792 }
3793
3794 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3795 self.selections
3796 .all_adjusted(cx)
3797 .iter()
3798 .any(|selection| !selection.is_empty())
3799 }
3800
3801 pub fn has_pending_nonempty_selection(&self) -> bool {
3802 let pending_nonempty_selection = match self.selections.pending_anchor() {
3803 Some(Selection { start, end, .. }) => start != end,
3804 None => false,
3805 };
3806
3807 pending_nonempty_selection
3808 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3809 }
3810
3811 pub fn has_pending_selection(&self) -> bool {
3812 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3813 }
3814
3815 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3816 self.selection_mark_mode = false;
3817 self.selection_drag_state = SelectionDragState::None;
3818
3819 if self.clear_expanded_diff_hunks(cx) {
3820 cx.notify();
3821 return;
3822 }
3823 if self.dismiss_menus_and_popups(true, window, cx) {
3824 return;
3825 }
3826
3827 if self.mode.is_full()
3828 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3829 {
3830 return;
3831 }
3832
3833 cx.propagate();
3834 }
3835
3836 pub fn dismiss_menus_and_popups(
3837 &mut self,
3838 is_user_requested: bool,
3839 window: &mut Window,
3840 cx: &mut Context<Self>,
3841 ) -> bool {
3842 if self.take_rename(false, window, cx).is_some() {
3843 return true;
3844 }
3845
3846 if hide_hover(self, cx) {
3847 return true;
3848 }
3849
3850 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3851 return true;
3852 }
3853
3854 if self.hide_context_menu(window, cx).is_some() {
3855 return true;
3856 }
3857
3858 if self.mouse_context_menu.take().is_some() {
3859 return true;
3860 }
3861
3862 if is_user_requested && self.discard_edit_prediction(true, cx) {
3863 return true;
3864 }
3865
3866 if self.snippet_stack.pop().is_some() {
3867 return true;
3868 }
3869
3870 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3871 self.dismiss_diagnostics(cx);
3872 return true;
3873 }
3874
3875 false
3876 }
3877
3878 fn linked_editing_ranges_for(
3879 &self,
3880 selection: Range<text::Anchor>,
3881 cx: &App,
3882 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3883 if self.linked_edit_ranges.is_empty() {
3884 return None;
3885 }
3886 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3887 selection.end.buffer_id.and_then(|end_buffer_id| {
3888 if selection.start.buffer_id != Some(end_buffer_id) {
3889 return None;
3890 }
3891 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3892 let snapshot = buffer.read(cx).snapshot();
3893 self.linked_edit_ranges
3894 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3895 .map(|ranges| (ranges, snapshot, buffer))
3896 })?;
3897 use text::ToOffset as TO;
3898 // find offset from the start of current range to current cursor position
3899 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3900
3901 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3902 let start_difference = start_offset - start_byte_offset;
3903 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3904 let end_difference = end_offset - start_byte_offset;
3905 // Current range has associated linked ranges.
3906 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3907 for range in linked_ranges.iter() {
3908 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3909 let end_offset = start_offset + end_difference;
3910 let start_offset = start_offset + start_difference;
3911 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3912 continue;
3913 }
3914 if self.selections.disjoint_anchor_ranges().any(|s| {
3915 if s.start.buffer_id != selection.start.buffer_id
3916 || s.end.buffer_id != selection.end.buffer_id
3917 {
3918 return false;
3919 }
3920 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3921 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3922 }) {
3923 continue;
3924 }
3925 let start = buffer_snapshot.anchor_after(start_offset);
3926 let end = buffer_snapshot.anchor_after(end_offset);
3927 linked_edits
3928 .entry(buffer.clone())
3929 .or_default()
3930 .push(start..end);
3931 }
3932 Some(linked_edits)
3933 }
3934
3935 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3936 let text: Arc<str> = text.into();
3937
3938 if self.read_only(cx) {
3939 return;
3940 }
3941
3942 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3943
3944 let selections = self.selections.all_adjusted(cx);
3945 let mut bracket_inserted = false;
3946 let mut edits = Vec::new();
3947 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3948 let mut new_selections = Vec::with_capacity(selections.len());
3949 let mut new_autoclose_regions = Vec::new();
3950 let snapshot = self.buffer.read(cx).read(cx);
3951 let mut clear_linked_edit_ranges = false;
3952
3953 for (selection, autoclose_region) in
3954 self.selections_with_autoclose_regions(selections, &snapshot)
3955 {
3956 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3957 // Determine if the inserted text matches the opening or closing
3958 // bracket of any of this language's bracket pairs.
3959 let mut bracket_pair = None;
3960 let mut is_bracket_pair_start = false;
3961 let mut is_bracket_pair_end = false;
3962 if !text.is_empty() {
3963 let mut bracket_pair_matching_end = None;
3964 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3965 // and they are removing the character that triggered IME popup.
3966 for (pair, enabled) in scope.brackets() {
3967 if !pair.close && !pair.surround {
3968 continue;
3969 }
3970
3971 if enabled && pair.start.ends_with(text.as_ref()) {
3972 let prefix_len = pair.start.len() - text.len();
3973 let preceding_text_matches_prefix = prefix_len == 0
3974 || (selection.start.column >= (prefix_len as u32)
3975 && snapshot.contains_str_at(
3976 Point::new(
3977 selection.start.row,
3978 selection.start.column - (prefix_len as u32),
3979 ),
3980 &pair.start[..prefix_len],
3981 ));
3982 if preceding_text_matches_prefix {
3983 bracket_pair = Some(pair.clone());
3984 is_bracket_pair_start = true;
3985 break;
3986 }
3987 }
3988 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3989 {
3990 // take first bracket pair matching end, but don't break in case a later bracket
3991 // pair matches start
3992 bracket_pair_matching_end = Some(pair.clone());
3993 }
3994 }
3995 if let Some(end) = bracket_pair_matching_end
3996 && bracket_pair.is_none()
3997 {
3998 bracket_pair = Some(end);
3999 is_bracket_pair_end = true;
4000 }
4001 }
4002
4003 if let Some(bracket_pair) = bracket_pair {
4004 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4005 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4006 let auto_surround =
4007 self.use_auto_surround && snapshot_settings.use_auto_surround;
4008 if selection.is_empty() {
4009 if is_bracket_pair_start {
4010 // If the inserted text is a suffix of an opening bracket and the
4011 // selection is preceded by the rest of the opening bracket, then
4012 // insert the closing bracket.
4013 let following_text_allows_autoclose = snapshot
4014 .chars_at(selection.start)
4015 .next()
4016 .map_or(true, |c| scope.should_autoclose_before(c));
4017
4018 let preceding_text_allows_autoclose = selection.start.column == 0
4019 || snapshot.reversed_chars_at(selection.start).next().map_or(
4020 true,
4021 |c| {
4022 bracket_pair.start != bracket_pair.end
4023 || !snapshot
4024 .char_classifier_at(selection.start)
4025 .is_word(c)
4026 },
4027 );
4028
4029 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4030 && bracket_pair.start.len() == 1
4031 {
4032 let target = bracket_pair.start.chars().next().unwrap();
4033 let current_line_count = snapshot
4034 .reversed_chars_at(selection.start)
4035 .take_while(|&c| c != '\n')
4036 .filter(|&c| c == target)
4037 .count();
4038 current_line_count % 2 == 1
4039 } else {
4040 false
4041 };
4042
4043 if autoclose
4044 && bracket_pair.close
4045 && following_text_allows_autoclose
4046 && preceding_text_allows_autoclose
4047 && !is_closing_quote
4048 {
4049 let anchor = snapshot.anchor_before(selection.end);
4050 new_selections.push((selection.map(|_| anchor), text.len()));
4051 new_autoclose_regions.push((
4052 anchor,
4053 text.len(),
4054 selection.id,
4055 bracket_pair.clone(),
4056 ));
4057 edits.push((
4058 selection.range(),
4059 format!("{}{}", text, bracket_pair.end).into(),
4060 ));
4061 bracket_inserted = true;
4062 continue;
4063 }
4064 }
4065
4066 if let Some(region) = autoclose_region {
4067 // If the selection is followed by an auto-inserted closing bracket,
4068 // then don't insert that closing bracket again; just move the selection
4069 // past the closing bracket.
4070 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4071 && text.as_ref() == region.pair.end.as_str()
4072 && snapshot.contains_str_at(region.range.end, text.as_ref());
4073 if should_skip {
4074 let anchor = snapshot.anchor_after(selection.end);
4075 new_selections
4076 .push((selection.map(|_| anchor), region.pair.end.len()));
4077 continue;
4078 }
4079 }
4080
4081 let always_treat_brackets_as_autoclosed = snapshot
4082 .language_settings_at(selection.start, cx)
4083 .always_treat_brackets_as_autoclosed;
4084 if always_treat_brackets_as_autoclosed
4085 && is_bracket_pair_end
4086 && snapshot.contains_str_at(selection.end, text.as_ref())
4087 {
4088 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4089 // and the inserted text is a closing bracket and the selection is followed
4090 // by the closing bracket then move the selection past the closing bracket.
4091 let anchor = snapshot.anchor_after(selection.end);
4092 new_selections.push((selection.map(|_| anchor), text.len()));
4093 continue;
4094 }
4095 }
4096 // If an opening bracket is 1 character long and is typed while
4097 // text is selected, then surround that text with the bracket pair.
4098 else if auto_surround
4099 && bracket_pair.surround
4100 && is_bracket_pair_start
4101 && bracket_pair.start.chars().count() == 1
4102 {
4103 edits.push((selection.start..selection.start, text.clone()));
4104 edits.push((
4105 selection.end..selection.end,
4106 bracket_pair.end.as_str().into(),
4107 ));
4108 bracket_inserted = true;
4109 new_selections.push((
4110 Selection {
4111 id: selection.id,
4112 start: snapshot.anchor_after(selection.start),
4113 end: snapshot.anchor_before(selection.end),
4114 reversed: selection.reversed,
4115 goal: selection.goal,
4116 },
4117 0,
4118 ));
4119 continue;
4120 }
4121 }
4122 }
4123
4124 if self.auto_replace_emoji_shortcode
4125 && selection.is_empty()
4126 && text.as_ref().ends_with(':')
4127 {
4128 if let Some(possible_emoji_short_code) =
4129 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4130 {
4131 if !possible_emoji_short_code.is_empty() {
4132 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4133 let emoji_shortcode_start = Point::new(
4134 selection.start.row,
4135 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4136 );
4137
4138 // Remove shortcode from buffer
4139 edits.push((
4140 emoji_shortcode_start..selection.start,
4141 "".to_string().into(),
4142 ));
4143 new_selections.push((
4144 Selection {
4145 id: selection.id,
4146 start: snapshot.anchor_after(emoji_shortcode_start),
4147 end: snapshot.anchor_before(selection.start),
4148 reversed: selection.reversed,
4149 goal: selection.goal,
4150 },
4151 0,
4152 ));
4153
4154 // Insert emoji
4155 let selection_start_anchor = snapshot.anchor_after(selection.start);
4156 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4157 edits.push((selection.start..selection.end, emoji.to_string().into()));
4158
4159 continue;
4160 }
4161 }
4162 }
4163 }
4164
4165 // If not handling any auto-close operation, then just replace the selected
4166 // text with the given input and move the selection to the end of the
4167 // newly inserted text.
4168 let anchor = snapshot.anchor_after(selection.end);
4169 if !self.linked_edit_ranges.is_empty() {
4170 let start_anchor = snapshot.anchor_before(selection.start);
4171
4172 let is_word_char = text.chars().next().map_or(true, |char| {
4173 let classifier = snapshot
4174 .char_classifier_at(start_anchor.to_offset(&snapshot))
4175 .ignore_punctuation(true);
4176 classifier.is_word(char)
4177 });
4178
4179 if is_word_char {
4180 if let Some(ranges) = self
4181 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4182 {
4183 for (buffer, edits) in ranges {
4184 linked_edits
4185 .entry(buffer.clone())
4186 .or_default()
4187 .extend(edits.into_iter().map(|range| (range, text.clone())));
4188 }
4189 }
4190 } else {
4191 clear_linked_edit_ranges = true;
4192 }
4193 }
4194
4195 new_selections.push((selection.map(|_| anchor), 0));
4196 edits.push((selection.start..selection.end, text.clone()));
4197 }
4198
4199 drop(snapshot);
4200
4201 self.transact(window, cx, |this, window, cx| {
4202 if clear_linked_edit_ranges {
4203 this.linked_edit_ranges.clear();
4204 }
4205 let initial_buffer_versions =
4206 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4207
4208 this.buffer.update(cx, |buffer, cx| {
4209 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4210 });
4211 for (buffer, edits) in linked_edits {
4212 buffer.update(cx, |buffer, cx| {
4213 let snapshot = buffer.snapshot();
4214 let edits = edits
4215 .into_iter()
4216 .map(|(range, text)| {
4217 use text::ToPoint as TP;
4218 let end_point = TP::to_point(&range.end, &snapshot);
4219 let start_point = TP::to_point(&range.start, &snapshot);
4220 (start_point..end_point, text)
4221 })
4222 .sorted_by_key(|(range, _)| range.start);
4223 buffer.edit(edits, None, cx);
4224 })
4225 }
4226 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4227 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4228 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4229 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4230 .zip(new_selection_deltas)
4231 .map(|(selection, delta)| Selection {
4232 id: selection.id,
4233 start: selection.start + delta,
4234 end: selection.end + delta,
4235 reversed: selection.reversed,
4236 goal: SelectionGoal::None,
4237 })
4238 .collect::<Vec<_>>();
4239
4240 let mut i = 0;
4241 for (position, delta, selection_id, pair) in new_autoclose_regions {
4242 let position = position.to_offset(&map.buffer_snapshot) + delta;
4243 let start = map.buffer_snapshot.anchor_before(position);
4244 let end = map.buffer_snapshot.anchor_after(position);
4245 while let Some(existing_state) = this.autoclose_regions.get(i) {
4246 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4247 Ordering::Less => i += 1,
4248 Ordering::Greater => break,
4249 Ordering::Equal => {
4250 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4251 Ordering::Less => i += 1,
4252 Ordering::Equal => break,
4253 Ordering::Greater => break,
4254 }
4255 }
4256 }
4257 }
4258 this.autoclose_regions.insert(
4259 i,
4260 AutocloseRegion {
4261 selection_id,
4262 range: start..end,
4263 pair,
4264 },
4265 );
4266 }
4267
4268 let had_active_edit_prediction = this.has_active_edit_prediction();
4269 this.change_selections(
4270 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4271 window,
4272 cx,
4273 |s| s.select(new_selections),
4274 );
4275
4276 if !bracket_inserted {
4277 if let Some(on_type_format_task) =
4278 this.trigger_on_type_formatting(text.to_string(), window, cx)
4279 {
4280 on_type_format_task.detach_and_log_err(cx);
4281 }
4282 }
4283
4284 let editor_settings = EditorSettings::get_global(cx);
4285 if bracket_inserted
4286 && (editor_settings.auto_signature_help
4287 || editor_settings.show_signature_help_after_edits)
4288 {
4289 this.show_signature_help(&ShowSignatureHelp, window, cx);
4290 }
4291
4292 let trigger_in_words =
4293 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4294 if this.hard_wrap.is_some() {
4295 let latest: Range<Point> = this.selections.newest(cx).range();
4296 if latest.is_empty()
4297 && this
4298 .buffer()
4299 .read(cx)
4300 .snapshot(cx)
4301 .line_len(MultiBufferRow(latest.start.row))
4302 == latest.start.column
4303 {
4304 this.rewrap_impl(
4305 RewrapOptions {
4306 override_language_settings: true,
4307 preserve_existing_whitespace: true,
4308 },
4309 cx,
4310 )
4311 }
4312 }
4313 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4314 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4315 this.refresh_edit_prediction(true, false, window, cx);
4316 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4317 });
4318 }
4319
4320 fn find_possible_emoji_shortcode_at_position(
4321 snapshot: &MultiBufferSnapshot,
4322 position: Point,
4323 ) -> Option<String> {
4324 let mut chars = Vec::new();
4325 let mut found_colon = false;
4326 for char in snapshot.reversed_chars_at(position).take(100) {
4327 // Found a possible emoji shortcode in the middle of the buffer
4328 if found_colon {
4329 if char.is_whitespace() {
4330 chars.reverse();
4331 return Some(chars.iter().collect());
4332 }
4333 // If the previous character is not a whitespace, we are in the middle of a word
4334 // and we only want to complete the shortcode if the word is made up of other emojis
4335 let mut containing_word = String::new();
4336 for ch in snapshot
4337 .reversed_chars_at(position)
4338 .skip(chars.len() + 1)
4339 .take(100)
4340 {
4341 if ch.is_whitespace() {
4342 break;
4343 }
4344 containing_word.push(ch);
4345 }
4346 let containing_word = containing_word.chars().rev().collect::<String>();
4347 if util::word_consists_of_emojis(containing_word.as_str()) {
4348 chars.reverse();
4349 return Some(chars.iter().collect());
4350 }
4351 }
4352
4353 if char.is_whitespace() || !char.is_ascii() {
4354 return None;
4355 }
4356 if char == ':' {
4357 found_colon = true;
4358 } else {
4359 chars.push(char);
4360 }
4361 }
4362 // Found a possible emoji shortcode at the beginning of the buffer
4363 chars.reverse();
4364 Some(chars.iter().collect())
4365 }
4366
4367 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4368 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4369 self.transact(window, cx, |this, window, cx| {
4370 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4371 let selections = this.selections.all::<usize>(cx);
4372 let multi_buffer = this.buffer.read(cx);
4373 let buffer = multi_buffer.snapshot(cx);
4374 selections
4375 .iter()
4376 .map(|selection| {
4377 let start_point = selection.start.to_point(&buffer);
4378 let mut existing_indent =
4379 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4380 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4381 let start = selection.start;
4382 let end = selection.end;
4383 let selection_is_empty = start == end;
4384 let language_scope = buffer.language_scope_at(start);
4385 let (
4386 comment_delimiter,
4387 doc_delimiter,
4388 insert_extra_newline,
4389 indent_on_newline,
4390 indent_on_extra_newline,
4391 ) = if let Some(language) = &language_scope {
4392 let mut insert_extra_newline =
4393 insert_extra_newline_brackets(&buffer, start..end, language)
4394 || insert_extra_newline_tree_sitter(&buffer, start..end);
4395
4396 // Comment extension on newline is allowed only for cursor selections
4397 let comment_delimiter = maybe!({
4398 if !selection_is_empty {
4399 return None;
4400 }
4401
4402 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4403 return None;
4404 }
4405
4406 let delimiters = language.line_comment_prefixes();
4407 let max_len_of_delimiter =
4408 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4409 let (snapshot, range) =
4410 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4411
4412 let num_of_whitespaces = snapshot
4413 .chars_for_range(range.clone())
4414 .take_while(|c| c.is_whitespace())
4415 .count();
4416 let comment_candidate = snapshot
4417 .chars_for_range(range.clone())
4418 .skip(num_of_whitespaces)
4419 .take(max_len_of_delimiter)
4420 .collect::<String>();
4421 let (delimiter, trimmed_len) = delimiters
4422 .iter()
4423 .filter_map(|delimiter| {
4424 let prefix = delimiter.trim_end();
4425 if comment_candidate.starts_with(prefix) {
4426 Some((delimiter, prefix.len()))
4427 } else {
4428 None
4429 }
4430 })
4431 .max_by_key(|(_, len)| *len)?;
4432
4433 if let Some(BlockCommentConfig {
4434 start: block_start, ..
4435 }) = language.block_comment()
4436 {
4437 let block_start_trimmed = block_start.trim_end();
4438 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4439 let line_content = snapshot
4440 .chars_for_range(range)
4441 .skip(num_of_whitespaces)
4442 .take(block_start_trimmed.len())
4443 .collect::<String>();
4444
4445 if line_content.starts_with(block_start_trimmed) {
4446 return None;
4447 }
4448 }
4449 }
4450
4451 let cursor_is_placed_after_comment_marker =
4452 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4453 if cursor_is_placed_after_comment_marker {
4454 Some(delimiter.clone())
4455 } else {
4456 None
4457 }
4458 });
4459
4460 let mut indent_on_newline = IndentSize::spaces(0);
4461 let mut indent_on_extra_newline = IndentSize::spaces(0);
4462
4463 let doc_delimiter = maybe!({
4464 if !selection_is_empty {
4465 return None;
4466 }
4467
4468 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4469 return None;
4470 }
4471
4472 let BlockCommentConfig {
4473 start: start_tag,
4474 end: end_tag,
4475 prefix: delimiter,
4476 tab_size: len,
4477 } = language.documentation_comment()?;
4478 let is_within_block_comment = buffer
4479 .language_scope_at(start_point)
4480 .is_some_and(|scope| scope.override_name() == Some("comment"));
4481 if !is_within_block_comment {
4482 return None;
4483 }
4484
4485 let (snapshot, range) =
4486 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4487
4488 let num_of_whitespaces = snapshot
4489 .chars_for_range(range.clone())
4490 .take_while(|c| c.is_whitespace())
4491 .count();
4492
4493 // 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.
4494 let column = start_point.column;
4495 let cursor_is_after_start_tag = {
4496 let start_tag_len = start_tag.len();
4497 let start_tag_line = snapshot
4498 .chars_for_range(range.clone())
4499 .skip(num_of_whitespaces)
4500 .take(start_tag_len)
4501 .collect::<String>();
4502 if start_tag_line.starts_with(start_tag.as_ref()) {
4503 num_of_whitespaces + start_tag_len <= column as usize
4504 } else {
4505 false
4506 }
4507 };
4508
4509 let cursor_is_after_delimiter = {
4510 let delimiter_trim = delimiter.trim_end();
4511 let delimiter_line = snapshot
4512 .chars_for_range(range.clone())
4513 .skip(num_of_whitespaces)
4514 .take(delimiter_trim.len())
4515 .collect::<String>();
4516 if delimiter_line.starts_with(delimiter_trim) {
4517 num_of_whitespaces + delimiter_trim.len() <= column as usize
4518 } else {
4519 false
4520 }
4521 };
4522
4523 let cursor_is_before_end_tag_if_exists = {
4524 let mut char_position = 0u32;
4525 let mut end_tag_offset = None;
4526
4527 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4528 if let Some(byte_pos) = chunk.find(&**end_tag) {
4529 let chars_before_match =
4530 chunk[..byte_pos].chars().count() as u32;
4531 end_tag_offset =
4532 Some(char_position + chars_before_match);
4533 break 'outer;
4534 }
4535 char_position += chunk.chars().count() as u32;
4536 }
4537
4538 if let Some(end_tag_offset) = end_tag_offset {
4539 let cursor_is_before_end_tag = column <= end_tag_offset;
4540 if cursor_is_after_start_tag {
4541 if cursor_is_before_end_tag {
4542 insert_extra_newline = true;
4543 }
4544 let cursor_is_at_start_of_end_tag =
4545 column == end_tag_offset;
4546 if cursor_is_at_start_of_end_tag {
4547 indent_on_extra_newline.len = *len;
4548 }
4549 }
4550 cursor_is_before_end_tag
4551 } else {
4552 true
4553 }
4554 };
4555
4556 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4557 && cursor_is_before_end_tag_if_exists
4558 {
4559 if cursor_is_after_start_tag {
4560 indent_on_newline.len = *len;
4561 }
4562 Some(delimiter.clone())
4563 } else {
4564 None
4565 }
4566 });
4567
4568 (
4569 comment_delimiter,
4570 doc_delimiter,
4571 insert_extra_newline,
4572 indent_on_newline,
4573 indent_on_extra_newline,
4574 )
4575 } else {
4576 (
4577 None,
4578 None,
4579 false,
4580 IndentSize::default(),
4581 IndentSize::default(),
4582 )
4583 };
4584
4585 let prevent_auto_indent = doc_delimiter.is_some();
4586 let delimiter = comment_delimiter.or(doc_delimiter);
4587
4588 let capacity_for_delimiter =
4589 delimiter.as_deref().map(str::len).unwrap_or_default();
4590 let mut new_text = String::with_capacity(
4591 1 + capacity_for_delimiter
4592 + existing_indent.len as usize
4593 + indent_on_newline.len as usize
4594 + indent_on_extra_newline.len as usize,
4595 );
4596 new_text.push('\n');
4597 new_text.extend(existing_indent.chars());
4598 new_text.extend(indent_on_newline.chars());
4599
4600 if let Some(delimiter) = &delimiter {
4601 new_text.push_str(delimiter);
4602 }
4603
4604 if insert_extra_newline {
4605 new_text.push('\n');
4606 new_text.extend(existing_indent.chars());
4607 new_text.extend(indent_on_extra_newline.chars());
4608 }
4609
4610 let anchor = buffer.anchor_after(end);
4611 let new_selection = selection.map(|_| anchor);
4612 (
4613 ((start..end, new_text), prevent_auto_indent),
4614 (insert_extra_newline, new_selection),
4615 )
4616 })
4617 .unzip()
4618 };
4619
4620 let mut auto_indent_edits = Vec::new();
4621 let mut edits = Vec::new();
4622 for (edit, prevent_auto_indent) in edits_with_flags {
4623 if prevent_auto_indent {
4624 edits.push(edit);
4625 } else {
4626 auto_indent_edits.push(edit);
4627 }
4628 }
4629 if !edits.is_empty() {
4630 this.edit(edits, cx);
4631 }
4632 if !auto_indent_edits.is_empty() {
4633 this.edit_with_autoindent(auto_indent_edits, cx);
4634 }
4635
4636 let buffer = this.buffer.read(cx).snapshot(cx);
4637 let new_selections = selection_info
4638 .into_iter()
4639 .map(|(extra_newline_inserted, new_selection)| {
4640 let mut cursor = new_selection.end.to_point(&buffer);
4641 if extra_newline_inserted {
4642 cursor.row -= 1;
4643 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4644 }
4645 new_selection.map(|_| cursor)
4646 })
4647 .collect();
4648
4649 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4650 this.refresh_edit_prediction(true, false, window, cx);
4651 });
4652 }
4653
4654 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4655 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4656
4657 let buffer = self.buffer.read(cx);
4658 let snapshot = buffer.snapshot(cx);
4659
4660 let mut edits = Vec::new();
4661 let mut rows = Vec::new();
4662
4663 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4664 let cursor = selection.head();
4665 let row = cursor.row;
4666
4667 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4668
4669 let newline = "\n".to_string();
4670 edits.push((start_of_line..start_of_line, newline));
4671
4672 rows.push(row + rows_inserted as u32);
4673 }
4674
4675 self.transact(window, cx, |editor, window, cx| {
4676 editor.edit(edits, cx);
4677
4678 editor.change_selections(Default::default(), window, cx, |s| {
4679 let mut index = 0;
4680 s.move_cursors_with(|map, _, _| {
4681 let row = rows[index];
4682 index += 1;
4683
4684 let point = Point::new(row, 0);
4685 let boundary = map.next_line_boundary(point).1;
4686 let clipped = map.clip_point(boundary, Bias::Left);
4687
4688 (clipped, SelectionGoal::None)
4689 });
4690 });
4691
4692 let mut indent_edits = Vec::new();
4693 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4694 for row in rows {
4695 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4696 for (row, indent) in indents {
4697 if indent.len == 0 {
4698 continue;
4699 }
4700
4701 let text = match indent.kind {
4702 IndentKind::Space => " ".repeat(indent.len as usize),
4703 IndentKind::Tab => "\t".repeat(indent.len as usize),
4704 };
4705 let point = Point::new(row.0, 0);
4706 indent_edits.push((point..point, text));
4707 }
4708 }
4709 editor.edit(indent_edits, cx);
4710 });
4711 }
4712
4713 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4714 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4715
4716 let buffer = self.buffer.read(cx);
4717 let snapshot = buffer.snapshot(cx);
4718
4719 let mut edits = Vec::new();
4720 let mut rows = Vec::new();
4721 let mut rows_inserted = 0;
4722
4723 for selection in self.selections.all_adjusted(cx) {
4724 let cursor = selection.head();
4725 let row = cursor.row;
4726
4727 let point = Point::new(row + 1, 0);
4728 let start_of_line = snapshot.clip_point(point, Bias::Left);
4729
4730 let newline = "\n".to_string();
4731 edits.push((start_of_line..start_of_line, newline));
4732
4733 rows_inserted += 1;
4734 rows.push(row + rows_inserted);
4735 }
4736
4737 self.transact(window, cx, |editor, window, cx| {
4738 editor.edit(edits, cx);
4739
4740 editor.change_selections(Default::default(), window, cx, |s| {
4741 let mut index = 0;
4742 s.move_cursors_with(|map, _, _| {
4743 let row = rows[index];
4744 index += 1;
4745
4746 let point = Point::new(row, 0);
4747 let boundary = map.next_line_boundary(point).1;
4748 let clipped = map.clip_point(boundary, Bias::Left);
4749
4750 (clipped, SelectionGoal::None)
4751 });
4752 });
4753
4754 let mut indent_edits = Vec::new();
4755 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4756 for row in rows {
4757 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4758 for (row, indent) in indents {
4759 if indent.len == 0 {
4760 continue;
4761 }
4762
4763 let text = match indent.kind {
4764 IndentKind::Space => " ".repeat(indent.len as usize),
4765 IndentKind::Tab => "\t".repeat(indent.len as usize),
4766 };
4767 let point = Point::new(row.0, 0);
4768 indent_edits.push((point..point, text));
4769 }
4770 }
4771 editor.edit(indent_edits, cx);
4772 });
4773 }
4774
4775 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4776 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4777 original_indent_columns: Vec::new(),
4778 });
4779 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4780 }
4781
4782 fn insert_with_autoindent_mode(
4783 &mut self,
4784 text: &str,
4785 autoindent_mode: Option<AutoindentMode>,
4786 window: &mut Window,
4787 cx: &mut Context<Self>,
4788 ) {
4789 if self.read_only(cx) {
4790 return;
4791 }
4792
4793 let text: Arc<str> = text.into();
4794 self.transact(window, cx, |this, window, cx| {
4795 let old_selections = this.selections.all_adjusted(cx);
4796 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4797 let anchors = {
4798 let snapshot = buffer.read(cx);
4799 old_selections
4800 .iter()
4801 .map(|s| {
4802 let anchor = snapshot.anchor_after(s.head());
4803 s.map(|_| anchor)
4804 })
4805 .collect::<Vec<_>>()
4806 };
4807 buffer.edit(
4808 old_selections
4809 .iter()
4810 .map(|s| (s.start..s.end, text.clone())),
4811 autoindent_mode,
4812 cx,
4813 );
4814 anchors
4815 });
4816
4817 this.change_selections(Default::default(), window, cx, |s| {
4818 s.select_anchors(selection_anchors);
4819 });
4820
4821 cx.notify();
4822 });
4823 }
4824
4825 fn trigger_completion_on_input(
4826 &mut self,
4827 text: &str,
4828 trigger_in_words: bool,
4829 window: &mut Window,
4830 cx: &mut Context<Self>,
4831 ) {
4832 let completions_source = self
4833 .context_menu
4834 .borrow()
4835 .as_ref()
4836 .and_then(|menu| match menu {
4837 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4838 CodeContextMenu::CodeActions(_) => None,
4839 });
4840
4841 match completions_source {
4842 Some(CompletionsMenuSource::Words) => {
4843 self.show_word_completions(&ShowWordCompletions, window, cx)
4844 }
4845 Some(CompletionsMenuSource::Normal)
4846 | Some(CompletionsMenuSource::SnippetChoices)
4847 | None
4848 if self.is_completion_trigger(
4849 text,
4850 trigger_in_words,
4851 completions_source.is_some(),
4852 cx,
4853 ) =>
4854 {
4855 self.show_completions(
4856 &ShowCompletions {
4857 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4858 },
4859 window,
4860 cx,
4861 )
4862 }
4863 _ => {
4864 self.hide_context_menu(window, cx);
4865 }
4866 }
4867 }
4868
4869 fn is_completion_trigger(
4870 &self,
4871 text: &str,
4872 trigger_in_words: bool,
4873 menu_is_open: bool,
4874 cx: &mut Context<Self>,
4875 ) -> bool {
4876 let position = self.selections.newest_anchor().head();
4877 let multibuffer = self.buffer.read(cx);
4878 let Some(buffer) = position
4879 .buffer_id
4880 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4881 else {
4882 return false;
4883 };
4884
4885 if let Some(completion_provider) = &self.completion_provider {
4886 completion_provider.is_completion_trigger(
4887 &buffer,
4888 position.text_anchor,
4889 text,
4890 trigger_in_words,
4891 menu_is_open,
4892 cx,
4893 )
4894 } else {
4895 false
4896 }
4897 }
4898
4899 /// If any empty selections is touching the start of its innermost containing autoclose
4900 /// region, expand it to select the brackets.
4901 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4902 let selections = self.selections.all::<usize>(cx);
4903 let buffer = self.buffer.read(cx).read(cx);
4904 let new_selections = self
4905 .selections_with_autoclose_regions(selections, &buffer)
4906 .map(|(mut selection, region)| {
4907 if !selection.is_empty() {
4908 return selection;
4909 }
4910
4911 if let Some(region) = region {
4912 let mut range = region.range.to_offset(&buffer);
4913 if selection.start == range.start && range.start >= region.pair.start.len() {
4914 range.start -= region.pair.start.len();
4915 if buffer.contains_str_at(range.start, ®ion.pair.start)
4916 && buffer.contains_str_at(range.end, ®ion.pair.end)
4917 {
4918 range.end += region.pair.end.len();
4919 selection.start = range.start;
4920 selection.end = range.end;
4921
4922 return selection;
4923 }
4924 }
4925 }
4926
4927 let always_treat_brackets_as_autoclosed = buffer
4928 .language_settings_at(selection.start, cx)
4929 .always_treat_brackets_as_autoclosed;
4930
4931 if !always_treat_brackets_as_autoclosed {
4932 return selection;
4933 }
4934
4935 if let Some(scope) = buffer.language_scope_at(selection.start) {
4936 for (pair, enabled) in scope.brackets() {
4937 if !enabled || !pair.close {
4938 continue;
4939 }
4940
4941 if buffer.contains_str_at(selection.start, &pair.end) {
4942 let pair_start_len = pair.start.len();
4943 if buffer.contains_str_at(
4944 selection.start.saturating_sub(pair_start_len),
4945 &pair.start,
4946 ) {
4947 selection.start -= pair_start_len;
4948 selection.end += pair.end.len();
4949
4950 return selection;
4951 }
4952 }
4953 }
4954 }
4955
4956 selection
4957 })
4958 .collect();
4959
4960 drop(buffer);
4961 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4962 selections.select(new_selections)
4963 });
4964 }
4965
4966 /// Iterate the given selections, and for each one, find the smallest surrounding
4967 /// autoclose region. This uses the ordering of the selections and the autoclose
4968 /// regions to avoid repeated comparisons.
4969 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4970 &'a self,
4971 selections: impl IntoIterator<Item = Selection<D>>,
4972 buffer: &'a MultiBufferSnapshot,
4973 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4974 let mut i = 0;
4975 let mut regions = self.autoclose_regions.as_slice();
4976 selections.into_iter().map(move |selection| {
4977 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4978
4979 let mut enclosing = None;
4980 while let Some(pair_state) = regions.get(i) {
4981 if pair_state.range.end.to_offset(buffer) < range.start {
4982 regions = ®ions[i + 1..];
4983 i = 0;
4984 } else if pair_state.range.start.to_offset(buffer) > range.end {
4985 break;
4986 } else {
4987 if pair_state.selection_id == selection.id {
4988 enclosing = Some(pair_state);
4989 }
4990 i += 1;
4991 }
4992 }
4993
4994 (selection, enclosing)
4995 })
4996 }
4997
4998 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4999 fn invalidate_autoclose_regions(
5000 &mut self,
5001 mut selections: &[Selection<Anchor>],
5002 buffer: &MultiBufferSnapshot,
5003 ) {
5004 self.autoclose_regions.retain(|state| {
5005 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5006 return false;
5007 }
5008
5009 let mut i = 0;
5010 while let Some(selection) = selections.get(i) {
5011 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5012 selections = &selections[1..];
5013 continue;
5014 }
5015 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5016 break;
5017 }
5018 if selection.id == state.selection_id {
5019 return true;
5020 } else {
5021 i += 1;
5022 }
5023 }
5024 false
5025 });
5026 }
5027
5028 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5029 let offset = position.to_offset(buffer);
5030 let (word_range, kind) = buffer.surrounding_word(offset, true);
5031 if offset > word_range.start && kind == Some(CharKind::Word) {
5032 Some(
5033 buffer
5034 .text_for_range(word_range.start..offset)
5035 .collect::<String>(),
5036 )
5037 } else {
5038 None
5039 }
5040 }
5041
5042 pub fn toggle_inline_values(
5043 &mut self,
5044 _: &ToggleInlineValues,
5045 _: &mut Window,
5046 cx: &mut Context<Self>,
5047 ) {
5048 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5049
5050 self.refresh_inline_values(cx);
5051 }
5052
5053 pub fn toggle_inlay_hints(
5054 &mut self,
5055 _: &ToggleInlayHints,
5056 _: &mut Window,
5057 cx: &mut Context<Self>,
5058 ) {
5059 self.refresh_inlay_hints(
5060 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5061 cx,
5062 );
5063 }
5064
5065 pub fn inlay_hints_enabled(&self) -> bool {
5066 self.inlay_hint_cache.enabled
5067 }
5068
5069 pub fn inline_values_enabled(&self) -> bool {
5070 self.inline_value_cache.enabled
5071 }
5072
5073 #[cfg(any(test, feature = "test-support"))]
5074 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5075 self.display_map
5076 .read(cx)
5077 .current_inlays()
5078 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5079 .cloned()
5080 .collect()
5081 }
5082
5083 #[cfg(any(test, feature = "test-support"))]
5084 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5085 self.display_map
5086 .read(cx)
5087 .current_inlays()
5088 .cloned()
5089 .collect()
5090 }
5091
5092 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5093 if self.semantics_provider.is_none() || !self.mode.is_full() {
5094 return;
5095 }
5096
5097 let reason_description = reason.description();
5098 let ignore_debounce = matches!(
5099 reason,
5100 InlayHintRefreshReason::SettingsChange(_)
5101 | InlayHintRefreshReason::Toggle(_)
5102 | InlayHintRefreshReason::ExcerptsRemoved(_)
5103 | InlayHintRefreshReason::ModifiersChanged(_)
5104 );
5105 let (invalidate_cache, required_languages) = match reason {
5106 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5107 match self.inlay_hint_cache.modifiers_override(enabled) {
5108 Some(enabled) => {
5109 if enabled {
5110 (InvalidationStrategy::RefreshRequested, None)
5111 } else {
5112 self.splice_inlays(
5113 &self
5114 .visible_inlay_hints(cx)
5115 .iter()
5116 .map(|inlay| inlay.id)
5117 .collect::<Vec<InlayId>>(),
5118 Vec::new(),
5119 cx,
5120 );
5121 return;
5122 }
5123 }
5124 None => return,
5125 }
5126 }
5127 InlayHintRefreshReason::Toggle(enabled) => {
5128 if self.inlay_hint_cache.toggle(enabled) {
5129 if enabled {
5130 (InvalidationStrategy::RefreshRequested, None)
5131 } else {
5132 self.splice_inlays(
5133 &self
5134 .visible_inlay_hints(cx)
5135 .iter()
5136 .map(|inlay| inlay.id)
5137 .collect::<Vec<InlayId>>(),
5138 Vec::new(),
5139 cx,
5140 );
5141 return;
5142 }
5143 } else {
5144 return;
5145 }
5146 }
5147 InlayHintRefreshReason::SettingsChange(new_settings) => {
5148 match self.inlay_hint_cache.update_settings(
5149 &self.buffer,
5150 new_settings,
5151 self.visible_inlay_hints(cx),
5152 cx,
5153 ) {
5154 ControlFlow::Break(Some(InlaySplice {
5155 to_remove,
5156 to_insert,
5157 })) => {
5158 self.splice_inlays(&to_remove, to_insert, cx);
5159 return;
5160 }
5161 ControlFlow::Break(None) => return,
5162 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5163 }
5164 }
5165 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5166 if let Some(InlaySplice {
5167 to_remove,
5168 to_insert,
5169 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5170 {
5171 self.splice_inlays(&to_remove, to_insert, cx);
5172 }
5173 self.display_map.update(cx, |display_map, _| {
5174 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5175 });
5176 return;
5177 }
5178 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5179 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5180 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5181 }
5182 InlayHintRefreshReason::RefreshRequested => {
5183 (InvalidationStrategy::RefreshRequested, None)
5184 }
5185 };
5186
5187 if let Some(InlaySplice {
5188 to_remove,
5189 to_insert,
5190 }) = self.inlay_hint_cache.spawn_hint_refresh(
5191 reason_description,
5192 self.visible_excerpts(required_languages.as_ref(), cx),
5193 invalidate_cache,
5194 ignore_debounce,
5195 cx,
5196 ) {
5197 self.splice_inlays(&to_remove, to_insert, cx);
5198 }
5199 }
5200
5201 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5202 self.display_map
5203 .read(cx)
5204 .current_inlays()
5205 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5206 .cloned()
5207 .collect()
5208 }
5209
5210 pub fn visible_excerpts(
5211 &self,
5212 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5213 cx: &mut Context<Editor>,
5214 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5215 let Some(project) = self.project.as_ref() else {
5216 return HashMap::default();
5217 };
5218 let project = project.read(cx);
5219 let multi_buffer = self.buffer().read(cx);
5220 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5221 let multi_buffer_visible_start = self
5222 .scroll_manager
5223 .anchor()
5224 .anchor
5225 .to_point(&multi_buffer_snapshot);
5226 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5227 multi_buffer_visible_start
5228 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5229 Bias::Left,
5230 );
5231 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5232 multi_buffer_snapshot
5233 .range_to_buffer_ranges(multi_buffer_visible_range)
5234 .into_iter()
5235 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5236 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5237 let buffer_file = project::File::from_dyn(buffer.file())?;
5238 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5239 let worktree_entry = buffer_worktree
5240 .read(cx)
5241 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5242 if worktree_entry.is_ignored {
5243 return None;
5244 }
5245
5246 let language = buffer.language()?;
5247 if let Some(restrict_to_languages) = restrict_to_languages {
5248 if !restrict_to_languages.contains(language) {
5249 return None;
5250 }
5251 }
5252 Some((
5253 excerpt_id,
5254 (
5255 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5256 buffer.version().clone(),
5257 excerpt_visible_range,
5258 ),
5259 ))
5260 })
5261 .collect()
5262 }
5263
5264 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5265 TextLayoutDetails {
5266 text_system: window.text_system().clone(),
5267 editor_style: self.style.clone().unwrap(),
5268 rem_size: window.rem_size(),
5269 scroll_anchor: self.scroll_manager.anchor(),
5270 visible_rows: self.visible_line_count(),
5271 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5272 }
5273 }
5274
5275 pub fn splice_inlays(
5276 &self,
5277 to_remove: &[InlayId],
5278 to_insert: Vec<Inlay>,
5279 cx: &mut Context<Self>,
5280 ) {
5281 self.display_map.update(cx, |display_map, cx| {
5282 display_map.splice_inlays(to_remove, to_insert, cx)
5283 });
5284 cx.notify();
5285 }
5286
5287 fn trigger_on_type_formatting(
5288 &self,
5289 input: String,
5290 window: &mut Window,
5291 cx: &mut Context<Self>,
5292 ) -> Option<Task<Result<()>>> {
5293 if input.len() != 1 {
5294 return None;
5295 }
5296
5297 let project = self.project.as_ref()?;
5298 let position = self.selections.newest_anchor().head();
5299 let (buffer, buffer_position) = self
5300 .buffer
5301 .read(cx)
5302 .text_anchor_for_position(position, cx)?;
5303
5304 let settings = language_settings::language_settings(
5305 buffer
5306 .read(cx)
5307 .language_at(buffer_position)
5308 .map(|l| l.name()),
5309 buffer.read(cx).file(),
5310 cx,
5311 );
5312 if !settings.use_on_type_format {
5313 return None;
5314 }
5315
5316 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5317 // hence we do LSP request & edit on host side only — add formats to host's history.
5318 let push_to_lsp_host_history = true;
5319 // If this is not the host, append its history with new edits.
5320 let push_to_client_history = project.read(cx).is_via_collab();
5321
5322 let on_type_formatting = project.update(cx, |project, cx| {
5323 project.on_type_format(
5324 buffer.clone(),
5325 buffer_position,
5326 input,
5327 push_to_lsp_host_history,
5328 cx,
5329 )
5330 });
5331 Some(cx.spawn_in(window, async move |editor, cx| {
5332 if let Some(transaction) = on_type_formatting.await? {
5333 if push_to_client_history {
5334 buffer
5335 .update(cx, |buffer, _| {
5336 buffer.push_transaction(transaction, Instant::now());
5337 buffer.finalize_last_transaction();
5338 })
5339 .ok();
5340 }
5341 editor.update(cx, |editor, cx| {
5342 editor.refresh_document_highlights(cx);
5343 })?;
5344 }
5345 Ok(())
5346 }))
5347 }
5348
5349 pub fn show_word_completions(
5350 &mut self,
5351 _: &ShowWordCompletions,
5352 window: &mut Window,
5353 cx: &mut Context<Self>,
5354 ) {
5355 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5356 }
5357
5358 pub fn show_completions(
5359 &mut self,
5360 options: &ShowCompletions,
5361 window: &mut Window,
5362 cx: &mut Context<Self>,
5363 ) {
5364 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5365 }
5366
5367 fn open_or_update_completions_menu(
5368 &mut self,
5369 requested_source: Option<CompletionsMenuSource>,
5370 trigger: Option<&str>,
5371 window: &mut Window,
5372 cx: &mut Context<Self>,
5373 ) {
5374 if self.pending_rename.is_some() {
5375 return;
5376 }
5377
5378 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5379
5380 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5381 // inserted and selected. To handle that case, the start of the selection is used so that
5382 // the menu starts with all choices.
5383 let position = self
5384 .selections
5385 .newest_anchor()
5386 .start
5387 .bias_right(&multibuffer_snapshot);
5388 if position.diff_base_anchor.is_some() {
5389 return;
5390 }
5391 let (buffer, buffer_position) =
5392 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5393 output
5394 } else {
5395 return;
5396 };
5397 let buffer_snapshot = buffer.read(cx).snapshot();
5398
5399 let query: Option<Arc<String>> =
5400 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5401
5402 drop(multibuffer_snapshot);
5403
5404 let provider = match requested_source {
5405 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5406 Some(CompletionsMenuSource::Words) => None,
5407 Some(CompletionsMenuSource::SnippetChoices) => {
5408 log::error!("bug: SnippetChoices requested_source is not handled");
5409 None
5410 }
5411 };
5412
5413 let sort_completions = provider
5414 .as_ref()
5415 .map_or(false, |provider| provider.sort_completions());
5416
5417 let filter_completions = provider
5418 .as_ref()
5419 .map_or(true, |provider| provider.filter_completions());
5420
5421 let trigger_kind = match trigger {
5422 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5423 CompletionTriggerKind::TRIGGER_CHARACTER
5424 }
5425 _ => CompletionTriggerKind::INVOKED,
5426 };
5427 let completion_context = CompletionContext {
5428 trigger_character: trigger.and_then(|trigger| {
5429 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5430 Some(String::from(trigger))
5431 } else {
5432 None
5433 }
5434 }),
5435 trigger_kind,
5436 };
5437
5438 // Hide the current completions menu when a trigger char is typed. Without this, cached
5439 // completions from before the trigger char may be reused (#32774). Snippet choices could
5440 // involve trigger chars, so this is skipped in that case.
5441 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5442 {
5443 let menu_is_open = matches!(
5444 self.context_menu.borrow().as_ref(),
5445 Some(CodeContextMenu::Completions(_))
5446 );
5447 if menu_is_open {
5448 self.hide_context_menu(window, cx);
5449 }
5450 }
5451
5452 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5453 if filter_completions {
5454 menu.filter(query.clone(), provider.clone(), window, cx);
5455 }
5456 // When `is_incomplete` is false, no need to re-query completions when the current query
5457 // is a suffix of the initial query.
5458 if !menu.is_incomplete {
5459 // If the new query is a suffix of the old query (typing more characters) and
5460 // the previous result was complete, the existing completions can be filtered.
5461 //
5462 // Note that this is always true for snippet completions.
5463 let query_matches = match (&menu.initial_query, &query) {
5464 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5465 (None, _) => true,
5466 _ => false,
5467 };
5468 if query_matches {
5469 let position_matches = if menu.initial_position == position {
5470 true
5471 } else {
5472 let snapshot = self.buffer.read(cx).read(cx);
5473 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5474 };
5475 if position_matches {
5476 return;
5477 }
5478 }
5479 }
5480 };
5481
5482 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5483 buffer_snapshot.surrounding_word(buffer_position, false)
5484 {
5485 let word_to_exclude = buffer_snapshot
5486 .text_for_range(word_range.clone())
5487 .collect::<String>();
5488 (
5489 buffer_snapshot.anchor_before(word_range.start)
5490 ..buffer_snapshot.anchor_after(buffer_position),
5491 Some(word_to_exclude),
5492 )
5493 } else {
5494 (buffer_position..buffer_position, None)
5495 };
5496
5497 let language = buffer_snapshot
5498 .language_at(buffer_position)
5499 .map(|language| language.name());
5500
5501 let completion_settings =
5502 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5503
5504 let show_completion_documentation = buffer_snapshot
5505 .settings_at(buffer_position, cx)
5506 .show_completion_documentation;
5507
5508 // The document can be large, so stay in reasonable bounds when searching for words,
5509 // otherwise completion pop-up might be slow to appear.
5510 const WORD_LOOKUP_ROWS: u32 = 5_000;
5511 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5512 let min_word_search = buffer_snapshot.clip_point(
5513 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5514 Bias::Left,
5515 );
5516 let max_word_search = buffer_snapshot.clip_point(
5517 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5518 Bias::Right,
5519 );
5520 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5521 ..buffer_snapshot.point_to_offset(max_word_search);
5522
5523 let skip_digits = query
5524 .as_ref()
5525 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5526
5527 let (mut words, provider_responses) = match &provider {
5528 Some(provider) => {
5529 let provider_responses = provider.completions(
5530 position.excerpt_id,
5531 &buffer,
5532 buffer_position,
5533 completion_context,
5534 window,
5535 cx,
5536 );
5537
5538 let words = match completion_settings.words {
5539 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5540 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5541 .background_spawn(async move {
5542 buffer_snapshot.words_in_range(WordsQuery {
5543 fuzzy_contents: None,
5544 range: word_search_range,
5545 skip_digits,
5546 })
5547 }),
5548 };
5549
5550 (words, provider_responses)
5551 }
5552 None => (
5553 cx.background_spawn(async move {
5554 buffer_snapshot.words_in_range(WordsQuery {
5555 fuzzy_contents: None,
5556 range: word_search_range,
5557 skip_digits,
5558 })
5559 }),
5560 Task::ready(Ok(Vec::new())),
5561 ),
5562 };
5563
5564 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5565
5566 let id = post_inc(&mut self.next_completion_id);
5567 let task = cx.spawn_in(window, async move |editor, cx| {
5568 let Ok(()) = editor.update(cx, |this, _| {
5569 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5570 }) else {
5571 return;
5572 };
5573
5574 // TODO: Ideally completions from different sources would be selectively re-queried, so
5575 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5576 let mut completions = Vec::new();
5577 let mut is_incomplete = false;
5578 if let Some(provider_responses) = provider_responses.await.log_err() {
5579 if !provider_responses.is_empty() {
5580 for response in provider_responses {
5581 completions.extend(response.completions);
5582 is_incomplete = is_incomplete || response.is_incomplete;
5583 }
5584 if completion_settings.words == WordsCompletionMode::Fallback {
5585 words = Task::ready(BTreeMap::default());
5586 }
5587 }
5588 }
5589
5590 let mut words = words.await;
5591 if let Some(word_to_exclude) = &word_to_exclude {
5592 words.remove(word_to_exclude);
5593 }
5594 for lsp_completion in &completions {
5595 words.remove(&lsp_completion.new_text);
5596 }
5597 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5598 replace_range: word_replace_range.clone(),
5599 new_text: word.clone(),
5600 label: CodeLabel::plain(word, None),
5601 icon_path: None,
5602 documentation: None,
5603 source: CompletionSource::BufferWord {
5604 word_range,
5605 resolved: false,
5606 },
5607 insert_text_mode: Some(InsertTextMode::AS_IS),
5608 confirm: None,
5609 }));
5610
5611 let menu = if completions.is_empty() {
5612 None
5613 } else {
5614 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5615 let languages = editor
5616 .workspace
5617 .as_ref()
5618 .and_then(|(workspace, _)| workspace.upgrade())
5619 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5620 let menu = CompletionsMenu::new(
5621 id,
5622 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5623 sort_completions,
5624 show_completion_documentation,
5625 position,
5626 query.clone(),
5627 is_incomplete,
5628 buffer.clone(),
5629 completions.into(),
5630 snippet_sort_order,
5631 languages,
5632 language,
5633 cx,
5634 );
5635
5636 let query = if filter_completions { query } else { None };
5637 let matches_task = if let Some(query) = query {
5638 menu.do_async_filtering(query, cx)
5639 } else {
5640 Task::ready(menu.unfiltered_matches())
5641 };
5642 (menu, matches_task)
5643 }) else {
5644 return;
5645 };
5646
5647 let matches = matches_task.await;
5648
5649 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5650 // Newer menu already set, so exit.
5651 match editor.context_menu.borrow().as_ref() {
5652 Some(CodeContextMenu::Completions(prev_menu)) => {
5653 if prev_menu.id > id {
5654 return;
5655 }
5656 }
5657 _ => {}
5658 };
5659
5660 // Only valid to take prev_menu because it the new menu is immediately set
5661 // below, or the menu is hidden.
5662 match editor.context_menu.borrow_mut().take() {
5663 Some(CodeContextMenu::Completions(prev_menu)) => {
5664 let position_matches =
5665 if prev_menu.initial_position == menu.initial_position {
5666 true
5667 } else {
5668 let snapshot = editor.buffer.read(cx).read(cx);
5669 prev_menu.initial_position.to_offset(&snapshot)
5670 == menu.initial_position.to_offset(&snapshot)
5671 };
5672 if position_matches {
5673 // Preserve markdown cache before `set_filter_results` because it will
5674 // try to populate the documentation cache.
5675 menu.preserve_markdown_cache(prev_menu);
5676 }
5677 }
5678 _ => {}
5679 };
5680
5681 menu.set_filter_results(matches, provider, window, cx);
5682 }) else {
5683 return;
5684 };
5685
5686 menu.visible().then_some(menu)
5687 };
5688
5689 editor
5690 .update_in(cx, |editor, window, cx| {
5691 if editor.focus_handle.is_focused(window) {
5692 if let Some(menu) = menu {
5693 *editor.context_menu.borrow_mut() =
5694 Some(CodeContextMenu::Completions(menu));
5695
5696 crate::hover_popover::hide_hover(editor, cx);
5697 if editor.show_edit_predictions_in_menu() {
5698 editor.update_visible_edit_prediction(window, cx);
5699 } else {
5700 editor.discard_edit_prediction(false, cx);
5701 }
5702
5703 cx.notify();
5704 return;
5705 }
5706 }
5707
5708 if editor.completion_tasks.len() <= 1 {
5709 // If there are no more completion tasks and the last menu was empty, we should hide it.
5710 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5711 // If it was already hidden and we don't show edit predictions in the menu,
5712 // we should also show the edit prediction when available.
5713 if was_hidden && editor.show_edit_predictions_in_menu() {
5714 editor.update_visible_edit_prediction(window, cx);
5715 }
5716 }
5717 })
5718 .ok();
5719 });
5720
5721 self.completion_tasks.push((id, task));
5722 }
5723
5724 #[cfg(feature = "test-support")]
5725 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5726 let menu = self.context_menu.borrow();
5727 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5728 let completions = menu.completions.borrow();
5729 Some(completions.to_vec())
5730 } else {
5731 None
5732 }
5733 }
5734
5735 pub fn with_completions_menu_matching_id<R>(
5736 &self,
5737 id: CompletionId,
5738 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5739 ) -> R {
5740 let mut context_menu = self.context_menu.borrow_mut();
5741 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5742 return f(None);
5743 };
5744 if completions_menu.id != id {
5745 return f(None);
5746 }
5747 f(Some(completions_menu))
5748 }
5749
5750 pub fn confirm_completion(
5751 &mut self,
5752 action: &ConfirmCompletion,
5753 window: &mut Window,
5754 cx: &mut Context<Self>,
5755 ) -> Option<Task<Result<()>>> {
5756 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5757 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5758 }
5759
5760 pub fn confirm_completion_insert(
5761 &mut self,
5762 _: &ConfirmCompletionInsert,
5763 window: &mut Window,
5764 cx: &mut Context<Self>,
5765 ) -> Option<Task<Result<()>>> {
5766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5767 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5768 }
5769
5770 pub fn confirm_completion_replace(
5771 &mut self,
5772 _: &ConfirmCompletionReplace,
5773 window: &mut Window,
5774 cx: &mut Context<Self>,
5775 ) -> Option<Task<Result<()>>> {
5776 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5777 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5778 }
5779
5780 pub fn compose_completion(
5781 &mut self,
5782 action: &ComposeCompletion,
5783 window: &mut Window,
5784 cx: &mut Context<Self>,
5785 ) -> Option<Task<Result<()>>> {
5786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5787 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5788 }
5789
5790 fn do_completion(
5791 &mut self,
5792 item_ix: Option<usize>,
5793 intent: CompletionIntent,
5794 window: &mut Window,
5795 cx: &mut Context<Editor>,
5796 ) -> Option<Task<Result<()>>> {
5797 use language::ToOffset as _;
5798
5799 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5800 else {
5801 return None;
5802 };
5803
5804 let candidate_id = {
5805 let entries = completions_menu.entries.borrow();
5806 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5807 if self.show_edit_predictions_in_menu() {
5808 self.discard_edit_prediction(true, cx);
5809 }
5810 mat.candidate_id
5811 };
5812
5813 let completion = completions_menu
5814 .completions
5815 .borrow()
5816 .get(candidate_id)?
5817 .clone();
5818 cx.stop_propagation();
5819
5820 let buffer_handle = completions_menu.buffer.clone();
5821
5822 let CompletionEdit {
5823 new_text,
5824 snippet,
5825 replace_range,
5826 } = process_completion_for_edit(
5827 &completion,
5828 intent,
5829 &buffer_handle,
5830 &completions_menu.initial_position.text_anchor,
5831 cx,
5832 );
5833
5834 let buffer = buffer_handle.read(cx);
5835 let snapshot = self.buffer.read(cx).snapshot(cx);
5836 let newest_anchor = self.selections.newest_anchor();
5837 let replace_range_multibuffer = {
5838 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5839 let multibuffer_anchor = snapshot
5840 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5841 .unwrap()
5842 ..snapshot
5843 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5844 .unwrap();
5845 multibuffer_anchor.start.to_offset(&snapshot)
5846 ..multibuffer_anchor.end.to_offset(&snapshot)
5847 };
5848 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5849 return None;
5850 }
5851
5852 let old_text = buffer
5853 .text_for_range(replace_range.clone())
5854 .collect::<String>();
5855 let lookbehind = newest_anchor
5856 .start
5857 .text_anchor
5858 .to_offset(buffer)
5859 .saturating_sub(replace_range.start);
5860 let lookahead = replace_range
5861 .end
5862 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5863 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5864 let suffix = &old_text[lookbehind.min(old_text.len())..];
5865
5866 let selections = self.selections.all::<usize>(cx);
5867 let mut ranges = Vec::new();
5868 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5869
5870 for selection in &selections {
5871 let range = if selection.id == newest_anchor.id {
5872 replace_range_multibuffer.clone()
5873 } else {
5874 let mut range = selection.range();
5875
5876 // if prefix is present, don't duplicate it
5877 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5878 range.start = range.start.saturating_sub(lookbehind);
5879
5880 // if suffix is also present, mimic the newest cursor and replace it
5881 if selection.id != newest_anchor.id
5882 && snapshot.contains_str_at(range.end, suffix)
5883 {
5884 range.end += lookahead;
5885 }
5886 }
5887 range
5888 };
5889
5890 ranges.push(range.clone());
5891
5892 if !self.linked_edit_ranges.is_empty() {
5893 let start_anchor = snapshot.anchor_before(range.start);
5894 let end_anchor = snapshot.anchor_after(range.end);
5895 if let Some(ranges) = self
5896 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5897 {
5898 for (buffer, edits) in ranges {
5899 linked_edits
5900 .entry(buffer.clone())
5901 .or_default()
5902 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5903 }
5904 }
5905 }
5906 }
5907
5908 let common_prefix_len = old_text
5909 .chars()
5910 .zip(new_text.chars())
5911 .take_while(|(a, b)| a == b)
5912 .map(|(a, _)| a.len_utf8())
5913 .sum::<usize>();
5914
5915 cx.emit(EditorEvent::InputHandled {
5916 utf16_range_to_replace: None,
5917 text: new_text[common_prefix_len..].into(),
5918 });
5919
5920 self.transact(window, cx, |editor, window, cx| {
5921 if let Some(mut snippet) = snippet {
5922 snippet.text = new_text.to_string();
5923 editor
5924 .insert_snippet(&ranges, snippet, window, cx)
5925 .log_err();
5926 } else {
5927 editor.buffer.update(cx, |multi_buffer, cx| {
5928 let auto_indent = match completion.insert_text_mode {
5929 Some(InsertTextMode::AS_IS) => None,
5930 _ => editor.autoindent_mode.clone(),
5931 };
5932 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5933 multi_buffer.edit(edits, auto_indent, cx);
5934 });
5935 }
5936 for (buffer, edits) in linked_edits {
5937 buffer.update(cx, |buffer, cx| {
5938 let snapshot = buffer.snapshot();
5939 let edits = edits
5940 .into_iter()
5941 .map(|(range, text)| {
5942 use text::ToPoint as TP;
5943 let end_point = TP::to_point(&range.end, &snapshot);
5944 let start_point = TP::to_point(&range.start, &snapshot);
5945 (start_point..end_point, text)
5946 })
5947 .sorted_by_key(|(range, _)| range.start);
5948 buffer.edit(edits, None, cx);
5949 })
5950 }
5951
5952 editor.refresh_edit_prediction(true, false, window, cx);
5953 });
5954 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5955
5956 let show_new_completions_on_confirm = completion
5957 .confirm
5958 .as_ref()
5959 .map_or(false, |confirm| confirm(intent, window, cx));
5960 if show_new_completions_on_confirm {
5961 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5962 }
5963
5964 let provider = self.completion_provider.as_ref()?;
5965 drop(completion);
5966 let apply_edits = provider.apply_additional_edits_for_completion(
5967 buffer_handle,
5968 completions_menu.completions.clone(),
5969 candidate_id,
5970 true,
5971 cx,
5972 );
5973
5974 let editor_settings = EditorSettings::get_global(cx);
5975 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5976 // After the code completion is finished, users often want to know what signatures are needed.
5977 // so we should automatically call signature_help
5978 self.show_signature_help(&ShowSignatureHelp, window, cx);
5979 }
5980
5981 Some(cx.foreground_executor().spawn(async move {
5982 apply_edits.await?;
5983 Ok(())
5984 }))
5985 }
5986
5987 pub fn toggle_code_actions(
5988 &mut self,
5989 action: &ToggleCodeActions,
5990 window: &mut Window,
5991 cx: &mut Context<Self>,
5992 ) {
5993 let quick_launch = action.quick_launch;
5994 let mut context_menu = self.context_menu.borrow_mut();
5995 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5996 if code_actions.deployed_from == action.deployed_from {
5997 // Toggle if we're selecting the same one
5998 *context_menu = None;
5999 cx.notify();
6000 return;
6001 } else {
6002 // Otherwise, clear it and start a new one
6003 *context_menu = None;
6004 cx.notify();
6005 }
6006 }
6007 drop(context_menu);
6008 let snapshot = self.snapshot(window, cx);
6009 let deployed_from = action.deployed_from.clone();
6010 let action = action.clone();
6011 self.completion_tasks.clear();
6012 self.discard_edit_prediction(false, cx);
6013
6014 let multibuffer_point = match &action.deployed_from {
6015 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6016 DisplayPoint::new(*row, 0).to_point(&snapshot)
6017 }
6018 _ => self.selections.newest::<Point>(cx).head(),
6019 };
6020 let Some((buffer, buffer_row)) = snapshot
6021 .buffer_snapshot
6022 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6023 .and_then(|(buffer_snapshot, range)| {
6024 self.buffer()
6025 .read(cx)
6026 .buffer(buffer_snapshot.remote_id())
6027 .map(|buffer| (buffer, range.start.row))
6028 })
6029 else {
6030 return;
6031 };
6032 let buffer_id = buffer.read(cx).remote_id();
6033 let tasks = self
6034 .tasks
6035 .get(&(buffer_id, buffer_row))
6036 .map(|t| Arc::new(t.to_owned()));
6037
6038 if !self.focus_handle.is_focused(window) {
6039 return;
6040 }
6041 let project = self.project.clone();
6042
6043 let code_actions_task = match deployed_from {
6044 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6045 _ => self.code_actions(buffer_row, window, cx),
6046 };
6047
6048 let runnable_task = match deployed_from {
6049 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6050 _ => {
6051 let mut task_context_task = Task::ready(None);
6052 if let Some(tasks) = &tasks {
6053 if let Some(project) = project {
6054 task_context_task =
6055 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6056 }
6057 }
6058
6059 cx.spawn_in(window, {
6060 let buffer = buffer.clone();
6061 async move |editor, cx| {
6062 let task_context = task_context_task.await;
6063
6064 let resolved_tasks =
6065 tasks
6066 .zip(task_context.clone())
6067 .map(|(tasks, task_context)| ResolvedTasks {
6068 templates: tasks.resolve(&task_context).collect(),
6069 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6070 multibuffer_point.row,
6071 tasks.column,
6072 )),
6073 });
6074 let debug_scenarios = editor
6075 .update(cx, |editor, cx| {
6076 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6077 })?
6078 .await;
6079 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6080 }
6081 })
6082 }
6083 };
6084
6085 cx.spawn_in(window, async move |editor, cx| {
6086 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6087 let code_actions = code_actions_task.await;
6088 let spawn_straight_away = quick_launch
6089 && resolved_tasks
6090 .as_ref()
6091 .map_or(false, |tasks| tasks.templates.len() == 1)
6092 && code_actions
6093 .as_ref()
6094 .map_or(true, |actions| actions.is_empty())
6095 && debug_scenarios.is_empty();
6096
6097 editor.update_in(cx, |editor, window, cx| {
6098 crate::hover_popover::hide_hover(editor, cx);
6099 let actions = CodeActionContents::new(
6100 resolved_tasks,
6101 code_actions,
6102 debug_scenarios,
6103 task_context.unwrap_or_default(),
6104 );
6105
6106 // Don't show the menu if there are no actions available
6107 if actions.is_empty() {
6108 cx.notify();
6109 return Task::ready(Ok(()));
6110 }
6111
6112 *editor.context_menu.borrow_mut() =
6113 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6114 buffer,
6115 actions,
6116 selected_item: Default::default(),
6117 scroll_handle: UniformListScrollHandle::default(),
6118 deployed_from,
6119 }));
6120 cx.notify();
6121 if spawn_straight_away {
6122 if let Some(task) = editor.confirm_code_action(
6123 &ConfirmCodeAction { item_ix: Some(0) },
6124 window,
6125 cx,
6126 ) {
6127 return task;
6128 }
6129 }
6130
6131 Task::ready(Ok(()))
6132 })
6133 })
6134 .detach_and_log_err(cx);
6135 }
6136
6137 fn debug_scenarios(
6138 &mut self,
6139 resolved_tasks: &Option<ResolvedTasks>,
6140 buffer: &Entity<Buffer>,
6141 cx: &mut App,
6142 ) -> Task<Vec<task::DebugScenario>> {
6143 maybe!({
6144 let project = self.project.as_ref()?;
6145 let dap_store = project.read(cx).dap_store();
6146 let mut scenarios = vec![];
6147 let resolved_tasks = resolved_tasks.as_ref()?;
6148 let buffer = buffer.read(cx);
6149 let language = buffer.language()?;
6150 let file = buffer.file();
6151 let debug_adapter = language_settings(language.name().into(), file, cx)
6152 .debuggers
6153 .first()
6154 .map(SharedString::from)
6155 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6156
6157 dap_store.update(cx, |dap_store, cx| {
6158 for (_, task) in &resolved_tasks.templates {
6159 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6160 task.original_task().clone(),
6161 debug_adapter.clone().into(),
6162 task.display_label().to_owned().into(),
6163 cx,
6164 );
6165 scenarios.push(maybe_scenario);
6166 }
6167 });
6168 Some(cx.background_spawn(async move {
6169 let scenarios = futures::future::join_all(scenarios)
6170 .await
6171 .into_iter()
6172 .flatten()
6173 .collect::<Vec<_>>();
6174 scenarios
6175 }))
6176 })
6177 .unwrap_or_else(|| Task::ready(vec![]))
6178 }
6179
6180 fn code_actions(
6181 &mut self,
6182 buffer_row: u32,
6183 window: &mut Window,
6184 cx: &mut Context<Self>,
6185 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6186 let mut task = self.code_actions_task.take();
6187 cx.spawn_in(window, async move |editor, cx| {
6188 while let Some(prev_task) = task {
6189 prev_task.await.log_err();
6190 task = editor
6191 .update(cx, |this, _| this.code_actions_task.take())
6192 .ok()?;
6193 }
6194
6195 editor
6196 .update(cx, |editor, cx| {
6197 editor
6198 .available_code_actions
6199 .clone()
6200 .and_then(|(location, code_actions)| {
6201 let snapshot = location.buffer.read(cx).snapshot();
6202 let point_range = location.range.to_point(&snapshot);
6203 let point_range = point_range.start.row..=point_range.end.row;
6204 if point_range.contains(&buffer_row) {
6205 Some(code_actions)
6206 } else {
6207 None
6208 }
6209 })
6210 })
6211 .ok()
6212 .flatten()
6213 })
6214 }
6215
6216 pub fn confirm_code_action(
6217 &mut self,
6218 action: &ConfirmCodeAction,
6219 window: &mut Window,
6220 cx: &mut Context<Self>,
6221 ) -> Option<Task<Result<()>>> {
6222 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6223
6224 let actions_menu =
6225 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6226 menu
6227 } else {
6228 return None;
6229 };
6230
6231 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6232 let action = actions_menu.actions.get(action_ix)?;
6233 let title = action.label();
6234 let buffer = actions_menu.buffer;
6235 let workspace = self.workspace()?;
6236
6237 match action {
6238 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6239 workspace.update(cx, |workspace, cx| {
6240 workspace.schedule_resolved_task(
6241 task_source_kind,
6242 resolved_task,
6243 false,
6244 window,
6245 cx,
6246 );
6247
6248 Some(Task::ready(Ok(())))
6249 })
6250 }
6251 CodeActionsItem::CodeAction {
6252 excerpt_id,
6253 action,
6254 provider,
6255 } => {
6256 let apply_code_action =
6257 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6258 let workspace = workspace.downgrade();
6259 Some(cx.spawn_in(window, async move |editor, cx| {
6260 let project_transaction = apply_code_action.await?;
6261 Self::open_project_transaction(
6262 &editor,
6263 workspace,
6264 project_transaction,
6265 title,
6266 cx,
6267 )
6268 .await
6269 }))
6270 }
6271 CodeActionsItem::DebugScenario(scenario) => {
6272 let context = actions_menu.actions.context.clone();
6273
6274 workspace.update(cx, |workspace, cx| {
6275 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6276 workspace.start_debug_session(
6277 scenario,
6278 context,
6279 Some(buffer),
6280 None,
6281 window,
6282 cx,
6283 );
6284 });
6285 Some(Task::ready(Ok(())))
6286 }
6287 }
6288 }
6289
6290 pub async fn open_project_transaction(
6291 this: &WeakEntity<Editor>,
6292 workspace: WeakEntity<Workspace>,
6293 transaction: ProjectTransaction,
6294 title: String,
6295 cx: &mut AsyncWindowContext,
6296 ) -> Result<()> {
6297 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6298 cx.update(|_, cx| {
6299 entries.sort_unstable_by_key(|(buffer, _)| {
6300 buffer.read(cx).file().map(|f| f.path().clone())
6301 });
6302 })?;
6303
6304 // If the project transaction's edits are all contained within this editor, then
6305 // avoid opening a new editor to display them.
6306
6307 if let Some((buffer, transaction)) = entries.first() {
6308 if entries.len() == 1 {
6309 let excerpt = this.update(cx, |editor, cx| {
6310 editor
6311 .buffer()
6312 .read(cx)
6313 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6314 })?;
6315 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6316 if excerpted_buffer == *buffer {
6317 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6318 let excerpt_range = excerpt_range.to_offset(buffer);
6319 buffer
6320 .edited_ranges_for_transaction::<usize>(transaction)
6321 .all(|range| {
6322 excerpt_range.start <= range.start
6323 && excerpt_range.end >= range.end
6324 })
6325 })?;
6326
6327 if all_edits_within_excerpt {
6328 return Ok(());
6329 }
6330 }
6331 }
6332 }
6333 } else {
6334 return Ok(());
6335 }
6336
6337 let mut ranges_to_highlight = Vec::new();
6338 let excerpt_buffer = cx.new(|cx| {
6339 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6340 for (buffer_handle, transaction) in &entries {
6341 let edited_ranges = buffer_handle
6342 .read(cx)
6343 .edited_ranges_for_transaction::<Point>(transaction)
6344 .collect::<Vec<_>>();
6345 let (ranges, _) = multibuffer.set_excerpts_for_path(
6346 PathKey::for_buffer(buffer_handle, cx),
6347 buffer_handle.clone(),
6348 edited_ranges,
6349 DEFAULT_MULTIBUFFER_CONTEXT,
6350 cx,
6351 );
6352
6353 ranges_to_highlight.extend(ranges);
6354 }
6355 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6356 multibuffer
6357 })?;
6358
6359 workspace.update_in(cx, |workspace, window, cx| {
6360 let project = workspace.project().clone();
6361 let editor =
6362 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6363 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6364 editor.update(cx, |editor, cx| {
6365 editor.highlight_background::<Self>(
6366 &ranges_to_highlight,
6367 |theme| theme.colors().editor_highlighted_line_background,
6368 cx,
6369 );
6370 });
6371 })?;
6372
6373 Ok(())
6374 }
6375
6376 pub fn clear_code_action_providers(&mut self) {
6377 self.code_action_providers.clear();
6378 self.available_code_actions.take();
6379 }
6380
6381 pub fn add_code_action_provider(
6382 &mut self,
6383 provider: Rc<dyn CodeActionProvider>,
6384 window: &mut Window,
6385 cx: &mut Context<Self>,
6386 ) {
6387 if self
6388 .code_action_providers
6389 .iter()
6390 .any(|existing_provider| existing_provider.id() == provider.id())
6391 {
6392 return;
6393 }
6394
6395 self.code_action_providers.push(provider);
6396 self.refresh_code_actions(window, cx);
6397 }
6398
6399 pub fn remove_code_action_provider(
6400 &mut self,
6401 id: Arc<str>,
6402 window: &mut Window,
6403 cx: &mut Context<Self>,
6404 ) {
6405 self.code_action_providers
6406 .retain(|provider| provider.id() != id);
6407 self.refresh_code_actions(window, cx);
6408 }
6409
6410 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6411 !self.code_action_providers.is_empty()
6412 && EditorSettings::get_global(cx).toolbar.code_actions
6413 }
6414
6415 pub fn has_available_code_actions(&self) -> bool {
6416 self.available_code_actions
6417 .as_ref()
6418 .is_some_and(|(_, actions)| !actions.is_empty())
6419 }
6420
6421 fn render_inline_code_actions(
6422 &self,
6423 icon_size: ui::IconSize,
6424 display_row: DisplayRow,
6425 is_active: bool,
6426 cx: &mut Context<Self>,
6427 ) -> AnyElement {
6428 let show_tooltip = !self.context_menu_visible();
6429 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6430 .icon_size(icon_size)
6431 .shape(ui::IconButtonShape::Square)
6432 .icon_color(ui::Color::Hidden)
6433 .toggle_state(is_active)
6434 .when(show_tooltip, |this| {
6435 this.tooltip({
6436 let focus_handle = self.focus_handle.clone();
6437 move |window, cx| {
6438 Tooltip::for_action_in(
6439 "Toggle Code Actions",
6440 &ToggleCodeActions {
6441 deployed_from: None,
6442 quick_launch: false,
6443 },
6444 &focus_handle,
6445 window,
6446 cx,
6447 )
6448 }
6449 })
6450 })
6451 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6452 window.focus(&editor.focus_handle(cx));
6453 editor.toggle_code_actions(
6454 &crate::actions::ToggleCodeActions {
6455 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6456 display_row,
6457 )),
6458 quick_launch: false,
6459 },
6460 window,
6461 cx,
6462 );
6463 }))
6464 .into_any_element()
6465 }
6466
6467 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6468 &self.context_menu
6469 }
6470
6471 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6472 let newest_selection = self.selections.newest_anchor().clone();
6473 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6474 let buffer = self.buffer.read(cx);
6475 if newest_selection.head().diff_base_anchor.is_some() {
6476 return None;
6477 }
6478 let (start_buffer, start) =
6479 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6480 let (end_buffer, end) =
6481 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6482 if start_buffer != end_buffer {
6483 return None;
6484 }
6485
6486 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6487 cx.background_executor()
6488 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6489 .await;
6490
6491 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6492 let providers = this.code_action_providers.clone();
6493 let tasks = this
6494 .code_action_providers
6495 .iter()
6496 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6497 .collect::<Vec<_>>();
6498 (providers, tasks)
6499 })?;
6500
6501 let mut actions = Vec::new();
6502 for (provider, provider_actions) in
6503 providers.into_iter().zip(future::join_all(tasks).await)
6504 {
6505 if let Some(provider_actions) = provider_actions.log_err() {
6506 actions.extend(provider_actions.into_iter().map(|action| {
6507 AvailableCodeAction {
6508 excerpt_id: newest_selection.start.excerpt_id,
6509 action,
6510 provider: provider.clone(),
6511 }
6512 }));
6513 }
6514 }
6515
6516 this.update(cx, |this, cx| {
6517 this.available_code_actions = if actions.is_empty() {
6518 None
6519 } else {
6520 Some((
6521 Location {
6522 buffer: start_buffer,
6523 range: start..end,
6524 },
6525 actions.into(),
6526 ))
6527 };
6528 cx.notify();
6529 })
6530 }));
6531 None
6532 }
6533
6534 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6535 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6536 self.show_git_blame_inline = false;
6537
6538 self.show_git_blame_inline_delay_task =
6539 Some(cx.spawn_in(window, async move |this, cx| {
6540 cx.background_executor().timer(delay).await;
6541
6542 this.update(cx, |this, cx| {
6543 this.show_git_blame_inline = true;
6544 cx.notify();
6545 })
6546 .log_err();
6547 }));
6548 }
6549 }
6550
6551 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6552 let snapshot = self.snapshot(window, cx);
6553 let cursor = self.selections.newest::<Point>(cx).head();
6554 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6555 else {
6556 return;
6557 };
6558
6559 let Some(blame) = self.blame.as_ref() else {
6560 return;
6561 };
6562
6563 let row_info = RowInfo {
6564 buffer_id: Some(buffer.remote_id()),
6565 buffer_row: Some(point.row),
6566 ..Default::default()
6567 };
6568 let Some(blame_entry) = blame
6569 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6570 .flatten()
6571 else {
6572 return;
6573 };
6574
6575 let anchor = self.selections.newest_anchor().head();
6576 let position = self.to_pixel_point(anchor, &snapshot, window);
6577 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6578 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6579 };
6580 }
6581
6582 fn show_blame_popover(
6583 &mut self,
6584 blame_entry: &BlameEntry,
6585 position: gpui::Point<Pixels>,
6586 ignore_timeout: bool,
6587 cx: &mut Context<Self>,
6588 ) {
6589 if let Some(state) = &mut self.inline_blame_popover {
6590 state.hide_task.take();
6591 } else {
6592 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6593 let blame_entry = blame_entry.clone();
6594 let show_task = cx.spawn(async move |editor, cx| {
6595 if !ignore_timeout {
6596 cx.background_executor()
6597 .timer(std::time::Duration::from_millis(blame_popover_delay))
6598 .await;
6599 }
6600 editor
6601 .update(cx, |editor, cx| {
6602 editor.inline_blame_popover_show_task.take();
6603 let Some(blame) = editor.blame.as_ref() else {
6604 return;
6605 };
6606 let blame = blame.read(cx);
6607 let details = blame.details_for_entry(&blame_entry);
6608 let markdown = cx.new(|cx| {
6609 Markdown::new(
6610 details
6611 .as_ref()
6612 .map(|message| message.message.clone())
6613 .unwrap_or_default(),
6614 None,
6615 None,
6616 cx,
6617 )
6618 });
6619 editor.inline_blame_popover = Some(InlineBlamePopover {
6620 position,
6621 hide_task: None,
6622 popover_bounds: None,
6623 popover_state: InlineBlamePopoverState {
6624 scroll_handle: ScrollHandle::new(),
6625 commit_message: details,
6626 markdown,
6627 },
6628 keyboard_grace: ignore_timeout,
6629 });
6630 cx.notify();
6631 })
6632 .ok();
6633 });
6634 self.inline_blame_popover_show_task = Some(show_task);
6635 }
6636 }
6637
6638 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6639 self.inline_blame_popover_show_task.take();
6640 if let Some(state) = &mut self.inline_blame_popover {
6641 let hide_task = cx.spawn(async move |editor, cx| {
6642 cx.background_executor()
6643 .timer(std::time::Duration::from_millis(100))
6644 .await;
6645 editor
6646 .update(cx, |editor, cx| {
6647 editor.inline_blame_popover.take();
6648 cx.notify();
6649 })
6650 .ok();
6651 });
6652 state.hide_task = Some(hide_task);
6653 }
6654 }
6655
6656 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6657 if self.pending_rename.is_some() {
6658 return None;
6659 }
6660
6661 let provider = self.semantics_provider.clone()?;
6662 let buffer = self.buffer.read(cx);
6663 let newest_selection = self.selections.newest_anchor().clone();
6664 let cursor_position = newest_selection.head();
6665 let (cursor_buffer, cursor_buffer_position) =
6666 buffer.text_anchor_for_position(cursor_position, cx)?;
6667 let (tail_buffer, tail_buffer_position) =
6668 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6669 if cursor_buffer != tail_buffer {
6670 return None;
6671 }
6672
6673 let snapshot = cursor_buffer.read(cx).snapshot();
6674 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6675 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6676 if start_word_range != end_word_range {
6677 self.document_highlights_task.take();
6678 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6679 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6680 return None;
6681 }
6682
6683 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6684 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6685 cx.background_executor()
6686 .timer(Duration::from_millis(debounce))
6687 .await;
6688
6689 let highlights = if let Some(highlights) = cx
6690 .update(|cx| {
6691 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6692 })
6693 .ok()
6694 .flatten()
6695 {
6696 highlights.await.log_err()
6697 } else {
6698 None
6699 };
6700
6701 if let Some(highlights) = highlights {
6702 this.update(cx, |this, cx| {
6703 if this.pending_rename.is_some() {
6704 return;
6705 }
6706
6707 let buffer_id = cursor_position.buffer_id;
6708 let buffer = this.buffer.read(cx);
6709 if !buffer
6710 .text_anchor_for_position(cursor_position, cx)
6711 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6712 {
6713 return;
6714 }
6715
6716 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6717 let mut write_ranges = Vec::new();
6718 let mut read_ranges = Vec::new();
6719 for highlight in highlights {
6720 for (excerpt_id, excerpt_range) in
6721 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6722 {
6723 let start = highlight
6724 .range
6725 .start
6726 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6727 let end = highlight
6728 .range
6729 .end
6730 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6731 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6732 continue;
6733 }
6734
6735 let range = Anchor {
6736 buffer_id,
6737 excerpt_id,
6738 text_anchor: start,
6739 diff_base_anchor: None,
6740 }..Anchor {
6741 buffer_id,
6742 excerpt_id,
6743 text_anchor: end,
6744 diff_base_anchor: None,
6745 };
6746 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6747 write_ranges.push(range);
6748 } else {
6749 read_ranges.push(range);
6750 }
6751 }
6752 }
6753
6754 this.highlight_background::<DocumentHighlightRead>(
6755 &read_ranges,
6756 |theme| theme.colors().editor_document_highlight_read_background,
6757 cx,
6758 );
6759 this.highlight_background::<DocumentHighlightWrite>(
6760 &write_ranges,
6761 |theme| theme.colors().editor_document_highlight_write_background,
6762 cx,
6763 );
6764 cx.notify();
6765 })
6766 .log_err();
6767 }
6768 }));
6769 None
6770 }
6771
6772 fn prepare_highlight_query_from_selection(
6773 &mut self,
6774 cx: &mut Context<Editor>,
6775 ) -> Option<(String, Range<Anchor>)> {
6776 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6777 return None;
6778 }
6779 if !EditorSettings::get_global(cx).selection_highlight {
6780 return None;
6781 }
6782 if self.selections.count() != 1 || self.selections.line_mode {
6783 return None;
6784 }
6785 let selection = self.selections.newest::<Point>(cx);
6786 if selection.is_empty() || selection.start.row != selection.end.row {
6787 return None;
6788 }
6789 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6790 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6791 let query = multi_buffer_snapshot
6792 .text_for_range(selection_anchor_range.clone())
6793 .collect::<String>();
6794 if query.trim().is_empty() {
6795 return None;
6796 }
6797 Some((query, selection_anchor_range))
6798 }
6799
6800 fn update_selection_occurrence_highlights(
6801 &mut self,
6802 query_text: String,
6803 query_range: Range<Anchor>,
6804 multi_buffer_range_to_query: Range<Point>,
6805 use_debounce: bool,
6806 window: &mut Window,
6807 cx: &mut Context<Editor>,
6808 ) -> Task<()> {
6809 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6810 cx.spawn_in(window, async move |editor, cx| {
6811 if use_debounce {
6812 cx.background_executor()
6813 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6814 .await;
6815 }
6816 let match_task = cx.background_spawn(async move {
6817 let buffer_ranges = multi_buffer_snapshot
6818 .range_to_buffer_ranges(multi_buffer_range_to_query)
6819 .into_iter()
6820 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6821 let mut match_ranges = Vec::new();
6822 let Ok(regex) = project::search::SearchQuery::text(
6823 query_text.clone(),
6824 false,
6825 false,
6826 false,
6827 Default::default(),
6828 Default::default(),
6829 false,
6830 None,
6831 ) else {
6832 return Vec::default();
6833 };
6834 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6835 match_ranges.extend(
6836 regex
6837 .search(&buffer_snapshot, Some(search_range.clone()))
6838 .await
6839 .into_iter()
6840 .filter_map(|match_range| {
6841 let match_start = buffer_snapshot
6842 .anchor_after(search_range.start + match_range.start);
6843 let match_end = buffer_snapshot
6844 .anchor_before(search_range.start + match_range.end);
6845 let match_anchor_range = Anchor::range_in_buffer(
6846 excerpt_id,
6847 buffer_snapshot.remote_id(),
6848 match_start..match_end,
6849 );
6850 (match_anchor_range != query_range).then_some(match_anchor_range)
6851 }),
6852 );
6853 }
6854 match_ranges
6855 });
6856 let match_ranges = match_task.await;
6857 editor
6858 .update_in(cx, |editor, _, cx| {
6859 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6860 if !match_ranges.is_empty() {
6861 editor.highlight_background::<SelectedTextHighlight>(
6862 &match_ranges,
6863 |theme| theme.colors().editor_document_highlight_bracket_background,
6864 cx,
6865 )
6866 }
6867 })
6868 .log_err();
6869 })
6870 }
6871
6872 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6873 struct NewlineFold;
6874 let type_id = std::any::TypeId::of::<NewlineFold>();
6875 if !self.mode.is_single_line() {
6876 return;
6877 }
6878 let snapshot = self.snapshot(window, cx);
6879 if snapshot.buffer_snapshot.max_point().row == 0 {
6880 return;
6881 }
6882 let task = cx.background_spawn(async move {
6883 let new_newlines = snapshot
6884 .buffer_chars_at(0)
6885 .filter_map(|(c, i)| {
6886 if c == '\n' {
6887 Some(
6888 snapshot.buffer_snapshot.anchor_after(i)
6889 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6890 )
6891 } else {
6892 None
6893 }
6894 })
6895 .collect::<Vec<_>>();
6896 let existing_newlines = snapshot
6897 .folds_in_range(0..snapshot.buffer_snapshot.len())
6898 .filter_map(|fold| {
6899 if fold.placeholder.type_tag == Some(type_id) {
6900 Some(fold.range.start..fold.range.end)
6901 } else {
6902 None
6903 }
6904 })
6905 .collect::<Vec<_>>();
6906
6907 (new_newlines, existing_newlines)
6908 });
6909 self.folding_newlines = cx.spawn(async move |this, cx| {
6910 let (new_newlines, existing_newlines) = task.await;
6911 if new_newlines == existing_newlines {
6912 return;
6913 }
6914 let placeholder = FoldPlaceholder {
6915 render: Arc::new(move |_, _, cx| {
6916 div()
6917 .bg(cx.theme().status().hint_background)
6918 .border_b_1()
6919 .size_full()
6920 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6921 .border_color(cx.theme().status().hint)
6922 .child("\\n")
6923 .into_any()
6924 }),
6925 constrain_width: false,
6926 merge_adjacent: false,
6927 type_tag: Some(type_id),
6928 };
6929 let creases = new_newlines
6930 .into_iter()
6931 .map(|range| Crease::simple(range, placeholder.clone()))
6932 .collect();
6933 this.update(cx, |this, cx| {
6934 this.display_map.update(cx, |display_map, cx| {
6935 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6936 display_map.fold(creases, cx);
6937 });
6938 })
6939 .ok();
6940 });
6941 }
6942
6943 fn refresh_selected_text_highlights(
6944 &mut self,
6945 on_buffer_edit: bool,
6946 window: &mut Window,
6947 cx: &mut Context<Editor>,
6948 ) {
6949 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6950 else {
6951 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6952 self.quick_selection_highlight_task.take();
6953 self.debounced_selection_highlight_task.take();
6954 return;
6955 };
6956 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6957 if on_buffer_edit
6958 || self
6959 .quick_selection_highlight_task
6960 .as_ref()
6961 .map_or(true, |(prev_anchor_range, _)| {
6962 prev_anchor_range != &query_range
6963 })
6964 {
6965 let multi_buffer_visible_start = self
6966 .scroll_manager
6967 .anchor()
6968 .anchor
6969 .to_point(&multi_buffer_snapshot);
6970 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6971 multi_buffer_visible_start
6972 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6973 Bias::Left,
6974 );
6975 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6976 self.quick_selection_highlight_task = Some((
6977 query_range.clone(),
6978 self.update_selection_occurrence_highlights(
6979 query_text.clone(),
6980 query_range.clone(),
6981 multi_buffer_visible_range,
6982 false,
6983 window,
6984 cx,
6985 ),
6986 ));
6987 }
6988 if on_buffer_edit
6989 || self
6990 .debounced_selection_highlight_task
6991 .as_ref()
6992 .map_or(true, |(prev_anchor_range, _)| {
6993 prev_anchor_range != &query_range
6994 })
6995 {
6996 let multi_buffer_start = multi_buffer_snapshot
6997 .anchor_before(0)
6998 .to_point(&multi_buffer_snapshot);
6999 let multi_buffer_end = multi_buffer_snapshot
7000 .anchor_after(multi_buffer_snapshot.len())
7001 .to_point(&multi_buffer_snapshot);
7002 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7003 self.debounced_selection_highlight_task = Some((
7004 query_range.clone(),
7005 self.update_selection_occurrence_highlights(
7006 query_text,
7007 query_range,
7008 multi_buffer_full_range,
7009 true,
7010 window,
7011 cx,
7012 ),
7013 ));
7014 }
7015 }
7016
7017 pub fn refresh_edit_prediction(
7018 &mut self,
7019 debounce: bool,
7020 user_requested: bool,
7021 window: &mut Window,
7022 cx: &mut Context<Self>,
7023 ) -> Option<()> {
7024 if DisableAiSettings::get_global(cx).disable_ai {
7025 return None;
7026 }
7027
7028 let provider = self.edit_prediction_provider()?;
7029 let cursor = self.selections.newest_anchor().head();
7030 let (buffer, cursor_buffer_position) =
7031 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7032
7033 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7034 self.discard_edit_prediction(false, cx);
7035 return None;
7036 }
7037
7038 if !user_requested
7039 && (!self.should_show_edit_predictions()
7040 || !self.is_focused(window)
7041 || buffer.read(cx).is_empty())
7042 {
7043 self.discard_edit_prediction(false, cx);
7044 return None;
7045 }
7046
7047 self.update_visible_edit_prediction(window, cx);
7048 provider.refresh(
7049 self.project.clone(),
7050 buffer,
7051 cursor_buffer_position,
7052 debounce,
7053 cx,
7054 );
7055 Some(())
7056 }
7057
7058 fn show_edit_predictions_in_menu(&self) -> bool {
7059 match self.edit_prediction_settings {
7060 EditPredictionSettings::Disabled => false,
7061 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7062 }
7063 }
7064
7065 pub fn edit_predictions_enabled(&self) -> bool {
7066 match self.edit_prediction_settings {
7067 EditPredictionSettings::Disabled => false,
7068 EditPredictionSettings::Enabled { .. } => true,
7069 }
7070 }
7071
7072 fn edit_prediction_requires_modifier(&self) -> bool {
7073 match self.edit_prediction_settings {
7074 EditPredictionSettings::Disabled => false,
7075 EditPredictionSettings::Enabled {
7076 preview_requires_modifier,
7077 ..
7078 } => preview_requires_modifier,
7079 }
7080 }
7081
7082 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7083 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7084 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7085 self.discard_edit_prediction(false, cx);
7086 } else {
7087 let selection = self.selections.newest_anchor();
7088 let cursor = selection.head();
7089
7090 if let Some((buffer, cursor_buffer_position)) =
7091 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7092 {
7093 self.edit_prediction_settings =
7094 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7095 }
7096 }
7097 }
7098
7099 fn edit_prediction_settings_at_position(
7100 &self,
7101 buffer: &Entity<Buffer>,
7102 buffer_position: language::Anchor,
7103 cx: &App,
7104 ) -> EditPredictionSettings {
7105 if !self.mode.is_full()
7106 || !self.show_edit_predictions_override.unwrap_or(true)
7107 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7108 {
7109 return EditPredictionSettings::Disabled;
7110 }
7111
7112 let buffer = buffer.read(cx);
7113
7114 let file = buffer.file();
7115
7116 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7117 return EditPredictionSettings::Disabled;
7118 };
7119
7120 let by_provider = matches!(
7121 self.menu_edit_predictions_policy,
7122 MenuEditPredictionsPolicy::ByProvider
7123 );
7124
7125 let show_in_menu = by_provider
7126 && self
7127 .edit_prediction_provider
7128 .as_ref()
7129 .map_or(false, |provider| {
7130 provider.provider.show_completions_in_menu()
7131 });
7132
7133 let preview_requires_modifier =
7134 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7135
7136 EditPredictionSettings::Enabled {
7137 show_in_menu,
7138 preview_requires_modifier,
7139 }
7140 }
7141
7142 fn should_show_edit_predictions(&self) -> bool {
7143 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7144 }
7145
7146 pub fn edit_prediction_preview_is_active(&self) -> bool {
7147 matches!(
7148 self.edit_prediction_preview,
7149 EditPredictionPreview::Active { .. }
7150 )
7151 }
7152
7153 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7154 let cursor = self.selections.newest_anchor().head();
7155 if let Some((buffer, cursor_position)) =
7156 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7157 {
7158 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7159 } else {
7160 false
7161 }
7162 }
7163
7164 pub fn supports_minimap(&self, cx: &App) -> bool {
7165 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7166 }
7167
7168 fn edit_predictions_enabled_in_buffer(
7169 &self,
7170 buffer: &Entity<Buffer>,
7171 buffer_position: language::Anchor,
7172 cx: &App,
7173 ) -> bool {
7174 maybe!({
7175 if self.read_only(cx) {
7176 return Some(false);
7177 }
7178 let provider = self.edit_prediction_provider()?;
7179 if !provider.is_enabled(&buffer, buffer_position, cx) {
7180 return Some(false);
7181 }
7182 let buffer = buffer.read(cx);
7183 let Some(file) = buffer.file() else {
7184 return Some(true);
7185 };
7186 let settings = all_language_settings(Some(file), cx);
7187 Some(settings.edit_predictions_enabled_for_file(file, cx))
7188 })
7189 .unwrap_or(false)
7190 }
7191
7192 fn cycle_edit_prediction(
7193 &mut self,
7194 direction: Direction,
7195 window: &mut Window,
7196 cx: &mut Context<Self>,
7197 ) -> Option<()> {
7198 let provider = self.edit_prediction_provider()?;
7199 let cursor = self.selections.newest_anchor().head();
7200 let (buffer, cursor_buffer_position) =
7201 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7202 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7203 return None;
7204 }
7205
7206 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7207 self.update_visible_edit_prediction(window, cx);
7208
7209 Some(())
7210 }
7211
7212 pub fn show_edit_prediction(
7213 &mut self,
7214 _: &ShowEditPrediction,
7215 window: &mut Window,
7216 cx: &mut Context<Self>,
7217 ) {
7218 if !self.has_active_edit_prediction() {
7219 self.refresh_edit_prediction(false, true, window, cx);
7220 return;
7221 }
7222
7223 self.update_visible_edit_prediction(window, cx);
7224 }
7225
7226 pub fn display_cursor_names(
7227 &mut self,
7228 _: &DisplayCursorNames,
7229 window: &mut Window,
7230 cx: &mut Context<Self>,
7231 ) {
7232 self.show_cursor_names(window, cx);
7233 }
7234
7235 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7236 self.show_cursor_names = true;
7237 cx.notify();
7238 cx.spawn_in(window, async move |this, cx| {
7239 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7240 this.update(cx, |this, cx| {
7241 this.show_cursor_names = false;
7242 cx.notify()
7243 })
7244 .ok()
7245 })
7246 .detach();
7247 }
7248
7249 pub fn next_edit_prediction(
7250 &mut self,
7251 _: &NextEditPrediction,
7252 window: &mut Window,
7253 cx: &mut Context<Self>,
7254 ) {
7255 if self.has_active_edit_prediction() {
7256 self.cycle_edit_prediction(Direction::Next, window, cx);
7257 } else {
7258 let is_copilot_disabled = self
7259 .refresh_edit_prediction(false, true, window, cx)
7260 .is_none();
7261 if is_copilot_disabled {
7262 cx.propagate();
7263 }
7264 }
7265 }
7266
7267 pub fn previous_edit_prediction(
7268 &mut self,
7269 _: &PreviousEditPrediction,
7270 window: &mut Window,
7271 cx: &mut Context<Self>,
7272 ) {
7273 if self.has_active_edit_prediction() {
7274 self.cycle_edit_prediction(Direction::Prev, window, cx);
7275 } else {
7276 let is_copilot_disabled = self
7277 .refresh_edit_prediction(false, true, window, cx)
7278 .is_none();
7279 if is_copilot_disabled {
7280 cx.propagate();
7281 }
7282 }
7283 }
7284
7285 pub fn accept_edit_prediction(
7286 &mut self,
7287 _: &AcceptEditPrediction,
7288 window: &mut Window,
7289 cx: &mut Context<Self>,
7290 ) {
7291 if self.show_edit_predictions_in_menu() {
7292 self.hide_context_menu(window, cx);
7293 }
7294
7295 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7296 return;
7297 };
7298
7299 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7300
7301 match &active_edit_prediction.completion {
7302 EditPrediction::Move { target, .. } => {
7303 let target = *target;
7304
7305 if let Some(position_map) = &self.last_position_map {
7306 if position_map
7307 .visible_row_range
7308 .contains(&target.to_display_point(&position_map.snapshot).row())
7309 || !self.edit_prediction_requires_modifier()
7310 {
7311 self.unfold_ranges(&[target..target], true, false, cx);
7312 // Note that this is also done in vim's handler of the Tab action.
7313 self.change_selections(
7314 SelectionEffects::scroll(Autoscroll::newest()),
7315 window,
7316 cx,
7317 |selections| {
7318 selections.select_anchor_ranges([target..target]);
7319 },
7320 );
7321 self.clear_row_highlights::<EditPredictionPreview>();
7322
7323 self.edit_prediction_preview
7324 .set_previous_scroll_position(None);
7325 } else {
7326 self.edit_prediction_preview
7327 .set_previous_scroll_position(Some(
7328 position_map.snapshot.scroll_anchor,
7329 ));
7330
7331 self.highlight_rows::<EditPredictionPreview>(
7332 target..target,
7333 cx.theme().colors().editor_highlighted_line_background,
7334 RowHighlightOptions {
7335 autoscroll: true,
7336 ..Default::default()
7337 },
7338 cx,
7339 );
7340 self.request_autoscroll(Autoscroll::fit(), cx);
7341 }
7342 }
7343 }
7344 EditPrediction::Edit { edits, .. } => {
7345 if let Some(provider) = self.edit_prediction_provider() {
7346 provider.accept(cx);
7347 }
7348
7349 // Store the transaction ID and selections before applying the edit
7350 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7351
7352 let snapshot = self.buffer.read(cx).snapshot(cx);
7353 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7354
7355 self.buffer.update(cx, |buffer, cx| {
7356 buffer.edit(edits.iter().cloned(), None, cx)
7357 });
7358
7359 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7360 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7361 });
7362
7363 let selections = self.selections.disjoint_anchors();
7364 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7365 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7366 if has_new_transaction {
7367 self.selection_history
7368 .insert_transaction(transaction_id_now, selections);
7369 }
7370 }
7371
7372 self.update_visible_edit_prediction(window, cx);
7373 if self.active_edit_prediction.is_none() {
7374 self.refresh_edit_prediction(true, true, window, cx);
7375 }
7376
7377 cx.notify();
7378 }
7379 }
7380
7381 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7382 }
7383
7384 pub fn accept_partial_edit_prediction(
7385 &mut self,
7386 _: &AcceptPartialEditPrediction,
7387 window: &mut Window,
7388 cx: &mut Context<Self>,
7389 ) {
7390 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7391 return;
7392 };
7393 if self.selections.count() != 1 {
7394 return;
7395 }
7396
7397 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7398
7399 match &active_edit_prediction.completion {
7400 EditPrediction::Move { target, .. } => {
7401 let target = *target;
7402 self.change_selections(
7403 SelectionEffects::scroll(Autoscroll::newest()),
7404 window,
7405 cx,
7406 |selections| {
7407 selections.select_anchor_ranges([target..target]);
7408 },
7409 );
7410 }
7411 EditPrediction::Edit { edits, .. } => {
7412 // Find an insertion that starts at the cursor position.
7413 let snapshot = self.buffer.read(cx).snapshot(cx);
7414 let cursor_offset = self.selections.newest::<usize>(cx).head();
7415 let insertion = edits.iter().find_map(|(range, text)| {
7416 let range = range.to_offset(&snapshot);
7417 if range.is_empty() && range.start == cursor_offset {
7418 Some(text)
7419 } else {
7420 None
7421 }
7422 });
7423
7424 if let Some(text) = insertion {
7425 let mut partial_completion = text
7426 .chars()
7427 .by_ref()
7428 .take_while(|c| c.is_alphabetic())
7429 .collect::<String>();
7430 if partial_completion.is_empty() {
7431 partial_completion = text
7432 .chars()
7433 .by_ref()
7434 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7435 .collect::<String>();
7436 }
7437
7438 cx.emit(EditorEvent::InputHandled {
7439 utf16_range_to_replace: None,
7440 text: partial_completion.clone().into(),
7441 });
7442
7443 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7444
7445 self.refresh_edit_prediction(true, true, window, cx);
7446 cx.notify();
7447 } else {
7448 self.accept_edit_prediction(&Default::default(), window, cx);
7449 }
7450 }
7451 }
7452 }
7453
7454 fn discard_edit_prediction(
7455 &mut self,
7456 should_report_edit_prediction_event: bool,
7457 cx: &mut Context<Self>,
7458 ) -> bool {
7459 if should_report_edit_prediction_event {
7460 let completion_id = self
7461 .active_edit_prediction
7462 .as_ref()
7463 .and_then(|active_completion| active_completion.completion_id.clone());
7464
7465 self.report_edit_prediction_event(completion_id, false, cx);
7466 }
7467
7468 if let Some(provider) = self.edit_prediction_provider() {
7469 provider.discard(cx);
7470 }
7471
7472 self.take_active_edit_prediction(cx)
7473 }
7474
7475 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7476 let Some(provider) = self.edit_prediction_provider() else {
7477 return;
7478 };
7479
7480 let Some((_, buffer, _)) = self
7481 .buffer
7482 .read(cx)
7483 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7484 else {
7485 return;
7486 };
7487
7488 let extension = buffer
7489 .read(cx)
7490 .file()
7491 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7492
7493 let event_type = match accepted {
7494 true => "Edit Prediction Accepted",
7495 false => "Edit Prediction Discarded",
7496 };
7497 telemetry::event!(
7498 event_type,
7499 provider = provider.name(),
7500 prediction_id = id,
7501 suggestion_accepted = accepted,
7502 file_extension = extension,
7503 );
7504 }
7505
7506 pub fn has_active_edit_prediction(&self) -> bool {
7507 self.active_edit_prediction.is_some()
7508 }
7509
7510 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7511 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7512 return false;
7513 };
7514
7515 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7516 self.clear_highlights::<EditPredictionHighlight>(cx);
7517 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7518 true
7519 }
7520
7521 /// Returns true when we're displaying the edit prediction popover below the cursor
7522 /// like we are not previewing and the LSP autocomplete menu is visible
7523 /// or we are in `when_holding_modifier` mode.
7524 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7525 if self.edit_prediction_preview_is_active()
7526 || !self.show_edit_predictions_in_menu()
7527 || !self.edit_predictions_enabled()
7528 {
7529 return false;
7530 }
7531
7532 if self.has_visible_completions_menu() {
7533 return true;
7534 }
7535
7536 has_completion && self.edit_prediction_requires_modifier()
7537 }
7538
7539 fn handle_modifiers_changed(
7540 &mut self,
7541 modifiers: Modifiers,
7542 position_map: &PositionMap,
7543 window: &mut Window,
7544 cx: &mut Context<Self>,
7545 ) {
7546 if self.show_edit_predictions_in_menu() {
7547 self.update_edit_prediction_preview(&modifiers, window, cx);
7548 }
7549
7550 self.update_selection_mode(&modifiers, position_map, window, cx);
7551
7552 let mouse_position = window.mouse_position();
7553 if !position_map.text_hitbox.is_hovered(window) {
7554 return;
7555 }
7556
7557 self.update_hovered_link(
7558 position_map.point_for_position(mouse_position),
7559 &position_map.snapshot,
7560 modifiers,
7561 window,
7562 cx,
7563 )
7564 }
7565
7566 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7567 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7568 if invert {
7569 match multi_cursor_setting {
7570 MultiCursorModifier::Alt => modifiers.alt,
7571 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7572 }
7573 } else {
7574 match multi_cursor_setting {
7575 MultiCursorModifier::Alt => modifiers.secondary(),
7576 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7577 }
7578 }
7579 }
7580
7581 fn columnar_selection_mode(
7582 modifiers: &Modifiers,
7583 cx: &mut Context<Self>,
7584 ) -> Option<ColumnarMode> {
7585 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7586 if Self::multi_cursor_modifier(false, modifiers, cx) {
7587 Some(ColumnarMode::FromMouse)
7588 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7589 Some(ColumnarMode::FromSelection)
7590 } else {
7591 None
7592 }
7593 } else {
7594 None
7595 }
7596 }
7597
7598 fn update_selection_mode(
7599 &mut self,
7600 modifiers: &Modifiers,
7601 position_map: &PositionMap,
7602 window: &mut Window,
7603 cx: &mut Context<Self>,
7604 ) {
7605 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7606 return;
7607 };
7608 if self.selections.pending.is_none() {
7609 return;
7610 }
7611
7612 let mouse_position = window.mouse_position();
7613 let point_for_position = position_map.point_for_position(mouse_position);
7614 let position = point_for_position.previous_valid;
7615
7616 self.select(
7617 SelectPhase::BeginColumnar {
7618 position,
7619 reset: false,
7620 mode,
7621 goal_column: point_for_position.exact_unclipped.column(),
7622 },
7623 window,
7624 cx,
7625 );
7626 }
7627
7628 fn update_edit_prediction_preview(
7629 &mut self,
7630 modifiers: &Modifiers,
7631 window: &mut Window,
7632 cx: &mut Context<Self>,
7633 ) {
7634 let mut modifiers_held = false;
7635 if let Some(accept_keystroke) = self
7636 .accept_edit_prediction_keybind(false, window, cx)
7637 .keystroke()
7638 {
7639 modifiers_held = modifiers_held
7640 || (&accept_keystroke.modifiers == modifiers
7641 && accept_keystroke.modifiers.modified());
7642 };
7643 if let Some(accept_partial_keystroke) = self
7644 .accept_edit_prediction_keybind(true, window, cx)
7645 .keystroke()
7646 {
7647 modifiers_held = modifiers_held
7648 || (&accept_partial_keystroke.modifiers == modifiers
7649 && accept_partial_keystroke.modifiers.modified());
7650 }
7651
7652 if modifiers_held {
7653 if matches!(
7654 self.edit_prediction_preview,
7655 EditPredictionPreview::Inactive { .. }
7656 ) {
7657 self.edit_prediction_preview = EditPredictionPreview::Active {
7658 previous_scroll_position: None,
7659 since: Instant::now(),
7660 };
7661
7662 self.update_visible_edit_prediction(window, cx);
7663 cx.notify();
7664 }
7665 } else if let EditPredictionPreview::Active {
7666 previous_scroll_position,
7667 since,
7668 } = self.edit_prediction_preview
7669 {
7670 if let (Some(previous_scroll_position), Some(position_map)) =
7671 (previous_scroll_position, self.last_position_map.as_ref())
7672 {
7673 self.set_scroll_position(
7674 previous_scroll_position
7675 .scroll_position(&position_map.snapshot.display_snapshot),
7676 window,
7677 cx,
7678 );
7679 }
7680
7681 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7682 released_too_fast: since.elapsed() < Duration::from_millis(200),
7683 };
7684 self.clear_row_highlights::<EditPredictionPreview>();
7685 self.update_visible_edit_prediction(window, cx);
7686 cx.notify();
7687 }
7688 }
7689
7690 fn update_visible_edit_prediction(
7691 &mut self,
7692 _window: &mut Window,
7693 cx: &mut Context<Self>,
7694 ) -> Option<()> {
7695 if DisableAiSettings::get_global(cx).disable_ai {
7696 return None;
7697 }
7698
7699 let selection = self.selections.newest_anchor();
7700 let cursor = selection.head();
7701 let multibuffer = self.buffer.read(cx).snapshot(cx);
7702 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7703 let excerpt_id = cursor.excerpt_id;
7704
7705 let show_in_menu = self.show_edit_predictions_in_menu();
7706 let completions_menu_has_precedence = !show_in_menu
7707 && (self.context_menu.borrow().is_some()
7708 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7709
7710 if completions_menu_has_precedence
7711 || !offset_selection.is_empty()
7712 || self
7713 .active_edit_prediction
7714 .as_ref()
7715 .map_or(false, |completion| {
7716 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7717 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7718 !invalidation_range.contains(&offset_selection.head())
7719 })
7720 {
7721 self.discard_edit_prediction(false, cx);
7722 return None;
7723 }
7724
7725 self.take_active_edit_prediction(cx);
7726 let Some(provider) = self.edit_prediction_provider() else {
7727 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7728 return None;
7729 };
7730
7731 let (buffer, cursor_buffer_position) =
7732 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7733
7734 self.edit_prediction_settings =
7735 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7736
7737 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7738
7739 if self.edit_prediction_indent_conflict {
7740 let cursor_point = cursor.to_point(&multibuffer);
7741
7742 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7743
7744 if let Some((_, indent)) = indents.iter().next() {
7745 if indent.len == cursor_point.column {
7746 self.edit_prediction_indent_conflict = false;
7747 }
7748 }
7749 }
7750
7751 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7752 let edits = edit_prediction
7753 .edits
7754 .into_iter()
7755 .flat_map(|(range, new_text)| {
7756 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7757 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7758 Some((start..end, new_text))
7759 })
7760 .collect::<Vec<_>>();
7761 if edits.is_empty() {
7762 return None;
7763 }
7764
7765 let first_edit_start = edits.first().unwrap().0.start;
7766 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7767 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7768
7769 let last_edit_end = edits.last().unwrap().0.end;
7770 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7771 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7772
7773 let cursor_row = cursor.to_point(&multibuffer).row;
7774
7775 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7776
7777 let mut inlay_ids = Vec::new();
7778 let invalidation_row_range;
7779 let move_invalidation_row_range = if cursor_row < edit_start_row {
7780 Some(cursor_row..edit_end_row)
7781 } else if cursor_row > edit_end_row {
7782 Some(edit_start_row..cursor_row)
7783 } else {
7784 None
7785 };
7786 let supports_jump = self
7787 .edit_prediction_provider
7788 .as_ref()
7789 .map(|provider| provider.provider.supports_jump_to_edit())
7790 .unwrap_or(true);
7791
7792 let is_move = supports_jump
7793 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7794 let completion = if is_move {
7795 invalidation_row_range =
7796 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7797 let target = first_edit_start;
7798 EditPrediction::Move { target, snapshot }
7799 } else {
7800 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7801 && !self.edit_predictions_hidden_for_vim_mode;
7802
7803 if show_completions_in_buffer {
7804 if edits
7805 .iter()
7806 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7807 {
7808 let mut inlays = Vec::new();
7809 for (range, new_text) in &edits {
7810 let inlay = Inlay::edit_prediction(
7811 post_inc(&mut self.next_inlay_id),
7812 range.start,
7813 new_text.as_str(),
7814 );
7815 inlay_ids.push(inlay.id);
7816 inlays.push(inlay);
7817 }
7818
7819 self.splice_inlays(&[], inlays, cx);
7820 } else {
7821 let background_color = cx.theme().status().deleted_background;
7822 self.highlight_text::<EditPredictionHighlight>(
7823 edits.iter().map(|(range, _)| range.clone()).collect(),
7824 HighlightStyle {
7825 background_color: Some(background_color),
7826 ..Default::default()
7827 },
7828 cx,
7829 );
7830 }
7831 }
7832
7833 invalidation_row_range = edit_start_row..edit_end_row;
7834
7835 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7836 if provider.show_tab_accept_marker() {
7837 EditDisplayMode::TabAccept
7838 } else {
7839 EditDisplayMode::Inline
7840 }
7841 } else {
7842 EditDisplayMode::DiffPopover
7843 };
7844
7845 EditPrediction::Edit {
7846 edits,
7847 edit_preview: edit_prediction.edit_preview,
7848 display_mode,
7849 snapshot,
7850 }
7851 };
7852
7853 let invalidation_range = multibuffer
7854 .anchor_before(Point::new(invalidation_row_range.start, 0))
7855 ..multibuffer.anchor_after(Point::new(
7856 invalidation_row_range.end,
7857 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7858 ));
7859
7860 self.stale_edit_prediction_in_menu = None;
7861 self.active_edit_prediction = Some(EditPredictionState {
7862 inlay_ids,
7863 completion,
7864 completion_id: edit_prediction.id,
7865 invalidation_range,
7866 });
7867
7868 cx.notify();
7869
7870 Some(())
7871 }
7872
7873 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7874 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7875 }
7876
7877 fn clear_tasks(&mut self) {
7878 self.tasks.clear()
7879 }
7880
7881 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7882 if self.tasks.insert(key, value).is_some() {
7883 // This case should hopefully be rare, but just in case...
7884 log::error!(
7885 "multiple different run targets found on a single line, only the last target will be rendered"
7886 )
7887 }
7888 }
7889
7890 /// Get all display points of breakpoints that will be rendered within editor
7891 ///
7892 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7893 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7894 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7895 fn active_breakpoints(
7896 &self,
7897 range: Range<DisplayRow>,
7898 window: &mut Window,
7899 cx: &mut Context<Self>,
7900 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7901 let mut breakpoint_display_points = HashMap::default();
7902
7903 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7904 return breakpoint_display_points;
7905 };
7906
7907 let snapshot = self.snapshot(window, cx);
7908
7909 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7910 let Some(project) = self.project.as_ref() else {
7911 return breakpoint_display_points;
7912 };
7913
7914 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7915 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7916
7917 for (buffer_snapshot, range, excerpt_id) in
7918 multi_buffer_snapshot.range_to_buffer_ranges(range)
7919 {
7920 let Some(buffer) = project
7921 .read(cx)
7922 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7923 else {
7924 continue;
7925 };
7926 let breakpoints = breakpoint_store.read(cx).breakpoints(
7927 &buffer,
7928 Some(
7929 buffer_snapshot.anchor_before(range.start)
7930 ..buffer_snapshot.anchor_after(range.end),
7931 ),
7932 buffer_snapshot,
7933 cx,
7934 );
7935 for (breakpoint, state) in breakpoints {
7936 let multi_buffer_anchor =
7937 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7938 let position = multi_buffer_anchor
7939 .to_point(&multi_buffer_snapshot)
7940 .to_display_point(&snapshot);
7941
7942 breakpoint_display_points.insert(
7943 position.row(),
7944 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7945 );
7946 }
7947 }
7948
7949 breakpoint_display_points
7950 }
7951
7952 fn breakpoint_context_menu(
7953 &self,
7954 anchor: Anchor,
7955 window: &mut Window,
7956 cx: &mut Context<Self>,
7957 ) -> Entity<ui::ContextMenu> {
7958 let weak_editor = cx.weak_entity();
7959 let focus_handle = self.focus_handle(cx);
7960
7961 let row = self
7962 .buffer
7963 .read(cx)
7964 .snapshot(cx)
7965 .summary_for_anchor::<Point>(&anchor)
7966 .row;
7967
7968 let breakpoint = self
7969 .breakpoint_at_row(row, window, cx)
7970 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7971
7972 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7973 "Edit Log Breakpoint"
7974 } else {
7975 "Set Log Breakpoint"
7976 };
7977
7978 let condition_breakpoint_msg = if breakpoint
7979 .as_ref()
7980 .is_some_and(|bp| bp.1.condition.is_some())
7981 {
7982 "Edit Condition Breakpoint"
7983 } else {
7984 "Set Condition Breakpoint"
7985 };
7986
7987 let hit_condition_breakpoint_msg = if breakpoint
7988 .as_ref()
7989 .is_some_and(|bp| bp.1.hit_condition.is_some())
7990 {
7991 "Edit Hit Condition Breakpoint"
7992 } else {
7993 "Set Hit Condition Breakpoint"
7994 };
7995
7996 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7997 "Unset Breakpoint"
7998 } else {
7999 "Set Breakpoint"
8000 };
8001
8002 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8003
8004 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8005 BreakpointState::Enabled => Some("Disable"),
8006 BreakpointState::Disabled => Some("Enable"),
8007 });
8008
8009 let (anchor, breakpoint) =
8010 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8011
8012 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8013 menu.on_blur_subscription(Subscription::new(|| {}))
8014 .context(focus_handle)
8015 .when(run_to_cursor, |this| {
8016 let weak_editor = weak_editor.clone();
8017 this.entry("Run to cursor", None, move |window, cx| {
8018 weak_editor
8019 .update(cx, |editor, cx| {
8020 editor.change_selections(
8021 SelectionEffects::no_scroll(),
8022 window,
8023 cx,
8024 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8025 );
8026 })
8027 .ok();
8028
8029 window.dispatch_action(Box::new(RunToCursor), cx);
8030 })
8031 .separator()
8032 })
8033 .when_some(toggle_state_msg, |this, msg| {
8034 this.entry(msg, None, {
8035 let weak_editor = weak_editor.clone();
8036 let breakpoint = breakpoint.clone();
8037 move |_window, cx| {
8038 weak_editor
8039 .update(cx, |this, cx| {
8040 this.edit_breakpoint_at_anchor(
8041 anchor,
8042 breakpoint.as_ref().clone(),
8043 BreakpointEditAction::InvertState,
8044 cx,
8045 );
8046 })
8047 .log_err();
8048 }
8049 })
8050 })
8051 .entry(set_breakpoint_msg, None, {
8052 let weak_editor = weak_editor.clone();
8053 let breakpoint = breakpoint.clone();
8054 move |_window, cx| {
8055 weak_editor
8056 .update(cx, |this, cx| {
8057 this.edit_breakpoint_at_anchor(
8058 anchor,
8059 breakpoint.as_ref().clone(),
8060 BreakpointEditAction::Toggle,
8061 cx,
8062 );
8063 })
8064 .log_err();
8065 }
8066 })
8067 .entry(log_breakpoint_msg, None, {
8068 let breakpoint = breakpoint.clone();
8069 let weak_editor = weak_editor.clone();
8070 move |window, cx| {
8071 weak_editor
8072 .update(cx, |this, cx| {
8073 this.add_edit_breakpoint_block(
8074 anchor,
8075 breakpoint.as_ref(),
8076 BreakpointPromptEditAction::Log,
8077 window,
8078 cx,
8079 );
8080 })
8081 .log_err();
8082 }
8083 })
8084 .entry(condition_breakpoint_msg, None, {
8085 let breakpoint = breakpoint.clone();
8086 let weak_editor = weak_editor.clone();
8087 move |window, cx| {
8088 weak_editor
8089 .update(cx, |this, cx| {
8090 this.add_edit_breakpoint_block(
8091 anchor,
8092 breakpoint.as_ref(),
8093 BreakpointPromptEditAction::Condition,
8094 window,
8095 cx,
8096 );
8097 })
8098 .log_err();
8099 }
8100 })
8101 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8102 weak_editor
8103 .update(cx, |this, cx| {
8104 this.add_edit_breakpoint_block(
8105 anchor,
8106 breakpoint.as_ref(),
8107 BreakpointPromptEditAction::HitCondition,
8108 window,
8109 cx,
8110 );
8111 })
8112 .log_err();
8113 })
8114 })
8115 }
8116
8117 fn render_breakpoint(
8118 &self,
8119 position: Anchor,
8120 row: DisplayRow,
8121 breakpoint: &Breakpoint,
8122 state: Option<BreakpointSessionState>,
8123 cx: &mut Context<Self>,
8124 ) -> IconButton {
8125 let is_rejected = state.is_some_and(|s| !s.verified);
8126 // Is it a breakpoint that shows up when hovering over gutter?
8127 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8128 (false, false),
8129 |PhantomBreakpointIndicator {
8130 is_active,
8131 display_row,
8132 collides_with_existing_breakpoint,
8133 }| {
8134 (
8135 is_active && display_row == row,
8136 collides_with_existing_breakpoint,
8137 )
8138 },
8139 );
8140
8141 let (color, icon) = {
8142 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8143 (false, false) => ui::IconName::DebugBreakpoint,
8144 (true, false) => ui::IconName::DebugLogBreakpoint,
8145 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8146 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8147 };
8148
8149 let color = if is_phantom {
8150 Color::Hint
8151 } else if is_rejected {
8152 Color::Disabled
8153 } else {
8154 Color::Debugger
8155 };
8156
8157 (color, icon)
8158 };
8159
8160 let breakpoint = Arc::from(breakpoint.clone());
8161
8162 let alt_as_text = gpui::Keystroke {
8163 modifiers: Modifiers::secondary_key(),
8164 ..Default::default()
8165 };
8166 let primary_action_text = if breakpoint.is_disabled() {
8167 "Enable breakpoint"
8168 } else if is_phantom && !collides_with_existing {
8169 "Set breakpoint"
8170 } else {
8171 "Unset breakpoint"
8172 };
8173 let focus_handle = self.focus_handle.clone();
8174
8175 let meta = if is_rejected {
8176 SharedString::from("No executable code is associated with this line.")
8177 } else if collides_with_existing && !breakpoint.is_disabled() {
8178 SharedString::from(format!(
8179 "{alt_as_text}-click to disable,\nright-click for more options."
8180 ))
8181 } else {
8182 SharedString::from("Right-click for more options.")
8183 };
8184 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8185 .icon_size(IconSize::XSmall)
8186 .size(ui::ButtonSize::None)
8187 .when(is_rejected, |this| {
8188 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8189 })
8190 .icon_color(color)
8191 .style(ButtonStyle::Transparent)
8192 .on_click(cx.listener({
8193 let breakpoint = breakpoint.clone();
8194
8195 move |editor, event: &ClickEvent, window, cx| {
8196 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8197 BreakpointEditAction::InvertState
8198 } else {
8199 BreakpointEditAction::Toggle
8200 };
8201
8202 window.focus(&editor.focus_handle(cx));
8203 editor.edit_breakpoint_at_anchor(
8204 position,
8205 breakpoint.as_ref().clone(),
8206 edit_action,
8207 cx,
8208 );
8209 }
8210 }))
8211 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8212 editor.set_breakpoint_context_menu(
8213 row,
8214 Some(position),
8215 event.position(),
8216 window,
8217 cx,
8218 );
8219 }))
8220 .tooltip(move |window, cx| {
8221 Tooltip::with_meta_in(
8222 primary_action_text,
8223 Some(&ToggleBreakpoint),
8224 meta.clone(),
8225 &focus_handle,
8226 window,
8227 cx,
8228 )
8229 })
8230 }
8231
8232 fn build_tasks_context(
8233 project: &Entity<Project>,
8234 buffer: &Entity<Buffer>,
8235 buffer_row: u32,
8236 tasks: &Arc<RunnableTasks>,
8237 cx: &mut Context<Self>,
8238 ) -> Task<Option<task::TaskContext>> {
8239 let position = Point::new(buffer_row, tasks.column);
8240 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8241 let location = Location {
8242 buffer: buffer.clone(),
8243 range: range_start..range_start,
8244 };
8245 // Fill in the environmental variables from the tree-sitter captures
8246 let mut captured_task_variables = TaskVariables::default();
8247 for (capture_name, value) in tasks.extra_variables.clone() {
8248 captured_task_variables.insert(
8249 task::VariableName::Custom(capture_name.into()),
8250 value.clone(),
8251 );
8252 }
8253 project.update(cx, |project, cx| {
8254 project.task_store().update(cx, |task_store, cx| {
8255 task_store.task_context_for_location(captured_task_variables, location, cx)
8256 })
8257 })
8258 }
8259
8260 pub fn spawn_nearest_task(
8261 &mut self,
8262 action: &SpawnNearestTask,
8263 window: &mut Window,
8264 cx: &mut Context<Self>,
8265 ) {
8266 let Some((workspace, _)) = self.workspace.clone() else {
8267 return;
8268 };
8269 let Some(project) = self.project.clone() else {
8270 return;
8271 };
8272
8273 // Try to find a closest, enclosing node using tree-sitter that has a task
8274 let Some((buffer, buffer_row, tasks)) = self
8275 .find_enclosing_node_task(cx)
8276 // Or find the task that's closest in row-distance.
8277 .or_else(|| self.find_closest_task(cx))
8278 else {
8279 return;
8280 };
8281
8282 let reveal_strategy = action.reveal;
8283 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8284 cx.spawn_in(window, async move |_, cx| {
8285 let context = task_context.await?;
8286 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8287
8288 let resolved = &mut resolved_task.resolved;
8289 resolved.reveal = reveal_strategy;
8290
8291 workspace
8292 .update_in(cx, |workspace, window, cx| {
8293 workspace.schedule_resolved_task(
8294 task_source_kind,
8295 resolved_task,
8296 false,
8297 window,
8298 cx,
8299 );
8300 })
8301 .ok()
8302 })
8303 .detach();
8304 }
8305
8306 fn find_closest_task(
8307 &mut self,
8308 cx: &mut Context<Self>,
8309 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8310 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8311
8312 let ((buffer_id, row), tasks) = self
8313 .tasks
8314 .iter()
8315 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8316
8317 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8318 let tasks = Arc::new(tasks.to_owned());
8319 Some((buffer, *row, tasks))
8320 }
8321
8322 fn find_enclosing_node_task(
8323 &mut self,
8324 cx: &mut Context<Self>,
8325 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8326 let snapshot = self.buffer.read(cx).snapshot(cx);
8327 let offset = self.selections.newest::<usize>(cx).head();
8328 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8329 let buffer_id = excerpt.buffer().remote_id();
8330
8331 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8332 let mut cursor = layer.node().walk();
8333
8334 while cursor.goto_first_child_for_byte(offset).is_some() {
8335 if cursor.node().end_byte() == offset {
8336 cursor.goto_next_sibling();
8337 }
8338 }
8339
8340 // Ascend to the smallest ancestor that contains the range and has a task.
8341 loop {
8342 let node = cursor.node();
8343 let node_range = node.byte_range();
8344 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8345
8346 // Check if this node contains our offset
8347 if node_range.start <= offset && node_range.end >= offset {
8348 // If it contains offset, check for task
8349 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8350 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8351 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8352 }
8353 }
8354
8355 if !cursor.goto_parent() {
8356 break;
8357 }
8358 }
8359 None
8360 }
8361
8362 fn render_run_indicator(
8363 &self,
8364 _style: &EditorStyle,
8365 is_active: bool,
8366 row: DisplayRow,
8367 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8368 cx: &mut Context<Self>,
8369 ) -> IconButton {
8370 let color = Color::Muted;
8371 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8372
8373 IconButton::new(
8374 ("run_indicator", row.0 as usize),
8375 ui::IconName::PlayOutlined,
8376 )
8377 .shape(ui::IconButtonShape::Square)
8378 .icon_size(IconSize::XSmall)
8379 .icon_color(color)
8380 .toggle_state(is_active)
8381 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8382 let quick_launch = match e {
8383 ClickEvent::Keyboard(_) => true,
8384 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8385 };
8386
8387 window.focus(&editor.focus_handle(cx));
8388 editor.toggle_code_actions(
8389 &ToggleCodeActions {
8390 deployed_from: Some(CodeActionSource::RunMenu(row)),
8391 quick_launch,
8392 },
8393 window,
8394 cx,
8395 );
8396 }))
8397 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8398 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8399 }))
8400 }
8401
8402 pub fn context_menu_visible(&self) -> bool {
8403 !self.edit_prediction_preview_is_active()
8404 && self
8405 .context_menu
8406 .borrow()
8407 .as_ref()
8408 .map_or(false, |menu| menu.visible())
8409 }
8410
8411 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8412 self.context_menu
8413 .borrow()
8414 .as_ref()
8415 .map(|menu| menu.origin())
8416 }
8417
8418 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8419 self.context_menu_options = Some(options);
8420 }
8421
8422 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8423 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8424
8425 fn render_edit_prediction_popover(
8426 &mut self,
8427 text_bounds: &Bounds<Pixels>,
8428 content_origin: gpui::Point<Pixels>,
8429 right_margin: Pixels,
8430 editor_snapshot: &EditorSnapshot,
8431 visible_row_range: Range<DisplayRow>,
8432 scroll_top: f32,
8433 scroll_bottom: f32,
8434 line_layouts: &[LineWithInvisibles],
8435 line_height: Pixels,
8436 scroll_pixel_position: gpui::Point<Pixels>,
8437 newest_selection_head: Option<DisplayPoint>,
8438 editor_width: Pixels,
8439 style: &EditorStyle,
8440 window: &mut Window,
8441 cx: &mut App,
8442 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8443 if self.mode().is_minimap() {
8444 return None;
8445 }
8446 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8447
8448 if self.edit_prediction_visible_in_cursor_popover(true) {
8449 return None;
8450 }
8451
8452 match &active_edit_prediction.completion {
8453 EditPrediction::Move { target, .. } => {
8454 let target_display_point = target.to_display_point(editor_snapshot);
8455
8456 if self.edit_prediction_requires_modifier() {
8457 if !self.edit_prediction_preview_is_active() {
8458 return None;
8459 }
8460
8461 self.render_edit_prediction_modifier_jump_popover(
8462 text_bounds,
8463 content_origin,
8464 visible_row_range,
8465 line_layouts,
8466 line_height,
8467 scroll_pixel_position,
8468 newest_selection_head,
8469 target_display_point,
8470 window,
8471 cx,
8472 )
8473 } else {
8474 self.render_edit_prediction_eager_jump_popover(
8475 text_bounds,
8476 content_origin,
8477 editor_snapshot,
8478 visible_row_range,
8479 scroll_top,
8480 scroll_bottom,
8481 line_height,
8482 scroll_pixel_position,
8483 target_display_point,
8484 editor_width,
8485 window,
8486 cx,
8487 )
8488 }
8489 }
8490 EditPrediction::Edit {
8491 display_mode: EditDisplayMode::Inline,
8492 ..
8493 } => None,
8494 EditPrediction::Edit {
8495 display_mode: EditDisplayMode::TabAccept,
8496 edits,
8497 ..
8498 } => {
8499 let range = &edits.first()?.0;
8500 let target_display_point = range.end.to_display_point(editor_snapshot);
8501
8502 self.render_edit_prediction_end_of_line_popover(
8503 "Accept",
8504 editor_snapshot,
8505 visible_row_range,
8506 target_display_point,
8507 line_height,
8508 scroll_pixel_position,
8509 content_origin,
8510 editor_width,
8511 window,
8512 cx,
8513 )
8514 }
8515 EditPrediction::Edit {
8516 edits,
8517 edit_preview,
8518 display_mode: EditDisplayMode::DiffPopover,
8519 snapshot,
8520 } => self.render_edit_prediction_diff_popover(
8521 text_bounds,
8522 content_origin,
8523 right_margin,
8524 editor_snapshot,
8525 visible_row_range,
8526 line_layouts,
8527 line_height,
8528 scroll_pixel_position,
8529 newest_selection_head,
8530 editor_width,
8531 style,
8532 edits,
8533 edit_preview,
8534 snapshot,
8535 window,
8536 cx,
8537 ),
8538 }
8539 }
8540
8541 fn render_edit_prediction_modifier_jump_popover(
8542 &mut self,
8543 text_bounds: &Bounds<Pixels>,
8544 content_origin: gpui::Point<Pixels>,
8545 visible_row_range: Range<DisplayRow>,
8546 line_layouts: &[LineWithInvisibles],
8547 line_height: Pixels,
8548 scroll_pixel_position: gpui::Point<Pixels>,
8549 newest_selection_head: Option<DisplayPoint>,
8550 target_display_point: DisplayPoint,
8551 window: &mut Window,
8552 cx: &mut App,
8553 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8554 let scrolled_content_origin =
8555 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8556
8557 const SCROLL_PADDING_Y: Pixels = px(12.);
8558
8559 if target_display_point.row() < visible_row_range.start {
8560 return self.render_edit_prediction_scroll_popover(
8561 |_| SCROLL_PADDING_Y,
8562 IconName::ArrowUp,
8563 visible_row_range,
8564 line_layouts,
8565 newest_selection_head,
8566 scrolled_content_origin,
8567 window,
8568 cx,
8569 );
8570 } else if target_display_point.row() >= visible_row_range.end {
8571 return self.render_edit_prediction_scroll_popover(
8572 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8573 IconName::ArrowDown,
8574 visible_row_range,
8575 line_layouts,
8576 newest_selection_head,
8577 scrolled_content_origin,
8578 window,
8579 cx,
8580 );
8581 }
8582
8583 const POLE_WIDTH: Pixels = px(2.);
8584
8585 let line_layout =
8586 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8587 let target_column = target_display_point.column() as usize;
8588
8589 let target_x = line_layout.x_for_index(target_column);
8590 let target_y =
8591 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8592
8593 let flag_on_right = target_x < text_bounds.size.width / 2.;
8594
8595 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8596 border_color.l += 0.001;
8597
8598 let mut element = v_flex()
8599 .items_end()
8600 .when(flag_on_right, |el| el.items_start())
8601 .child(if flag_on_right {
8602 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8603 .rounded_bl(px(0.))
8604 .rounded_tl(px(0.))
8605 .border_l_2()
8606 .border_color(border_color)
8607 } else {
8608 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8609 .rounded_br(px(0.))
8610 .rounded_tr(px(0.))
8611 .border_r_2()
8612 .border_color(border_color)
8613 })
8614 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8615 .into_any();
8616
8617 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8618
8619 let mut origin = scrolled_content_origin + point(target_x, target_y)
8620 - point(
8621 if flag_on_right {
8622 POLE_WIDTH
8623 } else {
8624 size.width - POLE_WIDTH
8625 },
8626 size.height - line_height,
8627 );
8628
8629 origin.x = origin.x.max(content_origin.x);
8630
8631 element.prepaint_at(origin, window, cx);
8632
8633 Some((element, origin))
8634 }
8635
8636 fn render_edit_prediction_scroll_popover(
8637 &mut self,
8638 to_y: impl Fn(Size<Pixels>) -> Pixels,
8639 scroll_icon: IconName,
8640 visible_row_range: Range<DisplayRow>,
8641 line_layouts: &[LineWithInvisibles],
8642 newest_selection_head: Option<DisplayPoint>,
8643 scrolled_content_origin: gpui::Point<Pixels>,
8644 window: &mut Window,
8645 cx: &mut App,
8646 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8647 let mut element = self
8648 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8649 .into_any();
8650
8651 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8652
8653 let cursor = newest_selection_head?;
8654 let cursor_row_layout =
8655 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8656 let cursor_column = cursor.column() as usize;
8657
8658 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8659
8660 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8661
8662 element.prepaint_at(origin, window, cx);
8663 Some((element, origin))
8664 }
8665
8666 fn render_edit_prediction_eager_jump_popover(
8667 &mut self,
8668 text_bounds: &Bounds<Pixels>,
8669 content_origin: gpui::Point<Pixels>,
8670 editor_snapshot: &EditorSnapshot,
8671 visible_row_range: Range<DisplayRow>,
8672 scroll_top: f32,
8673 scroll_bottom: f32,
8674 line_height: Pixels,
8675 scroll_pixel_position: gpui::Point<Pixels>,
8676 target_display_point: DisplayPoint,
8677 editor_width: Pixels,
8678 window: &mut Window,
8679 cx: &mut App,
8680 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8681 if target_display_point.row().as_f32() < scroll_top {
8682 let mut element = self
8683 .render_edit_prediction_line_popover(
8684 "Jump to Edit",
8685 Some(IconName::ArrowUp),
8686 window,
8687 cx,
8688 )?
8689 .into_any();
8690
8691 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8692 let offset = point(
8693 (text_bounds.size.width - size.width) / 2.,
8694 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8695 );
8696
8697 let origin = text_bounds.origin + offset;
8698 element.prepaint_at(origin, window, cx);
8699 Some((element, origin))
8700 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8701 let mut element = self
8702 .render_edit_prediction_line_popover(
8703 "Jump to Edit",
8704 Some(IconName::ArrowDown),
8705 window,
8706 cx,
8707 )?
8708 .into_any();
8709
8710 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8711 let offset = point(
8712 (text_bounds.size.width - size.width) / 2.,
8713 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8714 );
8715
8716 let origin = text_bounds.origin + offset;
8717 element.prepaint_at(origin, window, cx);
8718 Some((element, origin))
8719 } else {
8720 self.render_edit_prediction_end_of_line_popover(
8721 "Jump to Edit",
8722 editor_snapshot,
8723 visible_row_range,
8724 target_display_point,
8725 line_height,
8726 scroll_pixel_position,
8727 content_origin,
8728 editor_width,
8729 window,
8730 cx,
8731 )
8732 }
8733 }
8734
8735 fn render_edit_prediction_end_of_line_popover(
8736 self: &mut Editor,
8737 label: &'static str,
8738 editor_snapshot: &EditorSnapshot,
8739 visible_row_range: Range<DisplayRow>,
8740 target_display_point: DisplayPoint,
8741 line_height: Pixels,
8742 scroll_pixel_position: gpui::Point<Pixels>,
8743 content_origin: gpui::Point<Pixels>,
8744 editor_width: Pixels,
8745 window: &mut Window,
8746 cx: &mut App,
8747 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8748 let target_line_end = DisplayPoint::new(
8749 target_display_point.row(),
8750 editor_snapshot.line_len(target_display_point.row()),
8751 );
8752
8753 let mut element = self
8754 .render_edit_prediction_line_popover(label, None, window, cx)?
8755 .into_any();
8756
8757 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8758
8759 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8760
8761 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8762 let mut origin = start_point
8763 + line_origin
8764 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8765 origin.x = origin.x.max(content_origin.x);
8766
8767 let max_x = content_origin.x + editor_width - size.width;
8768
8769 if origin.x > max_x {
8770 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8771
8772 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8773 origin.y += offset;
8774 IconName::ArrowUp
8775 } else {
8776 origin.y -= offset;
8777 IconName::ArrowDown
8778 };
8779
8780 element = self
8781 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8782 .into_any();
8783
8784 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8785
8786 origin.x = content_origin.x + editor_width - size.width - px(2.);
8787 }
8788
8789 element.prepaint_at(origin, window, cx);
8790 Some((element, origin))
8791 }
8792
8793 fn render_edit_prediction_diff_popover(
8794 self: &Editor,
8795 text_bounds: &Bounds<Pixels>,
8796 content_origin: gpui::Point<Pixels>,
8797 right_margin: Pixels,
8798 editor_snapshot: &EditorSnapshot,
8799 visible_row_range: Range<DisplayRow>,
8800 line_layouts: &[LineWithInvisibles],
8801 line_height: Pixels,
8802 scroll_pixel_position: gpui::Point<Pixels>,
8803 newest_selection_head: Option<DisplayPoint>,
8804 editor_width: Pixels,
8805 style: &EditorStyle,
8806 edits: &Vec<(Range<Anchor>, String)>,
8807 edit_preview: &Option<language::EditPreview>,
8808 snapshot: &language::BufferSnapshot,
8809 window: &mut Window,
8810 cx: &mut App,
8811 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8812 let edit_start = edits
8813 .first()
8814 .unwrap()
8815 .0
8816 .start
8817 .to_display_point(editor_snapshot);
8818 let edit_end = edits
8819 .last()
8820 .unwrap()
8821 .0
8822 .end
8823 .to_display_point(editor_snapshot);
8824
8825 let is_visible = visible_row_range.contains(&edit_start.row())
8826 || visible_row_range.contains(&edit_end.row());
8827 if !is_visible {
8828 return None;
8829 }
8830
8831 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8832 crate::edit_prediction_edit_text(&snapshot, edits, edit_preview, false, cx)
8833 } else {
8834 // Fallback for providers without edit_preview
8835 crate::edit_prediction_fallback_text(edits, cx)
8836 };
8837
8838 let styled_text = highlighted_edits.to_styled_text(&style.text);
8839 let line_count = highlighted_edits.text.lines().count();
8840
8841 const BORDER_WIDTH: Pixels = px(1.);
8842
8843 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8844 let has_keybind = keybind.is_some();
8845
8846 let mut element = h_flex()
8847 .items_start()
8848 .child(
8849 h_flex()
8850 .bg(cx.theme().colors().editor_background)
8851 .border(BORDER_WIDTH)
8852 .shadow_xs()
8853 .border_color(cx.theme().colors().border)
8854 .rounded_l_lg()
8855 .when(line_count > 1, |el| el.rounded_br_lg())
8856 .pr_1()
8857 .child(styled_text),
8858 )
8859 .child(
8860 h_flex()
8861 .h(line_height + BORDER_WIDTH * 2.)
8862 .px_1p5()
8863 .gap_1()
8864 // Workaround: For some reason, there's a gap if we don't do this
8865 .ml(-BORDER_WIDTH)
8866 .shadow(vec![gpui::BoxShadow {
8867 color: gpui::black().opacity(0.05),
8868 offset: point(px(1.), px(1.)),
8869 blur_radius: px(2.),
8870 spread_radius: px(0.),
8871 }])
8872 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8873 .border(BORDER_WIDTH)
8874 .border_color(cx.theme().colors().border)
8875 .rounded_r_lg()
8876 .id("edit_prediction_diff_popover_keybind")
8877 .when(!has_keybind, |el| {
8878 let status_colors = cx.theme().status();
8879
8880 el.bg(status_colors.error_background)
8881 .border_color(status_colors.error.opacity(0.6))
8882 .child(Icon::new(IconName::Info).color(Color::Error))
8883 .cursor_default()
8884 .hoverable_tooltip(move |_window, cx| {
8885 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8886 })
8887 })
8888 .children(keybind),
8889 )
8890 .into_any();
8891
8892 let longest_row =
8893 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8894 let longest_line_width = if visible_row_range.contains(&longest_row) {
8895 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8896 } else {
8897 layout_line(
8898 longest_row,
8899 editor_snapshot,
8900 style,
8901 editor_width,
8902 |_| false,
8903 window,
8904 cx,
8905 )
8906 .width
8907 };
8908
8909 let viewport_bounds =
8910 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8911 right: -right_margin,
8912 ..Default::default()
8913 });
8914
8915 let x_after_longest =
8916 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8917 - scroll_pixel_position.x;
8918
8919 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8920
8921 // Fully visible if it can be displayed within the window (allow overlapping other
8922 // panes). However, this is only allowed if the popover starts within text_bounds.
8923 let can_position_to_the_right = x_after_longest < text_bounds.right()
8924 && x_after_longest + element_bounds.width < viewport_bounds.right();
8925
8926 let mut origin = if can_position_to_the_right {
8927 point(
8928 x_after_longest,
8929 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8930 - scroll_pixel_position.y,
8931 )
8932 } else {
8933 let cursor_row = newest_selection_head.map(|head| head.row());
8934 let above_edit = edit_start
8935 .row()
8936 .0
8937 .checked_sub(line_count as u32)
8938 .map(DisplayRow);
8939 let below_edit = Some(edit_end.row() + 1);
8940 let above_cursor =
8941 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8942 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8943
8944 // Place the edit popover adjacent to the edit if there is a location
8945 // available that is onscreen and does not obscure the cursor. Otherwise,
8946 // place it adjacent to the cursor.
8947 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8948 .into_iter()
8949 .flatten()
8950 .find(|&start_row| {
8951 let end_row = start_row + line_count as u32;
8952 visible_row_range.contains(&start_row)
8953 && visible_row_range.contains(&end_row)
8954 && cursor_row.map_or(true, |cursor_row| {
8955 !((start_row..end_row).contains(&cursor_row))
8956 })
8957 })?;
8958
8959 content_origin
8960 + point(
8961 -scroll_pixel_position.x,
8962 row_target.as_f32() * line_height - scroll_pixel_position.y,
8963 )
8964 };
8965
8966 origin.x -= BORDER_WIDTH;
8967
8968 window.defer_draw(element, origin, 1);
8969
8970 // Do not return an element, since it will already be drawn due to defer_draw.
8971 None
8972 }
8973
8974 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8975 px(30.)
8976 }
8977
8978 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8979 if self.read_only(cx) {
8980 cx.theme().players().read_only()
8981 } else {
8982 self.style.as_ref().unwrap().local_player
8983 }
8984 }
8985
8986 fn render_edit_prediction_accept_keybind(
8987 &self,
8988 window: &mut Window,
8989 cx: &App,
8990 ) -> Option<AnyElement> {
8991 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8992 let accept_keystroke = accept_binding.keystroke()?;
8993
8994 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8995
8996 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8997 Color::Accent
8998 } else {
8999 Color::Muted
9000 };
9001
9002 h_flex()
9003 .px_0p5()
9004 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9005 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9006 .text_size(TextSize::XSmall.rems(cx))
9007 .child(h_flex().children(ui::render_modifiers(
9008 &accept_keystroke.modifiers,
9009 PlatformStyle::platform(),
9010 Some(modifiers_color),
9011 Some(IconSize::XSmall.rems().into()),
9012 true,
9013 )))
9014 .when(is_platform_style_mac, |parent| {
9015 parent.child(accept_keystroke.key.clone())
9016 })
9017 .when(!is_platform_style_mac, |parent| {
9018 parent.child(
9019 Key::new(
9020 util::capitalize(&accept_keystroke.key),
9021 Some(Color::Default),
9022 )
9023 .size(Some(IconSize::XSmall.rems().into())),
9024 )
9025 })
9026 .into_any()
9027 .into()
9028 }
9029
9030 fn render_edit_prediction_line_popover(
9031 &self,
9032 label: impl Into<SharedString>,
9033 icon: Option<IconName>,
9034 window: &mut Window,
9035 cx: &App,
9036 ) -> Option<Stateful<Div>> {
9037 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9038
9039 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9040 let has_keybind = keybind.is_some();
9041
9042 let result = h_flex()
9043 .id("ep-line-popover")
9044 .py_0p5()
9045 .pl_1()
9046 .pr(padding_right)
9047 .gap_1()
9048 .rounded_md()
9049 .border_1()
9050 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9051 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9052 .shadow_xs()
9053 .when(!has_keybind, |el| {
9054 let status_colors = cx.theme().status();
9055
9056 el.bg(status_colors.error_background)
9057 .border_color(status_colors.error.opacity(0.6))
9058 .pl_2()
9059 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9060 .cursor_default()
9061 .hoverable_tooltip(move |_window, cx| {
9062 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9063 })
9064 })
9065 .children(keybind)
9066 .child(
9067 Label::new(label)
9068 .size(LabelSize::Small)
9069 .when(!has_keybind, |el| {
9070 el.color(cx.theme().status().error.into()).strikethrough()
9071 }),
9072 )
9073 .when(!has_keybind, |el| {
9074 el.child(
9075 h_flex().ml_1().child(
9076 Icon::new(IconName::Info)
9077 .size(IconSize::Small)
9078 .color(cx.theme().status().error.into()),
9079 ),
9080 )
9081 })
9082 .when_some(icon, |element, icon| {
9083 element.child(
9084 div()
9085 .mt(px(1.5))
9086 .child(Icon::new(icon).size(IconSize::Small)),
9087 )
9088 });
9089
9090 Some(result)
9091 }
9092
9093 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9094 let accent_color = cx.theme().colors().text_accent;
9095 let editor_bg_color = cx.theme().colors().editor_background;
9096 editor_bg_color.blend(accent_color.opacity(0.1))
9097 }
9098
9099 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9100 let accent_color = cx.theme().colors().text_accent;
9101 let editor_bg_color = cx.theme().colors().editor_background;
9102 editor_bg_color.blend(accent_color.opacity(0.6))
9103 }
9104 fn get_prediction_provider_icon_name(
9105 provider: &Option<RegisteredEditPredictionProvider>,
9106 ) -> IconName {
9107 match provider {
9108 Some(provider) => match provider.provider.name() {
9109 "copilot" => IconName::Copilot,
9110 "supermaven" => IconName::Supermaven,
9111 _ => IconName::ZedPredict,
9112 },
9113 None => IconName::ZedPredict,
9114 }
9115 }
9116
9117 fn render_edit_prediction_cursor_popover(
9118 &self,
9119 min_width: Pixels,
9120 max_width: Pixels,
9121 cursor_point: Point,
9122 style: &EditorStyle,
9123 accept_keystroke: Option<&gpui::Keystroke>,
9124 _window: &Window,
9125 cx: &mut Context<Editor>,
9126 ) -> Option<AnyElement> {
9127 let provider = self.edit_prediction_provider.as_ref()?;
9128 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9129
9130 if provider.provider.needs_terms_acceptance(cx) {
9131 return Some(
9132 h_flex()
9133 .min_w(min_width)
9134 .flex_1()
9135 .px_2()
9136 .py_1()
9137 .gap_3()
9138 .elevation_2(cx)
9139 .hover(|style| style.bg(cx.theme().colors().element_hover))
9140 .id("accept-terms")
9141 .cursor_pointer()
9142 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9143 .on_click(cx.listener(|this, _event, window, cx| {
9144 cx.stop_propagation();
9145 this.report_editor_event(ReportEditorEvent::ZetaTosClicked, None, cx);
9146 window.dispatch_action(
9147 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9148 cx,
9149 );
9150 }))
9151 .child(
9152 h_flex()
9153 .flex_1()
9154 .gap_2()
9155 .child(Icon::new(provider_icon))
9156 .child(Label::new("Accept Terms of Service"))
9157 .child(div().w_full())
9158 .child(
9159 Icon::new(IconName::ArrowUpRight)
9160 .color(Color::Muted)
9161 .size(IconSize::Small),
9162 )
9163 .into_any_element(),
9164 )
9165 .into_any(),
9166 );
9167 }
9168
9169 let is_refreshing = provider.provider.is_refreshing(cx);
9170
9171 fn pending_completion_container(icon: IconName) -> Div {
9172 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9173 }
9174
9175 let completion = match &self.active_edit_prediction {
9176 Some(prediction) => {
9177 if !self.has_visible_completions_menu() {
9178 const RADIUS: Pixels = px(6.);
9179 const BORDER_WIDTH: Pixels = px(1.);
9180
9181 return Some(
9182 h_flex()
9183 .elevation_2(cx)
9184 .border(BORDER_WIDTH)
9185 .border_color(cx.theme().colors().border)
9186 .when(accept_keystroke.is_none(), |el| {
9187 el.border_color(cx.theme().status().error)
9188 })
9189 .rounded(RADIUS)
9190 .rounded_tl(px(0.))
9191 .overflow_hidden()
9192 .child(div().px_1p5().child(match &prediction.completion {
9193 EditPrediction::Move { target, snapshot } => {
9194 use text::ToPoint as _;
9195 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9196 {
9197 Icon::new(IconName::ZedPredictDown)
9198 } else {
9199 Icon::new(IconName::ZedPredictUp)
9200 }
9201 }
9202 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9203 }))
9204 .child(
9205 h_flex()
9206 .gap_1()
9207 .py_1()
9208 .px_2()
9209 .rounded_r(RADIUS - BORDER_WIDTH)
9210 .border_l_1()
9211 .border_color(cx.theme().colors().border)
9212 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9213 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9214 el.child(
9215 Label::new("Hold")
9216 .size(LabelSize::Small)
9217 .when(accept_keystroke.is_none(), |el| {
9218 el.strikethrough()
9219 })
9220 .line_height_style(LineHeightStyle::UiLabel),
9221 )
9222 })
9223 .id("edit_prediction_cursor_popover_keybind")
9224 .when(accept_keystroke.is_none(), |el| {
9225 let status_colors = cx.theme().status();
9226
9227 el.bg(status_colors.error_background)
9228 .border_color(status_colors.error.opacity(0.6))
9229 .child(Icon::new(IconName::Info).color(Color::Error))
9230 .cursor_default()
9231 .hoverable_tooltip(move |_window, cx| {
9232 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9233 .into()
9234 })
9235 })
9236 .when_some(
9237 accept_keystroke.as_ref(),
9238 |el, accept_keystroke| {
9239 el.child(h_flex().children(ui::render_modifiers(
9240 &accept_keystroke.modifiers,
9241 PlatformStyle::platform(),
9242 Some(Color::Default),
9243 Some(IconSize::XSmall.rems().into()),
9244 false,
9245 )))
9246 },
9247 ),
9248 )
9249 .into_any(),
9250 );
9251 }
9252
9253 self.render_edit_prediction_cursor_popover_preview(
9254 prediction,
9255 cursor_point,
9256 style,
9257 cx,
9258 )?
9259 }
9260
9261 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9262 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9263 stale_completion,
9264 cursor_point,
9265 style,
9266 cx,
9267 )?,
9268
9269 None => pending_completion_container(provider_icon)
9270 .child(Label::new("...").size(LabelSize::Small)),
9271 },
9272
9273 None => pending_completion_container(provider_icon)
9274 .child(Label::new("...").size(LabelSize::Small)),
9275 };
9276
9277 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9278 completion
9279 .with_animation(
9280 "loading-completion",
9281 Animation::new(Duration::from_secs(2))
9282 .repeat()
9283 .with_easing(pulsating_between(0.4, 0.8)),
9284 |label, delta| label.opacity(delta),
9285 )
9286 .into_any_element()
9287 } else {
9288 completion.into_any_element()
9289 };
9290
9291 let has_completion = self.active_edit_prediction.is_some();
9292
9293 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9294 Some(
9295 h_flex()
9296 .min_w(min_width)
9297 .max_w(max_width)
9298 .flex_1()
9299 .elevation_2(cx)
9300 .border_color(cx.theme().colors().border)
9301 .child(
9302 div()
9303 .flex_1()
9304 .py_1()
9305 .px_2()
9306 .overflow_hidden()
9307 .child(completion),
9308 )
9309 .when_some(accept_keystroke, |el, accept_keystroke| {
9310 if !accept_keystroke.modifiers.modified() {
9311 return el;
9312 }
9313
9314 el.child(
9315 h_flex()
9316 .h_full()
9317 .border_l_1()
9318 .rounded_r_lg()
9319 .border_color(cx.theme().colors().border)
9320 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9321 .gap_1()
9322 .py_1()
9323 .px_2()
9324 .child(
9325 h_flex()
9326 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9327 .when(is_platform_style_mac, |parent| parent.gap_1())
9328 .child(h_flex().children(ui::render_modifiers(
9329 &accept_keystroke.modifiers,
9330 PlatformStyle::platform(),
9331 Some(if !has_completion {
9332 Color::Muted
9333 } else {
9334 Color::Default
9335 }),
9336 None,
9337 false,
9338 ))),
9339 )
9340 .child(Label::new("Preview").into_any_element())
9341 .opacity(if has_completion { 1.0 } else { 0.4 }),
9342 )
9343 })
9344 .into_any(),
9345 )
9346 }
9347
9348 fn render_edit_prediction_cursor_popover_preview(
9349 &self,
9350 completion: &EditPredictionState,
9351 cursor_point: Point,
9352 style: &EditorStyle,
9353 cx: &mut Context<Editor>,
9354 ) -> Option<Div> {
9355 use text::ToPoint as _;
9356
9357 fn render_relative_row_jump(
9358 prefix: impl Into<String>,
9359 current_row: u32,
9360 target_row: u32,
9361 ) -> Div {
9362 let (row_diff, arrow) = if target_row < current_row {
9363 (current_row - target_row, IconName::ArrowUp)
9364 } else {
9365 (target_row - current_row, IconName::ArrowDown)
9366 };
9367
9368 h_flex()
9369 .child(
9370 Label::new(format!("{}{}", prefix.into(), row_diff))
9371 .color(Color::Muted)
9372 .size(LabelSize::Small),
9373 )
9374 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9375 }
9376
9377 let supports_jump = self
9378 .edit_prediction_provider
9379 .as_ref()
9380 .map(|provider| provider.provider.supports_jump_to_edit())
9381 .unwrap_or(true);
9382
9383 match &completion.completion {
9384 EditPrediction::Move {
9385 target, snapshot, ..
9386 } => {
9387 if !supports_jump {
9388 return None;
9389 }
9390
9391 Some(
9392 h_flex()
9393 .px_2()
9394 .gap_2()
9395 .flex_1()
9396 .child(
9397 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9398 Icon::new(IconName::ZedPredictDown)
9399 } else {
9400 Icon::new(IconName::ZedPredictUp)
9401 },
9402 )
9403 .child(Label::new("Jump to Edit")),
9404 )
9405 }
9406
9407 EditPrediction::Edit {
9408 edits,
9409 edit_preview,
9410 snapshot,
9411 display_mode: _,
9412 } => {
9413 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9414
9415 let (highlighted_edits, has_more_lines) =
9416 if let Some(edit_preview) = edit_preview.as_ref() {
9417 crate::edit_prediction_edit_text(&snapshot, &edits, edit_preview, true, cx)
9418 .first_line_preview()
9419 } else {
9420 crate::edit_prediction_fallback_text(&edits, cx).first_line_preview()
9421 };
9422
9423 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9424 .with_default_highlights(&style.text, highlighted_edits.highlights);
9425
9426 let preview = h_flex()
9427 .gap_1()
9428 .min_w_16()
9429 .child(styled_text)
9430 .when(has_more_lines, |parent| parent.child("…"));
9431
9432 let left = if supports_jump && first_edit_row != cursor_point.row {
9433 render_relative_row_jump("", cursor_point.row, first_edit_row)
9434 .into_any_element()
9435 } else {
9436 let icon_name =
9437 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9438 Icon::new(icon_name).into_any_element()
9439 };
9440
9441 Some(
9442 h_flex()
9443 .h_full()
9444 .flex_1()
9445 .gap_2()
9446 .pr_1()
9447 .overflow_x_hidden()
9448 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9449 .child(left)
9450 .child(preview),
9451 )
9452 }
9453 }
9454 }
9455
9456 pub fn render_context_menu(
9457 &self,
9458 style: &EditorStyle,
9459 max_height_in_lines: u32,
9460 window: &mut Window,
9461 cx: &mut Context<Editor>,
9462 ) -> Option<AnyElement> {
9463 let menu = self.context_menu.borrow();
9464 let menu = menu.as_ref()?;
9465 if !menu.visible() {
9466 return None;
9467 };
9468 Some(menu.render(style, max_height_in_lines, window, cx))
9469 }
9470
9471 fn render_context_menu_aside(
9472 &mut self,
9473 max_size: Size<Pixels>,
9474 window: &mut Window,
9475 cx: &mut Context<Editor>,
9476 ) -> Option<AnyElement> {
9477 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9478 if menu.visible() {
9479 menu.render_aside(max_size, window, cx)
9480 } else {
9481 None
9482 }
9483 })
9484 }
9485
9486 fn hide_context_menu(
9487 &mut self,
9488 window: &mut Window,
9489 cx: &mut Context<Self>,
9490 ) -> Option<CodeContextMenu> {
9491 cx.notify();
9492 self.completion_tasks.clear();
9493 let context_menu = self.context_menu.borrow_mut().take();
9494 self.stale_edit_prediction_in_menu.take();
9495 self.update_visible_edit_prediction(window, cx);
9496 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9497 if let Some(completion_provider) = &self.completion_provider {
9498 completion_provider.selection_changed(None, window, cx);
9499 }
9500 }
9501 context_menu
9502 }
9503
9504 fn show_snippet_choices(
9505 &mut self,
9506 choices: &Vec<String>,
9507 selection: Range<Anchor>,
9508 cx: &mut Context<Self>,
9509 ) {
9510 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9511 (Some(a), Some(b)) if a == b => a,
9512 _ => {
9513 log::error!("expected anchor range to have matching buffer IDs");
9514 return;
9515 }
9516 };
9517 let multi_buffer = self.buffer().read(cx);
9518 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9519 return;
9520 };
9521
9522 let id = post_inc(&mut self.next_completion_id);
9523 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9524 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9525 CompletionsMenu::new_snippet_choices(
9526 id,
9527 true,
9528 choices,
9529 selection,
9530 buffer,
9531 snippet_sort_order,
9532 ),
9533 ));
9534 }
9535
9536 pub fn insert_snippet(
9537 &mut self,
9538 insertion_ranges: &[Range<usize>],
9539 snippet: Snippet,
9540 window: &mut Window,
9541 cx: &mut Context<Self>,
9542 ) -> Result<()> {
9543 struct Tabstop<T> {
9544 is_end_tabstop: bool,
9545 ranges: Vec<Range<T>>,
9546 choices: Option<Vec<String>>,
9547 }
9548
9549 let tabstops = self.buffer.update(cx, |buffer, cx| {
9550 let snippet_text: Arc<str> = snippet.text.clone().into();
9551 let edits = insertion_ranges
9552 .iter()
9553 .cloned()
9554 .map(|range| (range, snippet_text.clone()));
9555 let autoindent_mode = AutoindentMode::Block {
9556 original_indent_columns: Vec::new(),
9557 };
9558 buffer.edit(edits, Some(autoindent_mode), cx);
9559
9560 let snapshot = &*buffer.read(cx);
9561 let snippet = &snippet;
9562 snippet
9563 .tabstops
9564 .iter()
9565 .map(|tabstop| {
9566 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9567 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9568 });
9569 let mut tabstop_ranges = tabstop
9570 .ranges
9571 .iter()
9572 .flat_map(|tabstop_range| {
9573 let mut delta = 0_isize;
9574 insertion_ranges.iter().map(move |insertion_range| {
9575 let insertion_start = insertion_range.start as isize + delta;
9576 delta +=
9577 snippet.text.len() as isize - insertion_range.len() as isize;
9578
9579 let start = ((insertion_start + tabstop_range.start) as usize)
9580 .min(snapshot.len());
9581 let end = ((insertion_start + tabstop_range.end) as usize)
9582 .min(snapshot.len());
9583 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9584 })
9585 })
9586 .collect::<Vec<_>>();
9587 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9588
9589 Tabstop {
9590 is_end_tabstop,
9591 ranges: tabstop_ranges,
9592 choices: tabstop.choices.clone(),
9593 }
9594 })
9595 .collect::<Vec<_>>()
9596 });
9597 if let Some(tabstop) = tabstops.first() {
9598 self.change_selections(Default::default(), window, cx, |s| {
9599 // Reverse order so that the first range is the newest created selection.
9600 // Completions will use it and autoscroll will prioritize it.
9601 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9602 });
9603
9604 if let Some(choices) = &tabstop.choices {
9605 if let Some(selection) = tabstop.ranges.first() {
9606 self.show_snippet_choices(choices, selection.clone(), cx)
9607 }
9608 }
9609
9610 // If we're already at the last tabstop and it's at the end of the snippet,
9611 // we're done, we don't need to keep the state around.
9612 if !tabstop.is_end_tabstop {
9613 let choices = tabstops
9614 .iter()
9615 .map(|tabstop| tabstop.choices.clone())
9616 .collect();
9617
9618 let ranges = tabstops
9619 .into_iter()
9620 .map(|tabstop| tabstop.ranges)
9621 .collect::<Vec<_>>();
9622
9623 self.snippet_stack.push(SnippetState {
9624 active_index: 0,
9625 ranges,
9626 choices,
9627 });
9628 }
9629
9630 // Check whether the just-entered snippet ends with an auto-closable bracket.
9631 if self.autoclose_regions.is_empty() {
9632 let snapshot = self.buffer.read(cx).snapshot(cx);
9633 let mut all_selections = self.selections.all::<Point>(cx);
9634 for selection in &mut all_selections {
9635 let selection_head = selection.head();
9636 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9637 continue;
9638 };
9639
9640 let mut bracket_pair = None;
9641 let max_lookup_length = scope
9642 .brackets()
9643 .map(|(pair, _)| {
9644 pair.start
9645 .as_str()
9646 .chars()
9647 .count()
9648 .max(pair.end.as_str().chars().count())
9649 })
9650 .max();
9651 if let Some(max_lookup_length) = max_lookup_length {
9652 let next_text = snapshot
9653 .chars_at(selection_head)
9654 .take(max_lookup_length)
9655 .collect::<String>();
9656 let prev_text = snapshot
9657 .reversed_chars_at(selection_head)
9658 .take(max_lookup_length)
9659 .collect::<String>();
9660
9661 for (pair, enabled) in scope.brackets() {
9662 if enabled
9663 && pair.close
9664 && prev_text.starts_with(pair.start.as_str())
9665 && next_text.starts_with(pair.end.as_str())
9666 {
9667 bracket_pair = Some(pair.clone());
9668 break;
9669 }
9670 }
9671 }
9672
9673 if let Some(pair) = bracket_pair {
9674 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9675 let autoclose_enabled =
9676 self.use_autoclose && snapshot_settings.use_autoclose;
9677 if autoclose_enabled {
9678 let start = snapshot.anchor_after(selection_head);
9679 let end = snapshot.anchor_after(selection_head);
9680 self.autoclose_regions.push(AutocloseRegion {
9681 selection_id: selection.id,
9682 range: start..end,
9683 pair,
9684 });
9685 }
9686 }
9687 }
9688 }
9689 }
9690 Ok(())
9691 }
9692
9693 pub fn move_to_next_snippet_tabstop(
9694 &mut self,
9695 window: &mut Window,
9696 cx: &mut Context<Self>,
9697 ) -> bool {
9698 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9699 }
9700
9701 pub fn move_to_prev_snippet_tabstop(
9702 &mut self,
9703 window: &mut Window,
9704 cx: &mut Context<Self>,
9705 ) -> bool {
9706 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9707 }
9708
9709 pub fn move_to_snippet_tabstop(
9710 &mut self,
9711 bias: Bias,
9712 window: &mut Window,
9713 cx: &mut Context<Self>,
9714 ) -> bool {
9715 if let Some(mut snippet) = self.snippet_stack.pop() {
9716 match bias {
9717 Bias::Left => {
9718 if snippet.active_index > 0 {
9719 snippet.active_index -= 1;
9720 } else {
9721 self.snippet_stack.push(snippet);
9722 return false;
9723 }
9724 }
9725 Bias::Right => {
9726 if snippet.active_index + 1 < snippet.ranges.len() {
9727 snippet.active_index += 1;
9728 } else {
9729 self.snippet_stack.push(snippet);
9730 return false;
9731 }
9732 }
9733 }
9734 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9735 self.change_selections(Default::default(), window, cx, |s| {
9736 // Reverse order so that the first range is the newest created selection.
9737 // Completions will use it and autoscroll will prioritize it.
9738 s.select_ranges(current_ranges.iter().rev().cloned())
9739 });
9740
9741 if let Some(choices) = &snippet.choices[snippet.active_index] {
9742 if let Some(selection) = current_ranges.first() {
9743 self.show_snippet_choices(&choices, selection.clone(), cx);
9744 }
9745 }
9746
9747 // If snippet state is not at the last tabstop, push it back on the stack
9748 if snippet.active_index + 1 < snippet.ranges.len() {
9749 self.snippet_stack.push(snippet);
9750 }
9751 return true;
9752 }
9753 }
9754
9755 false
9756 }
9757
9758 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9759 self.transact(window, cx, |this, window, cx| {
9760 this.select_all(&SelectAll, window, cx);
9761 this.insert("", window, cx);
9762 });
9763 }
9764
9765 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9767 self.transact(window, cx, |this, window, cx| {
9768 this.select_autoclose_pair(window, cx);
9769 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9770 if !this.linked_edit_ranges.is_empty() {
9771 let selections = this.selections.all::<MultiBufferPoint>(cx);
9772 let snapshot = this.buffer.read(cx).snapshot(cx);
9773
9774 for selection in selections.iter() {
9775 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9776 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9777 if selection_start.buffer_id != selection_end.buffer_id {
9778 continue;
9779 }
9780 if let Some(ranges) =
9781 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9782 {
9783 for (buffer, entries) in ranges {
9784 linked_ranges.entry(buffer).or_default().extend(entries);
9785 }
9786 }
9787 }
9788 }
9789
9790 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9791 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9792 for selection in &mut selections {
9793 if selection.is_empty() {
9794 let old_head = selection.head();
9795 let mut new_head =
9796 movement::left(&display_map, old_head.to_display_point(&display_map))
9797 .to_point(&display_map);
9798 if let Some((buffer, line_buffer_range)) = display_map
9799 .buffer_snapshot
9800 .buffer_line_for_row(MultiBufferRow(old_head.row))
9801 {
9802 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9803 let indent_len = match indent_size.kind {
9804 IndentKind::Space => {
9805 buffer.settings_at(line_buffer_range.start, cx).tab_size
9806 }
9807 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9808 };
9809 if old_head.column <= indent_size.len && old_head.column > 0 {
9810 let indent_len = indent_len.get();
9811 new_head = cmp::min(
9812 new_head,
9813 MultiBufferPoint::new(
9814 old_head.row,
9815 ((old_head.column - 1) / indent_len) * indent_len,
9816 ),
9817 );
9818 }
9819 }
9820
9821 selection.set_head(new_head, SelectionGoal::None);
9822 }
9823 }
9824
9825 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9826 this.insert("", window, cx);
9827 let empty_str: Arc<str> = Arc::from("");
9828 for (buffer, edits) in linked_ranges {
9829 let snapshot = buffer.read(cx).snapshot();
9830 use text::ToPoint as TP;
9831
9832 let edits = edits
9833 .into_iter()
9834 .map(|range| {
9835 let end_point = TP::to_point(&range.end, &snapshot);
9836 let mut start_point = TP::to_point(&range.start, &snapshot);
9837
9838 if end_point == start_point {
9839 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9840 .saturating_sub(1);
9841 start_point =
9842 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9843 };
9844
9845 (start_point..end_point, empty_str.clone())
9846 })
9847 .sorted_by_key(|(range, _)| range.start)
9848 .collect::<Vec<_>>();
9849 buffer.update(cx, |this, cx| {
9850 this.edit(edits, None, cx);
9851 })
9852 }
9853 this.refresh_edit_prediction(true, false, window, cx);
9854 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9855 });
9856 }
9857
9858 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9859 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9860 self.transact(window, cx, |this, window, cx| {
9861 this.change_selections(Default::default(), window, cx, |s| {
9862 s.move_with(|map, selection| {
9863 if selection.is_empty() {
9864 let cursor = movement::right(map, selection.head());
9865 selection.end = cursor;
9866 selection.reversed = true;
9867 selection.goal = SelectionGoal::None;
9868 }
9869 })
9870 });
9871 this.insert("", window, cx);
9872 this.refresh_edit_prediction(true, false, window, cx);
9873 });
9874 }
9875
9876 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9877 if self.mode.is_single_line() {
9878 cx.propagate();
9879 return;
9880 }
9881
9882 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9883 if self.move_to_prev_snippet_tabstop(window, cx) {
9884 return;
9885 }
9886 self.outdent(&Outdent, window, cx);
9887 }
9888
9889 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9890 if self.mode.is_single_line() {
9891 cx.propagate();
9892 return;
9893 }
9894
9895 if self.move_to_next_snippet_tabstop(window, cx) {
9896 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9897 return;
9898 }
9899 if self.read_only(cx) {
9900 return;
9901 }
9902 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9903 let mut selections = self.selections.all_adjusted(cx);
9904 let buffer = self.buffer.read(cx);
9905 let snapshot = buffer.snapshot(cx);
9906 let rows_iter = selections.iter().map(|s| s.head().row);
9907 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9908
9909 let has_some_cursor_in_whitespace = selections
9910 .iter()
9911 .filter(|selection| selection.is_empty())
9912 .any(|selection| {
9913 let cursor = selection.head();
9914 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9915 cursor.column < current_indent.len
9916 });
9917
9918 let mut edits = Vec::new();
9919 let mut prev_edited_row = 0;
9920 let mut row_delta = 0;
9921 for selection in &mut selections {
9922 if selection.start.row != prev_edited_row {
9923 row_delta = 0;
9924 }
9925 prev_edited_row = selection.end.row;
9926
9927 // If the selection is non-empty, then increase the indentation of the selected lines.
9928 if !selection.is_empty() {
9929 row_delta =
9930 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9931 continue;
9932 }
9933
9934 let cursor = selection.head();
9935 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9936 if let Some(suggested_indent) =
9937 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9938 {
9939 // Don't do anything if already at suggested indent
9940 // and there is any other cursor which is not
9941 if has_some_cursor_in_whitespace
9942 && cursor.column == current_indent.len
9943 && current_indent.len == suggested_indent.len
9944 {
9945 continue;
9946 }
9947
9948 // Adjust line and move cursor to suggested indent
9949 // if cursor is not at suggested indent
9950 if cursor.column < suggested_indent.len
9951 && cursor.column <= current_indent.len
9952 && current_indent.len <= suggested_indent.len
9953 {
9954 selection.start = Point::new(cursor.row, suggested_indent.len);
9955 selection.end = selection.start;
9956 if row_delta == 0 {
9957 edits.extend(Buffer::edit_for_indent_size_adjustment(
9958 cursor.row,
9959 current_indent,
9960 suggested_indent,
9961 ));
9962 row_delta = suggested_indent.len - current_indent.len;
9963 }
9964 continue;
9965 }
9966
9967 // If current indent is more than suggested indent
9968 // only move cursor to current indent and skip indent
9969 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9970 selection.start = Point::new(cursor.row, current_indent.len);
9971 selection.end = selection.start;
9972 continue;
9973 }
9974 }
9975
9976 // Otherwise, insert a hard or soft tab.
9977 let settings = buffer.language_settings_at(cursor, cx);
9978 let tab_size = if settings.hard_tabs {
9979 IndentSize::tab()
9980 } else {
9981 let tab_size = settings.tab_size.get();
9982 let indent_remainder = snapshot
9983 .text_for_range(Point::new(cursor.row, 0)..cursor)
9984 .flat_map(str::chars)
9985 .fold(row_delta % tab_size, |counter: u32, c| {
9986 if c == '\t' {
9987 0
9988 } else {
9989 (counter + 1) % tab_size
9990 }
9991 });
9992
9993 let chars_to_next_tab_stop = tab_size - indent_remainder;
9994 IndentSize::spaces(chars_to_next_tab_stop)
9995 };
9996 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9997 selection.end = selection.start;
9998 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9999 row_delta += tab_size.len;
10000 }
10001
10002 self.transact(window, cx, |this, window, cx| {
10003 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10004 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10005 this.refresh_edit_prediction(true, false, window, cx);
10006 });
10007 }
10008
10009 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10010 if self.read_only(cx) {
10011 return;
10012 }
10013 if self.mode.is_single_line() {
10014 cx.propagate();
10015 return;
10016 }
10017
10018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10019 let mut selections = self.selections.all::<Point>(cx);
10020 let mut prev_edited_row = 0;
10021 let mut row_delta = 0;
10022 let mut edits = Vec::new();
10023 let buffer = self.buffer.read(cx);
10024 let snapshot = buffer.snapshot(cx);
10025 for selection in &mut selections {
10026 if selection.start.row != prev_edited_row {
10027 row_delta = 0;
10028 }
10029 prev_edited_row = selection.end.row;
10030
10031 row_delta =
10032 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10033 }
10034
10035 self.transact(window, cx, |this, window, cx| {
10036 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10037 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10038 });
10039 }
10040
10041 fn indent_selection(
10042 buffer: &MultiBuffer,
10043 snapshot: &MultiBufferSnapshot,
10044 selection: &mut Selection<Point>,
10045 edits: &mut Vec<(Range<Point>, String)>,
10046 delta_for_start_row: u32,
10047 cx: &App,
10048 ) -> u32 {
10049 let settings = buffer.language_settings_at(selection.start, cx);
10050 let tab_size = settings.tab_size.get();
10051 let indent_kind = if settings.hard_tabs {
10052 IndentKind::Tab
10053 } else {
10054 IndentKind::Space
10055 };
10056 let mut start_row = selection.start.row;
10057 let mut end_row = selection.end.row + 1;
10058
10059 // If a selection ends at the beginning of a line, don't indent
10060 // that last line.
10061 if selection.end.column == 0 && selection.end.row > selection.start.row {
10062 end_row -= 1;
10063 }
10064
10065 // Avoid re-indenting a row that has already been indented by a
10066 // previous selection, but still update this selection's column
10067 // to reflect that indentation.
10068 if delta_for_start_row > 0 {
10069 start_row += 1;
10070 selection.start.column += delta_for_start_row;
10071 if selection.end.row == selection.start.row {
10072 selection.end.column += delta_for_start_row;
10073 }
10074 }
10075
10076 let mut delta_for_end_row = 0;
10077 let has_multiple_rows = start_row + 1 != end_row;
10078 for row in start_row..end_row {
10079 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10080 let indent_delta = match (current_indent.kind, indent_kind) {
10081 (IndentKind::Space, IndentKind::Space) => {
10082 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10083 IndentSize::spaces(columns_to_next_tab_stop)
10084 }
10085 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10086 (_, IndentKind::Tab) => IndentSize::tab(),
10087 };
10088
10089 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10090 0
10091 } else {
10092 selection.start.column
10093 };
10094 let row_start = Point::new(row, start);
10095 edits.push((
10096 row_start..row_start,
10097 indent_delta.chars().collect::<String>(),
10098 ));
10099
10100 // Update this selection's endpoints to reflect the indentation.
10101 if row == selection.start.row {
10102 selection.start.column += indent_delta.len;
10103 }
10104 if row == selection.end.row {
10105 selection.end.column += indent_delta.len;
10106 delta_for_end_row = indent_delta.len;
10107 }
10108 }
10109
10110 if selection.start.row == selection.end.row {
10111 delta_for_start_row + delta_for_end_row
10112 } else {
10113 delta_for_end_row
10114 }
10115 }
10116
10117 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10118 if self.read_only(cx) {
10119 return;
10120 }
10121 if self.mode.is_single_line() {
10122 cx.propagate();
10123 return;
10124 }
10125
10126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10127 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10128 let selections = self.selections.all::<Point>(cx);
10129 let mut deletion_ranges = Vec::new();
10130 let mut last_outdent = None;
10131 {
10132 let buffer = self.buffer.read(cx);
10133 let snapshot = buffer.snapshot(cx);
10134 for selection in &selections {
10135 let settings = buffer.language_settings_at(selection.start, cx);
10136 let tab_size = settings.tab_size.get();
10137 let mut rows = selection.spanned_rows(false, &display_map);
10138
10139 // Avoid re-outdenting a row that has already been outdented by a
10140 // previous selection.
10141 if let Some(last_row) = last_outdent {
10142 if last_row == rows.start {
10143 rows.start = rows.start.next_row();
10144 }
10145 }
10146 let has_multiple_rows = rows.len() > 1;
10147 for row in rows.iter_rows() {
10148 let indent_size = snapshot.indent_size_for_line(row);
10149 if indent_size.len > 0 {
10150 let deletion_len = match indent_size.kind {
10151 IndentKind::Space => {
10152 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10153 if columns_to_prev_tab_stop == 0 {
10154 tab_size
10155 } else {
10156 columns_to_prev_tab_stop
10157 }
10158 }
10159 IndentKind::Tab => 1,
10160 };
10161 let start = if has_multiple_rows
10162 || deletion_len > selection.start.column
10163 || indent_size.len < selection.start.column
10164 {
10165 0
10166 } else {
10167 selection.start.column - deletion_len
10168 };
10169 deletion_ranges.push(
10170 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10171 );
10172 last_outdent = Some(row);
10173 }
10174 }
10175 }
10176 }
10177
10178 self.transact(window, cx, |this, window, cx| {
10179 this.buffer.update(cx, |buffer, cx| {
10180 let empty_str: Arc<str> = Arc::default();
10181 buffer.edit(
10182 deletion_ranges
10183 .into_iter()
10184 .map(|range| (range, empty_str.clone())),
10185 None,
10186 cx,
10187 );
10188 });
10189 let selections = this.selections.all::<usize>(cx);
10190 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10191 });
10192 }
10193
10194 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10195 if self.read_only(cx) {
10196 return;
10197 }
10198 if self.mode.is_single_line() {
10199 cx.propagate();
10200 return;
10201 }
10202
10203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10204 let selections = self
10205 .selections
10206 .all::<usize>(cx)
10207 .into_iter()
10208 .map(|s| s.range());
10209
10210 self.transact(window, cx, |this, window, cx| {
10211 this.buffer.update(cx, |buffer, cx| {
10212 buffer.autoindent_ranges(selections, cx);
10213 });
10214 let selections = this.selections.all::<usize>(cx);
10215 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10216 });
10217 }
10218
10219 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10220 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10221 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10222 let selections = self.selections.all::<Point>(cx);
10223
10224 let mut new_cursors = Vec::new();
10225 let mut edit_ranges = Vec::new();
10226 let mut selections = selections.iter().peekable();
10227 while let Some(selection) = selections.next() {
10228 let mut rows = selection.spanned_rows(false, &display_map);
10229 let goal_display_column = selection.head().to_display_point(&display_map).column();
10230
10231 // Accumulate contiguous regions of rows that we want to delete.
10232 while let Some(next_selection) = selections.peek() {
10233 let next_rows = next_selection.spanned_rows(false, &display_map);
10234 if next_rows.start <= rows.end {
10235 rows.end = next_rows.end;
10236 selections.next().unwrap();
10237 } else {
10238 break;
10239 }
10240 }
10241
10242 let buffer = &display_map.buffer_snapshot;
10243 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10244 let edit_end;
10245 let cursor_buffer_row;
10246 if buffer.max_point().row >= rows.end.0 {
10247 // If there's a line after the range, delete the \n from the end of the row range
10248 // and position the cursor on the next line.
10249 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10250 cursor_buffer_row = rows.end;
10251 } else {
10252 // If there isn't a line after the range, delete the \n from the line before the
10253 // start of the row range and position the cursor there.
10254 edit_start = edit_start.saturating_sub(1);
10255 edit_end = buffer.len();
10256 cursor_buffer_row = rows.start.previous_row();
10257 }
10258
10259 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10260 *cursor.column_mut() =
10261 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10262
10263 new_cursors.push((
10264 selection.id,
10265 buffer.anchor_after(cursor.to_point(&display_map)),
10266 ));
10267 edit_ranges.push(edit_start..edit_end);
10268 }
10269
10270 self.transact(window, cx, |this, window, cx| {
10271 let buffer = this.buffer.update(cx, |buffer, cx| {
10272 let empty_str: Arc<str> = Arc::default();
10273 buffer.edit(
10274 edit_ranges
10275 .into_iter()
10276 .map(|range| (range, empty_str.clone())),
10277 None,
10278 cx,
10279 );
10280 buffer.snapshot(cx)
10281 });
10282 let new_selections = new_cursors
10283 .into_iter()
10284 .map(|(id, cursor)| {
10285 let cursor = cursor.to_point(&buffer);
10286 Selection {
10287 id,
10288 start: cursor,
10289 end: cursor,
10290 reversed: false,
10291 goal: SelectionGoal::None,
10292 }
10293 })
10294 .collect();
10295
10296 this.change_selections(Default::default(), window, cx, |s| {
10297 s.select(new_selections);
10298 });
10299 });
10300 }
10301
10302 pub fn join_lines_impl(
10303 &mut self,
10304 insert_whitespace: bool,
10305 window: &mut Window,
10306 cx: &mut Context<Self>,
10307 ) {
10308 if self.read_only(cx) {
10309 return;
10310 }
10311 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10312 for selection in self.selections.all::<Point>(cx) {
10313 let start = MultiBufferRow(selection.start.row);
10314 // Treat single line selections as if they include the next line. Otherwise this action
10315 // would do nothing for single line selections individual cursors.
10316 let end = if selection.start.row == selection.end.row {
10317 MultiBufferRow(selection.start.row + 1)
10318 } else {
10319 MultiBufferRow(selection.end.row)
10320 };
10321
10322 if let Some(last_row_range) = row_ranges.last_mut() {
10323 if start <= last_row_range.end {
10324 last_row_range.end = end;
10325 continue;
10326 }
10327 }
10328 row_ranges.push(start..end);
10329 }
10330
10331 let snapshot = self.buffer.read(cx).snapshot(cx);
10332 let mut cursor_positions = Vec::new();
10333 for row_range in &row_ranges {
10334 let anchor = snapshot.anchor_before(Point::new(
10335 row_range.end.previous_row().0,
10336 snapshot.line_len(row_range.end.previous_row()),
10337 ));
10338 cursor_positions.push(anchor..anchor);
10339 }
10340
10341 self.transact(window, cx, |this, window, cx| {
10342 for row_range in row_ranges.into_iter().rev() {
10343 for row in row_range.iter_rows().rev() {
10344 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10345 let next_line_row = row.next_row();
10346 let indent = snapshot.indent_size_for_line(next_line_row);
10347 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10348
10349 let replace =
10350 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10351 " "
10352 } else {
10353 ""
10354 };
10355
10356 this.buffer.update(cx, |buffer, cx| {
10357 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10358 });
10359 }
10360 }
10361
10362 this.change_selections(Default::default(), window, cx, |s| {
10363 s.select_anchor_ranges(cursor_positions)
10364 });
10365 });
10366 }
10367
10368 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10369 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10370 self.join_lines_impl(true, window, cx);
10371 }
10372
10373 pub fn sort_lines_case_sensitive(
10374 &mut self,
10375 _: &SortLinesCaseSensitive,
10376 window: &mut Window,
10377 cx: &mut Context<Self>,
10378 ) {
10379 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10380 }
10381
10382 pub fn sort_lines_by_length(
10383 &mut self,
10384 _: &SortLinesByLength,
10385 window: &mut Window,
10386 cx: &mut Context<Self>,
10387 ) {
10388 self.manipulate_immutable_lines(window, cx, |lines| {
10389 lines.sort_by_key(|&line| line.chars().count())
10390 })
10391 }
10392
10393 pub fn sort_lines_case_insensitive(
10394 &mut self,
10395 _: &SortLinesCaseInsensitive,
10396 window: &mut Window,
10397 cx: &mut Context<Self>,
10398 ) {
10399 self.manipulate_immutable_lines(window, cx, |lines| {
10400 lines.sort_by_key(|line| line.to_lowercase())
10401 })
10402 }
10403
10404 pub fn unique_lines_case_insensitive(
10405 &mut self,
10406 _: &UniqueLinesCaseInsensitive,
10407 window: &mut Window,
10408 cx: &mut Context<Self>,
10409 ) {
10410 self.manipulate_immutable_lines(window, cx, |lines| {
10411 let mut seen = HashSet::default();
10412 lines.retain(|line| seen.insert(line.to_lowercase()));
10413 })
10414 }
10415
10416 pub fn unique_lines_case_sensitive(
10417 &mut self,
10418 _: &UniqueLinesCaseSensitive,
10419 window: &mut Window,
10420 cx: &mut Context<Self>,
10421 ) {
10422 self.manipulate_immutable_lines(window, cx, |lines| {
10423 let mut seen = HashSet::default();
10424 lines.retain(|line| seen.insert(*line));
10425 })
10426 }
10427
10428 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10429 let Some(project) = self.project.clone() else {
10430 return;
10431 };
10432 self.reload(project, window, cx)
10433 .detach_and_notify_err(window, cx);
10434 }
10435
10436 pub fn restore_file(
10437 &mut self,
10438 _: &::git::RestoreFile,
10439 window: &mut Window,
10440 cx: &mut Context<Self>,
10441 ) {
10442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10443 let mut buffer_ids = HashSet::default();
10444 let snapshot = self.buffer().read(cx).snapshot(cx);
10445 for selection in self.selections.all::<usize>(cx) {
10446 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10447 }
10448
10449 let buffer = self.buffer().read(cx);
10450 let ranges = buffer_ids
10451 .into_iter()
10452 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10453 .collect::<Vec<_>>();
10454
10455 self.restore_hunks_in_ranges(ranges, window, cx);
10456 }
10457
10458 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10459 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10460 let selections = self
10461 .selections
10462 .all(cx)
10463 .into_iter()
10464 .map(|s| s.range())
10465 .collect();
10466 self.restore_hunks_in_ranges(selections, window, cx);
10467 }
10468
10469 pub fn restore_hunks_in_ranges(
10470 &mut self,
10471 ranges: Vec<Range<Point>>,
10472 window: &mut Window,
10473 cx: &mut Context<Editor>,
10474 ) {
10475 let mut revert_changes = HashMap::default();
10476 let chunk_by = self
10477 .snapshot(window, cx)
10478 .hunks_for_ranges(ranges)
10479 .into_iter()
10480 .chunk_by(|hunk| hunk.buffer_id);
10481 for (buffer_id, hunks) in &chunk_by {
10482 let hunks = hunks.collect::<Vec<_>>();
10483 for hunk in &hunks {
10484 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10485 }
10486 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10487 }
10488 drop(chunk_by);
10489 if !revert_changes.is_empty() {
10490 self.transact(window, cx, |editor, window, cx| {
10491 editor.restore(revert_changes, window, cx);
10492 });
10493 }
10494 }
10495
10496 pub fn open_active_item_in_terminal(
10497 &mut self,
10498 _: &OpenInTerminal,
10499 window: &mut Window,
10500 cx: &mut Context<Self>,
10501 ) {
10502 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10503 let project_path = buffer.read(cx).project_path(cx)?;
10504 let project = self.project.as_ref()?.read(cx);
10505 let entry = project.entry_for_path(&project_path, cx)?;
10506 let parent = match &entry.canonical_path {
10507 Some(canonical_path) => canonical_path.to_path_buf(),
10508 None => project.absolute_path(&project_path, cx)?,
10509 }
10510 .parent()?
10511 .to_path_buf();
10512 Some(parent)
10513 }) {
10514 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10515 }
10516 }
10517
10518 fn set_breakpoint_context_menu(
10519 &mut self,
10520 display_row: DisplayRow,
10521 position: Option<Anchor>,
10522 clicked_point: gpui::Point<Pixels>,
10523 window: &mut Window,
10524 cx: &mut Context<Self>,
10525 ) {
10526 let source = self
10527 .buffer
10528 .read(cx)
10529 .snapshot(cx)
10530 .anchor_before(Point::new(display_row.0, 0u32));
10531
10532 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10533
10534 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10535 self,
10536 source,
10537 clicked_point,
10538 context_menu,
10539 window,
10540 cx,
10541 );
10542 }
10543
10544 fn add_edit_breakpoint_block(
10545 &mut self,
10546 anchor: Anchor,
10547 breakpoint: &Breakpoint,
10548 edit_action: BreakpointPromptEditAction,
10549 window: &mut Window,
10550 cx: &mut Context<Self>,
10551 ) {
10552 let weak_editor = cx.weak_entity();
10553 let bp_prompt = cx.new(|cx| {
10554 BreakpointPromptEditor::new(
10555 weak_editor,
10556 anchor,
10557 breakpoint.clone(),
10558 edit_action,
10559 window,
10560 cx,
10561 )
10562 });
10563
10564 let height = bp_prompt.update(cx, |this, cx| {
10565 this.prompt
10566 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10567 });
10568 let cloned_prompt = bp_prompt.clone();
10569 let blocks = vec![BlockProperties {
10570 style: BlockStyle::Sticky,
10571 placement: BlockPlacement::Above(anchor),
10572 height: Some(height),
10573 render: Arc::new(move |cx| {
10574 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10575 cloned_prompt.clone().into_any_element()
10576 }),
10577 priority: 0,
10578 }];
10579
10580 let focus_handle = bp_prompt.focus_handle(cx);
10581 window.focus(&focus_handle);
10582
10583 let block_ids = self.insert_blocks(blocks, None, cx);
10584 bp_prompt.update(cx, |prompt, _| {
10585 prompt.add_block_ids(block_ids);
10586 });
10587 }
10588
10589 pub(crate) fn breakpoint_at_row(
10590 &self,
10591 row: u32,
10592 window: &mut Window,
10593 cx: &mut Context<Self>,
10594 ) -> Option<(Anchor, Breakpoint)> {
10595 let snapshot = self.snapshot(window, cx);
10596 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10597
10598 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10599 }
10600
10601 pub(crate) fn breakpoint_at_anchor(
10602 &self,
10603 breakpoint_position: Anchor,
10604 snapshot: &EditorSnapshot,
10605 cx: &mut Context<Self>,
10606 ) -> Option<(Anchor, Breakpoint)> {
10607 let project = self.project.clone()?;
10608
10609 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10610 snapshot
10611 .buffer_snapshot
10612 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10613 })?;
10614
10615 let enclosing_excerpt = breakpoint_position.excerpt_id;
10616 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10617 let buffer_snapshot = buffer.read(cx).snapshot();
10618
10619 let row = buffer_snapshot
10620 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10621 .row;
10622
10623 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10624 let anchor_end = snapshot
10625 .buffer_snapshot
10626 .anchor_after(Point::new(row, line_len));
10627
10628 let bp = self
10629 .breakpoint_store
10630 .as_ref()?
10631 .read_with(cx, |breakpoint_store, cx| {
10632 breakpoint_store
10633 .breakpoints(
10634 &buffer,
10635 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10636 &buffer_snapshot,
10637 cx,
10638 )
10639 .next()
10640 .and_then(|(bp, _)| {
10641 let breakpoint_row = buffer_snapshot
10642 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10643 .row;
10644
10645 if breakpoint_row == row {
10646 snapshot
10647 .buffer_snapshot
10648 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10649 .map(|position| (position, bp.bp.clone()))
10650 } else {
10651 None
10652 }
10653 })
10654 });
10655 bp
10656 }
10657
10658 pub fn edit_log_breakpoint(
10659 &mut self,
10660 _: &EditLogBreakpoint,
10661 window: &mut Window,
10662 cx: &mut Context<Self>,
10663 ) {
10664 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10665 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10666 message: None,
10667 state: BreakpointState::Enabled,
10668 condition: None,
10669 hit_condition: None,
10670 });
10671
10672 self.add_edit_breakpoint_block(
10673 anchor,
10674 &breakpoint,
10675 BreakpointPromptEditAction::Log,
10676 window,
10677 cx,
10678 );
10679 }
10680 }
10681
10682 fn breakpoints_at_cursors(
10683 &self,
10684 window: &mut Window,
10685 cx: &mut Context<Self>,
10686 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10687 let snapshot = self.snapshot(window, cx);
10688 let cursors = self
10689 .selections
10690 .disjoint_anchors()
10691 .into_iter()
10692 .map(|selection| {
10693 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10694
10695 let breakpoint_position = self
10696 .breakpoint_at_row(cursor_position.row, window, cx)
10697 .map(|bp| bp.0)
10698 .unwrap_or_else(|| {
10699 snapshot
10700 .display_snapshot
10701 .buffer_snapshot
10702 .anchor_after(Point::new(cursor_position.row, 0))
10703 });
10704
10705 let breakpoint = self
10706 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10707 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10708
10709 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10710 })
10711 // 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.
10712 .collect::<HashMap<Anchor, _>>();
10713
10714 cursors.into_iter().collect()
10715 }
10716
10717 pub fn enable_breakpoint(
10718 &mut self,
10719 _: &crate::actions::EnableBreakpoint,
10720 window: &mut Window,
10721 cx: &mut Context<Self>,
10722 ) {
10723 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10724 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10725 continue;
10726 };
10727 self.edit_breakpoint_at_anchor(
10728 anchor,
10729 breakpoint,
10730 BreakpointEditAction::InvertState,
10731 cx,
10732 );
10733 }
10734 }
10735
10736 pub fn disable_breakpoint(
10737 &mut self,
10738 _: &crate::actions::DisableBreakpoint,
10739 window: &mut Window,
10740 cx: &mut Context<Self>,
10741 ) {
10742 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10743 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10744 continue;
10745 };
10746 self.edit_breakpoint_at_anchor(
10747 anchor,
10748 breakpoint,
10749 BreakpointEditAction::InvertState,
10750 cx,
10751 );
10752 }
10753 }
10754
10755 pub fn toggle_breakpoint(
10756 &mut self,
10757 _: &crate::actions::ToggleBreakpoint,
10758 window: &mut Window,
10759 cx: &mut Context<Self>,
10760 ) {
10761 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10762 if let Some(breakpoint) = breakpoint {
10763 self.edit_breakpoint_at_anchor(
10764 anchor,
10765 breakpoint,
10766 BreakpointEditAction::Toggle,
10767 cx,
10768 );
10769 } else {
10770 self.edit_breakpoint_at_anchor(
10771 anchor,
10772 Breakpoint::new_standard(),
10773 BreakpointEditAction::Toggle,
10774 cx,
10775 );
10776 }
10777 }
10778 }
10779
10780 pub fn edit_breakpoint_at_anchor(
10781 &mut self,
10782 breakpoint_position: Anchor,
10783 breakpoint: Breakpoint,
10784 edit_action: BreakpointEditAction,
10785 cx: &mut Context<Self>,
10786 ) {
10787 let Some(breakpoint_store) = &self.breakpoint_store else {
10788 return;
10789 };
10790
10791 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10792 if breakpoint_position == Anchor::min() {
10793 self.buffer()
10794 .read(cx)
10795 .excerpt_buffer_ids()
10796 .into_iter()
10797 .next()
10798 } else {
10799 None
10800 }
10801 }) else {
10802 return;
10803 };
10804
10805 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10806 return;
10807 };
10808
10809 breakpoint_store.update(cx, |breakpoint_store, cx| {
10810 breakpoint_store.toggle_breakpoint(
10811 buffer,
10812 BreakpointWithPosition {
10813 position: breakpoint_position.text_anchor,
10814 bp: breakpoint,
10815 },
10816 edit_action,
10817 cx,
10818 );
10819 });
10820
10821 cx.notify();
10822 }
10823
10824 #[cfg(any(test, feature = "test-support"))]
10825 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10826 self.breakpoint_store.clone()
10827 }
10828
10829 pub fn prepare_restore_change(
10830 &self,
10831 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10832 hunk: &MultiBufferDiffHunk,
10833 cx: &mut App,
10834 ) -> Option<()> {
10835 if hunk.is_created_file() {
10836 return None;
10837 }
10838 let buffer = self.buffer.read(cx);
10839 let diff = buffer.diff_for(hunk.buffer_id)?;
10840 let buffer = buffer.buffer(hunk.buffer_id)?;
10841 let buffer = buffer.read(cx);
10842 let original_text = diff
10843 .read(cx)
10844 .base_text()
10845 .as_rope()
10846 .slice(hunk.diff_base_byte_range.clone());
10847 let buffer_snapshot = buffer.snapshot();
10848 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10849 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10850 probe
10851 .0
10852 .start
10853 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10854 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10855 }) {
10856 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10857 Some(())
10858 } else {
10859 None
10860 }
10861 }
10862
10863 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10864 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10865 }
10866
10867 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10868 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10869 }
10870
10871 fn manipulate_lines<M>(
10872 &mut self,
10873 window: &mut Window,
10874 cx: &mut Context<Self>,
10875 mut manipulate: M,
10876 ) where
10877 M: FnMut(&str) -> LineManipulationResult,
10878 {
10879 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10880
10881 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10882 let buffer = self.buffer.read(cx).snapshot(cx);
10883
10884 let mut edits = Vec::new();
10885
10886 let selections = self.selections.all::<Point>(cx);
10887 let mut selections = selections.iter().peekable();
10888 let mut contiguous_row_selections = Vec::new();
10889 let mut new_selections = Vec::new();
10890 let mut added_lines = 0;
10891 let mut removed_lines = 0;
10892
10893 while let Some(selection) = selections.next() {
10894 let (start_row, end_row) = consume_contiguous_rows(
10895 &mut contiguous_row_selections,
10896 selection,
10897 &display_map,
10898 &mut selections,
10899 );
10900
10901 let start_point = Point::new(start_row.0, 0);
10902 let end_point = Point::new(
10903 end_row.previous_row().0,
10904 buffer.line_len(end_row.previous_row()),
10905 );
10906 let text = buffer
10907 .text_for_range(start_point..end_point)
10908 .collect::<String>();
10909
10910 let LineManipulationResult {
10911 new_text,
10912 line_count_before,
10913 line_count_after,
10914 } = manipulate(&text);
10915
10916 edits.push((start_point..end_point, new_text));
10917
10918 // Selections must change based on added and removed line count
10919 let start_row =
10920 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10921 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10922 new_selections.push(Selection {
10923 id: selection.id,
10924 start: start_row,
10925 end: end_row,
10926 goal: SelectionGoal::None,
10927 reversed: selection.reversed,
10928 });
10929
10930 if line_count_after > line_count_before {
10931 added_lines += line_count_after - line_count_before;
10932 } else if line_count_before > line_count_after {
10933 removed_lines += line_count_before - line_count_after;
10934 }
10935 }
10936
10937 self.transact(window, cx, |this, window, cx| {
10938 let buffer = this.buffer.update(cx, |buffer, cx| {
10939 buffer.edit(edits, None, cx);
10940 buffer.snapshot(cx)
10941 });
10942
10943 // Recalculate offsets on newly edited buffer
10944 let new_selections = new_selections
10945 .iter()
10946 .map(|s| {
10947 let start_point = Point::new(s.start.0, 0);
10948 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10949 Selection {
10950 id: s.id,
10951 start: buffer.point_to_offset(start_point),
10952 end: buffer.point_to_offset(end_point),
10953 goal: s.goal,
10954 reversed: s.reversed,
10955 }
10956 })
10957 .collect();
10958
10959 this.change_selections(Default::default(), window, cx, |s| {
10960 s.select(new_selections);
10961 });
10962
10963 this.request_autoscroll(Autoscroll::fit(), cx);
10964 });
10965 }
10966
10967 fn manipulate_immutable_lines<Fn>(
10968 &mut self,
10969 window: &mut Window,
10970 cx: &mut Context<Self>,
10971 mut callback: Fn,
10972 ) where
10973 Fn: FnMut(&mut Vec<&str>),
10974 {
10975 self.manipulate_lines(window, cx, |text| {
10976 let mut lines: Vec<&str> = text.split('\n').collect();
10977 let line_count_before = lines.len();
10978
10979 callback(&mut lines);
10980
10981 LineManipulationResult {
10982 new_text: lines.join("\n"),
10983 line_count_before,
10984 line_count_after: lines.len(),
10985 }
10986 });
10987 }
10988
10989 fn manipulate_mutable_lines<Fn>(
10990 &mut self,
10991 window: &mut Window,
10992 cx: &mut Context<Self>,
10993 mut callback: Fn,
10994 ) where
10995 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10996 {
10997 self.manipulate_lines(window, cx, |text| {
10998 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10999 let line_count_before = lines.len();
11000
11001 callback(&mut lines);
11002
11003 LineManipulationResult {
11004 new_text: lines.join("\n"),
11005 line_count_before,
11006 line_count_after: lines.len(),
11007 }
11008 });
11009 }
11010
11011 pub fn convert_indentation_to_spaces(
11012 &mut self,
11013 _: &ConvertIndentationToSpaces,
11014 window: &mut Window,
11015 cx: &mut Context<Self>,
11016 ) {
11017 let settings = self.buffer.read(cx).language_settings(cx);
11018 let tab_size = settings.tab_size.get() as usize;
11019
11020 self.manipulate_mutable_lines(window, cx, |lines| {
11021 // Allocates a reasonably sized scratch buffer once for the whole loop
11022 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11023 // Avoids recomputing spaces that could be inserted many times
11024 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11025 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11026 .collect();
11027
11028 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11029 let mut chars = line.as_ref().chars();
11030 let mut col = 0;
11031 let mut changed = false;
11032
11033 while let Some(ch) = chars.next() {
11034 match ch {
11035 ' ' => {
11036 reindented_line.push(' ');
11037 col += 1;
11038 }
11039 '\t' => {
11040 // \t are converted to spaces depending on the current column
11041 let spaces_len = tab_size - (col % tab_size);
11042 reindented_line.extend(&space_cache[spaces_len - 1]);
11043 col += spaces_len;
11044 changed = true;
11045 }
11046 _ => {
11047 // If we dont append before break, the character is consumed
11048 reindented_line.push(ch);
11049 break;
11050 }
11051 }
11052 }
11053
11054 if !changed {
11055 reindented_line.clear();
11056 continue;
11057 }
11058 // Append the rest of the line and replace old reference with new one
11059 reindented_line.extend(chars);
11060 *line = Cow::Owned(reindented_line.clone());
11061 reindented_line.clear();
11062 }
11063 });
11064 }
11065
11066 pub fn convert_indentation_to_tabs(
11067 &mut self,
11068 _: &ConvertIndentationToTabs,
11069 window: &mut Window,
11070 cx: &mut Context<Self>,
11071 ) {
11072 let settings = self.buffer.read(cx).language_settings(cx);
11073 let tab_size = settings.tab_size.get() as usize;
11074
11075 self.manipulate_mutable_lines(window, cx, |lines| {
11076 // Allocates a reasonably sized buffer once for the whole loop
11077 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11078 // Avoids recomputing spaces that could be inserted many times
11079 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11080 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11081 .collect();
11082
11083 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11084 let mut chars = line.chars();
11085 let mut spaces_count = 0;
11086 let mut first_non_indent_char = None;
11087 let mut changed = false;
11088
11089 while let Some(ch) = chars.next() {
11090 match ch {
11091 ' ' => {
11092 // Keep track of spaces. Append \t when we reach tab_size
11093 spaces_count += 1;
11094 changed = true;
11095 if spaces_count == tab_size {
11096 reindented_line.push('\t');
11097 spaces_count = 0;
11098 }
11099 }
11100 '\t' => {
11101 reindented_line.push('\t');
11102 spaces_count = 0;
11103 }
11104 _ => {
11105 // Dont append it yet, we might have remaining spaces
11106 first_non_indent_char = Some(ch);
11107 break;
11108 }
11109 }
11110 }
11111
11112 if !changed {
11113 reindented_line.clear();
11114 continue;
11115 }
11116 // Remaining spaces that didn't make a full tab stop
11117 if spaces_count > 0 {
11118 reindented_line.extend(&space_cache[spaces_count - 1]);
11119 }
11120 // If we consume an extra character that was not indentation, add it back
11121 if let Some(extra_char) = first_non_indent_char {
11122 reindented_line.push(extra_char);
11123 }
11124 // Append the rest of the line and replace old reference with new one
11125 reindented_line.extend(chars);
11126 *line = Cow::Owned(reindented_line.clone());
11127 reindented_line.clear();
11128 }
11129 });
11130 }
11131
11132 pub fn convert_to_upper_case(
11133 &mut self,
11134 _: &ConvertToUpperCase,
11135 window: &mut Window,
11136 cx: &mut Context<Self>,
11137 ) {
11138 self.manipulate_text(window, cx, |text| text.to_uppercase())
11139 }
11140
11141 pub fn convert_to_lower_case(
11142 &mut self,
11143 _: &ConvertToLowerCase,
11144 window: &mut Window,
11145 cx: &mut Context<Self>,
11146 ) {
11147 self.manipulate_text(window, cx, |text| text.to_lowercase())
11148 }
11149
11150 pub fn convert_to_title_case(
11151 &mut self,
11152 _: &ConvertToTitleCase,
11153 window: &mut Window,
11154 cx: &mut Context<Self>,
11155 ) {
11156 self.manipulate_text(window, cx, |text| {
11157 text.split('\n')
11158 .map(|line| line.to_case(Case::Title))
11159 .join("\n")
11160 })
11161 }
11162
11163 pub fn convert_to_snake_case(
11164 &mut self,
11165 _: &ConvertToSnakeCase,
11166 window: &mut Window,
11167 cx: &mut Context<Self>,
11168 ) {
11169 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11170 }
11171
11172 pub fn convert_to_kebab_case(
11173 &mut self,
11174 _: &ConvertToKebabCase,
11175 window: &mut Window,
11176 cx: &mut Context<Self>,
11177 ) {
11178 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11179 }
11180
11181 pub fn convert_to_upper_camel_case(
11182 &mut self,
11183 _: &ConvertToUpperCamelCase,
11184 window: &mut Window,
11185 cx: &mut Context<Self>,
11186 ) {
11187 self.manipulate_text(window, cx, |text| {
11188 text.split('\n')
11189 .map(|line| line.to_case(Case::UpperCamel))
11190 .join("\n")
11191 })
11192 }
11193
11194 pub fn convert_to_lower_camel_case(
11195 &mut self,
11196 _: &ConvertToLowerCamelCase,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11201 }
11202
11203 pub fn convert_to_opposite_case(
11204 &mut self,
11205 _: &ConvertToOppositeCase,
11206 window: &mut Window,
11207 cx: &mut Context<Self>,
11208 ) {
11209 self.manipulate_text(window, cx, |text| {
11210 text.chars()
11211 .fold(String::with_capacity(text.len()), |mut t, c| {
11212 if c.is_uppercase() {
11213 t.extend(c.to_lowercase());
11214 } else {
11215 t.extend(c.to_uppercase());
11216 }
11217 t
11218 })
11219 })
11220 }
11221
11222 pub fn convert_to_sentence_case(
11223 &mut self,
11224 _: &ConvertToSentenceCase,
11225 window: &mut Window,
11226 cx: &mut Context<Self>,
11227 ) {
11228 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11229 }
11230
11231 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11232 self.manipulate_text(window, cx, |text| {
11233 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11234 if has_upper_case_characters {
11235 text.to_lowercase()
11236 } else {
11237 text.to_uppercase()
11238 }
11239 })
11240 }
11241
11242 pub fn convert_to_rot13(
11243 &mut self,
11244 _: &ConvertToRot13,
11245 window: &mut Window,
11246 cx: &mut Context<Self>,
11247 ) {
11248 self.manipulate_text(window, cx, |text| {
11249 text.chars()
11250 .map(|c| match c {
11251 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11252 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11253 _ => c,
11254 })
11255 .collect()
11256 })
11257 }
11258
11259 pub fn convert_to_rot47(
11260 &mut self,
11261 _: &ConvertToRot47,
11262 window: &mut Window,
11263 cx: &mut Context<Self>,
11264 ) {
11265 self.manipulate_text(window, cx, |text| {
11266 text.chars()
11267 .map(|c| {
11268 let code_point = c as u32;
11269 if code_point >= 33 && code_point <= 126 {
11270 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11271 }
11272 c
11273 })
11274 .collect()
11275 })
11276 }
11277
11278 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11279 where
11280 Fn: FnMut(&str) -> String,
11281 {
11282 let buffer = self.buffer.read(cx).snapshot(cx);
11283
11284 let mut new_selections = Vec::new();
11285 let mut edits = Vec::new();
11286 let mut selection_adjustment = 0i32;
11287
11288 for selection in self.selections.all::<usize>(cx) {
11289 let selection_is_empty = selection.is_empty();
11290
11291 let (start, end) = if selection_is_empty {
11292 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11293 (word_range.start, word_range.end)
11294 } else {
11295 (selection.start, selection.end)
11296 };
11297
11298 let text = buffer.text_for_range(start..end).collect::<String>();
11299 let old_length = text.len() as i32;
11300 let text = callback(&text);
11301
11302 new_selections.push(Selection {
11303 start: (start as i32 - selection_adjustment) as usize,
11304 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11305 goal: SelectionGoal::None,
11306 ..selection
11307 });
11308
11309 selection_adjustment += old_length - text.len() as i32;
11310
11311 edits.push((start..end, text));
11312 }
11313
11314 self.transact(window, cx, |this, window, cx| {
11315 this.buffer.update(cx, |buffer, cx| {
11316 buffer.edit(edits, None, cx);
11317 });
11318
11319 this.change_selections(Default::default(), window, cx, |s| {
11320 s.select(new_selections);
11321 });
11322
11323 this.request_autoscroll(Autoscroll::fit(), cx);
11324 });
11325 }
11326
11327 pub fn move_selection_on_drop(
11328 &mut self,
11329 selection: &Selection<Anchor>,
11330 target: DisplayPoint,
11331 is_cut: bool,
11332 window: &mut Window,
11333 cx: &mut Context<Self>,
11334 ) {
11335 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11336 let buffer = &display_map.buffer_snapshot;
11337 let mut edits = Vec::new();
11338 let insert_point = display_map
11339 .clip_point(target, Bias::Left)
11340 .to_point(&display_map);
11341 let text = buffer
11342 .text_for_range(selection.start..selection.end)
11343 .collect::<String>();
11344 if is_cut {
11345 edits.push(((selection.start..selection.end), String::new()));
11346 }
11347 let insert_anchor = buffer.anchor_before(insert_point);
11348 edits.push(((insert_anchor..insert_anchor), text));
11349 let last_edit_start = insert_anchor.bias_left(buffer);
11350 let last_edit_end = insert_anchor.bias_right(buffer);
11351 self.transact(window, cx, |this, window, cx| {
11352 this.buffer.update(cx, |buffer, cx| {
11353 buffer.edit(edits, None, cx);
11354 });
11355 this.change_selections(Default::default(), window, cx, |s| {
11356 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11357 });
11358 });
11359 }
11360
11361 pub fn clear_selection_drag_state(&mut self) {
11362 self.selection_drag_state = SelectionDragState::None;
11363 }
11364
11365 pub fn duplicate(
11366 &mut self,
11367 upwards: bool,
11368 whole_lines: bool,
11369 window: &mut Window,
11370 cx: &mut Context<Self>,
11371 ) {
11372 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11373
11374 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11375 let buffer = &display_map.buffer_snapshot;
11376 let selections = self.selections.all::<Point>(cx);
11377
11378 let mut edits = Vec::new();
11379 let mut selections_iter = selections.iter().peekable();
11380 while let Some(selection) = selections_iter.next() {
11381 let mut rows = selection.spanned_rows(false, &display_map);
11382 // duplicate line-wise
11383 if whole_lines || selection.start == selection.end {
11384 // Avoid duplicating the same lines twice.
11385 while let Some(next_selection) = selections_iter.peek() {
11386 let next_rows = next_selection.spanned_rows(false, &display_map);
11387 if next_rows.start < rows.end {
11388 rows.end = next_rows.end;
11389 selections_iter.next().unwrap();
11390 } else {
11391 break;
11392 }
11393 }
11394
11395 // Copy the text from the selected row region and splice it either at the start
11396 // or end of the region.
11397 let start = Point::new(rows.start.0, 0);
11398 let end = Point::new(
11399 rows.end.previous_row().0,
11400 buffer.line_len(rows.end.previous_row()),
11401 );
11402 let text = buffer
11403 .text_for_range(start..end)
11404 .chain(Some("\n"))
11405 .collect::<String>();
11406 let insert_location = if upwards {
11407 Point::new(rows.end.0, 0)
11408 } else {
11409 start
11410 };
11411 edits.push((insert_location..insert_location, text));
11412 } else {
11413 // duplicate character-wise
11414 let start = selection.start;
11415 let end = selection.end;
11416 let text = buffer.text_for_range(start..end).collect::<String>();
11417 edits.push((selection.end..selection.end, text));
11418 }
11419 }
11420
11421 self.transact(window, cx, |this, _, cx| {
11422 this.buffer.update(cx, |buffer, cx| {
11423 buffer.edit(edits, None, cx);
11424 });
11425
11426 this.request_autoscroll(Autoscroll::fit(), cx);
11427 });
11428 }
11429
11430 pub fn duplicate_line_up(
11431 &mut self,
11432 _: &DuplicateLineUp,
11433 window: &mut Window,
11434 cx: &mut Context<Self>,
11435 ) {
11436 self.duplicate(true, true, window, cx);
11437 }
11438
11439 pub fn duplicate_line_down(
11440 &mut self,
11441 _: &DuplicateLineDown,
11442 window: &mut Window,
11443 cx: &mut Context<Self>,
11444 ) {
11445 self.duplicate(false, true, window, cx);
11446 }
11447
11448 pub fn duplicate_selection(
11449 &mut self,
11450 _: &DuplicateSelection,
11451 window: &mut Window,
11452 cx: &mut Context<Self>,
11453 ) {
11454 self.duplicate(false, false, window, cx);
11455 }
11456
11457 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11459 if self.mode.is_single_line() {
11460 cx.propagate();
11461 return;
11462 }
11463
11464 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11465 let buffer = self.buffer.read(cx).snapshot(cx);
11466
11467 let mut edits = Vec::new();
11468 let mut unfold_ranges = Vec::new();
11469 let mut refold_creases = Vec::new();
11470
11471 let selections = self.selections.all::<Point>(cx);
11472 let mut selections = selections.iter().peekable();
11473 let mut contiguous_row_selections = Vec::new();
11474 let mut new_selections = Vec::new();
11475
11476 while let Some(selection) = selections.next() {
11477 // Find all the selections that span a contiguous row range
11478 let (start_row, end_row) = consume_contiguous_rows(
11479 &mut contiguous_row_selections,
11480 selection,
11481 &display_map,
11482 &mut selections,
11483 );
11484
11485 // Move the text spanned by the row range to be before the line preceding the row range
11486 if start_row.0 > 0 {
11487 let range_to_move = Point::new(
11488 start_row.previous_row().0,
11489 buffer.line_len(start_row.previous_row()),
11490 )
11491 ..Point::new(
11492 end_row.previous_row().0,
11493 buffer.line_len(end_row.previous_row()),
11494 );
11495 let insertion_point = display_map
11496 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11497 .0;
11498
11499 // Don't move lines across excerpts
11500 if buffer
11501 .excerpt_containing(insertion_point..range_to_move.end)
11502 .is_some()
11503 {
11504 let text = buffer
11505 .text_for_range(range_to_move.clone())
11506 .flat_map(|s| s.chars())
11507 .skip(1)
11508 .chain(['\n'])
11509 .collect::<String>();
11510
11511 edits.push((
11512 buffer.anchor_after(range_to_move.start)
11513 ..buffer.anchor_before(range_to_move.end),
11514 String::new(),
11515 ));
11516 let insertion_anchor = buffer.anchor_after(insertion_point);
11517 edits.push((insertion_anchor..insertion_anchor, text));
11518
11519 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11520
11521 // Move selections up
11522 new_selections.extend(contiguous_row_selections.drain(..).map(
11523 |mut selection| {
11524 selection.start.row -= row_delta;
11525 selection.end.row -= row_delta;
11526 selection
11527 },
11528 ));
11529
11530 // Move folds up
11531 unfold_ranges.push(range_to_move.clone());
11532 for fold in display_map.folds_in_range(
11533 buffer.anchor_before(range_to_move.start)
11534 ..buffer.anchor_after(range_to_move.end),
11535 ) {
11536 let mut start = fold.range.start.to_point(&buffer);
11537 let mut end = fold.range.end.to_point(&buffer);
11538 start.row -= row_delta;
11539 end.row -= row_delta;
11540 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11541 }
11542 }
11543 }
11544
11545 // If we didn't move line(s), preserve the existing selections
11546 new_selections.append(&mut contiguous_row_selections);
11547 }
11548
11549 self.transact(window, cx, |this, window, cx| {
11550 this.unfold_ranges(&unfold_ranges, true, true, cx);
11551 this.buffer.update(cx, |buffer, cx| {
11552 for (range, text) in edits {
11553 buffer.edit([(range, text)], None, cx);
11554 }
11555 });
11556 this.fold_creases(refold_creases, true, window, cx);
11557 this.change_selections(Default::default(), window, cx, |s| {
11558 s.select(new_selections);
11559 })
11560 });
11561 }
11562
11563 pub fn move_line_down(
11564 &mut self,
11565 _: &MoveLineDown,
11566 window: &mut Window,
11567 cx: &mut Context<Self>,
11568 ) {
11569 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11570 if self.mode.is_single_line() {
11571 cx.propagate();
11572 return;
11573 }
11574
11575 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11576 let buffer = self.buffer.read(cx).snapshot(cx);
11577
11578 let mut edits = Vec::new();
11579 let mut unfold_ranges = Vec::new();
11580 let mut refold_creases = Vec::new();
11581
11582 let selections = self.selections.all::<Point>(cx);
11583 let mut selections = selections.iter().peekable();
11584 let mut contiguous_row_selections = Vec::new();
11585 let mut new_selections = Vec::new();
11586
11587 while let Some(selection) = selections.next() {
11588 // Find all the selections that span a contiguous row range
11589 let (start_row, end_row) = consume_contiguous_rows(
11590 &mut contiguous_row_selections,
11591 selection,
11592 &display_map,
11593 &mut selections,
11594 );
11595
11596 // Move the text spanned by the row range to be after the last line of the row range
11597 if end_row.0 <= buffer.max_point().row {
11598 let range_to_move =
11599 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11600 let insertion_point = display_map
11601 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11602 .0;
11603
11604 // Don't move lines across excerpt boundaries
11605 if buffer
11606 .excerpt_containing(range_to_move.start..insertion_point)
11607 .is_some()
11608 {
11609 let mut text = String::from("\n");
11610 text.extend(buffer.text_for_range(range_to_move.clone()));
11611 text.pop(); // Drop trailing newline
11612 edits.push((
11613 buffer.anchor_after(range_to_move.start)
11614 ..buffer.anchor_before(range_to_move.end),
11615 String::new(),
11616 ));
11617 let insertion_anchor = buffer.anchor_after(insertion_point);
11618 edits.push((insertion_anchor..insertion_anchor, text));
11619
11620 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11621
11622 // Move selections down
11623 new_selections.extend(contiguous_row_selections.drain(..).map(
11624 |mut selection| {
11625 selection.start.row += row_delta;
11626 selection.end.row += row_delta;
11627 selection
11628 },
11629 ));
11630
11631 // Move folds down
11632 unfold_ranges.push(range_to_move.clone());
11633 for fold in display_map.folds_in_range(
11634 buffer.anchor_before(range_to_move.start)
11635 ..buffer.anchor_after(range_to_move.end),
11636 ) {
11637 let mut start = fold.range.start.to_point(&buffer);
11638 let mut end = fold.range.end.to_point(&buffer);
11639 start.row += row_delta;
11640 end.row += row_delta;
11641 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11642 }
11643 }
11644 }
11645
11646 // If we didn't move line(s), preserve the existing selections
11647 new_selections.append(&mut contiguous_row_selections);
11648 }
11649
11650 self.transact(window, cx, |this, window, cx| {
11651 this.unfold_ranges(&unfold_ranges, true, true, cx);
11652 this.buffer.update(cx, |buffer, cx| {
11653 for (range, text) in edits {
11654 buffer.edit([(range, text)], None, cx);
11655 }
11656 });
11657 this.fold_creases(refold_creases, true, window, cx);
11658 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11659 });
11660 }
11661
11662 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11663 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11664 let text_layout_details = &self.text_layout_details(window);
11665 self.transact(window, cx, |this, window, cx| {
11666 let edits = this.change_selections(Default::default(), window, cx, |s| {
11667 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11668 s.move_with(|display_map, selection| {
11669 if !selection.is_empty() {
11670 return;
11671 }
11672
11673 let mut head = selection.head();
11674 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11675 if head.column() == display_map.line_len(head.row()) {
11676 transpose_offset = display_map
11677 .buffer_snapshot
11678 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11679 }
11680
11681 if transpose_offset == 0 {
11682 return;
11683 }
11684
11685 *head.column_mut() += 1;
11686 head = display_map.clip_point(head, Bias::Right);
11687 let goal = SelectionGoal::HorizontalPosition(
11688 display_map
11689 .x_for_display_point(head, text_layout_details)
11690 .into(),
11691 );
11692 selection.collapse_to(head, goal);
11693
11694 let transpose_start = display_map
11695 .buffer_snapshot
11696 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11697 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11698 let transpose_end = display_map
11699 .buffer_snapshot
11700 .clip_offset(transpose_offset + 1, Bias::Right);
11701 if let Some(ch) =
11702 display_map.buffer_snapshot.chars_at(transpose_start).next()
11703 {
11704 edits.push((transpose_start..transpose_offset, String::new()));
11705 edits.push((transpose_end..transpose_end, ch.to_string()));
11706 }
11707 }
11708 });
11709 edits
11710 });
11711 this.buffer
11712 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11713 let selections = this.selections.all::<usize>(cx);
11714 this.change_selections(Default::default(), window, cx, |s| {
11715 s.select(selections);
11716 });
11717 });
11718 }
11719
11720 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11721 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11722 if self.mode.is_single_line() {
11723 cx.propagate();
11724 return;
11725 }
11726
11727 self.rewrap_impl(RewrapOptions::default(), cx)
11728 }
11729
11730 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11731 let buffer = self.buffer.read(cx).snapshot(cx);
11732 let selections = self.selections.all::<Point>(cx);
11733
11734 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11735 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11736 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11737 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11738 .peekable();
11739
11740 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11741 row
11742 } else {
11743 return Vec::new();
11744 };
11745
11746 let language_settings = buffer.language_settings_at(selection.head(), cx);
11747 let language_scope = buffer.language_scope_at(selection.head());
11748
11749 let indent_and_prefix_for_row =
11750 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11751 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11752 let (comment_prefix, rewrap_prefix) =
11753 if let Some(language_scope) = &language_scope {
11754 let indent_end = Point::new(row, indent.len);
11755 let comment_prefix = language_scope
11756 .line_comment_prefixes()
11757 .iter()
11758 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11759 .map(|prefix| prefix.to_string());
11760 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11761 let line_text_after_indent = buffer
11762 .text_for_range(indent_end..line_end)
11763 .collect::<String>();
11764 let rewrap_prefix = language_scope
11765 .rewrap_prefixes()
11766 .iter()
11767 .find_map(|prefix_regex| {
11768 prefix_regex.find(&line_text_after_indent).map(|mat| {
11769 if mat.start() == 0 {
11770 Some(mat.as_str().to_string())
11771 } else {
11772 None
11773 }
11774 })
11775 })
11776 .flatten();
11777 (comment_prefix, rewrap_prefix)
11778 } else {
11779 (None, None)
11780 };
11781 (indent, comment_prefix, rewrap_prefix)
11782 };
11783
11784 let mut ranges = Vec::new();
11785 let from_empty_selection = selection.is_empty();
11786
11787 let mut current_range_start = first_row;
11788 let mut prev_row = first_row;
11789 let (
11790 mut current_range_indent,
11791 mut current_range_comment_prefix,
11792 mut current_range_rewrap_prefix,
11793 ) = indent_and_prefix_for_row(first_row);
11794
11795 for row in non_blank_rows_iter.skip(1) {
11796 let has_paragraph_break = row > prev_row + 1;
11797
11798 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11799 indent_and_prefix_for_row(row);
11800
11801 let has_indent_change = row_indent != current_range_indent;
11802 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11803
11804 let has_boundary_change = has_comment_change
11805 || row_rewrap_prefix.is_some()
11806 || (has_indent_change && current_range_comment_prefix.is_some());
11807
11808 if has_paragraph_break || has_boundary_change {
11809 ranges.push((
11810 language_settings.clone(),
11811 Point::new(current_range_start, 0)
11812 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11813 current_range_indent,
11814 current_range_comment_prefix.clone(),
11815 current_range_rewrap_prefix.clone(),
11816 from_empty_selection,
11817 ));
11818 current_range_start = row;
11819 current_range_indent = row_indent;
11820 current_range_comment_prefix = row_comment_prefix;
11821 current_range_rewrap_prefix = row_rewrap_prefix;
11822 }
11823 prev_row = row;
11824 }
11825
11826 ranges.push((
11827 language_settings.clone(),
11828 Point::new(current_range_start, 0)
11829 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11830 current_range_indent,
11831 current_range_comment_prefix,
11832 current_range_rewrap_prefix,
11833 from_empty_selection,
11834 ));
11835
11836 ranges
11837 });
11838
11839 let mut edits = Vec::new();
11840 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11841
11842 for (
11843 language_settings,
11844 wrap_range,
11845 indent_size,
11846 comment_prefix,
11847 rewrap_prefix,
11848 from_empty_selection,
11849 ) in wrap_ranges
11850 {
11851 let mut start_row = wrap_range.start.row;
11852 let mut end_row = wrap_range.end.row;
11853
11854 // Skip selections that overlap with a range that has already been rewrapped.
11855 let selection_range = start_row..end_row;
11856 if rewrapped_row_ranges
11857 .iter()
11858 .any(|range| range.overlaps(&selection_range))
11859 {
11860 continue;
11861 }
11862
11863 let tab_size = language_settings.tab_size;
11864
11865 let indent_prefix = indent_size.chars().collect::<String>();
11866 let mut line_prefix = indent_prefix.clone();
11867 let mut inside_comment = false;
11868 if let Some(prefix) = &comment_prefix {
11869 line_prefix.push_str(prefix);
11870 inside_comment = true;
11871 }
11872 if let Some(prefix) = &rewrap_prefix {
11873 line_prefix.push_str(prefix);
11874 }
11875
11876 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11877 RewrapBehavior::InComments => inside_comment,
11878 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11879 RewrapBehavior::Anywhere => true,
11880 };
11881
11882 let should_rewrap = options.override_language_settings
11883 || allow_rewrap_based_on_language
11884 || self.hard_wrap.is_some();
11885 if !should_rewrap {
11886 continue;
11887 }
11888
11889 if from_empty_selection {
11890 'expand_upwards: while start_row > 0 {
11891 let prev_row = start_row - 1;
11892 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11893 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11894 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11895 {
11896 start_row = prev_row;
11897 } else {
11898 break 'expand_upwards;
11899 }
11900 }
11901
11902 'expand_downwards: while end_row < buffer.max_point().row {
11903 let next_row = end_row + 1;
11904 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11905 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11906 && !buffer.is_line_blank(MultiBufferRow(next_row))
11907 {
11908 end_row = next_row;
11909 } else {
11910 break 'expand_downwards;
11911 }
11912 }
11913 }
11914
11915 let start = Point::new(start_row, 0);
11916 let start_offset = start.to_offset(&buffer);
11917 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11918 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11919 let Some(lines_without_prefixes) = selection_text
11920 .lines()
11921 .enumerate()
11922 .map(|(ix, line)| {
11923 let line_trimmed = line.trim_start();
11924 if rewrap_prefix.is_some() && ix > 0 {
11925 Ok(line_trimmed)
11926 } else {
11927 line_trimmed
11928 .strip_prefix(&line_prefix.trim_start())
11929 .with_context(|| {
11930 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11931 })
11932 }
11933 })
11934 .collect::<Result<Vec<_>, _>>()
11935 .log_err()
11936 else {
11937 continue;
11938 };
11939
11940 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11941 buffer
11942 .language_settings_at(Point::new(start_row, 0), cx)
11943 .preferred_line_length as usize
11944 });
11945
11946 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11947 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11948 } else {
11949 line_prefix.clone()
11950 };
11951
11952 let wrapped_text = wrap_with_prefix(
11953 line_prefix,
11954 subsequent_lines_prefix,
11955 lines_without_prefixes.join("\n"),
11956 wrap_column,
11957 tab_size,
11958 options.preserve_existing_whitespace,
11959 );
11960
11961 // TODO: should always use char-based diff while still supporting cursor behavior that
11962 // matches vim.
11963 let mut diff_options = DiffOptions::default();
11964 if options.override_language_settings {
11965 diff_options.max_word_diff_len = 0;
11966 diff_options.max_word_diff_line_count = 0;
11967 } else {
11968 diff_options.max_word_diff_len = usize::MAX;
11969 diff_options.max_word_diff_line_count = usize::MAX;
11970 }
11971
11972 for (old_range, new_text) in
11973 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11974 {
11975 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11976 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11977 edits.push((edit_start..edit_end, new_text));
11978 }
11979
11980 rewrapped_row_ranges.push(start_row..=end_row);
11981 }
11982
11983 self.buffer
11984 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11985 }
11986
11987 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11988 let mut text = String::new();
11989 let buffer = self.buffer.read(cx).snapshot(cx);
11990 let mut selections = self.selections.all::<Point>(cx);
11991 let mut clipboard_selections = Vec::with_capacity(selections.len());
11992 {
11993 let max_point = buffer.max_point();
11994 let mut is_first = true;
11995 for selection in &mut selections {
11996 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11997 if is_entire_line {
11998 selection.start = Point::new(selection.start.row, 0);
11999 if !selection.is_empty() && selection.end.column == 0 {
12000 selection.end = cmp::min(max_point, selection.end);
12001 } else {
12002 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12003 }
12004 selection.goal = SelectionGoal::None;
12005 }
12006 if is_first {
12007 is_first = false;
12008 } else {
12009 text += "\n";
12010 }
12011 let mut len = 0;
12012 for chunk in buffer.text_for_range(selection.start..selection.end) {
12013 text.push_str(chunk);
12014 len += chunk.len();
12015 }
12016 clipboard_selections.push(ClipboardSelection {
12017 len,
12018 is_entire_line,
12019 first_line_indent: buffer
12020 .indent_size_for_line(MultiBufferRow(selection.start.row))
12021 .len,
12022 });
12023 }
12024 }
12025
12026 self.transact(window, cx, |this, window, cx| {
12027 this.change_selections(Default::default(), window, cx, |s| {
12028 s.select(selections);
12029 });
12030 this.insert("", window, cx);
12031 });
12032 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12033 }
12034
12035 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12036 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12037 let item = self.cut_common(window, cx);
12038 cx.write_to_clipboard(item);
12039 }
12040
12041 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12043 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12044 s.move_with(|snapshot, sel| {
12045 if sel.is_empty() {
12046 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12047 }
12048 });
12049 });
12050 let item = self.cut_common(window, cx);
12051 cx.set_global(KillRing(item))
12052 }
12053
12054 pub fn kill_ring_yank(
12055 &mut self,
12056 _: &KillRingYank,
12057 window: &mut Window,
12058 cx: &mut Context<Self>,
12059 ) {
12060 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12061 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12062 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12063 (kill_ring.text().to_string(), kill_ring.metadata_json())
12064 } else {
12065 return;
12066 }
12067 } else {
12068 return;
12069 };
12070 self.do_paste(&text, metadata, false, window, cx);
12071 }
12072
12073 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12074 self.do_copy(true, cx);
12075 }
12076
12077 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12078 self.do_copy(false, cx);
12079 }
12080
12081 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12082 let selections = self.selections.all::<Point>(cx);
12083 let buffer = self.buffer.read(cx).read(cx);
12084 let mut text = String::new();
12085
12086 let mut clipboard_selections = Vec::with_capacity(selections.len());
12087 {
12088 let max_point = buffer.max_point();
12089 let mut is_first = true;
12090 for selection in &selections {
12091 let mut start = selection.start;
12092 let mut end = selection.end;
12093 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12094 if is_entire_line {
12095 start = Point::new(start.row, 0);
12096 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12097 }
12098
12099 let mut trimmed_selections = Vec::new();
12100 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12101 let row = MultiBufferRow(start.row);
12102 let first_indent = buffer.indent_size_for_line(row);
12103 if first_indent.len == 0 || start.column > first_indent.len {
12104 trimmed_selections.push(start..end);
12105 } else {
12106 trimmed_selections.push(
12107 Point::new(row.0, first_indent.len)
12108 ..Point::new(row.0, buffer.line_len(row)),
12109 );
12110 for row in start.row + 1..=end.row {
12111 let mut line_len = buffer.line_len(MultiBufferRow(row));
12112 if row == end.row {
12113 line_len = end.column;
12114 }
12115 if line_len == 0 {
12116 trimmed_selections
12117 .push(Point::new(row, 0)..Point::new(row, line_len));
12118 continue;
12119 }
12120 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12121 if row_indent_size.len >= first_indent.len {
12122 trimmed_selections.push(
12123 Point::new(row, first_indent.len)..Point::new(row, line_len),
12124 );
12125 } else {
12126 trimmed_selections.clear();
12127 trimmed_selections.push(start..end);
12128 break;
12129 }
12130 }
12131 }
12132 } else {
12133 trimmed_selections.push(start..end);
12134 }
12135
12136 for trimmed_range in trimmed_selections {
12137 if is_first {
12138 is_first = false;
12139 } else {
12140 text += "\n";
12141 }
12142 let mut len = 0;
12143 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12144 text.push_str(chunk);
12145 len += chunk.len();
12146 }
12147 clipboard_selections.push(ClipboardSelection {
12148 len,
12149 is_entire_line,
12150 first_line_indent: buffer
12151 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12152 .len,
12153 });
12154 }
12155 }
12156 }
12157
12158 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12159 text,
12160 clipboard_selections,
12161 ));
12162 }
12163
12164 pub fn do_paste(
12165 &mut self,
12166 text: &String,
12167 clipboard_selections: Option<Vec<ClipboardSelection>>,
12168 handle_entire_lines: bool,
12169 window: &mut Window,
12170 cx: &mut Context<Self>,
12171 ) {
12172 if self.read_only(cx) {
12173 return;
12174 }
12175
12176 let clipboard_text = Cow::Borrowed(text);
12177
12178 self.transact(window, cx, |this, window, cx| {
12179 let had_active_edit_prediction = this.has_active_edit_prediction();
12180
12181 if let Some(mut clipboard_selections) = clipboard_selections {
12182 let old_selections = this.selections.all::<usize>(cx);
12183 let all_selections_were_entire_line =
12184 clipboard_selections.iter().all(|s| s.is_entire_line);
12185 let first_selection_indent_column =
12186 clipboard_selections.first().map(|s| s.first_line_indent);
12187 if clipboard_selections.len() != old_selections.len() {
12188 clipboard_selections.drain(..);
12189 }
12190 let cursor_offset = this.selections.last::<usize>(cx).head();
12191 let mut auto_indent_on_paste = true;
12192
12193 this.buffer.update(cx, |buffer, cx| {
12194 let snapshot = buffer.read(cx);
12195 auto_indent_on_paste = snapshot
12196 .language_settings_at(cursor_offset, cx)
12197 .auto_indent_on_paste;
12198
12199 let mut start_offset = 0;
12200 let mut edits = Vec::new();
12201 let mut original_indent_columns = Vec::new();
12202 for (ix, selection) in old_selections.iter().enumerate() {
12203 let to_insert;
12204 let entire_line;
12205 let original_indent_column;
12206 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12207 let end_offset = start_offset + clipboard_selection.len;
12208 to_insert = &clipboard_text[start_offset..end_offset];
12209 entire_line = clipboard_selection.is_entire_line;
12210 start_offset = end_offset + 1;
12211 original_indent_column = Some(clipboard_selection.first_line_indent);
12212 } else {
12213 to_insert = clipboard_text.as_str();
12214 entire_line = all_selections_were_entire_line;
12215 original_indent_column = first_selection_indent_column
12216 }
12217
12218 // If the corresponding selection was empty when this slice of the
12219 // clipboard text was written, then the entire line containing the
12220 // selection was copied. If this selection is also currently empty,
12221 // then paste the line before the current line of the buffer.
12222 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12223 let column = selection.start.to_point(&snapshot).column as usize;
12224 let line_start = selection.start - column;
12225 line_start..line_start
12226 } else {
12227 selection.range()
12228 };
12229
12230 edits.push((range, to_insert));
12231 original_indent_columns.push(original_indent_column);
12232 }
12233 drop(snapshot);
12234
12235 buffer.edit(
12236 edits,
12237 if auto_indent_on_paste {
12238 Some(AutoindentMode::Block {
12239 original_indent_columns,
12240 })
12241 } else {
12242 None
12243 },
12244 cx,
12245 );
12246 });
12247
12248 let selections = this.selections.all::<usize>(cx);
12249 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12250 } else {
12251 this.insert(&clipboard_text, window, cx);
12252 }
12253
12254 let trigger_in_words =
12255 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12256
12257 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
12258 });
12259 }
12260
12261 pub fn diff_clipboard_with_selection(
12262 &mut self,
12263 _: &DiffClipboardWithSelection,
12264 window: &mut Window,
12265 cx: &mut Context<Self>,
12266 ) {
12267 let selections = self.selections.all::<usize>(cx);
12268
12269 if selections.is_empty() {
12270 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12271 return;
12272 };
12273
12274 let clipboard_text = match cx.read_from_clipboard() {
12275 Some(item) => match item.entries().first() {
12276 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12277 _ => None,
12278 },
12279 None => None,
12280 };
12281
12282 let Some(clipboard_text) = clipboard_text else {
12283 log::warn!("Clipboard doesn't contain text.");
12284 return;
12285 };
12286
12287 window.dispatch_action(
12288 Box::new(DiffClipboardWithSelectionData {
12289 clipboard_text,
12290 editor: cx.entity(),
12291 }),
12292 cx,
12293 );
12294 }
12295
12296 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12297 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12298 if let Some(item) = cx.read_from_clipboard() {
12299 let entries = item.entries();
12300
12301 match entries.first() {
12302 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12303 // of all the pasted entries.
12304 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12305 .do_paste(
12306 clipboard_string.text(),
12307 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12308 true,
12309 window,
12310 cx,
12311 ),
12312 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12313 }
12314 }
12315 }
12316
12317 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12318 if self.read_only(cx) {
12319 return;
12320 }
12321
12322 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12323
12324 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12325 if let Some((selections, _)) =
12326 self.selection_history.transaction(transaction_id).cloned()
12327 {
12328 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12329 s.select_anchors(selections.to_vec());
12330 });
12331 } else {
12332 log::error!(
12333 "No entry in selection_history found for undo. \
12334 This may correspond to a bug where undo does not update the selection. \
12335 If this is occurring, please add details to \
12336 https://github.com/zed-industries/zed/issues/22692"
12337 );
12338 }
12339 self.request_autoscroll(Autoscroll::fit(), cx);
12340 self.unmark_text(window, cx);
12341 self.refresh_edit_prediction(true, false, window, cx);
12342 cx.emit(EditorEvent::Edited { transaction_id });
12343 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12344 }
12345 }
12346
12347 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12348 if self.read_only(cx) {
12349 return;
12350 }
12351
12352 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12353
12354 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12355 if let Some((_, Some(selections))) =
12356 self.selection_history.transaction(transaction_id).cloned()
12357 {
12358 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12359 s.select_anchors(selections.to_vec());
12360 });
12361 } else {
12362 log::error!(
12363 "No entry in selection_history found for redo. \
12364 This may correspond to a bug where undo does not update the selection. \
12365 If this is occurring, please add details to \
12366 https://github.com/zed-industries/zed/issues/22692"
12367 );
12368 }
12369 self.request_autoscroll(Autoscroll::fit(), cx);
12370 self.unmark_text(window, cx);
12371 self.refresh_edit_prediction(true, false, window, cx);
12372 cx.emit(EditorEvent::Edited { transaction_id });
12373 }
12374 }
12375
12376 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12377 self.buffer
12378 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12379 }
12380
12381 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12382 self.buffer
12383 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12384 }
12385
12386 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12387 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12388 self.change_selections(Default::default(), window, cx, |s| {
12389 s.move_with(|map, selection| {
12390 let cursor = if selection.is_empty() {
12391 movement::left(map, selection.start)
12392 } else {
12393 selection.start
12394 };
12395 selection.collapse_to(cursor, SelectionGoal::None);
12396 });
12397 })
12398 }
12399
12400 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12402 self.change_selections(Default::default(), window, cx, |s| {
12403 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12404 })
12405 }
12406
12407 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12408 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12409 self.change_selections(Default::default(), window, cx, |s| {
12410 s.move_with(|map, selection| {
12411 let cursor = if selection.is_empty() {
12412 movement::right(map, selection.end)
12413 } else {
12414 selection.end
12415 };
12416 selection.collapse_to(cursor, SelectionGoal::None)
12417 });
12418 })
12419 }
12420
12421 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12422 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12423 self.change_selections(Default::default(), window, cx, |s| {
12424 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12425 })
12426 }
12427
12428 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12429 if self.take_rename(true, window, cx).is_some() {
12430 return;
12431 }
12432
12433 if self.mode.is_single_line() {
12434 cx.propagate();
12435 return;
12436 }
12437
12438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12439
12440 let text_layout_details = &self.text_layout_details(window);
12441 let selection_count = self.selections.count();
12442 let first_selection = self.selections.first_anchor();
12443
12444 self.change_selections(Default::default(), window, cx, |s| {
12445 s.move_with(|map, selection| {
12446 if !selection.is_empty() {
12447 selection.goal = SelectionGoal::None;
12448 }
12449 let (cursor, goal) = movement::up(
12450 map,
12451 selection.start,
12452 selection.goal,
12453 false,
12454 text_layout_details,
12455 );
12456 selection.collapse_to(cursor, goal);
12457 });
12458 });
12459
12460 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12461 {
12462 cx.propagate();
12463 }
12464 }
12465
12466 pub fn move_up_by_lines(
12467 &mut self,
12468 action: &MoveUpByLines,
12469 window: &mut Window,
12470 cx: &mut Context<Self>,
12471 ) {
12472 if self.take_rename(true, window, cx).is_some() {
12473 return;
12474 }
12475
12476 if self.mode.is_single_line() {
12477 cx.propagate();
12478 return;
12479 }
12480
12481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12482
12483 let text_layout_details = &self.text_layout_details(window);
12484
12485 self.change_selections(Default::default(), window, cx, |s| {
12486 s.move_with(|map, selection| {
12487 if !selection.is_empty() {
12488 selection.goal = SelectionGoal::None;
12489 }
12490 let (cursor, goal) = movement::up_by_rows(
12491 map,
12492 selection.start,
12493 action.lines,
12494 selection.goal,
12495 false,
12496 text_layout_details,
12497 );
12498 selection.collapse_to(cursor, goal);
12499 });
12500 })
12501 }
12502
12503 pub fn move_down_by_lines(
12504 &mut self,
12505 action: &MoveDownByLines,
12506 window: &mut Window,
12507 cx: &mut Context<Self>,
12508 ) {
12509 if self.take_rename(true, window, cx).is_some() {
12510 return;
12511 }
12512
12513 if self.mode.is_single_line() {
12514 cx.propagate();
12515 return;
12516 }
12517
12518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12519
12520 let text_layout_details = &self.text_layout_details(window);
12521
12522 self.change_selections(Default::default(), window, cx, |s| {
12523 s.move_with(|map, selection| {
12524 if !selection.is_empty() {
12525 selection.goal = SelectionGoal::None;
12526 }
12527 let (cursor, goal) = movement::down_by_rows(
12528 map,
12529 selection.start,
12530 action.lines,
12531 selection.goal,
12532 false,
12533 text_layout_details,
12534 );
12535 selection.collapse_to(cursor, goal);
12536 });
12537 })
12538 }
12539
12540 pub fn select_down_by_lines(
12541 &mut self,
12542 action: &SelectDownByLines,
12543 window: &mut Window,
12544 cx: &mut Context<Self>,
12545 ) {
12546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12547 let text_layout_details = &self.text_layout_details(window);
12548 self.change_selections(Default::default(), window, cx, |s| {
12549 s.move_heads_with(|map, head, goal| {
12550 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12551 })
12552 })
12553 }
12554
12555 pub fn select_up_by_lines(
12556 &mut self,
12557 action: &SelectUpByLines,
12558 window: &mut Window,
12559 cx: &mut Context<Self>,
12560 ) {
12561 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12562 let text_layout_details = &self.text_layout_details(window);
12563 self.change_selections(Default::default(), window, cx, |s| {
12564 s.move_heads_with(|map, head, goal| {
12565 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12566 })
12567 })
12568 }
12569
12570 pub fn select_page_up(
12571 &mut self,
12572 _: &SelectPageUp,
12573 window: &mut Window,
12574 cx: &mut Context<Self>,
12575 ) {
12576 let Some(row_count) = self.visible_row_count() else {
12577 return;
12578 };
12579
12580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12581
12582 let text_layout_details = &self.text_layout_details(window);
12583
12584 self.change_selections(Default::default(), window, cx, |s| {
12585 s.move_heads_with(|map, head, goal| {
12586 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12587 })
12588 })
12589 }
12590
12591 pub fn move_page_up(
12592 &mut self,
12593 action: &MovePageUp,
12594 window: &mut Window,
12595 cx: &mut Context<Self>,
12596 ) {
12597 if self.take_rename(true, window, cx).is_some() {
12598 return;
12599 }
12600
12601 if self
12602 .context_menu
12603 .borrow_mut()
12604 .as_mut()
12605 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12606 .unwrap_or(false)
12607 {
12608 return;
12609 }
12610
12611 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12612 cx.propagate();
12613 return;
12614 }
12615
12616 let Some(row_count) = self.visible_row_count() else {
12617 return;
12618 };
12619
12620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12621
12622 let effects = if action.center_cursor {
12623 SelectionEffects::scroll(Autoscroll::center())
12624 } else {
12625 SelectionEffects::default()
12626 };
12627
12628 let text_layout_details = &self.text_layout_details(window);
12629
12630 self.change_selections(effects, window, cx, |s| {
12631 s.move_with(|map, selection| {
12632 if !selection.is_empty() {
12633 selection.goal = SelectionGoal::None;
12634 }
12635 let (cursor, goal) = movement::up_by_rows(
12636 map,
12637 selection.end,
12638 row_count,
12639 selection.goal,
12640 false,
12641 text_layout_details,
12642 );
12643 selection.collapse_to(cursor, goal);
12644 });
12645 });
12646 }
12647
12648 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12649 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12650 let text_layout_details = &self.text_layout_details(window);
12651 self.change_selections(Default::default(), window, cx, |s| {
12652 s.move_heads_with(|map, head, goal| {
12653 movement::up(map, head, goal, false, text_layout_details)
12654 })
12655 })
12656 }
12657
12658 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12659 self.take_rename(true, window, cx);
12660
12661 if self.mode.is_single_line() {
12662 cx.propagate();
12663 return;
12664 }
12665
12666 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12667
12668 let text_layout_details = &self.text_layout_details(window);
12669 let selection_count = self.selections.count();
12670 let first_selection = self.selections.first_anchor();
12671
12672 self.change_selections(Default::default(), window, cx, |s| {
12673 s.move_with(|map, selection| {
12674 if !selection.is_empty() {
12675 selection.goal = SelectionGoal::None;
12676 }
12677 let (cursor, goal) = movement::down(
12678 map,
12679 selection.end,
12680 selection.goal,
12681 false,
12682 text_layout_details,
12683 );
12684 selection.collapse_to(cursor, goal);
12685 });
12686 });
12687
12688 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12689 {
12690 cx.propagate();
12691 }
12692 }
12693
12694 pub fn select_page_down(
12695 &mut self,
12696 _: &SelectPageDown,
12697 window: &mut Window,
12698 cx: &mut Context<Self>,
12699 ) {
12700 let Some(row_count) = self.visible_row_count() else {
12701 return;
12702 };
12703
12704 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12705
12706 let text_layout_details = &self.text_layout_details(window);
12707
12708 self.change_selections(Default::default(), window, cx, |s| {
12709 s.move_heads_with(|map, head, goal| {
12710 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12711 })
12712 })
12713 }
12714
12715 pub fn move_page_down(
12716 &mut self,
12717 action: &MovePageDown,
12718 window: &mut Window,
12719 cx: &mut Context<Self>,
12720 ) {
12721 if self.take_rename(true, window, cx).is_some() {
12722 return;
12723 }
12724
12725 if self
12726 .context_menu
12727 .borrow_mut()
12728 .as_mut()
12729 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12730 .unwrap_or(false)
12731 {
12732 return;
12733 }
12734
12735 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12736 cx.propagate();
12737 return;
12738 }
12739
12740 let Some(row_count) = self.visible_row_count() else {
12741 return;
12742 };
12743
12744 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12745
12746 let effects = if action.center_cursor {
12747 SelectionEffects::scroll(Autoscroll::center())
12748 } else {
12749 SelectionEffects::default()
12750 };
12751
12752 let text_layout_details = &self.text_layout_details(window);
12753 self.change_selections(effects, window, cx, |s| {
12754 s.move_with(|map, selection| {
12755 if !selection.is_empty() {
12756 selection.goal = SelectionGoal::None;
12757 }
12758 let (cursor, goal) = movement::down_by_rows(
12759 map,
12760 selection.end,
12761 row_count,
12762 selection.goal,
12763 false,
12764 text_layout_details,
12765 );
12766 selection.collapse_to(cursor, goal);
12767 });
12768 });
12769 }
12770
12771 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12773 let text_layout_details = &self.text_layout_details(window);
12774 self.change_selections(Default::default(), window, cx, |s| {
12775 s.move_heads_with(|map, head, goal| {
12776 movement::down(map, head, goal, false, text_layout_details)
12777 })
12778 });
12779 }
12780
12781 pub fn context_menu_first(
12782 &mut self,
12783 _: &ContextMenuFirst,
12784 window: &mut Window,
12785 cx: &mut Context<Self>,
12786 ) {
12787 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12788 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12789 }
12790 }
12791
12792 pub fn context_menu_prev(
12793 &mut self,
12794 _: &ContextMenuPrevious,
12795 window: &mut Window,
12796 cx: &mut Context<Self>,
12797 ) {
12798 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12799 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12800 }
12801 }
12802
12803 pub fn context_menu_next(
12804 &mut self,
12805 _: &ContextMenuNext,
12806 window: &mut Window,
12807 cx: &mut Context<Self>,
12808 ) {
12809 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12810 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12811 }
12812 }
12813
12814 pub fn context_menu_last(
12815 &mut self,
12816 _: &ContextMenuLast,
12817 window: &mut Window,
12818 cx: &mut Context<Self>,
12819 ) {
12820 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12821 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12822 }
12823 }
12824
12825 pub fn signature_help_prev(
12826 &mut self,
12827 _: &SignatureHelpPrevious,
12828 _: &mut Window,
12829 cx: &mut Context<Self>,
12830 ) {
12831 if let Some(popover) = self.signature_help_state.popover_mut() {
12832 if popover.current_signature == 0 {
12833 popover.current_signature = popover.signatures.len() - 1;
12834 } else {
12835 popover.current_signature -= 1;
12836 }
12837 cx.notify();
12838 }
12839 }
12840
12841 pub fn signature_help_next(
12842 &mut self,
12843 _: &SignatureHelpNext,
12844 _: &mut Window,
12845 cx: &mut Context<Self>,
12846 ) {
12847 if let Some(popover) = self.signature_help_state.popover_mut() {
12848 if popover.current_signature + 1 == popover.signatures.len() {
12849 popover.current_signature = 0;
12850 } else {
12851 popover.current_signature += 1;
12852 }
12853 cx.notify();
12854 }
12855 }
12856
12857 pub fn move_to_previous_word_start(
12858 &mut self,
12859 _: &MoveToPreviousWordStart,
12860 window: &mut Window,
12861 cx: &mut Context<Self>,
12862 ) {
12863 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12864 self.change_selections(Default::default(), window, cx, |s| {
12865 s.move_cursors_with(|map, head, _| {
12866 (
12867 movement::previous_word_start(map, head),
12868 SelectionGoal::None,
12869 )
12870 });
12871 })
12872 }
12873
12874 pub fn move_to_previous_subword_start(
12875 &mut self,
12876 _: &MoveToPreviousSubwordStart,
12877 window: &mut Window,
12878 cx: &mut Context<Self>,
12879 ) {
12880 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12881 self.change_selections(Default::default(), window, cx, |s| {
12882 s.move_cursors_with(|map, head, _| {
12883 (
12884 movement::previous_subword_start(map, head),
12885 SelectionGoal::None,
12886 )
12887 });
12888 })
12889 }
12890
12891 pub fn select_to_previous_word_start(
12892 &mut self,
12893 _: &SelectToPreviousWordStart,
12894 window: &mut Window,
12895 cx: &mut Context<Self>,
12896 ) {
12897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12898 self.change_selections(Default::default(), window, cx, |s| {
12899 s.move_heads_with(|map, head, _| {
12900 (
12901 movement::previous_word_start(map, head),
12902 SelectionGoal::None,
12903 )
12904 });
12905 })
12906 }
12907
12908 pub fn select_to_previous_subword_start(
12909 &mut self,
12910 _: &SelectToPreviousSubwordStart,
12911 window: &mut Window,
12912 cx: &mut Context<Self>,
12913 ) {
12914 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12915 self.change_selections(Default::default(), window, cx, |s| {
12916 s.move_heads_with(|map, head, _| {
12917 (
12918 movement::previous_subword_start(map, head),
12919 SelectionGoal::None,
12920 )
12921 });
12922 })
12923 }
12924
12925 pub fn delete_to_previous_word_start(
12926 &mut self,
12927 action: &DeleteToPreviousWordStart,
12928 window: &mut Window,
12929 cx: &mut Context<Self>,
12930 ) {
12931 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12932 self.transact(window, cx, |this, window, cx| {
12933 this.select_autoclose_pair(window, cx);
12934 this.change_selections(Default::default(), window, cx, |s| {
12935 s.move_with(|map, selection| {
12936 if selection.is_empty() {
12937 let cursor = if action.ignore_newlines {
12938 movement::previous_word_start(map, selection.head())
12939 } else {
12940 movement::previous_word_start_or_newline(map, selection.head())
12941 };
12942 selection.set_head(cursor, SelectionGoal::None);
12943 }
12944 });
12945 });
12946 this.insert("", window, cx);
12947 });
12948 }
12949
12950 pub fn delete_to_previous_subword_start(
12951 &mut self,
12952 _: &DeleteToPreviousSubwordStart,
12953 window: &mut Window,
12954 cx: &mut Context<Self>,
12955 ) {
12956 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12957 self.transact(window, cx, |this, window, cx| {
12958 this.select_autoclose_pair(window, cx);
12959 this.change_selections(Default::default(), window, cx, |s| {
12960 s.move_with(|map, selection| {
12961 if selection.is_empty() {
12962 let cursor = movement::previous_subword_start(map, selection.head());
12963 selection.set_head(cursor, SelectionGoal::None);
12964 }
12965 });
12966 });
12967 this.insert("", window, cx);
12968 });
12969 }
12970
12971 pub fn move_to_next_word_end(
12972 &mut self,
12973 _: &MoveToNextWordEnd,
12974 window: &mut Window,
12975 cx: &mut Context<Self>,
12976 ) {
12977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12978 self.change_selections(Default::default(), window, cx, |s| {
12979 s.move_cursors_with(|map, head, _| {
12980 (movement::next_word_end(map, head), SelectionGoal::None)
12981 });
12982 })
12983 }
12984
12985 pub fn move_to_next_subword_end(
12986 &mut self,
12987 _: &MoveToNextSubwordEnd,
12988 window: &mut Window,
12989 cx: &mut Context<Self>,
12990 ) {
12991 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12992 self.change_selections(Default::default(), window, cx, |s| {
12993 s.move_cursors_with(|map, head, _| {
12994 (movement::next_subword_end(map, head), SelectionGoal::None)
12995 });
12996 })
12997 }
12998
12999 pub fn select_to_next_word_end(
13000 &mut self,
13001 _: &SelectToNextWordEnd,
13002 window: &mut Window,
13003 cx: &mut Context<Self>,
13004 ) {
13005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13006 self.change_selections(Default::default(), window, cx, |s| {
13007 s.move_heads_with(|map, head, _| {
13008 (movement::next_word_end(map, head), SelectionGoal::None)
13009 });
13010 })
13011 }
13012
13013 pub fn select_to_next_subword_end(
13014 &mut self,
13015 _: &SelectToNextSubwordEnd,
13016 window: &mut Window,
13017 cx: &mut Context<Self>,
13018 ) {
13019 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13020 self.change_selections(Default::default(), window, cx, |s| {
13021 s.move_heads_with(|map, head, _| {
13022 (movement::next_subword_end(map, head), SelectionGoal::None)
13023 });
13024 })
13025 }
13026
13027 pub fn delete_to_next_word_end(
13028 &mut self,
13029 action: &DeleteToNextWordEnd,
13030 window: &mut Window,
13031 cx: &mut Context<Self>,
13032 ) {
13033 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13034 self.transact(window, cx, |this, window, cx| {
13035 this.change_selections(Default::default(), window, cx, |s| {
13036 s.move_with(|map, selection| {
13037 if selection.is_empty() {
13038 let cursor = if action.ignore_newlines {
13039 movement::next_word_end(map, selection.head())
13040 } else {
13041 movement::next_word_end_or_newline(map, selection.head())
13042 };
13043 selection.set_head(cursor, SelectionGoal::None);
13044 }
13045 });
13046 });
13047 this.insert("", window, cx);
13048 });
13049 }
13050
13051 pub fn delete_to_next_subword_end(
13052 &mut self,
13053 _: &DeleteToNextSubwordEnd,
13054 window: &mut Window,
13055 cx: &mut Context<Self>,
13056 ) {
13057 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13058 self.transact(window, cx, |this, window, cx| {
13059 this.change_selections(Default::default(), window, cx, |s| {
13060 s.move_with(|map, selection| {
13061 if selection.is_empty() {
13062 let cursor = movement::next_subword_end(map, selection.head());
13063 selection.set_head(cursor, SelectionGoal::None);
13064 }
13065 });
13066 });
13067 this.insert("", window, cx);
13068 });
13069 }
13070
13071 pub fn move_to_beginning_of_line(
13072 &mut self,
13073 action: &MoveToBeginningOfLine,
13074 window: &mut Window,
13075 cx: &mut Context<Self>,
13076 ) {
13077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13078 self.change_selections(Default::default(), window, cx, |s| {
13079 s.move_cursors_with(|map, head, _| {
13080 (
13081 movement::indented_line_beginning(
13082 map,
13083 head,
13084 action.stop_at_soft_wraps,
13085 action.stop_at_indent,
13086 ),
13087 SelectionGoal::None,
13088 )
13089 });
13090 })
13091 }
13092
13093 pub fn select_to_beginning_of_line(
13094 &mut self,
13095 action: &SelectToBeginningOfLine,
13096 window: &mut Window,
13097 cx: &mut Context<Self>,
13098 ) {
13099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13100 self.change_selections(Default::default(), window, cx, |s| {
13101 s.move_heads_with(|map, head, _| {
13102 (
13103 movement::indented_line_beginning(
13104 map,
13105 head,
13106 action.stop_at_soft_wraps,
13107 action.stop_at_indent,
13108 ),
13109 SelectionGoal::None,
13110 )
13111 });
13112 });
13113 }
13114
13115 pub fn delete_to_beginning_of_line(
13116 &mut self,
13117 action: &DeleteToBeginningOfLine,
13118 window: &mut Window,
13119 cx: &mut Context<Self>,
13120 ) {
13121 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13122 self.transact(window, cx, |this, window, cx| {
13123 this.change_selections(Default::default(), window, cx, |s| {
13124 s.move_with(|_, selection| {
13125 selection.reversed = true;
13126 });
13127 });
13128
13129 this.select_to_beginning_of_line(
13130 &SelectToBeginningOfLine {
13131 stop_at_soft_wraps: false,
13132 stop_at_indent: action.stop_at_indent,
13133 },
13134 window,
13135 cx,
13136 );
13137 this.backspace(&Backspace, window, cx);
13138 });
13139 }
13140
13141 pub fn move_to_end_of_line(
13142 &mut self,
13143 action: &MoveToEndOfLine,
13144 window: &mut Window,
13145 cx: &mut Context<Self>,
13146 ) {
13147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13148 self.change_selections(Default::default(), window, cx, |s| {
13149 s.move_cursors_with(|map, head, _| {
13150 (
13151 movement::line_end(map, head, action.stop_at_soft_wraps),
13152 SelectionGoal::None,
13153 )
13154 });
13155 })
13156 }
13157
13158 pub fn select_to_end_of_line(
13159 &mut self,
13160 action: &SelectToEndOfLine,
13161 window: &mut Window,
13162 cx: &mut Context<Self>,
13163 ) {
13164 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13165 self.change_selections(Default::default(), window, cx, |s| {
13166 s.move_heads_with(|map, head, _| {
13167 (
13168 movement::line_end(map, head, action.stop_at_soft_wraps),
13169 SelectionGoal::None,
13170 )
13171 });
13172 })
13173 }
13174
13175 pub fn delete_to_end_of_line(
13176 &mut self,
13177 _: &DeleteToEndOfLine,
13178 window: &mut Window,
13179 cx: &mut Context<Self>,
13180 ) {
13181 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13182 self.transact(window, cx, |this, window, cx| {
13183 this.select_to_end_of_line(
13184 &SelectToEndOfLine {
13185 stop_at_soft_wraps: false,
13186 },
13187 window,
13188 cx,
13189 );
13190 this.delete(&Delete, window, cx);
13191 });
13192 }
13193
13194 pub fn cut_to_end_of_line(
13195 &mut self,
13196 _: &CutToEndOfLine,
13197 window: &mut Window,
13198 cx: &mut Context<Self>,
13199 ) {
13200 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13201 self.transact(window, cx, |this, window, cx| {
13202 this.select_to_end_of_line(
13203 &SelectToEndOfLine {
13204 stop_at_soft_wraps: false,
13205 },
13206 window,
13207 cx,
13208 );
13209 this.cut(&Cut, window, cx);
13210 });
13211 }
13212
13213 pub fn move_to_start_of_paragraph(
13214 &mut self,
13215 _: &MoveToStartOfParagraph,
13216 window: &mut Window,
13217 cx: &mut Context<Self>,
13218 ) {
13219 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13220 cx.propagate();
13221 return;
13222 }
13223 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13224 self.change_selections(Default::default(), window, cx, |s| {
13225 s.move_with(|map, selection| {
13226 selection.collapse_to(
13227 movement::start_of_paragraph(map, selection.head(), 1),
13228 SelectionGoal::None,
13229 )
13230 });
13231 })
13232 }
13233
13234 pub fn move_to_end_of_paragraph(
13235 &mut self,
13236 _: &MoveToEndOfParagraph,
13237 window: &mut Window,
13238 cx: &mut Context<Self>,
13239 ) {
13240 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13241 cx.propagate();
13242 return;
13243 }
13244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13245 self.change_selections(Default::default(), window, cx, |s| {
13246 s.move_with(|map, selection| {
13247 selection.collapse_to(
13248 movement::end_of_paragraph(map, selection.head(), 1),
13249 SelectionGoal::None,
13250 )
13251 });
13252 })
13253 }
13254
13255 pub fn select_to_start_of_paragraph(
13256 &mut self,
13257 _: &SelectToStartOfParagraph,
13258 window: &mut Window,
13259 cx: &mut Context<Self>,
13260 ) {
13261 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13262 cx.propagate();
13263 return;
13264 }
13265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13266 self.change_selections(Default::default(), window, cx, |s| {
13267 s.move_heads_with(|map, head, _| {
13268 (
13269 movement::start_of_paragraph(map, head, 1),
13270 SelectionGoal::None,
13271 )
13272 });
13273 })
13274 }
13275
13276 pub fn select_to_end_of_paragraph(
13277 &mut self,
13278 _: &SelectToEndOfParagraph,
13279 window: &mut Window,
13280 cx: &mut Context<Self>,
13281 ) {
13282 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13283 cx.propagate();
13284 return;
13285 }
13286 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13287 self.change_selections(Default::default(), window, cx, |s| {
13288 s.move_heads_with(|map, head, _| {
13289 (
13290 movement::end_of_paragraph(map, head, 1),
13291 SelectionGoal::None,
13292 )
13293 });
13294 })
13295 }
13296
13297 pub fn move_to_start_of_excerpt(
13298 &mut self,
13299 _: &MoveToStartOfExcerpt,
13300 window: &mut Window,
13301 cx: &mut Context<Self>,
13302 ) {
13303 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13304 cx.propagate();
13305 return;
13306 }
13307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13308 self.change_selections(Default::default(), window, cx, |s| {
13309 s.move_with(|map, selection| {
13310 selection.collapse_to(
13311 movement::start_of_excerpt(
13312 map,
13313 selection.head(),
13314 workspace::searchable::Direction::Prev,
13315 ),
13316 SelectionGoal::None,
13317 )
13318 });
13319 })
13320 }
13321
13322 pub fn move_to_start_of_next_excerpt(
13323 &mut self,
13324 _: &MoveToStartOfNextExcerpt,
13325 window: &mut Window,
13326 cx: &mut Context<Self>,
13327 ) {
13328 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13329 cx.propagate();
13330 return;
13331 }
13332
13333 self.change_selections(Default::default(), window, cx, |s| {
13334 s.move_with(|map, selection| {
13335 selection.collapse_to(
13336 movement::start_of_excerpt(
13337 map,
13338 selection.head(),
13339 workspace::searchable::Direction::Next,
13340 ),
13341 SelectionGoal::None,
13342 )
13343 });
13344 })
13345 }
13346
13347 pub fn move_to_end_of_excerpt(
13348 &mut self,
13349 _: &MoveToEndOfExcerpt,
13350 window: &mut Window,
13351 cx: &mut Context<Self>,
13352 ) {
13353 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13354 cx.propagate();
13355 return;
13356 }
13357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13358 self.change_selections(Default::default(), window, cx, |s| {
13359 s.move_with(|map, selection| {
13360 selection.collapse_to(
13361 movement::end_of_excerpt(
13362 map,
13363 selection.head(),
13364 workspace::searchable::Direction::Next,
13365 ),
13366 SelectionGoal::None,
13367 )
13368 });
13369 })
13370 }
13371
13372 pub fn move_to_end_of_previous_excerpt(
13373 &mut self,
13374 _: &MoveToEndOfPreviousExcerpt,
13375 window: &mut Window,
13376 cx: &mut Context<Self>,
13377 ) {
13378 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13379 cx.propagate();
13380 return;
13381 }
13382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13383 self.change_selections(Default::default(), window, cx, |s| {
13384 s.move_with(|map, selection| {
13385 selection.collapse_to(
13386 movement::end_of_excerpt(
13387 map,
13388 selection.head(),
13389 workspace::searchable::Direction::Prev,
13390 ),
13391 SelectionGoal::None,
13392 )
13393 });
13394 })
13395 }
13396
13397 pub fn select_to_start_of_excerpt(
13398 &mut self,
13399 _: &SelectToStartOfExcerpt,
13400 window: &mut Window,
13401 cx: &mut Context<Self>,
13402 ) {
13403 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13404 cx.propagate();
13405 return;
13406 }
13407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13408 self.change_selections(Default::default(), window, cx, |s| {
13409 s.move_heads_with(|map, head, _| {
13410 (
13411 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13412 SelectionGoal::None,
13413 )
13414 });
13415 })
13416 }
13417
13418 pub fn select_to_start_of_next_excerpt(
13419 &mut self,
13420 _: &SelectToStartOfNextExcerpt,
13421 window: &mut Window,
13422 cx: &mut Context<Self>,
13423 ) {
13424 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13425 cx.propagate();
13426 return;
13427 }
13428 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13429 self.change_selections(Default::default(), window, cx, |s| {
13430 s.move_heads_with(|map, head, _| {
13431 (
13432 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13433 SelectionGoal::None,
13434 )
13435 });
13436 })
13437 }
13438
13439 pub fn select_to_end_of_excerpt(
13440 &mut self,
13441 _: &SelectToEndOfExcerpt,
13442 window: &mut Window,
13443 cx: &mut Context<Self>,
13444 ) {
13445 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13446 cx.propagate();
13447 return;
13448 }
13449 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13450 self.change_selections(Default::default(), window, cx, |s| {
13451 s.move_heads_with(|map, head, _| {
13452 (
13453 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13454 SelectionGoal::None,
13455 )
13456 });
13457 })
13458 }
13459
13460 pub fn select_to_end_of_previous_excerpt(
13461 &mut self,
13462 _: &SelectToEndOfPreviousExcerpt,
13463 window: &mut Window,
13464 cx: &mut Context<Self>,
13465 ) {
13466 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13467 cx.propagate();
13468 return;
13469 }
13470 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13471 self.change_selections(Default::default(), window, cx, |s| {
13472 s.move_heads_with(|map, head, _| {
13473 (
13474 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13475 SelectionGoal::None,
13476 )
13477 });
13478 })
13479 }
13480
13481 pub fn move_to_beginning(
13482 &mut self,
13483 _: &MoveToBeginning,
13484 window: &mut Window,
13485 cx: &mut Context<Self>,
13486 ) {
13487 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13488 cx.propagate();
13489 return;
13490 }
13491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13492 self.change_selections(Default::default(), window, cx, |s| {
13493 s.select_ranges(vec![0..0]);
13494 });
13495 }
13496
13497 pub fn select_to_beginning(
13498 &mut self,
13499 _: &SelectToBeginning,
13500 window: &mut Window,
13501 cx: &mut Context<Self>,
13502 ) {
13503 let mut selection = self.selections.last::<Point>(cx);
13504 selection.set_head(Point::zero(), SelectionGoal::None);
13505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13506 self.change_selections(Default::default(), window, cx, |s| {
13507 s.select(vec![selection]);
13508 });
13509 }
13510
13511 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13512 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13513 cx.propagate();
13514 return;
13515 }
13516 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13517 let cursor = self.buffer.read(cx).read(cx).len();
13518 self.change_selections(Default::default(), window, cx, |s| {
13519 s.select_ranges(vec![cursor..cursor])
13520 });
13521 }
13522
13523 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13524 self.nav_history = nav_history;
13525 }
13526
13527 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13528 self.nav_history.as_ref()
13529 }
13530
13531 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13532 self.push_to_nav_history(
13533 self.selections.newest_anchor().head(),
13534 None,
13535 false,
13536 true,
13537 cx,
13538 );
13539 }
13540
13541 fn push_to_nav_history(
13542 &mut self,
13543 cursor_anchor: Anchor,
13544 new_position: Option<Point>,
13545 is_deactivate: bool,
13546 always: bool,
13547 cx: &mut Context<Self>,
13548 ) {
13549 if let Some(nav_history) = self.nav_history.as_mut() {
13550 let buffer = self.buffer.read(cx).read(cx);
13551 let cursor_position = cursor_anchor.to_point(&buffer);
13552 let scroll_state = self.scroll_manager.anchor();
13553 let scroll_top_row = scroll_state.top_row(&buffer);
13554 drop(buffer);
13555
13556 if let Some(new_position) = new_position {
13557 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13558 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13559 return;
13560 }
13561 }
13562
13563 nav_history.push(
13564 Some(NavigationData {
13565 cursor_anchor,
13566 cursor_position,
13567 scroll_anchor: scroll_state,
13568 scroll_top_row,
13569 }),
13570 cx,
13571 );
13572 cx.emit(EditorEvent::PushedToNavHistory {
13573 anchor: cursor_anchor,
13574 is_deactivate,
13575 })
13576 }
13577 }
13578
13579 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13581 let buffer = self.buffer.read(cx).snapshot(cx);
13582 let mut selection = self.selections.first::<usize>(cx);
13583 selection.set_head(buffer.len(), SelectionGoal::None);
13584 self.change_selections(Default::default(), window, cx, |s| {
13585 s.select(vec![selection]);
13586 });
13587 }
13588
13589 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13591 let end = self.buffer.read(cx).read(cx).len();
13592 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13593 s.select_ranges(vec![0..end]);
13594 });
13595 }
13596
13597 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13598 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13599 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13600 let mut selections = self.selections.all::<Point>(cx);
13601 let max_point = display_map.buffer_snapshot.max_point();
13602 for selection in &mut selections {
13603 let rows = selection.spanned_rows(true, &display_map);
13604 selection.start = Point::new(rows.start.0, 0);
13605 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13606 selection.reversed = false;
13607 }
13608 self.change_selections(Default::default(), window, cx, |s| {
13609 s.select(selections);
13610 });
13611 }
13612
13613 pub fn split_selection_into_lines(
13614 &mut self,
13615 _: &SplitSelectionIntoLines,
13616 window: &mut Window,
13617 cx: &mut Context<Self>,
13618 ) {
13619 let selections = self
13620 .selections
13621 .all::<Point>(cx)
13622 .into_iter()
13623 .map(|selection| selection.start..selection.end)
13624 .collect::<Vec<_>>();
13625 self.unfold_ranges(&selections, true, true, cx);
13626
13627 let mut new_selection_ranges = Vec::new();
13628 {
13629 let buffer = self.buffer.read(cx).read(cx);
13630 for selection in selections {
13631 for row in selection.start.row..selection.end.row {
13632 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13633 new_selection_ranges.push(cursor..cursor);
13634 }
13635
13636 let is_multiline_selection = selection.start.row != selection.end.row;
13637 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13638 // so this action feels more ergonomic when paired with other selection operations
13639 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13640 if !should_skip_last {
13641 new_selection_ranges.push(selection.end..selection.end);
13642 }
13643 }
13644 }
13645 self.change_selections(Default::default(), window, cx, |s| {
13646 s.select_ranges(new_selection_ranges);
13647 });
13648 }
13649
13650 pub fn add_selection_above(
13651 &mut self,
13652 _: &AddSelectionAbove,
13653 window: &mut Window,
13654 cx: &mut Context<Self>,
13655 ) {
13656 self.add_selection(true, window, cx);
13657 }
13658
13659 pub fn add_selection_below(
13660 &mut self,
13661 _: &AddSelectionBelow,
13662 window: &mut Window,
13663 cx: &mut Context<Self>,
13664 ) {
13665 self.add_selection(false, window, cx);
13666 }
13667
13668 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13669 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13670
13671 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13672 let all_selections = self.selections.all::<Point>(cx);
13673 let text_layout_details = self.text_layout_details(window);
13674
13675 let (mut columnar_selections, new_selections_to_columnarize) = {
13676 if let Some(state) = self.add_selections_state.as_ref() {
13677 let columnar_selection_ids: HashSet<_> = state
13678 .groups
13679 .iter()
13680 .flat_map(|group| group.stack.iter())
13681 .copied()
13682 .collect();
13683
13684 all_selections
13685 .into_iter()
13686 .partition(|s| columnar_selection_ids.contains(&s.id))
13687 } else {
13688 (Vec::new(), all_selections)
13689 }
13690 };
13691
13692 let mut state = self
13693 .add_selections_state
13694 .take()
13695 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13696
13697 for selection in new_selections_to_columnarize {
13698 let range = selection.display_range(&display_map).sorted();
13699 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13700 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13701 let positions = start_x.min(end_x)..start_x.max(end_x);
13702 let mut stack = Vec::new();
13703 for row in range.start.row().0..=range.end.row().0 {
13704 if let Some(selection) = self.selections.build_columnar_selection(
13705 &display_map,
13706 DisplayRow(row),
13707 &positions,
13708 selection.reversed,
13709 &text_layout_details,
13710 ) {
13711 stack.push(selection.id);
13712 columnar_selections.push(selection);
13713 }
13714 }
13715 if !stack.is_empty() {
13716 if above {
13717 stack.reverse();
13718 }
13719 state.groups.push(AddSelectionsGroup { above, stack });
13720 }
13721 }
13722
13723 let mut final_selections = Vec::new();
13724 let end_row = if above {
13725 DisplayRow(0)
13726 } else {
13727 display_map.max_point().row()
13728 };
13729
13730 let mut last_added_item_per_group = HashMap::default();
13731 for group in state.groups.iter_mut() {
13732 if let Some(last_id) = group.stack.last() {
13733 last_added_item_per_group.insert(*last_id, group);
13734 }
13735 }
13736
13737 for selection in columnar_selections {
13738 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13739 if above == group.above {
13740 let range = selection.display_range(&display_map).sorted();
13741 debug_assert_eq!(range.start.row(), range.end.row());
13742 let mut row = range.start.row();
13743 let positions =
13744 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13745 px(start)..px(end)
13746 } else {
13747 let start_x =
13748 display_map.x_for_display_point(range.start, &text_layout_details);
13749 let end_x =
13750 display_map.x_for_display_point(range.end, &text_layout_details);
13751 start_x.min(end_x)..start_x.max(end_x)
13752 };
13753
13754 let mut maybe_new_selection = None;
13755 while row != end_row {
13756 if above {
13757 row.0 -= 1;
13758 } else {
13759 row.0 += 1;
13760 }
13761 if let Some(new_selection) = self.selections.build_columnar_selection(
13762 &display_map,
13763 row,
13764 &positions,
13765 selection.reversed,
13766 &text_layout_details,
13767 ) {
13768 maybe_new_selection = Some(new_selection);
13769 break;
13770 }
13771 }
13772
13773 if let Some(new_selection) = maybe_new_selection {
13774 group.stack.push(new_selection.id);
13775 if above {
13776 final_selections.push(new_selection);
13777 final_selections.push(selection);
13778 } else {
13779 final_selections.push(selection);
13780 final_selections.push(new_selection);
13781 }
13782 } else {
13783 final_selections.push(selection);
13784 }
13785 } else {
13786 group.stack.pop();
13787 }
13788 } else {
13789 final_selections.push(selection);
13790 }
13791 }
13792
13793 self.change_selections(Default::default(), window, cx, |s| {
13794 s.select(final_selections);
13795 });
13796
13797 let final_selection_ids: HashSet<_> = self
13798 .selections
13799 .all::<Point>(cx)
13800 .iter()
13801 .map(|s| s.id)
13802 .collect();
13803 state.groups.retain_mut(|group| {
13804 // selections might get merged above so we remove invalid items from stacks
13805 group.stack.retain(|id| final_selection_ids.contains(id));
13806
13807 // single selection in stack can be treated as initial state
13808 group.stack.len() > 1
13809 });
13810
13811 if !state.groups.is_empty() {
13812 self.add_selections_state = Some(state);
13813 }
13814 }
13815
13816 fn select_match_ranges(
13817 &mut self,
13818 range: Range<usize>,
13819 reversed: bool,
13820 replace_newest: bool,
13821 auto_scroll: Option<Autoscroll>,
13822 window: &mut Window,
13823 cx: &mut Context<Editor>,
13824 ) {
13825 self.unfold_ranges(
13826 std::slice::from_ref(&range),
13827 false,
13828 auto_scroll.is_some(),
13829 cx,
13830 );
13831 let effects = if let Some(scroll) = auto_scroll {
13832 SelectionEffects::scroll(scroll)
13833 } else {
13834 SelectionEffects::no_scroll()
13835 };
13836 self.change_selections(effects, window, cx, |s| {
13837 if replace_newest {
13838 s.delete(s.newest_anchor().id);
13839 }
13840 if reversed {
13841 s.insert_range(range.end..range.start);
13842 } else {
13843 s.insert_range(range);
13844 }
13845 });
13846 }
13847
13848 pub fn select_next_match_internal(
13849 &mut self,
13850 display_map: &DisplaySnapshot,
13851 replace_newest: bool,
13852 autoscroll: Option<Autoscroll>,
13853 window: &mut Window,
13854 cx: &mut Context<Self>,
13855 ) -> Result<()> {
13856 let buffer = &display_map.buffer_snapshot;
13857 let mut selections = self.selections.all::<usize>(cx);
13858 if let Some(mut select_next_state) = self.select_next_state.take() {
13859 let query = &select_next_state.query;
13860 if !select_next_state.done {
13861 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13862 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13863 let mut next_selected_range = None;
13864
13865 let bytes_after_last_selection =
13866 buffer.bytes_in_range(last_selection.end..buffer.len());
13867 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13868 let query_matches = query
13869 .stream_find_iter(bytes_after_last_selection)
13870 .map(|result| (last_selection.end, result))
13871 .chain(
13872 query
13873 .stream_find_iter(bytes_before_first_selection)
13874 .map(|result| (0, result)),
13875 );
13876
13877 for (start_offset, query_match) in query_matches {
13878 let query_match = query_match.unwrap(); // can only fail due to I/O
13879 let offset_range =
13880 start_offset + query_match.start()..start_offset + query_match.end();
13881
13882 if !select_next_state.wordwise
13883 || (!buffer.is_inside_word(offset_range.start, false)
13884 && !buffer.is_inside_word(offset_range.end, false))
13885 {
13886 // TODO: This is n^2, because we might check all the selections
13887 if !selections
13888 .iter()
13889 .any(|selection| selection.range().overlaps(&offset_range))
13890 {
13891 next_selected_range = Some(offset_range);
13892 break;
13893 }
13894 }
13895 }
13896
13897 if let Some(next_selected_range) = next_selected_range {
13898 self.select_match_ranges(
13899 next_selected_range,
13900 last_selection.reversed,
13901 replace_newest,
13902 autoscroll,
13903 window,
13904 cx,
13905 );
13906 } else {
13907 select_next_state.done = true;
13908 }
13909 }
13910
13911 self.select_next_state = Some(select_next_state);
13912 } else {
13913 let mut only_carets = true;
13914 let mut same_text_selected = true;
13915 let mut selected_text = None;
13916
13917 let mut selections_iter = selections.iter().peekable();
13918 while let Some(selection) = selections_iter.next() {
13919 if selection.start != selection.end {
13920 only_carets = false;
13921 }
13922
13923 if same_text_selected {
13924 if selected_text.is_none() {
13925 selected_text =
13926 Some(buffer.text_for_range(selection.range()).collect::<String>());
13927 }
13928
13929 if let Some(next_selection) = selections_iter.peek() {
13930 if next_selection.range().len() == selection.range().len() {
13931 let next_selected_text = buffer
13932 .text_for_range(next_selection.range())
13933 .collect::<String>();
13934 if Some(next_selected_text) != selected_text {
13935 same_text_selected = false;
13936 selected_text = None;
13937 }
13938 } else {
13939 same_text_selected = false;
13940 selected_text = None;
13941 }
13942 }
13943 }
13944 }
13945
13946 if only_carets {
13947 for selection in &mut selections {
13948 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13949 selection.start = word_range.start;
13950 selection.end = word_range.end;
13951 selection.goal = SelectionGoal::None;
13952 selection.reversed = false;
13953 self.select_match_ranges(
13954 selection.start..selection.end,
13955 selection.reversed,
13956 replace_newest,
13957 autoscroll,
13958 window,
13959 cx,
13960 );
13961 }
13962
13963 if selections.len() == 1 {
13964 let selection = selections
13965 .last()
13966 .expect("ensured that there's only one selection");
13967 let query = buffer
13968 .text_for_range(selection.start..selection.end)
13969 .collect::<String>();
13970 let is_empty = query.is_empty();
13971 let select_state = SelectNextState {
13972 query: AhoCorasick::new(&[query])?,
13973 wordwise: true,
13974 done: is_empty,
13975 };
13976 self.select_next_state = Some(select_state);
13977 } else {
13978 self.select_next_state = None;
13979 }
13980 } else if let Some(selected_text) = selected_text {
13981 self.select_next_state = Some(SelectNextState {
13982 query: AhoCorasick::new(&[selected_text])?,
13983 wordwise: false,
13984 done: false,
13985 });
13986 self.select_next_match_internal(
13987 display_map,
13988 replace_newest,
13989 autoscroll,
13990 window,
13991 cx,
13992 )?;
13993 }
13994 }
13995 Ok(())
13996 }
13997
13998 pub fn select_all_matches(
13999 &mut self,
14000 _action: &SelectAllMatches,
14001 window: &mut Window,
14002 cx: &mut Context<Self>,
14003 ) -> Result<()> {
14004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14005
14006 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14007
14008 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14009 let Some(select_next_state) = self.select_next_state.as_mut() else {
14010 return Ok(());
14011 };
14012 if select_next_state.done {
14013 return Ok(());
14014 }
14015
14016 let mut new_selections = Vec::new();
14017
14018 let reversed = self.selections.oldest::<usize>(cx).reversed;
14019 let buffer = &display_map.buffer_snapshot;
14020 let query_matches = select_next_state
14021 .query
14022 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14023
14024 for query_match in query_matches.into_iter() {
14025 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14026 let offset_range = if reversed {
14027 query_match.end()..query_match.start()
14028 } else {
14029 query_match.start()..query_match.end()
14030 };
14031
14032 if !select_next_state.wordwise
14033 || (!buffer.is_inside_word(offset_range.start, false)
14034 && !buffer.is_inside_word(offset_range.end, false))
14035 {
14036 new_selections.push(offset_range.start..offset_range.end);
14037 }
14038 }
14039
14040 select_next_state.done = true;
14041
14042 if new_selections.is_empty() {
14043 log::error!("bug: new_selections is empty in select_all_matches");
14044 return Ok(());
14045 }
14046
14047 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14048 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14049 selections.select_ranges(new_selections)
14050 });
14051
14052 Ok(())
14053 }
14054
14055 pub fn select_next(
14056 &mut self,
14057 action: &SelectNext,
14058 window: &mut Window,
14059 cx: &mut Context<Self>,
14060 ) -> Result<()> {
14061 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14062 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14063 self.select_next_match_internal(
14064 &display_map,
14065 action.replace_newest,
14066 Some(Autoscroll::newest()),
14067 window,
14068 cx,
14069 )?;
14070 Ok(())
14071 }
14072
14073 pub fn select_previous(
14074 &mut self,
14075 action: &SelectPrevious,
14076 window: &mut Window,
14077 cx: &mut Context<Self>,
14078 ) -> Result<()> {
14079 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14080 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14081 let buffer = &display_map.buffer_snapshot;
14082 let mut selections = self.selections.all::<usize>(cx);
14083 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14084 let query = &select_prev_state.query;
14085 if !select_prev_state.done {
14086 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14087 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14088 let mut next_selected_range = None;
14089 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14090 let bytes_before_last_selection =
14091 buffer.reversed_bytes_in_range(0..last_selection.start);
14092 let bytes_after_first_selection =
14093 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14094 let query_matches = query
14095 .stream_find_iter(bytes_before_last_selection)
14096 .map(|result| (last_selection.start, result))
14097 .chain(
14098 query
14099 .stream_find_iter(bytes_after_first_selection)
14100 .map(|result| (buffer.len(), result)),
14101 );
14102 for (end_offset, query_match) in query_matches {
14103 let query_match = query_match.unwrap(); // can only fail due to I/O
14104 let offset_range =
14105 end_offset - query_match.end()..end_offset - query_match.start();
14106
14107 if !select_prev_state.wordwise
14108 || (!buffer.is_inside_word(offset_range.start, false)
14109 && !buffer.is_inside_word(offset_range.end, false))
14110 {
14111 next_selected_range = Some(offset_range);
14112 break;
14113 }
14114 }
14115
14116 if let Some(next_selected_range) = next_selected_range {
14117 self.select_match_ranges(
14118 next_selected_range,
14119 last_selection.reversed,
14120 action.replace_newest,
14121 Some(Autoscroll::newest()),
14122 window,
14123 cx,
14124 );
14125 } else {
14126 select_prev_state.done = true;
14127 }
14128 }
14129
14130 self.select_prev_state = Some(select_prev_state);
14131 } else {
14132 let mut only_carets = true;
14133 let mut same_text_selected = true;
14134 let mut selected_text = None;
14135
14136 let mut selections_iter = selections.iter().peekable();
14137 while let Some(selection) = selections_iter.next() {
14138 if selection.start != selection.end {
14139 only_carets = false;
14140 }
14141
14142 if same_text_selected {
14143 if selected_text.is_none() {
14144 selected_text =
14145 Some(buffer.text_for_range(selection.range()).collect::<String>());
14146 }
14147
14148 if let Some(next_selection) = selections_iter.peek() {
14149 if next_selection.range().len() == selection.range().len() {
14150 let next_selected_text = buffer
14151 .text_for_range(next_selection.range())
14152 .collect::<String>();
14153 if Some(next_selected_text) != selected_text {
14154 same_text_selected = false;
14155 selected_text = None;
14156 }
14157 } else {
14158 same_text_selected = false;
14159 selected_text = None;
14160 }
14161 }
14162 }
14163 }
14164
14165 if only_carets {
14166 for selection in &mut selections {
14167 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14168 selection.start = word_range.start;
14169 selection.end = word_range.end;
14170 selection.goal = SelectionGoal::None;
14171 selection.reversed = false;
14172 self.select_match_ranges(
14173 selection.start..selection.end,
14174 selection.reversed,
14175 action.replace_newest,
14176 Some(Autoscroll::newest()),
14177 window,
14178 cx,
14179 );
14180 }
14181 if selections.len() == 1 {
14182 let selection = selections
14183 .last()
14184 .expect("ensured that there's only one selection");
14185 let query = buffer
14186 .text_for_range(selection.start..selection.end)
14187 .collect::<String>();
14188 let is_empty = query.is_empty();
14189 let select_state = SelectNextState {
14190 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14191 wordwise: true,
14192 done: is_empty,
14193 };
14194 self.select_prev_state = Some(select_state);
14195 } else {
14196 self.select_prev_state = None;
14197 }
14198 } else if let Some(selected_text) = selected_text {
14199 self.select_prev_state = Some(SelectNextState {
14200 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14201 wordwise: false,
14202 done: false,
14203 });
14204 self.select_previous(action, window, cx)?;
14205 }
14206 }
14207 Ok(())
14208 }
14209
14210 pub fn find_next_match(
14211 &mut self,
14212 _: &FindNextMatch,
14213 window: &mut Window,
14214 cx: &mut Context<Self>,
14215 ) -> Result<()> {
14216 let selections = self.selections.disjoint_anchors();
14217 match selections.first() {
14218 Some(first) if selections.len() >= 2 => {
14219 self.change_selections(Default::default(), window, cx, |s| {
14220 s.select_ranges([first.range()]);
14221 });
14222 }
14223 _ => self.select_next(
14224 &SelectNext {
14225 replace_newest: true,
14226 },
14227 window,
14228 cx,
14229 )?,
14230 }
14231 Ok(())
14232 }
14233
14234 pub fn find_previous_match(
14235 &mut self,
14236 _: &FindPreviousMatch,
14237 window: &mut Window,
14238 cx: &mut Context<Self>,
14239 ) -> Result<()> {
14240 let selections = self.selections.disjoint_anchors();
14241 match selections.last() {
14242 Some(last) if selections.len() >= 2 => {
14243 self.change_selections(Default::default(), window, cx, |s| {
14244 s.select_ranges([last.range()]);
14245 });
14246 }
14247 _ => self.select_previous(
14248 &SelectPrevious {
14249 replace_newest: true,
14250 },
14251 window,
14252 cx,
14253 )?,
14254 }
14255 Ok(())
14256 }
14257
14258 pub fn toggle_comments(
14259 &mut self,
14260 action: &ToggleComments,
14261 window: &mut Window,
14262 cx: &mut Context<Self>,
14263 ) {
14264 if self.read_only(cx) {
14265 return;
14266 }
14267 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14268 let text_layout_details = &self.text_layout_details(window);
14269 self.transact(window, cx, |this, window, cx| {
14270 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14271 let mut edits = Vec::new();
14272 let mut selection_edit_ranges = Vec::new();
14273 let mut last_toggled_row = None;
14274 let snapshot = this.buffer.read(cx).read(cx);
14275 let empty_str: Arc<str> = Arc::default();
14276 let mut suffixes_inserted = Vec::new();
14277 let ignore_indent = action.ignore_indent;
14278
14279 fn comment_prefix_range(
14280 snapshot: &MultiBufferSnapshot,
14281 row: MultiBufferRow,
14282 comment_prefix: &str,
14283 comment_prefix_whitespace: &str,
14284 ignore_indent: bool,
14285 ) -> Range<Point> {
14286 let indent_size = if ignore_indent {
14287 0
14288 } else {
14289 snapshot.indent_size_for_line(row).len
14290 };
14291
14292 let start = Point::new(row.0, indent_size);
14293
14294 let mut line_bytes = snapshot
14295 .bytes_in_range(start..snapshot.max_point())
14296 .flatten()
14297 .copied();
14298
14299 // If this line currently begins with the line comment prefix, then record
14300 // the range containing the prefix.
14301 if line_bytes
14302 .by_ref()
14303 .take(comment_prefix.len())
14304 .eq(comment_prefix.bytes())
14305 {
14306 // Include any whitespace that matches the comment prefix.
14307 let matching_whitespace_len = line_bytes
14308 .zip(comment_prefix_whitespace.bytes())
14309 .take_while(|(a, b)| a == b)
14310 .count() as u32;
14311 let end = Point::new(
14312 start.row,
14313 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14314 );
14315 start..end
14316 } else {
14317 start..start
14318 }
14319 }
14320
14321 fn comment_suffix_range(
14322 snapshot: &MultiBufferSnapshot,
14323 row: MultiBufferRow,
14324 comment_suffix: &str,
14325 comment_suffix_has_leading_space: bool,
14326 ) -> Range<Point> {
14327 let end = Point::new(row.0, snapshot.line_len(row));
14328 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14329
14330 let mut line_end_bytes = snapshot
14331 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14332 .flatten()
14333 .copied();
14334
14335 let leading_space_len = if suffix_start_column > 0
14336 && line_end_bytes.next() == Some(b' ')
14337 && comment_suffix_has_leading_space
14338 {
14339 1
14340 } else {
14341 0
14342 };
14343
14344 // If this line currently begins with the line comment prefix, then record
14345 // the range containing the prefix.
14346 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14347 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14348 start..end
14349 } else {
14350 end..end
14351 }
14352 }
14353
14354 // TODO: Handle selections that cross excerpts
14355 for selection in &mut selections {
14356 let start_column = snapshot
14357 .indent_size_for_line(MultiBufferRow(selection.start.row))
14358 .len;
14359 let language = if let Some(language) =
14360 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14361 {
14362 language
14363 } else {
14364 continue;
14365 };
14366
14367 selection_edit_ranges.clear();
14368
14369 // If multiple selections contain a given row, avoid processing that
14370 // row more than once.
14371 let mut start_row = MultiBufferRow(selection.start.row);
14372 if last_toggled_row == Some(start_row) {
14373 start_row = start_row.next_row();
14374 }
14375 let end_row =
14376 if selection.end.row > selection.start.row && selection.end.column == 0 {
14377 MultiBufferRow(selection.end.row - 1)
14378 } else {
14379 MultiBufferRow(selection.end.row)
14380 };
14381 last_toggled_row = Some(end_row);
14382
14383 if start_row > end_row {
14384 continue;
14385 }
14386
14387 // If the language has line comments, toggle those.
14388 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14389
14390 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14391 if ignore_indent {
14392 full_comment_prefixes = full_comment_prefixes
14393 .into_iter()
14394 .map(|s| Arc::from(s.trim_end()))
14395 .collect();
14396 }
14397
14398 if !full_comment_prefixes.is_empty() {
14399 let first_prefix = full_comment_prefixes
14400 .first()
14401 .expect("prefixes is non-empty");
14402 let prefix_trimmed_lengths = full_comment_prefixes
14403 .iter()
14404 .map(|p| p.trim_end_matches(' ').len())
14405 .collect::<SmallVec<[usize; 4]>>();
14406
14407 let mut all_selection_lines_are_comments = true;
14408
14409 for row in start_row.0..=end_row.0 {
14410 let row = MultiBufferRow(row);
14411 if start_row < end_row && snapshot.is_line_blank(row) {
14412 continue;
14413 }
14414
14415 let prefix_range = full_comment_prefixes
14416 .iter()
14417 .zip(prefix_trimmed_lengths.iter().copied())
14418 .map(|(prefix, trimmed_prefix_len)| {
14419 comment_prefix_range(
14420 snapshot.deref(),
14421 row,
14422 &prefix[..trimmed_prefix_len],
14423 &prefix[trimmed_prefix_len..],
14424 ignore_indent,
14425 )
14426 })
14427 .max_by_key(|range| range.end.column - range.start.column)
14428 .expect("prefixes is non-empty");
14429
14430 if prefix_range.is_empty() {
14431 all_selection_lines_are_comments = false;
14432 }
14433
14434 selection_edit_ranges.push(prefix_range);
14435 }
14436
14437 if all_selection_lines_are_comments {
14438 edits.extend(
14439 selection_edit_ranges
14440 .iter()
14441 .cloned()
14442 .map(|range| (range, empty_str.clone())),
14443 );
14444 } else {
14445 let min_column = selection_edit_ranges
14446 .iter()
14447 .map(|range| range.start.column)
14448 .min()
14449 .unwrap_or(0);
14450 edits.extend(selection_edit_ranges.iter().map(|range| {
14451 let position = Point::new(range.start.row, min_column);
14452 (position..position, first_prefix.clone())
14453 }));
14454 }
14455 } else if let Some(BlockCommentConfig {
14456 start: full_comment_prefix,
14457 end: comment_suffix,
14458 ..
14459 }) = language.block_comment()
14460 {
14461 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14462 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14463 let prefix_range = comment_prefix_range(
14464 snapshot.deref(),
14465 start_row,
14466 comment_prefix,
14467 comment_prefix_whitespace,
14468 ignore_indent,
14469 );
14470 let suffix_range = comment_suffix_range(
14471 snapshot.deref(),
14472 end_row,
14473 comment_suffix.trim_start_matches(' '),
14474 comment_suffix.starts_with(' '),
14475 );
14476
14477 if prefix_range.is_empty() || suffix_range.is_empty() {
14478 edits.push((
14479 prefix_range.start..prefix_range.start,
14480 full_comment_prefix.clone(),
14481 ));
14482 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14483 suffixes_inserted.push((end_row, comment_suffix.len()));
14484 } else {
14485 edits.push((prefix_range, empty_str.clone()));
14486 edits.push((suffix_range, empty_str.clone()));
14487 }
14488 } else {
14489 continue;
14490 }
14491 }
14492
14493 drop(snapshot);
14494 this.buffer.update(cx, |buffer, cx| {
14495 buffer.edit(edits, None, cx);
14496 });
14497
14498 // Adjust selections so that they end before any comment suffixes that
14499 // were inserted.
14500 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14501 let mut selections = this.selections.all::<Point>(cx);
14502 let snapshot = this.buffer.read(cx).read(cx);
14503 for selection in &mut selections {
14504 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14505 match row.cmp(&MultiBufferRow(selection.end.row)) {
14506 Ordering::Less => {
14507 suffixes_inserted.next();
14508 continue;
14509 }
14510 Ordering::Greater => break,
14511 Ordering::Equal => {
14512 if selection.end.column == snapshot.line_len(row) {
14513 if selection.is_empty() {
14514 selection.start.column -= suffix_len as u32;
14515 }
14516 selection.end.column -= suffix_len as u32;
14517 }
14518 break;
14519 }
14520 }
14521 }
14522 }
14523
14524 drop(snapshot);
14525 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14526
14527 let selections = this.selections.all::<Point>(cx);
14528 let selections_on_single_row = selections.windows(2).all(|selections| {
14529 selections[0].start.row == selections[1].start.row
14530 && selections[0].end.row == selections[1].end.row
14531 && selections[0].start.row == selections[0].end.row
14532 });
14533 let selections_selecting = selections
14534 .iter()
14535 .any(|selection| selection.start != selection.end);
14536 let advance_downwards = action.advance_downwards
14537 && selections_on_single_row
14538 && !selections_selecting
14539 && !matches!(this.mode, EditorMode::SingleLine { .. });
14540
14541 if advance_downwards {
14542 let snapshot = this.buffer.read(cx).snapshot(cx);
14543
14544 this.change_selections(Default::default(), window, cx, |s| {
14545 s.move_cursors_with(|display_snapshot, display_point, _| {
14546 let mut point = display_point.to_point(display_snapshot);
14547 point.row += 1;
14548 point = snapshot.clip_point(point, Bias::Left);
14549 let display_point = point.to_display_point(display_snapshot);
14550 let goal = SelectionGoal::HorizontalPosition(
14551 display_snapshot
14552 .x_for_display_point(display_point, text_layout_details)
14553 .into(),
14554 );
14555 (display_point, goal)
14556 })
14557 });
14558 }
14559 });
14560 }
14561
14562 pub fn select_enclosing_symbol(
14563 &mut self,
14564 _: &SelectEnclosingSymbol,
14565 window: &mut Window,
14566 cx: &mut Context<Self>,
14567 ) {
14568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14569
14570 let buffer = self.buffer.read(cx).snapshot(cx);
14571 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14572
14573 fn update_selection(
14574 selection: &Selection<usize>,
14575 buffer_snap: &MultiBufferSnapshot,
14576 ) -> Option<Selection<usize>> {
14577 let cursor = selection.head();
14578 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14579 for symbol in symbols.iter().rev() {
14580 let start = symbol.range.start.to_offset(buffer_snap);
14581 let end = symbol.range.end.to_offset(buffer_snap);
14582 let new_range = start..end;
14583 if start < selection.start || end > selection.end {
14584 return Some(Selection {
14585 id: selection.id,
14586 start: new_range.start,
14587 end: new_range.end,
14588 goal: SelectionGoal::None,
14589 reversed: selection.reversed,
14590 });
14591 }
14592 }
14593 None
14594 }
14595
14596 let mut selected_larger_symbol = false;
14597 let new_selections = old_selections
14598 .iter()
14599 .map(|selection| match update_selection(selection, &buffer) {
14600 Some(new_selection) => {
14601 if new_selection.range() != selection.range() {
14602 selected_larger_symbol = true;
14603 }
14604 new_selection
14605 }
14606 None => selection.clone(),
14607 })
14608 .collect::<Vec<_>>();
14609
14610 if selected_larger_symbol {
14611 self.change_selections(Default::default(), window, cx, |s| {
14612 s.select(new_selections);
14613 });
14614 }
14615 }
14616
14617 pub fn select_larger_syntax_node(
14618 &mut self,
14619 _: &SelectLargerSyntaxNode,
14620 window: &mut Window,
14621 cx: &mut Context<Self>,
14622 ) {
14623 let Some(visible_row_count) = self.visible_row_count() else {
14624 return;
14625 };
14626 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14627 if old_selections.is_empty() {
14628 return;
14629 }
14630
14631 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14632
14633 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14634 let buffer = self.buffer.read(cx).snapshot(cx);
14635
14636 let mut selected_larger_node = false;
14637 let mut new_selections = old_selections
14638 .iter()
14639 .map(|selection| {
14640 let old_range = selection.start..selection.end;
14641
14642 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14643 // manually select word at selection
14644 if ["string_content", "inline"].contains(&node.kind()) {
14645 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14646 // ignore if word is already selected
14647 if !word_range.is_empty() && old_range != word_range {
14648 let (last_word_range, _) =
14649 buffer.surrounding_word(old_range.end, false);
14650 // only select word if start and end point belongs to same word
14651 if word_range == last_word_range {
14652 selected_larger_node = true;
14653 return Selection {
14654 id: selection.id,
14655 start: word_range.start,
14656 end: word_range.end,
14657 goal: SelectionGoal::None,
14658 reversed: selection.reversed,
14659 };
14660 }
14661 }
14662 }
14663 }
14664
14665 let mut new_range = old_range.clone();
14666 while let Some((_node, containing_range)) =
14667 buffer.syntax_ancestor(new_range.clone())
14668 {
14669 new_range = match containing_range {
14670 MultiOrSingleBufferOffsetRange::Single(_) => break,
14671 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14672 };
14673 if !display_map.intersects_fold(new_range.start)
14674 && !display_map.intersects_fold(new_range.end)
14675 {
14676 break;
14677 }
14678 }
14679
14680 selected_larger_node |= new_range != old_range;
14681 Selection {
14682 id: selection.id,
14683 start: new_range.start,
14684 end: new_range.end,
14685 goal: SelectionGoal::None,
14686 reversed: selection.reversed,
14687 }
14688 })
14689 .collect::<Vec<_>>();
14690
14691 if !selected_larger_node {
14692 return; // don't put this call in the history
14693 }
14694
14695 // scroll based on transformation done to the last selection created by the user
14696 let (last_old, last_new) = old_selections
14697 .last()
14698 .zip(new_selections.last().cloned())
14699 .expect("old_selections isn't empty");
14700
14701 // revert selection
14702 let is_selection_reversed = {
14703 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14704 new_selections.last_mut().expect("checked above").reversed =
14705 should_newest_selection_be_reversed;
14706 should_newest_selection_be_reversed
14707 };
14708
14709 if selected_larger_node {
14710 self.select_syntax_node_history.disable_clearing = true;
14711 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14712 s.select(new_selections.clone());
14713 });
14714 self.select_syntax_node_history.disable_clearing = false;
14715 }
14716
14717 let start_row = last_new.start.to_display_point(&display_map).row().0;
14718 let end_row = last_new.end.to_display_point(&display_map).row().0;
14719 let selection_height = end_row - start_row + 1;
14720 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14721
14722 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14723 let scroll_behavior = if fits_on_the_screen {
14724 self.request_autoscroll(Autoscroll::fit(), cx);
14725 SelectSyntaxNodeScrollBehavior::FitSelection
14726 } else if is_selection_reversed {
14727 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14728 SelectSyntaxNodeScrollBehavior::CursorTop
14729 } else {
14730 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14731 SelectSyntaxNodeScrollBehavior::CursorBottom
14732 };
14733
14734 self.select_syntax_node_history.push((
14735 old_selections,
14736 scroll_behavior,
14737 is_selection_reversed,
14738 ));
14739 }
14740
14741 pub fn select_smaller_syntax_node(
14742 &mut self,
14743 _: &SelectSmallerSyntaxNode,
14744 window: &mut Window,
14745 cx: &mut Context<Self>,
14746 ) {
14747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14748
14749 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14750 self.select_syntax_node_history.pop()
14751 {
14752 if let Some(selection) = selections.last_mut() {
14753 selection.reversed = is_selection_reversed;
14754 }
14755
14756 self.select_syntax_node_history.disable_clearing = true;
14757 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14758 s.select(selections.to_vec());
14759 });
14760 self.select_syntax_node_history.disable_clearing = false;
14761
14762 match scroll_behavior {
14763 SelectSyntaxNodeScrollBehavior::CursorTop => {
14764 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14765 }
14766 SelectSyntaxNodeScrollBehavior::FitSelection => {
14767 self.request_autoscroll(Autoscroll::fit(), cx);
14768 }
14769 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14770 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14771 }
14772 }
14773 }
14774 }
14775
14776 pub fn unwrap_syntax_node(
14777 &mut self,
14778 _: &UnwrapSyntaxNode,
14779 window: &mut Window,
14780 cx: &mut Context<Self>,
14781 ) {
14782 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14783
14784 let buffer = self.buffer.read(cx).snapshot(cx);
14785 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14786
14787 let edits = old_selections
14788 .iter()
14789 // only consider the first selection for now
14790 .take(1)
14791 .map(|selection| {
14792 // Only requires two branches once if-let-chains stabilize (#53667)
14793 let selection_range = if !selection.is_empty() {
14794 selection.range()
14795 } else if let Some((_, ancestor_range)) =
14796 buffer.syntax_ancestor(selection.start..selection.end)
14797 {
14798 match ancestor_range {
14799 MultiOrSingleBufferOffsetRange::Single(range) => range,
14800 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14801 }
14802 } else {
14803 selection.range()
14804 };
14805
14806 let mut new_range = selection_range.clone();
14807 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(new_range.clone()) {
14808 new_range = match ancestor_range {
14809 MultiOrSingleBufferOffsetRange::Single(range) => range,
14810 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14811 };
14812 if new_range.start < selection_range.start
14813 || new_range.end > selection_range.end
14814 {
14815 break;
14816 }
14817 }
14818
14819 (selection, selection_range, new_range)
14820 })
14821 .collect::<Vec<_>>();
14822
14823 self.transact(window, cx, |editor, window, cx| {
14824 for (_, child, parent) in &edits {
14825 let text = buffer.text_for_range(child.clone()).collect::<String>();
14826 editor.replace_text_in_range(Some(parent.clone()), &text, window, cx);
14827 }
14828
14829 editor.change_selections(
14830 SelectionEffects::scroll(Autoscroll::fit()),
14831 window,
14832 cx,
14833 |s| {
14834 s.select(
14835 edits
14836 .iter()
14837 .map(|(s, old, new)| Selection {
14838 id: s.id,
14839 start: new.start,
14840 end: new.start + old.len(),
14841 goal: SelectionGoal::None,
14842 reversed: s.reversed,
14843 })
14844 .collect(),
14845 );
14846 },
14847 );
14848 });
14849 }
14850
14851 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14852 if !EditorSettings::get_global(cx).gutter.runnables {
14853 self.clear_tasks();
14854 return Task::ready(());
14855 }
14856 let project = self.project.as_ref().map(Entity::downgrade);
14857 let task_sources = self.lsp_task_sources(cx);
14858 let multi_buffer = self.buffer.downgrade();
14859 cx.spawn_in(window, async move |editor, cx| {
14860 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14861 let Some(project) = project.and_then(|p| p.upgrade()) else {
14862 return;
14863 };
14864 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14865 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14866 }) else {
14867 return;
14868 };
14869
14870 let hide_runnables = project
14871 .update(cx, |project, cx| {
14872 // Do not display any test indicators in non-dev server remote projects.
14873 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14874 })
14875 .unwrap_or(true);
14876 if hide_runnables {
14877 return;
14878 }
14879 let new_rows =
14880 cx.background_spawn({
14881 let snapshot = display_snapshot.clone();
14882 async move {
14883 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14884 }
14885 })
14886 .await;
14887 let Ok(lsp_tasks) =
14888 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14889 else {
14890 return;
14891 };
14892 let lsp_tasks = lsp_tasks.await;
14893
14894 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14895 lsp_tasks
14896 .into_iter()
14897 .flat_map(|(kind, tasks)| {
14898 tasks.into_iter().filter_map(move |(location, task)| {
14899 Some((kind.clone(), location?, task))
14900 })
14901 })
14902 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14903 let buffer = location.target.buffer;
14904 let buffer_snapshot = buffer.read(cx).snapshot();
14905 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14906 |(excerpt_id, snapshot, _)| {
14907 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14908 display_snapshot
14909 .buffer_snapshot
14910 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14911 } else {
14912 None
14913 }
14914 },
14915 );
14916 if let Some(offset) = offset {
14917 let task_buffer_range =
14918 location.target.range.to_point(&buffer_snapshot);
14919 let context_buffer_range =
14920 task_buffer_range.to_offset(&buffer_snapshot);
14921 let context_range = BufferOffset(context_buffer_range.start)
14922 ..BufferOffset(context_buffer_range.end);
14923
14924 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14925 .or_insert_with(|| RunnableTasks {
14926 templates: Vec::new(),
14927 offset,
14928 column: task_buffer_range.start.column,
14929 extra_variables: HashMap::default(),
14930 context_range,
14931 })
14932 .templates
14933 .push((kind, task.original_task().clone()));
14934 }
14935
14936 acc
14937 })
14938 }) else {
14939 return;
14940 };
14941
14942 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14943 buffer.language_settings(cx).tasks.prefer_lsp
14944 }) else {
14945 return;
14946 };
14947
14948 let rows = Self::runnable_rows(
14949 project,
14950 display_snapshot,
14951 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14952 new_rows,
14953 cx.clone(),
14954 )
14955 .await;
14956 editor
14957 .update(cx, |editor, _| {
14958 editor.clear_tasks();
14959 for (key, mut value) in rows {
14960 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14961 value.templates.extend(lsp_tasks.templates);
14962 }
14963
14964 editor.insert_tasks(key, value);
14965 }
14966 for (key, value) in lsp_tasks_by_rows {
14967 editor.insert_tasks(key, value);
14968 }
14969 })
14970 .ok();
14971 })
14972 }
14973 fn fetch_runnable_ranges(
14974 snapshot: &DisplaySnapshot,
14975 range: Range<Anchor>,
14976 ) -> Vec<language::RunnableRange> {
14977 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14978 }
14979
14980 fn runnable_rows(
14981 project: Entity<Project>,
14982 snapshot: DisplaySnapshot,
14983 prefer_lsp: bool,
14984 runnable_ranges: Vec<RunnableRange>,
14985 cx: AsyncWindowContext,
14986 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14987 cx.spawn(async move |cx| {
14988 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14989 for mut runnable in runnable_ranges {
14990 let Some(tasks) = cx
14991 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14992 .ok()
14993 else {
14994 continue;
14995 };
14996 let mut tasks = tasks.await;
14997
14998 if prefer_lsp {
14999 tasks.retain(|(task_kind, _)| {
15000 !matches!(task_kind, TaskSourceKind::Language { .. })
15001 });
15002 }
15003 if tasks.is_empty() {
15004 continue;
15005 }
15006
15007 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15008 let Some(row) = snapshot
15009 .buffer_snapshot
15010 .buffer_line_for_row(MultiBufferRow(point.row))
15011 .map(|(_, range)| range.start.row)
15012 else {
15013 continue;
15014 };
15015
15016 let context_range =
15017 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15018 runnable_rows.push((
15019 (runnable.buffer_id, row),
15020 RunnableTasks {
15021 templates: tasks,
15022 offset: snapshot
15023 .buffer_snapshot
15024 .anchor_before(runnable.run_range.start),
15025 context_range,
15026 column: point.column,
15027 extra_variables: runnable.extra_captures,
15028 },
15029 ));
15030 }
15031 runnable_rows
15032 })
15033 }
15034
15035 fn templates_with_tags(
15036 project: &Entity<Project>,
15037 runnable: &mut Runnable,
15038 cx: &mut App,
15039 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15040 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15041 let (worktree_id, file) = project
15042 .buffer_for_id(runnable.buffer, cx)
15043 .and_then(|buffer| buffer.read(cx).file())
15044 .map(|file| (file.worktree_id(cx), file.clone()))
15045 .unzip();
15046
15047 (
15048 project.task_store().read(cx).task_inventory().cloned(),
15049 worktree_id,
15050 file,
15051 )
15052 });
15053
15054 let tags = mem::take(&mut runnable.tags);
15055 let language = runnable.language.clone();
15056 cx.spawn(async move |cx| {
15057 let mut templates_with_tags = Vec::new();
15058 if let Some(inventory) = inventory {
15059 for RunnableTag(tag) in tags {
15060 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15061 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15062 }) else {
15063 return templates_with_tags;
15064 };
15065 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15066 move |(_, template)| {
15067 template.tags.iter().any(|source_tag| source_tag == &tag)
15068 },
15069 ));
15070 }
15071 }
15072 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15073
15074 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15075 // Strongest source wins; if we have worktree tag binding, prefer that to
15076 // global and language bindings;
15077 // if we have a global binding, prefer that to language binding.
15078 let first_mismatch = templates_with_tags
15079 .iter()
15080 .position(|(tag_source, _)| tag_source != leading_tag_source);
15081 if let Some(index) = first_mismatch {
15082 templates_with_tags.truncate(index);
15083 }
15084 }
15085
15086 templates_with_tags
15087 })
15088 }
15089
15090 pub fn move_to_enclosing_bracket(
15091 &mut self,
15092 _: &MoveToEnclosingBracket,
15093 window: &mut Window,
15094 cx: &mut Context<Self>,
15095 ) {
15096 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15097 self.change_selections(Default::default(), window, cx, |s| {
15098 s.move_offsets_with(|snapshot, selection| {
15099 let Some(enclosing_bracket_ranges) =
15100 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15101 else {
15102 return;
15103 };
15104
15105 let mut best_length = usize::MAX;
15106 let mut best_inside = false;
15107 let mut best_in_bracket_range = false;
15108 let mut best_destination = None;
15109 for (open, close) in enclosing_bracket_ranges {
15110 let close = close.to_inclusive();
15111 let length = close.end() - open.start;
15112 let inside = selection.start >= open.end && selection.end <= *close.start();
15113 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15114 || close.contains(&selection.head());
15115
15116 // If best is next to a bracket and current isn't, skip
15117 if !in_bracket_range && best_in_bracket_range {
15118 continue;
15119 }
15120
15121 // Prefer smaller lengths unless best is inside and current isn't
15122 if length > best_length && (best_inside || !inside) {
15123 continue;
15124 }
15125
15126 best_length = length;
15127 best_inside = inside;
15128 best_in_bracket_range = in_bracket_range;
15129 best_destination = Some(
15130 if close.contains(&selection.start) && close.contains(&selection.end) {
15131 if inside { open.end } else { open.start }
15132 } else if inside {
15133 *close.start()
15134 } else {
15135 *close.end()
15136 },
15137 );
15138 }
15139
15140 if let Some(destination) = best_destination {
15141 selection.collapse_to(destination, SelectionGoal::None);
15142 }
15143 })
15144 });
15145 }
15146
15147 pub fn undo_selection(
15148 &mut self,
15149 _: &UndoSelection,
15150 window: &mut Window,
15151 cx: &mut Context<Self>,
15152 ) {
15153 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15154 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15155 self.selection_history.mode = SelectionHistoryMode::Undoing;
15156 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15157 this.end_selection(window, cx);
15158 this.change_selections(
15159 SelectionEffects::scroll(Autoscroll::newest()),
15160 window,
15161 cx,
15162 |s| s.select_anchors(entry.selections.to_vec()),
15163 );
15164 });
15165 self.selection_history.mode = SelectionHistoryMode::Normal;
15166
15167 self.select_next_state = entry.select_next_state;
15168 self.select_prev_state = entry.select_prev_state;
15169 self.add_selections_state = entry.add_selections_state;
15170 }
15171 }
15172
15173 pub fn redo_selection(
15174 &mut self,
15175 _: &RedoSelection,
15176 window: &mut Window,
15177 cx: &mut Context<Self>,
15178 ) {
15179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15180 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15181 self.selection_history.mode = SelectionHistoryMode::Redoing;
15182 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15183 this.end_selection(window, cx);
15184 this.change_selections(
15185 SelectionEffects::scroll(Autoscroll::newest()),
15186 window,
15187 cx,
15188 |s| s.select_anchors(entry.selections.to_vec()),
15189 );
15190 });
15191 self.selection_history.mode = SelectionHistoryMode::Normal;
15192
15193 self.select_next_state = entry.select_next_state;
15194 self.select_prev_state = entry.select_prev_state;
15195 self.add_selections_state = entry.add_selections_state;
15196 }
15197 }
15198
15199 pub fn expand_excerpts(
15200 &mut self,
15201 action: &ExpandExcerpts,
15202 _: &mut Window,
15203 cx: &mut Context<Self>,
15204 ) {
15205 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15206 }
15207
15208 pub fn expand_excerpts_down(
15209 &mut self,
15210 action: &ExpandExcerptsDown,
15211 _: &mut Window,
15212 cx: &mut Context<Self>,
15213 ) {
15214 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15215 }
15216
15217 pub fn expand_excerpts_up(
15218 &mut self,
15219 action: &ExpandExcerptsUp,
15220 _: &mut Window,
15221 cx: &mut Context<Self>,
15222 ) {
15223 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15224 }
15225
15226 pub fn expand_excerpts_for_direction(
15227 &mut self,
15228 lines: u32,
15229 direction: ExpandExcerptDirection,
15230
15231 cx: &mut Context<Self>,
15232 ) {
15233 let selections = self.selections.disjoint_anchors();
15234
15235 let lines = if lines == 0 {
15236 EditorSettings::get_global(cx).expand_excerpt_lines
15237 } else {
15238 lines
15239 };
15240
15241 self.buffer.update(cx, |buffer, cx| {
15242 let snapshot = buffer.snapshot(cx);
15243 let mut excerpt_ids = selections
15244 .iter()
15245 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15246 .collect::<Vec<_>>();
15247 excerpt_ids.sort();
15248 excerpt_ids.dedup();
15249 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15250 })
15251 }
15252
15253 pub fn expand_excerpt(
15254 &mut self,
15255 excerpt: ExcerptId,
15256 direction: ExpandExcerptDirection,
15257 window: &mut Window,
15258 cx: &mut Context<Self>,
15259 ) {
15260 let current_scroll_position = self.scroll_position(cx);
15261 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15262 let mut should_scroll_up = false;
15263
15264 if direction == ExpandExcerptDirection::Down {
15265 let multi_buffer = self.buffer.read(cx);
15266 let snapshot = multi_buffer.snapshot(cx);
15267 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15268 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15269 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15270 let buffer_snapshot = buffer.read(cx).snapshot();
15271 let excerpt_end_row =
15272 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15273 let last_row = buffer_snapshot.max_point().row;
15274 let lines_below = last_row.saturating_sub(excerpt_end_row);
15275 should_scroll_up = lines_below >= lines_to_expand;
15276 }
15277 }
15278 }
15279 }
15280
15281 self.buffer.update(cx, |buffer, cx| {
15282 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15283 });
15284
15285 if should_scroll_up {
15286 let new_scroll_position =
15287 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15288 self.set_scroll_position(new_scroll_position, window, cx);
15289 }
15290 }
15291
15292 pub fn go_to_singleton_buffer_point(
15293 &mut self,
15294 point: Point,
15295 window: &mut Window,
15296 cx: &mut Context<Self>,
15297 ) {
15298 self.go_to_singleton_buffer_range(point..point, window, cx);
15299 }
15300
15301 pub fn go_to_singleton_buffer_range(
15302 &mut self,
15303 range: Range<Point>,
15304 window: &mut Window,
15305 cx: &mut Context<Self>,
15306 ) {
15307 let multibuffer = self.buffer().read(cx);
15308 let Some(buffer) = multibuffer.as_singleton() else {
15309 return;
15310 };
15311 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15312 return;
15313 };
15314 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15315 return;
15316 };
15317 self.change_selections(
15318 SelectionEffects::default().nav_history(true),
15319 window,
15320 cx,
15321 |s| s.select_anchor_ranges([start..end]),
15322 );
15323 }
15324
15325 pub fn go_to_diagnostic(
15326 &mut self,
15327 action: &GoToDiagnostic,
15328 window: &mut Window,
15329 cx: &mut Context<Self>,
15330 ) {
15331 if !self.diagnostics_enabled() {
15332 return;
15333 }
15334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15335 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15336 }
15337
15338 pub fn go_to_prev_diagnostic(
15339 &mut self,
15340 action: &GoToPreviousDiagnostic,
15341 window: &mut Window,
15342 cx: &mut Context<Self>,
15343 ) {
15344 if !self.diagnostics_enabled() {
15345 return;
15346 }
15347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15348 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15349 }
15350
15351 pub fn go_to_diagnostic_impl(
15352 &mut self,
15353 direction: Direction,
15354 severity: GoToDiagnosticSeverityFilter,
15355 window: &mut Window,
15356 cx: &mut Context<Self>,
15357 ) {
15358 let buffer = self.buffer.read(cx).snapshot(cx);
15359 let selection = self.selections.newest::<usize>(cx);
15360
15361 let mut active_group_id = None;
15362 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15363 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15364 active_group_id = Some(active_group.group_id);
15365 }
15366 }
15367
15368 fn filtered(
15369 snapshot: EditorSnapshot,
15370 severity: GoToDiagnosticSeverityFilter,
15371 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15372 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15373 diagnostics
15374 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15375 .filter(|entry| entry.range.start != entry.range.end)
15376 .filter(|entry| !entry.diagnostic.is_unnecessary)
15377 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15378 }
15379
15380 let snapshot = self.snapshot(window, cx);
15381 let before = filtered(
15382 snapshot.clone(),
15383 severity,
15384 buffer
15385 .diagnostics_in_range(0..selection.start)
15386 .filter(|entry| entry.range.start <= selection.start),
15387 );
15388 let after = filtered(
15389 snapshot,
15390 severity,
15391 buffer
15392 .diagnostics_in_range(selection.start..buffer.len())
15393 .filter(|entry| entry.range.start >= selection.start),
15394 );
15395
15396 let mut found: Option<DiagnosticEntry<usize>> = None;
15397 if direction == Direction::Prev {
15398 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15399 {
15400 for diagnostic in prev_diagnostics.into_iter().rev() {
15401 if diagnostic.range.start != selection.start
15402 || active_group_id
15403 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15404 {
15405 found = Some(diagnostic);
15406 break 'outer;
15407 }
15408 }
15409 }
15410 } else {
15411 for diagnostic in after.chain(before) {
15412 if diagnostic.range.start != selection.start
15413 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15414 {
15415 found = Some(diagnostic);
15416 break;
15417 }
15418 }
15419 }
15420 let Some(next_diagnostic) = found else {
15421 return;
15422 };
15423
15424 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15425 return;
15426 };
15427 self.change_selections(Default::default(), window, cx, |s| {
15428 s.select_ranges(vec![
15429 next_diagnostic.range.start..next_diagnostic.range.start,
15430 ])
15431 });
15432 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15433 self.refresh_edit_prediction(false, true, window, cx);
15434 }
15435
15436 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15437 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15438 let snapshot = self.snapshot(window, cx);
15439 let selection = self.selections.newest::<Point>(cx);
15440 self.go_to_hunk_before_or_after_position(
15441 &snapshot,
15442 selection.head(),
15443 Direction::Next,
15444 window,
15445 cx,
15446 );
15447 }
15448
15449 pub fn go_to_hunk_before_or_after_position(
15450 &mut self,
15451 snapshot: &EditorSnapshot,
15452 position: Point,
15453 direction: Direction,
15454 window: &mut Window,
15455 cx: &mut Context<Editor>,
15456 ) {
15457 let row = if direction == Direction::Next {
15458 self.hunk_after_position(snapshot, position)
15459 .map(|hunk| hunk.row_range.start)
15460 } else {
15461 self.hunk_before_position(snapshot, position)
15462 };
15463
15464 if let Some(row) = row {
15465 let destination = Point::new(row.0, 0);
15466 let autoscroll = Autoscroll::center();
15467
15468 self.unfold_ranges(&[destination..destination], false, false, cx);
15469 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15470 s.select_ranges([destination..destination]);
15471 });
15472 }
15473 }
15474
15475 fn hunk_after_position(
15476 &mut self,
15477 snapshot: &EditorSnapshot,
15478 position: Point,
15479 ) -> Option<MultiBufferDiffHunk> {
15480 snapshot
15481 .buffer_snapshot
15482 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15483 .find(|hunk| hunk.row_range.start.0 > position.row)
15484 .or_else(|| {
15485 snapshot
15486 .buffer_snapshot
15487 .diff_hunks_in_range(Point::zero()..position)
15488 .find(|hunk| hunk.row_range.end.0 < position.row)
15489 })
15490 }
15491
15492 fn go_to_prev_hunk(
15493 &mut self,
15494 _: &GoToPreviousHunk,
15495 window: &mut Window,
15496 cx: &mut Context<Self>,
15497 ) {
15498 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15499 let snapshot = self.snapshot(window, cx);
15500 let selection = self.selections.newest::<Point>(cx);
15501 self.go_to_hunk_before_or_after_position(
15502 &snapshot,
15503 selection.head(),
15504 Direction::Prev,
15505 window,
15506 cx,
15507 );
15508 }
15509
15510 fn hunk_before_position(
15511 &mut self,
15512 snapshot: &EditorSnapshot,
15513 position: Point,
15514 ) -> Option<MultiBufferRow> {
15515 snapshot
15516 .buffer_snapshot
15517 .diff_hunk_before(position)
15518 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15519 }
15520
15521 fn go_to_next_change(
15522 &mut self,
15523 _: &GoToNextChange,
15524 window: &mut Window,
15525 cx: &mut Context<Self>,
15526 ) {
15527 if let Some(selections) = self
15528 .change_list
15529 .next_change(1, Direction::Next)
15530 .map(|s| s.to_vec())
15531 {
15532 self.change_selections(Default::default(), window, cx, |s| {
15533 let map = s.display_map();
15534 s.select_display_ranges(selections.iter().map(|a| {
15535 let point = a.to_display_point(&map);
15536 point..point
15537 }))
15538 })
15539 }
15540 }
15541
15542 fn go_to_previous_change(
15543 &mut self,
15544 _: &GoToPreviousChange,
15545 window: &mut Window,
15546 cx: &mut Context<Self>,
15547 ) {
15548 if let Some(selections) = self
15549 .change_list
15550 .next_change(1, Direction::Prev)
15551 .map(|s| s.to_vec())
15552 {
15553 self.change_selections(Default::default(), window, cx, |s| {
15554 let map = s.display_map();
15555 s.select_display_ranges(selections.iter().map(|a| {
15556 let point = a.to_display_point(&map);
15557 point..point
15558 }))
15559 })
15560 }
15561 }
15562
15563 fn go_to_line<T: 'static>(
15564 &mut self,
15565 position: Anchor,
15566 highlight_color: Option<Hsla>,
15567 window: &mut Window,
15568 cx: &mut Context<Self>,
15569 ) {
15570 let snapshot = self.snapshot(window, cx).display_snapshot;
15571 let position = position.to_point(&snapshot.buffer_snapshot);
15572 let start = snapshot
15573 .buffer_snapshot
15574 .clip_point(Point::new(position.row, 0), Bias::Left);
15575 let end = start + Point::new(1, 0);
15576 let start = snapshot.buffer_snapshot.anchor_before(start);
15577 let end = snapshot.buffer_snapshot.anchor_before(end);
15578
15579 self.highlight_rows::<T>(
15580 start..end,
15581 highlight_color
15582 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15583 Default::default(),
15584 cx,
15585 );
15586
15587 if self.buffer.read(cx).is_singleton() {
15588 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15589 }
15590 }
15591
15592 pub fn go_to_definition(
15593 &mut self,
15594 _: &GoToDefinition,
15595 window: &mut Window,
15596 cx: &mut Context<Self>,
15597 ) -> Task<Result<Navigated>> {
15598 let definition =
15599 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15600 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15601 cx.spawn_in(window, async move |editor, cx| {
15602 if definition.await? == Navigated::Yes {
15603 return Ok(Navigated::Yes);
15604 }
15605 match fallback_strategy {
15606 GoToDefinitionFallback::None => Ok(Navigated::No),
15607 GoToDefinitionFallback::FindAllReferences => {
15608 match editor.update_in(cx, |editor, window, cx| {
15609 editor.find_all_references(&FindAllReferences, window, cx)
15610 })? {
15611 Some(references) => references.await,
15612 None => Ok(Navigated::No),
15613 }
15614 }
15615 }
15616 })
15617 }
15618
15619 pub fn go_to_declaration(
15620 &mut self,
15621 _: &GoToDeclaration,
15622 window: &mut Window,
15623 cx: &mut Context<Self>,
15624 ) -> Task<Result<Navigated>> {
15625 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15626 }
15627
15628 pub fn go_to_declaration_split(
15629 &mut self,
15630 _: &GoToDeclaration,
15631 window: &mut Window,
15632 cx: &mut Context<Self>,
15633 ) -> Task<Result<Navigated>> {
15634 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15635 }
15636
15637 pub fn go_to_implementation(
15638 &mut self,
15639 _: &GoToImplementation,
15640 window: &mut Window,
15641 cx: &mut Context<Self>,
15642 ) -> Task<Result<Navigated>> {
15643 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15644 }
15645
15646 pub fn go_to_implementation_split(
15647 &mut self,
15648 _: &GoToImplementationSplit,
15649 window: &mut Window,
15650 cx: &mut Context<Self>,
15651 ) -> Task<Result<Navigated>> {
15652 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15653 }
15654
15655 pub fn go_to_type_definition(
15656 &mut self,
15657 _: &GoToTypeDefinition,
15658 window: &mut Window,
15659 cx: &mut Context<Self>,
15660 ) -> Task<Result<Navigated>> {
15661 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15662 }
15663
15664 pub fn go_to_definition_split(
15665 &mut self,
15666 _: &GoToDefinitionSplit,
15667 window: &mut Window,
15668 cx: &mut Context<Self>,
15669 ) -> Task<Result<Navigated>> {
15670 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15671 }
15672
15673 pub fn go_to_type_definition_split(
15674 &mut self,
15675 _: &GoToTypeDefinitionSplit,
15676 window: &mut Window,
15677 cx: &mut Context<Self>,
15678 ) -> Task<Result<Navigated>> {
15679 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15680 }
15681
15682 fn go_to_definition_of_kind(
15683 &mut self,
15684 kind: GotoDefinitionKind,
15685 split: bool,
15686 window: &mut Window,
15687 cx: &mut Context<Self>,
15688 ) -> Task<Result<Navigated>> {
15689 let Some(provider) = self.semantics_provider.clone() else {
15690 return Task::ready(Ok(Navigated::No));
15691 };
15692 let head = self.selections.newest::<usize>(cx).head();
15693 let buffer = self.buffer.read(cx);
15694 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15695 return Task::ready(Ok(Navigated::No));
15696 };
15697 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15698 return Task::ready(Ok(Navigated::No));
15699 };
15700
15701 cx.spawn_in(window, async move |editor, cx| {
15702 let definitions = definitions.await?;
15703 let navigated = editor
15704 .update_in(cx, |editor, window, cx| {
15705 editor.navigate_to_hover_links(
15706 Some(kind),
15707 definitions
15708 .into_iter()
15709 .filter(|location| {
15710 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15711 })
15712 .map(HoverLink::Text)
15713 .collect::<Vec<_>>(),
15714 split,
15715 window,
15716 cx,
15717 )
15718 })?
15719 .await?;
15720 anyhow::Ok(navigated)
15721 })
15722 }
15723
15724 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15725 let selection = self.selections.newest_anchor();
15726 let head = selection.head();
15727 let tail = selection.tail();
15728
15729 let Some((buffer, start_position)) =
15730 self.buffer.read(cx).text_anchor_for_position(head, cx)
15731 else {
15732 return;
15733 };
15734
15735 let end_position = if head != tail {
15736 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15737 return;
15738 };
15739 Some(pos)
15740 } else {
15741 None
15742 };
15743
15744 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15745 let url = if let Some(end_pos) = end_position {
15746 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15747 } else {
15748 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15749 };
15750
15751 if let Some(url) = url {
15752 editor.update(cx, |_, cx| {
15753 cx.open_url(&url);
15754 })
15755 } else {
15756 Ok(())
15757 }
15758 });
15759
15760 url_finder.detach();
15761 }
15762
15763 pub fn open_selected_filename(
15764 &mut self,
15765 _: &OpenSelectedFilename,
15766 window: &mut Window,
15767 cx: &mut Context<Self>,
15768 ) {
15769 let Some(workspace) = self.workspace() else {
15770 return;
15771 };
15772
15773 let position = self.selections.newest_anchor().head();
15774
15775 let Some((buffer, buffer_position)) =
15776 self.buffer.read(cx).text_anchor_for_position(position, cx)
15777 else {
15778 return;
15779 };
15780
15781 let project = self.project.clone();
15782
15783 cx.spawn_in(window, async move |_, cx| {
15784 let result = find_file(&buffer, project, buffer_position, cx).await;
15785
15786 if let Some((_, path)) = result {
15787 workspace
15788 .update_in(cx, |workspace, window, cx| {
15789 workspace.open_resolved_path(path, window, cx)
15790 })?
15791 .await?;
15792 }
15793 anyhow::Ok(())
15794 })
15795 .detach();
15796 }
15797
15798 pub(crate) fn navigate_to_hover_links(
15799 &mut self,
15800 kind: Option<GotoDefinitionKind>,
15801 definitions: Vec<HoverLink>,
15802 split: bool,
15803 window: &mut Window,
15804 cx: &mut Context<Editor>,
15805 ) -> Task<Result<Navigated>> {
15806 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15807 let mut first_url_or_file = None;
15808 let definitions: Vec<_> = definitions
15809 .into_iter()
15810 .filter_map(|def| match def {
15811 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15812 HoverLink::InlayHint(lsp_location, server_id) => {
15813 let computation =
15814 self.compute_target_location(lsp_location, server_id, window, cx);
15815 Some(cx.background_spawn(computation))
15816 }
15817 HoverLink::Url(url) => {
15818 first_url_or_file = Some(Either::Left(url));
15819 None
15820 }
15821 HoverLink::File(path) => {
15822 first_url_or_file = Some(Either::Right(path));
15823 None
15824 }
15825 })
15826 .collect();
15827
15828 let workspace = self.workspace();
15829
15830 cx.spawn_in(window, async move |editor, acx| {
15831 let mut locations: Vec<Location> = future::join_all(definitions)
15832 .await
15833 .into_iter()
15834 .filter_map(|location| location.transpose())
15835 .collect::<Result<_>>()
15836 .context("location tasks")?;
15837
15838 if locations.len() > 1 {
15839 let Some(workspace) = workspace else {
15840 return Ok(Navigated::No);
15841 };
15842
15843 let tab_kind = match kind {
15844 Some(GotoDefinitionKind::Implementation) => "Implementations",
15845 _ => "Definitions",
15846 };
15847 let title = editor
15848 .update_in(acx, |_, _, cx| {
15849 let origin = locations.first().unwrap();
15850 let buffer = origin.buffer.read(cx);
15851 format!(
15852 "{} for {}",
15853 tab_kind,
15854 buffer
15855 .text_for_range(origin.range.clone())
15856 .collect::<String>()
15857 )
15858 })
15859 .context("buffer title")?;
15860
15861 let opened = workspace
15862 .update_in(acx, |workspace, window, cx| {
15863 Self::open_locations_in_multibuffer(
15864 workspace,
15865 locations,
15866 title,
15867 split,
15868 MultibufferSelectionMode::First,
15869 window,
15870 cx,
15871 )
15872 })
15873 .is_ok();
15874
15875 anyhow::Ok(Navigated::from_bool(opened))
15876 } else if locations.is_empty() {
15877 // If there is one definition, just open it directly
15878 match first_url_or_file {
15879 Some(Either::Left(url)) => {
15880 acx.update(|_, cx| cx.open_url(&url))?;
15881 Ok(Navigated::Yes)
15882 }
15883 Some(Either::Right(path)) => {
15884 let Some(workspace) = workspace else {
15885 return Ok(Navigated::No);
15886 };
15887
15888 workspace
15889 .update_in(acx, |workspace, window, cx| {
15890 workspace.open_resolved_path(path, window, cx)
15891 })?
15892 .await?;
15893 Ok(Navigated::Yes)
15894 }
15895 None => Ok(Navigated::No),
15896 }
15897 } else {
15898 let Some(workspace) = workspace else {
15899 return Ok(Navigated::No);
15900 };
15901
15902 let target = locations.pop().unwrap();
15903 editor.update_in(acx, |editor, window, cx| {
15904 let pane = workspace.read(cx).active_pane().clone();
15905
15906 let range = target.range.to_point(target.buffer.read(cx));
15907 let range = editor.range_for_match(&range);
15908 let range = collapse_multiline_range(range);
15909
15910 if !split
15911 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15912 {
15913 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15914 } else {
15915 window.defer(cx, move |window, cx| {
15916 let target_editor: Entity<Self> =
15917 workspace.update(cx, |workspace, cx| {
15918 let pane = if split {
15919 workspace.adjacent_pane(window, cx)
15920 } else {
15921 workspace.active_pane().clone()
15922 };
15923
15924 workspace.open_project_item(
15925 pane,
15926 target.buffer.clone(),
15927 true,
15928 true,
15929 window,
15930 cx,
15931 )
15932 });
15933 target_editor.update(cx, |target_editor, cx| {
15934 // When selecting a definition in a different buffer, disable the nav history
15935 // to avoid creating a history entry at the previous cursor location.
15936 pane.update(cx, |pane, _| pane.disable_history());
15937 target_editor.go_to_singleton_buffer_range(range, window, cx);
15938 pane.update(cx, |pane, _| pane.enable_history());
15939 });
15940 });
15941 }
15942 Navigated::Yes
15943 })
15944 }
15945 })
15946 }
15947
15948 fn compute_target_location(
15949 &self,
15950 lsp_location: lsp::Location,
15951 server_id: LanguageServerId,
15952 window: &mut Window,
15953 cx: &mut Context<Self>,
15954 ) -> Task<anyhow::Result<Option<Location>>> {
15955 let Some(project) = self.project.clone() else {
15956 return Task::ready(Ok(None));
15957 };
15958
15959 cx.spawn_in(window, async move |editor, cx| {
15960 let location_task = editor.update(cx, |_, cx| {
15961 project.update(cx, |project, cx| {
15962 let language_server_name = project
15963 .language_server_statuses(cx)
15964 .find(|(id, _)| server_id == *id)
15965 .map(|(_, status)| status.name.clone());
15966 language_server_name.map(|language_server_name| {
15967 project.open_local_buffer_via_lsp(
15968 lsp_location.uri.clone(),
15969 server_id,
15970 language_server_name,
15971 cx,
15972 )
15973 })
15974 })
15975 })?;
15976 let location = match location_task {
15977 Some(task) => Some({
15978 let target_buffer_handle = task.await.context("open local buffer")?;
15979 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15980 let target_start = target_buffer
15981 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15982 let target_end = target_buffer
15983 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15984 target_buffer.anchor_after(target_start)
15985 ..target_buffer.anchor_before(target_end)
15986 })?;
15987 Location {
15988 buffer: target_buffer_handle,
15989 range,
15990 }
15991 }),
15992 None => None,
15993 };
15994 Ok(location)
15995 })
15996 }
15997
15998 pub fn find_all_references(
15999 &mut self,
16000 _: &FindAllReferences,
16001 window: &mut Window,
16002 cx: &mut Context<Self>,
16003 ) -> Option<Task<Result<Navigated>>> {
16004 let selection = self.selections.newest::<usize>(cx);
16005 let multi_buffer = self.buffer.read(cx);
16006 let head = selection.head();
16007
16008 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16009 let head_anchor = multi_buffer_snapshot.anchor_at(
16010 head,
16011 if head < selection.tail() {
16012 Bias::Right
16013 } else {
16014 Bias::Left
16015 },
16016 );
16017
16018 match self
16019 .find_all_references_task_sources
16020 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16021 {
16022 Ok(_) => {
16023 log::info!(
16024 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16025 );
16026 return None;
16027 }
16028 Err(i) => {
16029 self.find_all_references_task_sources.insert(i, head_anchor);
16030 }
16031 }
16032
16033 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16034 let workspace = self.workspace()?;
16035 let project = workspace.read(cx).project().clone();
16036 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16037 Some(cx.spawn_in(window, async move |editor, cx| {
16038 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16039 if let Ok(i) = editor
16040 .find_all_references_task_sources
16041 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16042 {
16043 editor.find_all_references_task_sources.remove(i);
16044 }
16045 });
16046
16047 let locations = references.await?;
16048 if locations.is_empty() {
16049 return anyhow::Ok(Navigated::No);
16050 }
16051
16052 workspace.update_in(cx, |workspace, window, cx| {
16053 let title = locations
16054 .first()
16055 .as_ref()
16056 .map(|location| {
16057 let buffer = location.buffer.read(cx);
16058 format!(
16059 "References to `{}`",
16060 buffer
16061 .text_for_range(location.range.clone())
16062 .collect::<String>()
16063 )
16064 })
16065 .unwrap();
16066 Self::open_locations_in_multibuffer(
16067 workspace,
16068 locations,
16069 title,
16070 false,
16071 MultibufferSelectionMode::First,
16072 window,
16073 cx,
16074 );
16075 Navigated::Yes
16076 })
16077 }))
16078 }
16079
16080 /// Opens a multibuffer with the given project locations in it
16081 pub fn open_locations_in_multibuffer(
16082 workspace: &mut Workspace,
16083 mut locations: Vec<Location>,
16084 title: String,
16085 split: bool,
16086 multibuffer_selection_mode: MultibufferSelectionMode,
16087 window: &mut Window,
16088 cx: &mut Context<Workspace>,
16089 ) {
16090 if locations.is_empty() {
16091 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16092 return;
16093 }
16094
16095 // If there are multiple definitions, open them in a multibuffer
16096 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16097 let mut locations = locations.into_iter().peekable();
16098 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16099 let capability = workspace.project().read(cx).capability();
16100
16101 let excerpt_buffer = cx.new(|cx| {
16102 let mut multibuffer = MultiBuffer::new(capability);
16103 while let Some(location) = locations.next() {
16104 let buffer = location.buffer.read(cx);
16105 let mut ranges_for_buffer = Vec::new();
16106 let range = location.range.to_point(buffer);
16107 ranges_for_buffer.push(range.clone());
16108
16109 while let Some(next_location) = locations.peek() {
16110 if next_location.buffer == location.buffer {
16111 ranges_for_buffer.push(next_location.range.to_point(buffer));
16112 locations.next();
16113 } else {
16114 break;
16115 }
16116 }
16117
16118 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16119 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16120 PathKey::for_buffer(&location.buffer, cx),
16121 location.buffer.clone(),
16122 ranges_for_buffer,
16123 DEFAULT_MULTIBUFFER_CONTEXT,
16124 cx,
16125 );
16126 ranges.extend(new_ranges)
16127 }
16128
16129 multibuffer.with_title(title)
16130 });
16131
16132 let editor = cx.new(|cx| {
16133 Editor::for_multibuffer(
16134 excerpt_buffer,
16135 Some(workspace.project().clone()),
16136 window,
16137 cx,
16138 )
16139 });
16140 editor.update(cx, |editor, cx| {
16141 match multibuffer_selection_mode {
16142 MultibufferSelectionMode::First => {
16143 if let Some(first_range) = ranges.first() {
16144 editor.change_selections(
16145 SelectionEffects::no_scroll(),
16146 window,
16147 cx,
16148 |selections| {
16149 selections.clear_disjoint();
16150 selections
16151 .select_anchor_ranges(std::iter::once(first_range.clone()));
16152 },
16153 );
16154 }
16155 editor.highlight_background::<Self>(
16156 &ranges,
16157 |theme| theme.colors().editor_highlighted_line_background,
16158 cx,
16159 );
16160 }
16161 MultibufferSelectionMode::All => {
16162 editor.change_selections(
16163 SelectionEffects::no_scroll(),
16164 window,
16165 cx,
16166 |selections| {
16167 selections.clear_disjoint();
16168 selections.select_anchor_ranges(ranges);
16169 },
16170 );
16171 }
16172 }
16173 editor.register_buffers_with_language_servers(cx);
16174 });
16175
16176 let item = Box::new(editor);
16177 let item_id = item.item_id();
16178
16179 if split {
16180 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16181 } else {
16182 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16183 let (preview_item_id, preview_item_idx) =
16184 workspace.active_pane().read_with(cx, |pane, _| {
16185 (pane.preview_item_id(), pane.preview_item_idx())
16186 });
16187
16188 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16189
16190 if let Some(preview_item_id) = preview_item_id {
16191 workspace.active_pane().update(cx, |pane, cx| {
16192 pane.remove_item(preview_item_id, false, false, window, cx);
16193 });
16194 }
16195 } else {
16196 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16197 }
16198 }
16199 workspace.active_pane().update(cx, |pane, cx| {
16200 pane.set_preview_item_id(Some(item_id), cx);
16201 });
16202 }
16203
16204 pub fn rename(
16205 &mut self,
16206 _: &Rename,
16207 window: &mut Window,
16208 cx: &mut Context<Self>,
16209 ) -> Option<Task<Result<()>>> {
16210 use language::ToOffset as _;
16211
16212 let provider = self.semantics_provider.clone()?;
16213 let selection = self.selections.newest_anchor().clone();
16214 let (cursor_buffer, cursor_buffer_position) = self
16215 .buffer
16216 .read(cx)
16217 .text_anchor_for_position(selection.head(), cx)?;
16218 let (tail_buffer, cursor_buffer_position_end) = self
16219 .buffer
16220 .read(cx)
16221 .text_anchor_for_position(selection.tail(), cx)?;
16222 if tail_buffer != cursor_buffer {
16223 return None;
16224 }
16225
16226 let snapshot = cursor_buffer.read(cx).snapshot();
16227 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16228 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16229 let prepare_rename = provider
16230 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16231 .unwrap_or_else(|| Task::ready(Ok(None)));
16232 drop(snapshot);
16233
16234 Some(cx.spawn_in(window, async move |this, cx| {
16235 let rename_range = if let Some(range) = prepare_rename.await? {
16236 Some(range)
16237 } else {
16238 this.update(cx, |this, cx| {
16239 let buffer = this.buffer.read(cx).snapshot(cx);
16240 let mut buffer_highlights = this
16241 .document_highlights_for_position(selection.head(), &buffer)
16242 .filter(|highlight| {
16243 highlight.start.excerpt_id == selection.head().excerpt_id
16244 && highlight.end.excerpt_id == selection.head().excerpt_id
16245 });
16246 buffer_highlights
16247 .next()
16248 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16249 })?
16250 };
16251 if let Some(rename_range) = rename_range {
16252 this.update_in(cx, |this, window, cx| {
16253 let snapshot = cursor_buffer.read(cx).snapshot();
16254 let rename_buffer_range = rename_range.to_offset(&snapshot);
16255 let cursor_offset_in_rename_range =
16256 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16257 let cursor_offset_in_rename_range_end =
16258 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16259
16260 this.take_rename(false, window, cx);
16261 let buffer = this.buffer.read(cx).read(cx);
16262 let cursor_offset = selection.head().to_offset(&buffer);
16263 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16264 let rename_end = rename_start + rename_buffer_range.len();
16265 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16266 let mut old_highlight_id = None;
16267 let old_name: Arc<str> = buffer
16268 .chunks(rename_start..rename_end, true)
16269 .map(|chunk| {
16270 if old_highlight_id.is_none() {
16271 old_highlight_id = chunk.syntax_highlight_id;
16272 }
16273 chunk.text
16274 })
16275 .collect::<String>()
16276 .into();
16277
16278 drop(buffer);
16279
16280 // Position the selection in the rename editor so that it matches the current selection.
16281 this.show_local_selections = false;
16282 let rename_editor = cx.new(|cx| {
16283 let mut editor = Editor::single_line(window, cx);
16284 editor.buffer.update(cx, |buffer, cx| {
16285 buffer.edit([(0..0, old_name.clone())], None, cx)
16286 });
16287 let rename_selection_range = match cursor_offset_in_rename_range
16288 .cmp(&cursor_offset_in_rename_range_end)
16289 {
16290 Ordering::Equal => {
16291 editor.select_all(&SelectAll, window, cx);
16292 return editor;
16293 }
16294 Ordering::Less => {
16295 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16296 }
16297 Ordering::Greater => {
16298 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16299 }
16300 };
16301 if rename_selection_range.end > old_name.len() {
16302 editor.select_all(&SelectAll, window, cx);
16303 } else {
16304 editor.change_selections(Default::default(), window, cx, |s| {
16305 s.select_ranges([rename_selection_range]);
16306 });
16307 }
16308 editor
16309 });
16310 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16311 if e == &EditorEvent::Focused {
16312 cx.emit(EditorEvent::FocusedIn)
16313 }
16314 })
16315 .detach();
16316
16317 let write_highlights =
16318 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16319 let read_highlights =
16320 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16321 let ranges = write_highlights
16322 .iter()
16323 .flat_map(|(_, ranges)| ranges.iter())
16324 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16325 .cloned()
16326 .collect();
16327
16328 this.highlight_text::<Rename>(
16329 ranges,
16330 HighlightStyle {
16331 fade_out: Some(0.6),
16332 ..Default::default()
16333 },
16334 cx,
16335 );
16336 let rename_focus_handle = rename_editor.focus_handle(cx);
16337 window.focus(&rename_focus_handle);
16338 let block_id = this.insert_blocks(
16339 [BlockProperties {
16340 style: BlockStyle::Flex,
16341 placement: BlockPlacement::Below(range.start),
16342 height: Some(1),
16343 render: Arc::new({
16344 let rename_editor = rename_editor.clone();
16345 move |cx: &mut BlockContext| {
16346 let mut text_style = cx.editor_style.text.clone();
16347 if let Some(highlight_style) = old_highlight_id
16348 .and_then(|h| h.style(&cx.editor_style.syntax))
16349 {
16350 text_style = text_style.highlight(highlight_style);
16351 }
16352 div()
16353 .block_mouse_except_scroll()
16354 .pl(cx.anchor_x)
16355 .child(EditorElement::new(
16356 &rename_editor,
16357 EditorStyle {
16358 background: cx.theme().system().transparent,
16359 local_player: cx.editor_style.local_player,
16360 text: text_style,
16361 scrollbar_width: cx.editor_style.scrollbar_width,
16362 syntax: cx.editor_style.syntax.clone(),
16363 status: cx.editor_style.status.clone(),
16364 inlay_hints_style: HighlightStyle {
16365 font_weight: Some(FontWeight::BOLD),
16366 ..make_inlay_hints_style(cx.app)
16367 },
16368 edit_prediction_styles: make_suggestion_styles(
16369 cx.app,
16370 ),
16371 ..EditorStyle::default()
16372 },
16373 ))
16374 .into_any_element()
16375 }
16376 }),
16377 priority: 0,
16378 }],
16379 Some(Autoscroll::fit()),
16380 cx,
16381 )[0];
16382 this.pending_rename = Some(RenameState {
16383 range,
16384 old_name,
16385 editor: rename_editor,
16386 block_id,
16387 });
16388 })?;
16389 }
16390
16391 Ok(())
16392 }))
16393 }
16394
16395 pub fn confirm_rename(
16396 &mut self,
16397 _: &ConfirmRename,
16398 window: &mut Window,
16399 cx: &mut Context<Self>,
16400 ) -> Option<Task<Result<()>>> {
16401 let rename = self.take_rename(false, window, cx)?;
16402 let workspace = self.workspace()?.downgrade();
16403 let (buffer, start) = self
16404 .buffer
16405 .read(cx)
16406 .text_anchor_for_position(rename.range.start, cx)?;
16407 let (end_buffer, _) = self
16408 .buffer
16409 .read(cx)
16410 .text_anchor_for_position(rename.range.end, cx)?;
16411 if buffer != end_buffer {
16412 return None;
16413 }
16414
16415 let old_name = rename.old_name;
16416 let new_name = rename.editor.read(cx).text(cx);
16417
16418 let rename = self.semantics_provider.as_ref()?.perform_rename(
16419 &buffer,
16420 start,
16421 new_name.clone(),
16422 cx,
16423 )?;
16424
16425 Some(cx.spawn_in(window, async move |editor, cx| {
16426 let project_transaction = rename.await?;
16427 Self::open_project_transaction(
16428 &editor,
16429 workspace,
16430 project_transaction,
16431 format!("Rename: {} → {}", old_name, new_name),
16432 cx,
16433 )
16434 .await?;
16435
16436 editor.update(cx, |editor, cx| {
16437 editor.refresh_document_highlights(cx);
16438 })?;
16439 Ok(())
16440 }))
16441 }
16442
16443 fn take_rename(
16444 &mut self,
16445 moving_cursor: bool,
16446 window: &mut Window,
16447 cx: &mut Context<Self>,
16448 ) -> Option<RenameState> {
16449 let rename = self.pending_rename.take()?;
16450 if rename.editor.focus_handle(cx).is_focused(window) {
16451 window.focus(&self.focus_handle);
16452 }
16453
16454 self.remove_blocks(
16455 [rename.block_id].into_iter().collect(),
16456 Some(Autoscroll::fit()),
16457 cx,
16458 );
16459 self.clear_highlights::<Rename>(cx);
16460 self.show_local_selections = true;
16461
16462 if moving_cursor {
16463 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16464 editor.selections.newest::<usize>(cx).head()
16465 });
16466
16467 // Update the selection to match the position of the selection inside
16468 // the rename editor.
16469 let snapshot = self.buffer.read(cx).read(cx);
16470 let rename_range = rename.range.to_offset(&snapshot);
16471 let cursor_in_editor = snapshot
16472 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16473 .min(rename_range.end);
16474 drop(snapshot);
16475
16476 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16477 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16478 });
16479 } else {
16480 self.refresh_document_highlights(cx);
16481 }
16482
16483 Some(rename)
16484 }
16485
16486 pub fn pending_rename(&self) -> Option<&RenameState> {
16487 self.pending_rename.as_ref()
16488 }
16489
16490 fn format(
16491 &mut self,
16492 _: &Format,
16493 window: &mut Window,
16494 cx: &mut Context<Self>,
16495 ) -> Option<Task<Result<()>>> {
16496 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16497
16498 let project = match &self.project {
16499 Some(project) => project.clone(),
16500 None => return None,
16501 };
16502
16503 Some(self.perform_format(
16504 project,
16505 FormatTrigger::Manual,
16506 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16507 window,
16508 cx,
16509 ))
16510 }
16511
16512 fn format_selections(
16513 &mut self,
16514 _: &FormatSelections,
16515 window: &mut Window,
16516 cx: &mut Context<Self>,
16517 ) -> Option<Task<Result<()>>> {
16518 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16519
16520 let project = match &self.project {
16521 Some(project) => project.clone(),
16522 None => return None,
16523 };
16524
16525 let ranges = self
16526 .selections
16527 .all_adjusted(cx)
16528 .into_iter()
16529 .map(|selection| selection.range())
16530 .collect_vec();
16531
16532 Some(self.perform_format(
16533 project,
16534 FormatTrigger::Manual,
16535 FormatTarget::Ranges(ranges),
16536 window,
16537 cx,
16538 ))
16539 }
16540
16541 fn perform_format(
16542 &mut self,
16543 project: Entity<Project>,
16544 trigger: FormatTrigger,
16545 target: FormatTarget,
16546 window: &mut Window,
16547 cx: &mut Context<Self>,
16548 ) -> Task<Result<()>> {
16549 let buffer = self.buffer.clone();
16550 let (buffers, target) = match target {
16551 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16552 FormatTarget::Ranges(selection_ranges) => {
16553 let multi_buffer = buffer.read(cx);
16554 let snapshot = multi_buffer.read(cx);
16555 let mut buffers = HashSet::default();
16556 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16557 BTreeMap::new();
16558 for selection_range in selection_ranges {
16559 for (buffer, buffer_range, _) in
16560 snapshot.range_to_buffer_ranges(selection_range)
16561 {
16562 let buffer_id = buffer.remote_id();
16563 let start = buffer.anchor_before(buffer_range.start);
16564 let end = buffer.anchor_after(buffer_range.end);
16565 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16566 buffer_id_to_ranges
16567 .entry(buffer_id)
16568 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16569 .or_insert_with(|| vec![start..end]);
16570 }
16571 }
16572 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16573 }
16574 };
16575
16576 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16577 let selections_prev = transaction_id_prev
16578 .and_then(|transaction_id_prev| {
16579 // default to selections as they were after the last edit, if we have them,
16580 // instead of how they are now.
16581 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16582 // will take you back to where you made the last edit, instead of staying where you scrolled
16583 self.selection_history
16584 .transaction(transaction_id_prev)
16585 .map(|t| t.0.clone())
16586 })
16587 .unwrap_or_else(|| {
16588 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16589 self.selections.disjoint_anchors()
16590 });
16591
16592 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16593 let format = project.update(cx, |project, cx| {
16594 project.format(buffers, target, true, trigger, cx)
16595 });
16596
16597 cx.spawn_in(window, async move |editor, cx| {
16598 let transaction = futures::select_biased! {
16599 transaction = format.log_err().fuse() => transaction,
16600 () = timeout => {
16601 log::warn!("timed out waiting for formatting");
16602 None
16603 }
16604 };
16605
16606 buffer
16607 .update(cx, |buffer, cx| {
16608 if let Some(transaction) = transaction {
16609 if !buffer.is_singleton() {
16610 buffer.push_transaction(&transaction.0, cx);
16611 }
16612 }
16613 cx.notify();
16614 })
16615 .ok();
16616
16617 if let Some(transaction_id_now) =
16618 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16619 {
16620 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16621 if has_new_transaction {
16622 _ = editor.update(cx, |editor, _| {
16623 editor
16624 .selection_history
16625 .insert_transaction(transaction_id_now, selections_prev);
16626 });
16627 }
16628 }
16629
16630 Ok(())
16631 })
16632 }
16633
16634 fn organize_imports(
16635 &mut self,
16636 _: &OrganizeImports,
16637 window: &mut Window,
16638 cx: &mut Context<Self>,
16639 ) -> Option<Task<Result<()>>> {
16640 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16641 let project = match &self.project {
16642 Some(project) => project.clone(),
16643 None => return None,
16644 };
16645 Some(self.perform_code_action_kind(
16646 project,
16647 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16648 window,
16649 cx,
16650 ))
16651 }
16652
16653 fn perform_code_action_kind(
16654 &mut self,
16655 project: Entity<Project>,
16656 kind: CodeActionKind,
16657 window: &mut Window,
16658 cx: &mut Context<Self>,
16659 ) -> Task<Result<()>> {
16660 let buffer = self.buffer.clone();
16661 let buffers = buffer.read(cx).all_buffers();
16662 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16663 let apply_action = project.update(cx, |project, cx| {
16664 project.apply_code_action_kind(buffers, kind, true, cx)
16665 });
16666 cx.spawn_in(window, async move |_, cx| {
16667 let transaction = futures::select_biased! {
16668 () = timeout => {
16669 log::warn!("timed out waiting for executing code action");
16670 None
16671 }
16672 transaction = apply_action.log_err().fuse() => transaction,
16673 };
16674 buffer
16675 .update(cx, |buffer, cx| {
16676 // check if we need this
16677 if let Some(transaction) = transaction {
16678 if !buffer.is_singleton() {
16679 buffer.push_transaction(&transaction.0, cx);
16680 }
16681 }
16682 cx.notify();
16683 })
16684 .ok();
16685 Ok(())
16686 })
16687 }
16688
16689 pub fn restart_language_server(
16690 &mut self,
16691 _: &RestartLanguageServer,
16692 _: &mut Window,
16693 cx: &mut Context<Self>,
16694 ) {
16695 if let Some(project) = self.project.clone() {
16696 self.buffer.update(cx, |multi_buffer, cx| {
16697 project.update(cx, |project, cx| {
16698 project.restart_language_servers_for_buffers(
16699 multi_buffer.all_buffers().into_iter().collect(),
16700 HashSet::default(),
16701 cx,
16702 );
16703 });
16704 })
16705 }
16706 }
16707
16708 pub fn stop_language_server(
16709 &mut self,
16710 _: &StopLanguageServer,
16711 _: &mut Window,
16712 cx: &mut Context<Self>,
16713 ) {
16714 if let Some(project) = self.project.clone() {
16715 self.buffer.update(cx, |multi_buffer, cx| {
16716 project.update(cx, |project, cx| {
16717 project.stop_language_servers_for_buffers(
16718 multi_buffer.all_buffers().into_iter().collect(),
16719 HashSet::default(),
16720 cx,
16721 );
16722 cx.emit(project::Event::RefreshInlayHints);
16723 });
16724 });
16725 }
16726 }
16727
16728 fn cancel_language_server_work(
16729 workspace: &mut Workspace,
16730 _: &actions::CancelLanguageServerWork,
16731 _: &mut Window,
16732 cx: &mut Context<Workspace>,
16733 ) {
16734 let project = workspace.project();
16735 let buffers = workspace
16736 .active_item(cx)
16737 .and_then(|item| item.act_as::<Editor>(cx))
16738 .map_or(HashSet::default(), |editor| {
16739 editor.read(cx).buffer.read(cx).all_buffers()
16740 });
16741 project.update(cx, |project, cx| {
16742 project.cancel_language_server_work_for_buffers(buffers, cx);
16743 });
16744 }
16745
16746 fn show_character_palette(
16747 &mut self,
16748 _: &ShowCharacterPalette,
16749 window: &mut Window,
16750 _: &mut Context<Self>,
16751 ) {
16752 window.show_character_palette();
16753 }
16754
16755 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16756 if !self.diagnostics_enabled() {
16757 return;
16758 }
16759
16760 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16761 let buffer = self.buffer.read(cx).snapshot(cx);
16762 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16763 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16764 let is_valid = buffer
16765 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16766 .any(|entry| {
16767 entry.diagnostic.is_primary
16768 && !entry.range.is_empty()
16769 && entry.range.start == primary_range_start
16770 && entry.diagnostic.message == active_diagnostics.active_message
16771 });
16772
16773 if !is_valid {
16774 self.dismiss_diagnostics(cx);
16775 }
16776 }
16777 }
16778
16779 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16780 match &self.active_diagnostics {
16781 ActiveDiagnostic::Group(group) => Some(group),
16782 _ => None,
16783 }
16784 }
16785
16786 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16787 if !self.diagnostics_enabled() {
16788 return;
16789 }
16790 self.dismiss_diagnostics(cx);
16791 self.active_diagnostics = ActiveDiagnostic::All;
16792 }
16793
16794 fn activate_diagnostics(
16795 &mut self,
16796 buffer_id: BufferId,
16797 diagnostic: DiagnosticEntry<usize>,
16798 window: &mut Window,
16799 cx: &mut Context<Self>,
16800 ) {
16801 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16802 return;
16803 }
16804 self.dismiss_diagnostics(cx);
16805 let snapshot = self.snapshot(window, cx);
16806 let buffer = self.buffer.read(cx).snapshot(cx);
16807 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16808 return;
16809 };
16810
16811 let diagnostic_group = buffer
16812 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16813 .collect::<Vec<_>>();
16814
16815 let blocks =
16816 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16817
16818 let blocks = self.display_map.update(cx, |display_map, cx| {
16819 display_map.insert_blocks(blocks, cx).into_iter().collect()
16820 });
16821 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16822 active_range: buffer.anchor_before(diagnostic.range.start)
16823 ..buffer.anchor_after(diagnostic.range.end),
16824 active_message: diagnostic.diagnostic.message.clone(),
16825 group_id: diagnostic.diagnostic.group_id,
16826 blocks,
16827 });
16828 cx.notify();
16829 }
16830
16831 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16832 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16833 return;
16834 };
16835
16836 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16837 if let ActiveDiagnostic::Group(group) = prev {
16838 self.display_map.update(cx, |display_map, cx| {
16839 display_map.remove_blocks(group.blocks, cx);
16840 });
16841 cx.notify();
16842 }
16843 }
16844
16845 /// Disable inline diagnostics rendering for this editor.
16846 pub fn disable_inline_diagnostics(&mut self) {
16847 self.inline_diagnostics_enabled = false;
16848 self.inline_diagnostics_update = Task::ready(());
16849 self.inline_diagnostics.clear();
16850 }
16851
16852 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16853 self.diagnostics_enabled = false;
16854 self.dismiss_diagnostics(cx);
16855 self.inline_diagnostics_update = Task::ready(());
16856 self.inline_diagnostics.clear();
16857 }
16858
16859 pub fn diagnostics_enabled(&self) -> bool {
16860 self.diagnostics_enabled && self.mode.is_full()
16861 }
16862
16863 pub fn inline_diagnostics_enabled(&self) -> bool {
16864 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16865 }
16866
16867 pub fn show_inline_diagnostics(&self) -> bool {
16868 self.show_inline_diagnostics
16869 }
16870
16871 pub fn toggle_inline_diagnostics(
16872 &mut self,
16873 _: &ToggleInlineDiagnostics,
16874 window: &mut Window,
16875 cx: &mut Context<Editor>,
16876 ) {
16877 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16878 self.refresh_inline_diagnostics(false, window, cx);
16879 }
16880
16881 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16882 self.diagnostics_max_severity = severity;
16883 self.display_map.update(cx, |display_map, _| {
16884 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16885 });
16886 }
16887
16888 pub fn toggle_diagnostics(
16889 &mut self,
16890 _: &ToggleDiagnostics,
16891 window: &mut Window,
16892 cx: &mut Context<Editor>,
16893 ) {
16894 if !self.diagnostics_enabled() {
16895 return;
16896 }
16897
16898 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16899 EditorSettings::get_global(cx)
16900 .diagnostics_max_severity
16901 .filter(|severity| severity != &DiagnosticSeverity::Off)
16902 .unwrap_or(DiagnosticSeverity::Hint)
16903 } else {
16904 DiagnosticSeverity::Off
16905 };
16906 self.set_max_diagnostics_severity(new_severity, cx);
16907 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16908 self.active_diagnostics = ActiveDiagnostic::None;
16909 self.inline_diagnostics_update = Task::ready(());
16910 self.inline_diagnostics.clear();
16911 } else {
16912 self.refresh_inline_diagnostics(false, window, cx);
16913 }
16914
16915 cx.notify();
16916 }
16917
16918 pub fn toggle_minimap(
16919 &mut self,
16920 _: &ToggleMinimap,
16921 window: &mut Window,
16922 cx: &mut Context<Editor>,
16923 ) {
16924 if self.supports_minimap(cx) {
16925 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16926 }
16927 }
16928
16929 fn refresh_inline_diagnostics(
16930 &mut self,
16931 debounce: bool,
16932 window: &mut Window,
16933 cx: &mut Context<Self>,
16934 ) {
16935 let max_severity = ProjectSettings::get_global(cx)
16936 .diagnostics
16937 .inline
16938 .max_severity
16939 .unwrap_or(self.diagnostics_max_severity);
16940
16941 if !self.inline_diagnostics_enabled()
16942 || !self.show_inline_diagnostics
16943 || max_severity == DiagnosticSeverity::Off
16944 {
16945 self.inline_diagnostics_update = Task::ready(());
16946 self.inline_diagnostics.clear();
16947 return;
16948 }
16949
16950 let debounce_ms = ProjectSettings::get_global(cx)
16951 .diagnostics
16952 .inline
16953 .update_debounce_ms;
16954 let debounce = if debounce && debounce_ms > 0 {
16955 Some(Duration::from_millis(debounce_ms))
16956 } else {
16957 None
16958 };
16959 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16960 if let Some(debounce) = debounce {
16961 cx.background_executor().timer(debounce).await;
16962 }
16963 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16964 editor
16965 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16966 .ok()
16967 }) else {
16968 return;
16969 };
16970
16971 let new_inline_diagnostics = cx
16972 .background_spawn(async move {
16973 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16974 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16975 let message = diagnostic_entry
16976 .diagnostic
16977 .message
16978 .split_once('\n')
16979 .map(|(line, _)| line)
16980 .map(SharedString::new)
16981 .unwrap_or_else(|| {
16982 SharedString::from(diagnostic_entry.diagnostic.message)
16983 });
16984 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16985 let (Ok(i) | Err(i)) = inline_diagnostics
16986 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16987 inline_diagnostics.insert(
16988 i,
16989 (
16990 start_anchor,
16991 InlineDiagnostic {
16992 message,
16993 group_id: diagnostic_entry.diagnostic.group_id,
16994 start: diagnostic_entry.range.start.to_point(&snapshot),
16995 is_primary: diagnostic_entry.diagnostic.is_primary,
16996 severity: diagnostic_entry.diagnostic.severity,
16997 },
16998 ),
16999 );
17000 }
17001 inline_diagnostics
17002 })
17003 .await;
17004
17005 editor
17006 .update(cx, |editor, cx| {
17007 editor.inline_diagnostics = new_inline_diagnostics;
17008 cx.notify();
17009 })
17010 .ok();
17011 });
17012 }
17013
17014 fn pull_diagnostics(
17015 &mut self,
17016 buffer_id: Option<BufferId>,
17017 window: &Window,
17018 cx: &mut Context<Self>,
17019 ) -> Option<()> {
17020 if !self.mode().is_full() {
17021 return None;
17022 }
17023 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17024 .diagnostics
17025 .lsp_pull_diagnostics;
17026 if !pull_diagnostics_settings.enabled {
17027 return None;
17028 }
17029 let project = self.project.as_ref()?.downgrade();
17030 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17031 let mut buffers = self.buffer.read(cx).all_buffers();
17032 if let Some(buffer_id) = buffer_id {
17033 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17034 }
17035
17036 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17037 cx.background_executor().timer(debounce).await;
17038
17039 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17040 buffers
17041 .into_iter()
17042 .filter_map(|buffer| {
17043 project
17044 .update(cx, |project, cx| {
17045 project.lsp_store().update(cx, |lsp_store, cx| {
17046 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17047 })
17048 })
17049 .ok()
17050 })
17051 .collect::<FuturesUnordered<_>>()
17052 }) else {
17053 return;
17054 };
17055
17056 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17057 match pull_task {
17058 Ok(()) => {
17059 if editor
17060 .update_in(cx, |editor, window, cx| {
17061 editor.update_diagnostics_state(window, cx);
17062 })
17063 .is_err()
17064 {
17065 return;
17066 }
17067 }
17068 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17069 }
17070 }
17071 });
17072
17073 Some(())
17074 }
17075
17076 pub fn set_selections_from_remote(
17077 &mut self,
17078 selections: Vec<Selection<Anchor>>,
17079 pending_selection: Option<Selection<Anchor>>,
17080 window: &mut Window,
17081 cx: &mut Context<Self>,
17082 ) {
17083 let old_cursor_position = self.selections.newest_anchor().head();
17084 self.selections.change_with(cx, |s| {
17085 s.select_anchors(selections);
17086 if let Some(pending_selection) = pending_selection {
17087 s.set_pending(pending_selection, SelectMode::Character);
17088 } else {
17089 s.clear_pending();
17090 }
17091 });
17092 self.selections_did_change(
17093 false,
17094 &old_cursor_position,
17095 SelectionEffects::default(),
17096 window,
17097 cx,
17098 );
17099 }
17100
17101 pub fn transact(
17102 &mut self,
17103 window: &mut Window,
17104 cx: &mut Context<Self>,
17105 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17106 ) -> Option<TransactionId> {
17107 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17108 this.start_transaction_at(Instant::now(), window, cx);
17109 update(this, window, cx);
17110 this.end_transaction_at(Instant::now(), cx)
17111 })
17112 }
17113
17114 pub fn start_transaction_at(
17115 &mut self,
17116 now: Instant,
17117 window: &mut Window,
17118 cx: &mut Context<Self>,
17119 ) -> Option<TransactionId> {
17120 self.end_selection(window, cx);
17121 if let Some(tx_id) = self
17122 .buffer
17123 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17124 {
17125 self.selection_history
17126 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17127 cx.emit(EditorEvent::TransactionBegun {
17128 transaction_id: tx_id,
17129 });
17130 Some(tx_id)
17131 } else {
17132 None
17133 }
17134 }
17135
17136 pub fn end_transaction_at(
17137 &mut self,
17138 now: Instant,
17139 cx: &mut Context<Self>,
17140 ) -> Option<TransactionId> {
17141 if let Some(transaction_id) = self
17142 .buffer
17143 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17144 {
17145 if let Some((_, end_selections)) =
17146 self.selection_history.transaction_mut(transaction_id)
17147 {
17148 *end_selections = Some(self.selections.disjoint_anchors());
17149 } else {
17150 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17151 }
17152
17153 cx.emit(EditorEvent::Edited { transaction_id });
17154 Some(transaction_id)
17155 } else {
17156 None
17157 }
17158 }
17159
17160 pub fn modify_transaction_selection_history(
17161 &mut self,
17162 transaction_id: TransactionId,
17163 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17164 ) -> bool {
17165 self.selection_history
17166 .transaction_mut(transaction_id)
17167 .map(modify)
17168 .is_some()
17169 }
17170
17171 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17172 if self.selection_mark_mode {
17173 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17174 s.move_with(|_, sel| {
17175 sel.collapse_to(sel.head(), SelectionGoal::None);
17176 });
17177 })
17178 }
17179 self.selection_mark_mode = true;
17180 cx.notify();
17181 }
17182
17183 pub fn swap_selection_ends(
17184 &mut self,
17185 _: &actions::SwapSelectionEnds,
17186 window: &mut Window,
17187 cx: &mut Context<Self>,
17188 ) {
17189 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17190 s.move_with(|_, sel| {
17191 if sel.start != sel.end {
17192 sel.reversed = !sel.reversed
17193 }
17194 });
17195 });
17196 self.request_autoscroll(Autoscroll::newest(), cx);
17197 cx.notify();
17198 }
17199
17200 pub fn toggle_focus(
17201 workspace: &mut Workspace,
17202 _: &actions::ToggleFocus,
17203 window: &mut Window,
17204 cx: &mut Context<Workspace>,
17205 ) {
17206 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17207 return;
17208 };
17209 workspace.activate_item(&item, true, true, window, cx);
17210 }
17211
17212 pub fn toggle_fold(
17213 &mut self,
17214 _: &actions::ToggleFold,
17215 window: &mut Window,
17216 cx: &mut Context<Self>,
17217 ) {
17218 if self.is_singleton(cx) {
17219 let selection = self.selections.newest::<Point>(cx);
17220
17221 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17222 let range = if selection.is_empty() {
17223 let point = selection.head().to_display_point(&display_map);
17224 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17225 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17226 .to_point(&display_map);
17227 start..end
17228 } else {
17229 selection.range()
17230 };
17231 if display_map.folds_in_range(range).next().is_some() {
17232 self.unfold_lines(&Default::default(), window, cx)
17233 } else {
17234 self.fold(&Default::default(), window, cx)
17235 }
17236 } else {
17237 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17238 let buffer_ids: HashSet<_> = self
17239 .selections
17240 .disjoint_anchor_ranges()
17241 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17242 .collect();
17243
17244 let should_unfold = buffer_ids
17245 .iter()
17246 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17247
17248 for buffer_id in buffer_ids {
17249 if should_unfold {
17250 self.unfold_buffer(buffer_id, cx);
17251 } else {
17252 self.fold_buffer(buffer_id, cx);
17253 }
17254 }
17255 }
17256 }
17257
17258 pub fn toggle_fold_recursive(
17259 &mut self,
17260 _: &actions::ToggleFoldRecursive,
17261 window: &mut Window,
17262 cx: &mut Context<Self>,
17263 ) {
17264 let selection = self.selections.newest::<Point>(cx);
17265
17266 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17267 let range = if selection.is_empty() {
17268 let point = selection.head().to_display_point(&display_map);
17269 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17270 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17271 .to_point(&display_map);
17272 start..end
17273 } else {
17274 selection.range()
17275 };
17276 if display_map.folds_in_range(range).next().is_some() {
17277 self.unfold_recursive(&Default::default(), window, cx)
17278 } else {
17279 self.fold_recursive(&Default::default(), window, cx)
17280 }
17281 }
17282
17283 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17284 if self.is_singleton(cx) {
17285 let mut to_fold = Vec::new();
17286 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17287 let selections = self.selections.all_adjusted(cx);
17288
17289 for selection in selections {
17290 let range = selection.range().sorted();
17291 let buffer_start_row = range.start.row;
17292
17293 if range.start.row != range.end.row {
17294 let mut found = false;
17295 let mut row = range.start.row;
17296 while row <= range.end.row {
17297 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17298 {
17299 found = true;
17300 row = crease.range().end.row + 1;
17301 to_fold.push(crease);
17302 } else {
17303 row += 1
17304 }
17305 }
17306 if found {
17307 continue;
17308 }
17309 }
17310
17311 for row in (0..=range.start.row).rev() {
17312 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17313 if crease.range().end.row >= buffer_start_row {
17314 to_fold.push(crease);
17315 if row <= range.start.row {
17316 break;
17317 }
17318 }
17319 }
17320 }
17321 }
17322
17323 self.fold_creases(to_fold, true, window, cx);
17324 } else {
17325 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17326 let buffer_ids = self
17327 .selections
17328 .disjoint_anchor_ranges()
17329 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17330 .collect::<HashSet<_>>();
17331 for buffer_id in buffer_ids {
17332 self.fold_buffer(buffer_id, cx);
17333 }
17334 }
17335 }
17336
17337 pub fn toggle_fold_all(
17338 &mut self,
17339 _: &actions::ToggleFoldAll,
17340 window: &mut Window,
17341 cx: &mut Context<Self>,
17342 ) {
17343 if self.buffer.read(cx).is_singleton() {
17344 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17345 let has_folds = display_map
17346 .folds_in_range(0..display_map.buffer_snapshot.len())
17347 .next()
17348 .is_some();
17349
17350 if has_folds {
17351 self.unfold_all(&actions::UnfoldAll, window, cx);
17352 } else {
17353 self.fold_all(&actions::FoldAll, window, cx);
17354 }
17355 } else {
17356 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17357 let should_unfold = buffer_ids
17358 .iter()
17359 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17360
17361 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17362 editor
17363 .update_in(cx, |editor, _, cx| {
17364 for buffer_id in buffer_ids {
17365 if should_unfold {
17366 editor.unfold_buffer(buffer_id, cx);
17367 } else {
17368 editor.fold_buffer(buffer_id, cx);
17369 }
17370 }
17371 })
17372 .ok();
17373 });
17374 }
17375 }
17376
17377 fn fold_at_level(
17378 &mut self,
17379 fold_at: &FoldAtLevel,
17380 window: &mut Window,
17381 cx: &mut Context<Self>,
17382 ) {
17383 if !self.buffer.read(cx).is_singleton() {
17384 return;
17385 }
17386
17387 let fold_at_level = fold_at.0;
17388 let snapshot = self.buffer.read(cx).snapshot(cx);
17389 let mut to_fold = Vec::new();
17390 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17391
17392 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17393 while start_row < end_row {
17394 match self
17395 .snapshot(window, cx)
17396 .crease_for_buffer_row(MultiBufferRow(start_row))
17397 {
17398 Some(crease) => {
17399 let nested_start_row = crease.range().start.row + 1;
17400 let nested_end_row = crease.range().end.row;
17401
17402 if current_level < fold_at_level {
17403 stack.push((nested_start_row, nested_end_row, current_level + 1));
17404 } else if current_level == fold_at_level {
17405 to_fold.push(crease);
17406 }
17407
17408 start_row = nested_end_row + 1;
17409 }
17410 None => start_row += 1,
17411 }
17412 }
17413 }
17414
17415 self.fold_creases(to_fold, true, window, cx);
17416 }
17417
17418 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17419 if self.buffer.read(cx).is_singleton() {
17420 let mut fold_ranges = Vec::new();
17421 let snapshot = self.buffer.read(cx).snapshot(cx);
17422
17423 for row in 0..snapshot.max_row().0 {
17424 if let Some(foldable_range) = self
17425 .snapshot(window, cx)
17426 .crease_for_buffer_row(MultiBufferRow(row))
17427 {
17428 fold_ranges.push(foldable_range);
17429 }
17430 }
17431
17432 self.fold_creases(fold_ranges, true, window, cx);
17433 } else {
17434 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17435 editor
17436 .update_in(cx, |editor, _, cx| {
17437 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17438 editor.fold_buffer(buffer_id, cx);
17439 }
17440 })
17441 .ok();
17442 });
17443 }
17444 }
17445
17446 pub fn fold_function_bodies(
17447 &mut self,
17448 _: &actions::FoldFunctionBodies,
17449 window: &mut Window,
17450 cx: &mut Context<Self>,
17451 ) {
17452 let snapshot = self.buffer.read(cx).snapshot(cx);
17453
17454 let ranges = snapshot
17455 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17456 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17457 .collect::<Vec<_>>();
17458
17459 let creases = ranges
17460 .into_iter()
17461 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17462 .collect();
17463
17464 self.fold_creases(creases, true, window, cx);
17465 }
17466
17467 pub fn fold_recursive(
17468 &mut self,
17469 _: &actions::FoldRecursive,
17470 window: &mut Window,
17471 cx: &mut Context<Self>,
17472 ) {
17473 let mut to_fold = Vec::new();
17474 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17475 let selections = self.selections.all_adjusted(cx);
17476
17477 for selection in selections {
17478 let range = selection.range().sorted();
17479 let buffer_start_row = range.start.row;
17480
17481 if range.start.row != range.end.row {
17482 let mut found = false;
17483 for row in range.start.row..=range.end.row {
17484 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17485 found = true;
17486 to_fold.push(crease);
17487 }
17488 }
17489 if found {
17490 continue;
17491 }
17492 }
17493
17494 for row in (0..=range.start.row).rev() {
17495 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17496 if crease.range().end.row >= buffer_start_row {
17497 to_fold.push(crease);
17498 } else {
17499 break;
17500 }
17501 }
17502 }
17503 }
17504
17505 self.fold_creases(to_fold, true, window, cx);
17506 }
17507
17508 pub fn fold_at(
17509 &mut self,
17510 buffer_row: MultiBufferRow,
17511 window: &mut Window,
17512 cx: &mut Context<Self>,
17513 ) {
17514 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17515
17516 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17517 let autoscroll = self
17518 .selections
17519 .all::<Point>(cx)
17520 .iter()
17521 .any(|selection| crease.range().overlaps(&selection.range()));
17522
17523 self.fold_creases(vec![crease], autoscroll, window, cx);
17524 }
17525 }
17526
17527 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17528 if self.is_singleton(cx) {
17529 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17530 let buffer = &display_map.buffer_snapshot;
17531 let selections = self.selections.all::<Point>(cx);
17532 let ranges = selections
17533 .iter()
17534 .map(|s| {
17535 let range = s.display_range(&display_map).sorted();
17536 let mut start = range.start.to_point(&display_map);
17537 let mut end = range.end.to_point(&display_map);
17538 start.column = 0;
17539 end.column = buffer.line_len(MultiBufferRow(end.row));
17540 start..end
17541 })
17542 .collect::<Vec<_>>();
17543
17544 self.unfold_ranges(&ranges, true, true, cx);
17545 } else {
17546 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17547 let buffer_ids = self
17548 .selections
17549 .disjoint_anchor_ranges()
17550 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17551 .collect::<HashSet<_>>();
17552 for buffer_id in buffer_ids {
17553 self.unfold_buffer(buffer_id, cx);
17554 }
17555 }
17556 }
17557
17558 pub fn unfold_recursive(
17559 &mut self,
17560 _: &UnfoldRecursive,
17561 _window: &mut Window,
17562 cx: &mut Context<Self>,
17563 ) {
17564 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17565 let selections = self.selections.all::<Point>(cx);
17566 let ranges = selections
17567 .iter()
17568 .map(|s| {
17569 let mut range = s.display_range(&display_map).sorted();
17570 *range.start.column_mut() = 0;
17571 *range.end.column_mut() = display_map.line_len(range.end.row());
17572 let start = range.start.to_point(&display_map);
17573 let end = range.end.to_point(&display_map);
17574 start..end
17575 })
17576 .collect::<Vec<_>>();
17577
17578 self.unfold_ranges(&ranges, true, true, cx);
17579 }
17580
17581 pub fn unfold_at(
17582 &mut self,
17583 buffer_row: MultiBufferRow,
17584 _window: &mut Window,
17585 cx: &mut Context<Self>,
17586 ) {
17587 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17588
17589 let intersection_range = Point::new(buffer_row.0, 0)
17590 ..Point::new(
17591 buffer_row.0,
17592 display_map.buffer_snapshot.line_len(buffer_row),
17593 );
17594
17595 let autoscroll = self
17596 .selections
17597 .all::<Point>(cx)
17598 .iter()
17599 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17600
17601 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17602 }
17603
17604 pub fn unfold_all(
17605 &mut self,
17606 _: &actions::UnfoldAll,
17607 _window: &mut Window,
17608 cx: &mut Context<Self>,
17609 ) {
17610 if self.buffer.read(cx).is_singleton() {
17611 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17612 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17613 } else {
17614 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17615 editor
17616 .update(cx, |editor, cx| {
17617 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17618 editor.unfold_buffer(buffer_id, cx);
17619 }
17620 })
17621 .ok();
17622 });
17623 }
17624 }
17625
17626 pub fn fold_selected_ranges(
17627 &mut self,
17628 _: &FoldSelectedRanges,
17629 window: &mut Window,
17630 cx: &mut Context<Self>,
17631 ) {
17632 let selections = self.selections.all_adjusted(cx);
17633 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17634 let ranges = selections
17635 .into_iter()
17636 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17637 .collect::<Vec<_>>();
17638 self.fold_creases(ranges, true, window, cx);
17639 }
17640
17641 pub fn fold_ranges<T: ToOffset + Clone>(
17642 &mut self,
17643 ranges: Vec<Range<T>>,
17644 auto_scroll: bool,
17645 window: &mut Window,
17646 cx: &mut Context<Self>,
17647 ) {
17648 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17649 let ranges = ranges
17650 .into_iter()
17651 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17652 .collect::<Vec<_>>();
17653 self.fold_creases(ranges, auto_scroll, window, cx);
17654 }
17655
17656 pub fn fold_creases<T: ToOffset + Clone>(
17657 &mut self,
17658 creases: Vec<Crease<T>>,
17659 auto_scroll: bool,
17660 _window: &mut Window,
17661 cx: &mut Context<Self>,
17662 ) {
17663 if creases.is_empty() {
17664 return;
17665 }
17666
17667 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17668
17669 if auto_scroll {
17670 self.request_autoscroll(Autoscroll::fit(), cx);
17671 }
17672
17673 cx.notify();
17674
17675 self.scrollbar_marker_state.dirty = true;
17676 self.folds_did_change(cx);
17677 }
17678
17679 /// Removes any folds whose ranges intersect any of the given ranges.
17680 pub fn unfold_ranges<T: ToOffset + Clone>(
17681 &mut self,
17682 ranges: &[Range<T>],
17683 inclusive: bool,
17684 auto_scroll: bool,
17685 cx: &mut Context<Self>,
17686 ) {
17687 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17688 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17689 });
17690 self.folds_did_change(cx);
17691 }
17692
17693 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17694 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17695 return;
17696 }
17697 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17698 self.display_map.update(cx, |display_map, cx| {
17699 display_map.fold_buffers([buffer_id], cx)
17700 });
17701 cx.emit(EditorEvent::BufferFoldToggled {
17702 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17703 folded: true,
17704 });
17705 cx.notify();
17706 }
17707
17708 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17709 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17710 return;
17711 }
17712 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17713 self.display_map.update(cx, |display_map, cx| {
17714 display_map.unfold_buffers([buffer_id], cx);
17715 });
17716 cx.emit(EditorEvent::BufferFoldToggled {
17717 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17718 folded: false,
17719 });
17720 cx.notify();
17721 }
17722
17723 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17724 self.display_map.read(cx).is_buffer_folded(buffer)
17725 }
17726
17727 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17728 self.display_map.read(cx).folded_buffers()
17729 }
17730
17731 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17732 self.display_map.update(cx, |display_map, cx| {
17733 display_map.disable_header_for_buffer(buffer_id, cx);
17734 });
17735 cx.notify();
17736 }
17737
17738 /// Removes any folds with the given ranges.
17739 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17740 &mut self,
17741 ranges: &[Range<T>],
17742 type_id: TypeId,
17743 auto_scroll: bool,
17744 cx: &mut Context<Self>,
17745 ) {
17746 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17747 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17748 });
17749 self.folds_did_change(cx);
17750 }
17751
17752 fn remove_folds_with<T: ToOffset + Clone>(
17753 &mut self,
17754 ranges: &[Range<T>],
17755 auto_scroll: bool,
17756 cx: &mut Context<Self>,
17757 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17758 ) {
17759 if ranges.is_empty() {
17760 return;
17761 }
17762
17763 let mut buffers_affected = HashSet::default();
17764 let multi_buffer = self.buffer().read(cx);
17765 for range in ranges {
17766 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17767 buffers_affected.insert(buffer.read(cx).remote_id());
17768 };
17769 }
17770
17771 self.display_map.update(cx, update);
17772
17773 if auto_scroll {
17774 self.request_autoscroll(Autoscroll::fit(), cx);
17775 }
17776
17777 cx.notify();
17778 self.scrollbar_marker_state.dirty = true;
17779 self.active_indent_guides_state.dirty = true;
17780 }
17781
17782 pub fn update_renderer_widths(
17783 &mut self,
17784 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17785 cx: &mut Context<Self>,
17786 ) -> bool {
17787 self.display_map
17788 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17789 }
17790
17791 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17792 self.display_map.read(cx).fold_placeholder.clone()
17793 }
17794
17795 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17796 self.buffer.update(cx, |buffer, cx| {
17797 buffer.set_all_diff_hunks_expanded(cx);
17798 });
17799 }
17800
17801 pub fn expand_all_diff_hunks(
17802 &mut self,
17803 _: &ExpandAllDiffHunks,
17804 _window: &mut Window,
17805 cx: &mut Context<Self>,
17806 ) {
17807 self.buffer.update(cx, |buffer, cx| {
17808 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17809 });
17810 }
17811
17812 pub fn toggle_selected_diff_hunks(
17813 &mut self,
17814 _: &ToggleSelectedDiffHunks,
17815 _window: &mut Window,
17816 cx: &mut Context<Self>,
17817 ) {
17818 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17819 self.toggle_diff_hunks_in_ranges(ranges, cx);
17820 }
17821
17822 pub fn diff_hunks_in_ranges<'a>(
17823 &'a self,
17824 ranges: &'a [Range<Anchor>],
17825 buffer: &'a MultiBufferSnapshot,
17826 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17827 ranges.iter().flat_map(move |range| {
17828 let end_excerpt_id = range.end.excerpt_id;
17829 let range = range.to_point(buffer);
17830 let mut peek_end = range.end;
17831 if range.end.row < buffer.max_row().0 {
17832 peek_end = Point::new(range.end.row + 1, 0);
17833 }
17834 buffer
17835 .diff_hunks_in_range(range.start..peek_end)
17836 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17837 })
17838 }
17839
17840 pub fn has_stageable_diff_hunks_in_ranges(
17841 &self,
17842 ranges: &[Range<Anchor>],
17843 snapshot: &MultiBufferSnapshot,
17844 ) -> bool {
17845 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17846 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17847 }
17848
17849 pub fn toggle_staged_selected_diff_hunks(
17850 &mut self,
17851 _: &::git::ToggleStaged,
17852 _: &mut Window,
17853 cx: &mut Context<Self>,
17854 ) {
17855 let snapshot = self.buffer.read(cx).snapshot(cx);
17856 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17857 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17858 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17859 }
17860
17861 pub fn set_render_diff_hunk_controls(
17862 &mut self,
17863 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17864 cx: &mut Context<Self>,
17865 ) {
17866 self.render_diff_hunk_controls = render_diff_hunk_controls;
17867 cx.notify();
17868 }
17869
17870 pub fn stage_and_next(
17871 &mut self,
17872 _: &::git::StageAndNext,
17873 window: &mut Window,
17874 cx: &mut Context<Self>,
17875 ) {
17876 self.do_stage_or_unstage_and_next(true, window, cx);
17877 }
17878
17879 pub fn unstage_and_next(
17880 &mut self,
17881 _: &::git::UnstageAndNext,
17882 window: &mut Window,
17883 cx: &mut Context<Self>,
17884 ) {
17885 self.do_stage_or_unstage_and_next(false, window, cx);
17886 }
17887
17888 pub fn stage_or_unstage_diff_hunks(
17889 &mut self,
17890 stage: bool,
17891 ranges: Vec<Range<Anchor>>,
17892 cx: &mut Context<Self>,
17893 ) {
17894 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17895 cx.spawn(async move |this, cx| {
17896 task.await?;
17897 this.update(cx, |this, cx| {
17898 let snapshot = this.buffer.read(cx).snapshot(cx);
17899 let chunk_by = this
17900 .diff_hunks_in_ranges(&ranges, &snapshot)
17901 .chunk_by(|hunk| hunk.buffer_id);
17902 for (buffer_id, hunks) in &chunk_by {
17903 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17904 }
17905 })
17906 })
17907 .detach_and_log_err(cx);
17908 }
17909
17910 fn save_buffers_for_ranges_if_needed(
17911 &mut self,
17912 ranges: &[Range<Anchor>],
17913 cx: &mut Context<Editor>,
17914 ) -> Task<Result<()>> {
17915 let multibuffer = self.buffer.read(cx);
17916 let snapshot = multibuffer.read(cx);
17917 let buffer_ids: HashSet<_> = ranges
17918 .iter()
17919 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17920 .collect();
17921 drop(snapshot);
17922
17923 let mut buffers = HashSet::default();
17924 for buffer_id in buffer_ids {
17925 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17926 let buffer = buffer_entity.read(cx);
17927 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17928 {
17929 buffers.insert(buffer_entity);
17930 }
17931 }
17932 }
17933
17934 if let Some(project) = &self.project {
17935 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17936 } else {
17937 Task::ready(Ok(()))
17938 }
17939 }
17940
17941 fn do_stage_or_unstage_and_next(
17942 &mut self,
17943 stage: bool,
17944 window: &mut Window,
17945 cx: &mut Context<Self>,
17946 ) {
17947 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17948
17949 if ranges.iter().any(|range| range.start != range.end) {
17950 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17951 return;
17952 }
17953
17954 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17955 let snapshot = self.snapshot(window, cx);
17956 let position = self.selections.newest::<Point>(cx).head();
17957 let mut row = snapshot
17958 .buffer_snapshot
17959 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17960 .find(|hunk| hunk.row_range.start.0 > position.row)
17961 .map(|hunk| hunk.row_range.start);
17962
17963 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17964 // Outside of the project diff editor, wrap around to the beginning.
17965 if !all_diff_hunks_expanded {
17966 row = row.or_else(|| {
17967 snapshot
17968 .buffer_snapshot
17969 .diff_hunks_in_range(Point::zero()..position)
17970 .find(|hunk| hunk.row_range.end.0 < position.row)
17971 .map(|hunk| hunk.row_range.start)
17972 });
17973 }
17974
17975 if let Some(row) = row {
17976 let destination = Point::new(row.0, 0);
17977 let autoscroll = Autoscroll::center();
17978
17979 self.unfold_ranges(&[destination..destination], false, false, cx);
17980 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17981 s.select_ranges([destination..destination]);
17982 });
17983 }
17984 }
17985
17986 fn do_stage_or_unstage(
17987 &self,
17988 stage: bool,
17989 buffer_id: BufferId,
17990 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17991 cx: &mut App,
17992 ) -> Option<()> {
17993 let project = self.project.as_ref()?;
17994 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17995 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17996 let buffer_snapshot = buffer.read(cx).snapshot();
17997 let file_exists = buffer_snapshot
17998 .file()
17999 .is_some_and(|file| file.disk_state().exists());
18000 diff.update(cx, |diff, cx| {
18001 diff.stage_or_unstage_hunks(
18002 stage,
18003 &hunks
18004 .map(|hunk| buffer_diff::DiffHunk {
18005 buffer_range: hunk.buffer_range,
18006 diff_base_byte_range: hunk.diff_base_byte_range,
18007 secondary_status: hunk.secondary_status,
18008 range: Point::zero()..Point::zero(), // unused
18009 })
18010 .collect::<Vec<_>>(),
18011 &buffer_snapshot,
18012 file_exists,
18013 cx,
18014 )
18015 });
18016 None
18017 }
18018
18019 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18020 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18021 self.buffer
18022 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18023 }
18024
18025 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18026 self.buffer.update(cx, |buffer, cx| {
18027 let ranges = vec![Anchor::min()..Anchor::max()];
18028 if !buffer.all_diff_hunks_expanded()
18029 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18030 {
18031 buffer.collapse_diff_hunks(ranges, cx);
18032 true
18033 } else {
18034 false
18035 }
18036 })
18037 }
18038
18039 fn toggle_diff_hunks_in_ranges(
18040 &mut self,
18041 ranges: Vec<Range<Anchor>>,
18042 cx: &mut Context<Editor>,
18043 ) {
18044 self.buffer.update(cx, |buffer, cx| {
18045 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18046 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18047 })
18048 }
18049
18050 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18051 self.buffer.update(cx, |buffer, cx| {
18052 let snapshot = buffer.snapshot(cx);
18053 let excerpt_id = range.end.excerpt_id;
18054 let point_range = range.to_point(&snapshot);
18055 let expand = !buffer.single_hunk_is_expanded(range, cx);
18056 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18057 })
18058 }
18059
18060 pub(crate) fn apply_all_diff_hunks(
18061 &mut self,
18062 _: &ApplyAllDiffHunks,
18063 window: &mut Window,
18064 cx: &mut Context<Self>,
18065 ) {
18066 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18067
18068 let buffers = self.buffer.read(cx).all_buffers();
18069 for branch_buffer in buffers {
18070 branch_buffer.update(cx, |branch_buffer, cx| {
18071 branch_buffer.merge_into_base(Vec::new(), cx);
18072 });
18073 }
18074
18075 if let Some(project) = self.project.clone() {
18076 self.save(
18077 SaveOptions {
18078 format: true,
18079 autosave: false,
18080 },
18081 project,
18082 window,
18083 cx,
18084 )
18085 .detach_and_log_err(cx);
18086 }
18087 }
18088
18089 pub(crate) fn apply_selected_diff_hunks(
18090 &mut self,
18091 _: &ApplyDiffHunk,
18092 window: &mut Window,
18093 cx: &mut Context<Self>,
18094 ) {
18095 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18096 let snapshot = self.snapshot(window, cx);
18097 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18098 let mut ranges_by_buffer = HashMap::default();
18099 self.transact(window, cx, |editor, _window, cx| {
18100 for hunk in hunks {
18101 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18102 ranges_by_buffer
18103 .entry(buffer.clone())
18104 .or_insert_with(Vec::new)
18105 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18106 }
18107 }
18108
18109 for (buffer, ranges) in ranges_by_buffer {
18110 buffer.update(cx, |buffer, cx| {
18111 buffer.merge_into_base(ranges, cx);
18112 });
18113 }
18114 });
18115
18116 if let Some(project) = self.project.clone() {
18117 self.save(
18118 SaveOptions {
18119 format: true,
18120 autosave: false,
18121 },
18122 project,
18123 window,
18124 cx,
18125 )
18126 .detach_and_log_err(cx);
18127 }
18128 }
18129
18130 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18131 if hovered != self.gutter_hovered {
18132 self.gutter_hovered = hovered;
18133 cx.notify();
18134 }
18135 }
18136
18137 pub fn insert_blocks(
18138 &mut self,
18139 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18140 autoscroll: Option<Autoscroll>,
18141 cx: &mut Context<Self>,
18142 ) -> Vec<CustomBlockId> {
18143 let blocks = self
18144 .display_map
18145 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18146 if let Some(autoscroll) = autoscroll {
18147 self.request_autoscroll(autoscroll, cx);
18148 }
18149 cx.notify();
18150 blocks
18151 }
18152
18153 pub fn resize_blocks(
18154 &mut self,
18155 heights: HashMap<CustomBlockId, u32>,
18156 autoscroll: Option<Autoscroll>,
18157 cx: &mut Context<Self>,
18158 ) {
18159 self.display_map
18160 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18161 if let Some(autoscroll) = autoscroll {
18162 self.request_autoscroll(autoscroll, cx);
18163 }
18164 cx.notify();
18165 }
18166
18167 pub fn replace_blocks(
18168 &mut self,
18169 renderers: HashMap<CustomBlockId, RenderBlock>,
18170 autoscroll: Option<Autoscroll>,
18171 cx: &mut Context<Self>,
18172 ) {
18173 self.display_map
18174 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18175 if let Some(autoscroll) = autoscroll {
18176 self.request_autoscroll(autoscroll, cx);
18177 }
18178 cx.notify();
18179 }
18180
18181 pub fn remove_blocks(
18182 &mut self,
18183 block_ids: HashSet<CustomBlockId>,
18184 autoscroll: Option<Autoscroll>,
18185 cx: &mut Context<Self>,
18186 ) {
18187 self.display_map.update(cx, |display_map, cx| {
18188 display_map.remove_blocks(block_ids, cx)
18189 });
18190 if let Some(autoscroll) = autoscroll {
18191 self.request_autoscroll(autoscroll, cx);
18192 }
18193 cx.notify();
18194 }
18195
18196 pub fn row_for_block(
18197 &self,
18198 block_id: CustomBlockId,
18199 cx: &mut Context<Self>,
18200 ) -> Option<DisplayRow> {
18201 self.display_map
18202 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18203 }
18204
18205 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18206 self.focused_block = Some(focused_block);
18207 }
18208
18209 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18210 self.focused_block.take()
18211 }
18212
18213 pub fn insert_creases(
18214 &mut self,
18215 creases: impl IntoIterator<Item = Crease<Anchor>>,
18216 cx: &mut Context<Self>,
18217 ) -> Vec<CreaseId> {
18218 self.display_map
18219 .update(cx, |map, cx| map.insert_creases(creases, cx))
18220 }
18221
18222 pub fn remove_creases(
18223 &mut self,
18224 ids: impl IntoIterator<Item = CreaseId>,
18225 cx: &mut Context<Self>,
18226 ) -> Vec<(CreaseId, Range<Anchor>)> {
18227 self.display_map
18228 .update(cx, |map, cx| map.remove_creases(ids, cx))
18229 }
18230
18231 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18232 self.display_map
18233 .update(cx, |map, cx| map.snapshot(cx))
18234 .longest_row()
18235 }
18236
18237 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18238 self.display_map
18239 .update(cx, |map, cx| map.snapshot(cx))
18240 .max_point()
18241 }
18242
18243 pub fn text(&self, cx: &App) -> String {
18244 self.buffer.read(cx).read(cx).text()
18245 }
18246
18247 pub fn is_empty(&self, cx: &App) -> bool {
18248 self.buffer.read(cx).read(cx).is_empty()
18249 }
18250
18251 pub fn text_option(&self, cx: &App) -> Option<String> {
18252 let text = self.text(cx);
18253 let text = text.trim();
18254
18255 if text.is_empty() {
18256 return None;
18257 }
18258
18259 Some(text.to_string())
18260 }
18261
18262 pub fn set_text(
18263 &mut self,
18264 text: impl Into<Arc<str>>,
18265 window: &mut Window,
18266 cx: &mut Context<Self>,
18267 ) {
18268 self.transact(window, cx, |this, _, cx| {
18269 this.buffer
18270 .read(cx)
18271 .as_singleton()
18272 .expect("you can only call set_text on editors for singleton buffers")
18273 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18274 });
18275 }
18276
18277 pub fn display_text(&self, cx: &mut App) -> String {
18278 self.display_map
18279 .update(cx, |map, cx| map.snapshot(cx))
18280 .text()
18281 }
18282
18283 fn create_minimap(
18284 &self,
18285 minimap_settings: MinimapSettings,
18286 window: &mut Window,
18287 cx: &mut Context<Self>,
18288 ) -> Option<Entity<Self>> {
18289 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18290 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18291 }
18292
18293 fn initialize_new_minimap(
18294 &self,
18295 minimap_settings: MinimapSettings,
18296 window: &mut Window,
18297 cx: &mut Context<Self>,
18298 ) -> Entity<Self> {
18299 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18300
18301 let mut minimap = Editor::new_internal(
18302 EditorMode::Minimap {
18303 parent: cx.weak_entity(),
18304 },
18305 self.buffer.clone(),
18306 None,
18307 Some(self.display_map.clone()),
18308 window,
18309 cx,
18310 );
18311 minimap.scroll_manager.clone_state(&self.scroll_manager);
18312 minimap.set_text_style_refinement(TextStyleRefinement {
18313 font_size: Some(MINIMAP_FONT_SIZE),
18314 font_weight: Some(MINIMAP_FONT_WEIGHT),
18315 ..Default::default()
18316 });
18317 minimap.update_minimap_configuration(minimap_settings, cx);
18318 cx.new(|_| minimap)
18319 }
18320
18321 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18322 let current_line_highlight = minimap_settings
18323 .current_line_highlight
18324 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18325 self.set_current_line_highlight(Some(current_line_highlight));
18326 }
18327
18328 pub fn minimap(&self) -> Option<&Entity<Self>> {
18329 self.minimap
18330 .as_ref()
18331 .filter(|_| self.minimap_visibility.visible())
18332 }
18333
18334 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18335 let mut wrap_guides = smallvec![];
18336
18337 if self.show_wrap_guides == Some(false) {
18338 return wrap_guides;
18339 }
18340
18341 let settings = self.buffer.read(cx).language_settings(cx);
18342 if settings.show_wrap_guides {
18343 match self.soft_wrap_mode(cx) {
18344 SoftWrap::Column(soft_wrap) => {
18345 wrap_guides.push((soft_wrap as usize, true));
18346 }
18347 SoftWrap::Bounded(soft_wrap) => {
18348 wrap_guides.push((soft_wrap as usize, true));
18349 }
18350 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18351 }
18352 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18353 }
18354
18355 wrap_guides
18356 }
18357
18358 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18359 let settings = self.buffer.read(cx).language_settings(cx);
18360 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18361 match mode {
18362 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18363 SoftWrap::None
18364 }
18365 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18366 language_settings::SoftWrap::PreferredLineLength => {
18367 SoftWrap::Column(settings.preferred_line_length)
18368 }
18369 language_settings::SoftWrap::Bounded => {
18370 SoftWrap::Bounded(settings.preferred_line_length)
18371 }
18372 }
18373 }
18374
18375 pub fn set_soft_wrap_mode(
18376 &mut self,
18377 mode: language_settings::SoftWrap,
18378
18379 cx: &mut Context<Self>,
18380 ) {
18381 self.soft_wrap_mode_override = Some(mode);
18382 cx.notify();
18383 }
18384
18385 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18386 self.hard_wrap = hard_wrap;
18387 cx.notify();
18388 }
18389
18390 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18391 self.text_style_refinement = Some(style);
18392 }
18393
18394 /// called by the Element so we know what style we were most recently rendered with.
18395 pub(crate) fn set_style(
18396 &mut self,
18397 style: EditorStyle,
18398 window: &mut Window,
18399 cx: &mut Context<Self>,
18400 ) {
18401 // We intentionally do not inform the display map about the minimap style
18402 // so that wrapping is not recalculated and stays consistent for the editor
18403 // and its linked minimap.
18404 if !self.mode.is_minimap() {
18405 let rem_size = window.rem_size();
18406 self.display_map.update(cx, |map, cx| {
18407 map.set_font(
18408 style.text.font(),
18409 style.text.font_size.to_pixels(rem_size),
18410 cx,
18411 )
18412 });
18413 }
18414 self.style = Some(style);
18415 }
18416
18417 pub fn style(&self) -> Option<&EditorStyle> {
18418 self.style.as_ref()
18419 }
18420
18421 // Called by the element. This method is not designed to be called outside of the editor
18422 // element's layout code because it does not notify when rewrapping is computed synchronously.
18423 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18424 self.display_map
18425 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18426 }
18427
18428 pub fn set_soft_wrap(&mut self) {
18429 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18430 }
18431
18432 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18433 if self.soft_wrap_mode_override.is_some() {
18434 self.soft_wrap_mode_override.take();
18435 } else {
18436 let soft_wrap = match self.soft_wrap_mode(cx) {
18437 SoftWrap::GitDiff => return,
18438 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18439 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18440 language_settings::SoftWrap::None
18441 }
18442 };
18443 self.soft_wrap_mode_override = Some(soft_wrap);
18444 }
18445 cx.notify();
18446 }
18447
18448 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18449 let Some(workspace) = self.workspace() else {
18450 return;
18451 };
18452 let fs = workspace.read(cx).app_state().fs.clone();
18453 let current_show = TabBarSettings::get_global(cx).show;
18454 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18455 setting.show = Some(!current_show);
18456 });
18457 }
18458
18459 pub fn toggle_indent_guides(
18460 &mut self,
18461 _: &ToggleIndentGuides,
18462 _: &mut Window,
18463 cx: &mut Context<Self>,
18464 ) {
18465 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18466 self.buffer
18467 .read(cx)
18468 .language_settings(cx)
18469 .indent_guides
18470 .enabled
18471 });
18472 self.show_indent_guides = Some(!currently_enabled);
18473 cx.notify();
18474 }
18475
18476 fn should_show_indent_guides(&self) -> Option<bool> {
18477 self.show_indent_guides
18478 }
18479
18480 pub fn toggle_line_numbers(
18481 &mut self,
18482 _: &ToggleLineNumbers,
18483 _: &mut Window,
18484 cx: &mut Context<Self>,
18485 ) {
18486 let mut editor_settings = EditorSettings::get_global(cx).clone();
18487 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18488 EditorSettings::override_global(editor_settings, cx);
18489 }
18490
18491 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18492 if let Some(show_line_numbers) = self.show_line_numbers {
18493 return show_line_numbers;
18494 }
18495 EditorSettings::get_global(cx).gutter.line_numbers
18496 }
18497
18498 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18499 self.use_relative_line_numbers
18500 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18501 }
18502
18503 pub fn toggle_relative_line_numbers(
18504 &mut self,
18505 _: &ToggleRelativeLineNumbers,
18506 _: &mut Window,
18507 cx: &mut Context<Self>,
18508 ) {
18509 let is_relative = self.should_use_relative_line_numbers(cx);
18510 self.set_relative_line_number(Some(!is_relative), cx)
18511 }
18512
18513 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18514 self.use_relative_line_numbers = is_relative;
18515 cx.notify();
18516 }
18517
18518 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18519 self.show_gutter = show_gutter;
18520 cx.notify();
18521 }
18522
18523 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18524 self.show_scrollbars = ScrollbarAxes {
18525 horizontal: show,
18526 vertical: show,
18527 };
18528 cx.notify();
18529 }
18530
18531 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18532 self.show_scrollbars.vertical = show;
18533 cx.notify();
18534 }
18535
18536 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18537 self.show_scrollbars.horizontal = show;
18538 cx.notify();
18539 }
18540
18541 pub fn set_minimap_visibility(
18542 &mut self,
18543 minimap_visibility: MinimapVisibility,
18544 window: &mut Window,
18545 cx: &mut Context<Self>,
18546 ) {
18547 if self.minimap_visibility != minimap_visibility {
18548 if minimap_visibility.visible() && self.minimap.is_none() {
18549 let minimap_settings = EditorSettings::get_global(cx).minimap;
18550 self.minimap =
18551 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18552 }
18553 self.minimap_visibility = minimap_visibility;
18554 cx.notify();
18555 }
18556 }
18557
18558 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18559 self.set_show_scrollbars(false, cx);
18560 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18561 }
18562
18563 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18564 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18565 }
18566
18567 /// Normally the text in full mode and auto height editors is padded on the
18568 /// left side by roughly half a character width for improved hit testing.
18569 ///
18570 /// Use this method to disable this for cases where this is not wanted (e.g.
18571 /// if you want to align the editor text with some other text above or below)
18572 /// or if you want to add this padding to single-line editors.
18573 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18574 self.offset_content = offset_content;
18575 cx.notify();
18576 }
18577
18578 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18579 self.show_line_numbers = Some(show_line_numbers);
18580 cx.notify();
18581 }
18582
18583 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18584 self.disable_expand_excerpt_buttons = true;
18585 cx.notify();
18586 }
18587
18588 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18589 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18590 cx.notify();
18591 }
18592
18593 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18594 self.show_code_actions = Some(show_code_actions);
18595 cx.notify();
18596 }
18597
18598 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18599 self.show_runnables = Some(show_runnables);
18600 cx.notify();
18601 }
18602
18603 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18604 self.show_breakpoints = Some(show_breakpoints);
18605 cx.notify();
18606 }
18607
18608 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18609 if self.display_map.read(cx).masked != masked {
18610 self.display_map.update(cx, |map, _| map.masked = masked);
18611 }
18612 cx.notify()
18613 }
18614
18615 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18616 self.show_wrap_guides = Some(show_wrap_guides);
18617 cx.notify();
18618 }
18619
18620 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18621 self.show_indent_guides = Some(show_indent_guides);
18622 cx.notify();
18623 }
18624
18625 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18626 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18627 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18628 if let Some(dir) = file.abs_path(cx).parent() {
18629 return Some(dir.to_owned());
18630 }
18631 }
18632
18633 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18634 return Some(project_path.path.to_path_buf());
18635 }
18636 }
18637
18638 None
18639 }
18640
18641 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18642 self.active_excerpt(cx)?
18643 .1
18644 .read(cx)
18645 .file()
18646 .and_then(|f| f.as_local())
18647 }
18648
18649 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18650 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18651 let buffer = buffer.read(cx);
18652 if let Some(project_path) = buffer.project_path(cx) {
18653 let project = self.project.as_ref()?.read(cx);
18654 project.absolute_path(&project_path, cx)
18655 } else {
18656 buffer
18657 .file()
18658 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18659 }
18660 })
18661 }
18662
18663 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18664 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18665 let project_path = buffer.read(cx).project_path(cx)?;
18666 let project = self.project.as_ref()?.read(cx);
18667 let entry = project.entry_for_path(&project_path, cx)?;
18668 let path = entry.path.to_path_buf();
18669 Some(path)
18670 })
18671 }
18672
18673 pub fn reveal_in_finder(
18674 &mut self,
18675 _: &RevealInFileManager,
18676 _window: &mut Window,
18677 cx: &mut Context<Self>,
18678 ) {
18679 if let Some(target) = self.target_file(cx) {
18680 cx.reveal_path(&target.abs_path(cx));
18681 }
18682 }
18683
18684 pub fn copy_path(
18685 &mut self,
18686 _: &zed_actions::workspace::CopyPath,
18687 _window: &mut Window,
18688 cx: &mut Context<Self>,
18689 ) {
18690 if let Some(path) = self.target_file_abs_path(cx) {
18691 if let Some(path) = path.to_str() {
18692 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18693 }
18694 }
18695 }
18696
18697 pub fn copy_relative_path(
18698 &mut self,
18699 _: &zed_actions::workspace::CopyRelativePath,
18700 _window: &mut Window,
18701 cx: &mut Context<Self>,
18702 ) {
18703 if let Some(path) = self.target_file_path(cx) {
18704 if let Some(path) = path.to_str() {
18705 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18706 }
18707 }
18708 }
18709
18710 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18711 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18712 buffer.read(cx).project_path(cx)
18713 } else {
18714 None
18715 }
18716 }
18717
18718 // Returns true if the editor handled a go-to-line request
18719 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18720 maybe!({
18721 let breakpoint_store = self.breakpoint_store.as_ref()?;
18722
18723 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18724 else {
18725 self.clear_row_highlights::<ActiveDebugLine>();
18726 return None;
18727 };
18728
18729 let position = active_stack_frame.position;
18730 let buffer_id = position.buffer_id?;
18731 let snapshot = self
18732 .project
18733 .as_ref()?
18734 .read(cx)
18735 .buffer_for_id(buffer_id, cx)?
18736 .read(cx)
18737 .snapshot();
18738
18739 let mut handled = false;
18740 for (id, ExcerptRange { context, .. }) in
18741 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18742 {
18743 if context.start.cmp(&position, &snapshot).is_ge()
18744 || context.end.cmp(&position, &snapshot).is_lt()
18745 {
18746 continue;
18747 }
18748 let snapshot = self.buffer.read(cx).snapshot(cx);
18749 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18750
18751 handled = true;
18752 self.clear_row_highlights::<ActiveDebugLine>();
18753
18754 self.go_to_line::<ActiveDebugLine>(
18755 multibuffer_anchor,
18756 Some(cx.theme().colors().editor_debugger_active_line_background),
18757 window,
18758 cx,
18759 );
18760
18761 cx.notify();
18762 }
18763
18764 handled.then_some(())
18765 })
18766 .is_some()
18767 }
18768
18769 pub fn copy_file_name_without_extension(
18770 &mut self,
18771 _: &CopyFileNameWithoutExtension,
18772 _: &mut Window,
18773 cx: &mut Context<Self>,
18774 ) {
18775 if let Some(file) = self.target_file(cx) {
18776 if let Some(file_stem) = file.path().file_stem() {
18777 if let Some(name) = file_stem.to_str() {
18778 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18779 }
18780 }
18781 }
18782 }
18783
18784 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18785 if let Some(file) = self.target_file(cx) {
18786 if let Some(file_name) = file.path().file_name() {
18787 if let Some(name) = file_name.to_str() {
18788 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18789 }
18790 }
18791 }
18792 }
18793
18794 pub fn toggle_git_blame(
18795 &mut self,
18796 _: &::git::Blame,
18797 window: &mut Window,
18798 cx: &mut Context<Self>,
18799 ) {
18800 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18801
18802 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18803 self.start_git_blame(true, window, cx);
18804 }
18805
18806 cx.notify();
18807 }
18808
18809 pub fn toggle_git_blame_inline(
18810 &mut self,
18811 _: &ToggleGitBlameInline,
18812 window: &mut Window,
18813 cx: &mut Context<Self>,
18814 ) {
18815 self.toggle_git_blame_inline_internal(true, window, cx);
18816 cx.notify();
18817 }
18818
18819 pub fn open_git_blame_commit(
18820 &mut self,
18821 _: &OpenGitBlameCommit,
18822 window: &mut Window,
18823 cx: &mut Context<Self>,
18824 ) {
18825 self.open_git_blame_commit_internal(window, cx);
18826 }
18827
18828 fn open_git_blame_commit_internal(
18829 &mut self,
18830 window: &mut Window,
18831 cx: &mut Context<Self>,
18832 ) -> Option<()> {
18833 let blame = self.blame.as_ref()?;
18834 let snapshot = self.snapshot(window, cx);
18835 let cursor = self.selections.newest::<Point>(cx).head();
18836 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18837 let blame_entry = blame
18838 .update(cx, |blame, cx| {
18839 blame
18840 .blame_for_rows(
18841 &[RowInfo {
18842 buffer_id: Some(buffer.remote_id()),
18843 buffer_row: Some(point.row),
18844 ..Default::default()
18845 }],
18846 cx,
18847 )
18848 .next()
18849 })
18850 .flatten()?;
18851 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18852 let repo = blame.read(cx).repository(cx)?;
18853 let workspace = self.workspace()?.downgrade();
18854 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18855 None
18856 }
18857
18858 pub fn git_blame_inline_enabled(&self) -> bool {
18859 self.git_blame_inline_enabled
18860 }
18861
18862 pub fn toggle_selection_menu(
18863 &mut self,
18864 _: &ToggleSelectionMenu,
18865 _: &mut Window,
18866 cx: &mut Context<Self>,
18867 ) {
18868 self.show_selection_menu = self
18869 .show_selection_menu
18870 .map(|show_selections_menu| !show_selections_menu)
18871 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18872
18873 cx.notify();
18874 }
18875
18876 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18877 self.show_selection_menu
18878 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18879 }
18880
18881 fn start_git_blame(
18882 &mut self,
18883 user_triggered: bool,
18884 window: &mut Window,
18885 cx: &mut Context<Self>,
18886 ) {
18887 if let Some(project) = self.project.as_ref() {
18888 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18889 return;
18890 };
18891
18892 if buffer.read(cx).file().is_none() {
18893 return;
18894 }
18895
18896 let focused = self.focus_handle(cx).contains_focused(window, cx);
18897
18898 let project = project.clone();
18899 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18900 self.blame_subscription =
18901 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18902 self.blame = Some(blame);
18903 }
18904 }
18905
18906 fn toggle_git_blame_inline_internal(
18907 &mut self,
18908 user_triggered: bool,
18909 window: &mut Window,
18910 cx: &mut Context<Self>,
18911 ) {
18912 if self.git_blame_inline_enabled {
18913 self.git_blame_inline_enabled = false;
18914 self.show_git_blame_inline = false;
18915 self.show_git_blame_inline_delay_task.take();
18916 } else {
18917 self.git_blame_inline_enabled = true;
18918 self.start_git_blame_inline(user_triggered, window, cx);
18919 }
18920
18921 cx.notify();
18922 }
18923
18924 fn start_git_blame_inline(
18925 &mut self,
18926 user_triggered: bool,
18927 window: &mut Window,
18928 cx: &mut Context<Self>,
18929 ) {
18930 self.start_git_blame(user_triggered, window, cx);
18931
18932 if ProjectSettings::get_global(cx)
18933 .git
18934 .inline_blame_delay()
18935 .is_some()
18936 {
18937 self.start_inline_blame_timer(window, cx);
18938 } else {
18939 self.show_git_blame_inline = true
18940 }
18941 }
18942
18943 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18944 self.blame.as_ref()
18945 }
18946
18947 pub fn show_git_blame_gutter(&self) -> bool {
18948 self.show_git_blame_gutter
18949 }
18950
18951 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18952 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18953 }
18954
18955 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18956 self.show_git_blame_inline
18957 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18958 && !self.newest_selection_head_on_empty_line(cx)
18959 && self.has_blame_entries(cx)
18960 }
18961
18962 fn has_blame_entries(&self, cx: &App) -> bool {
18963 self.blame()
18964 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18965 }
18966
18967 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18968 let cursor_anchor = self.selections.newest_anchor().head();
18969
18970 let snapshot = self.buffer.read(cx).snapshot(cx);
18971 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18972
18973 snapshot.line_len(buffer_row) == 0
18974 }
18975
18976 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18977 let buffer_and_selection = maybe!({
18978 let selection = self.selections.newest::<Point>(cx);
18979 let selection_range = selection.range();
18980
18981 let multi_buffer = self.buffer().read(cx);
18982 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18983 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18984
18985 let (buffer, range, _) = if selection.reversed {
18986 buffer_ranges.first()
18987 } else {
18988 buffer_ranges.last()
18989 }?;
18990
18991 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18992 ..text::ToPoint::to_point(&range.end, &buffer).row;
18993 Some((
18994 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18995 selection,
18996 ))
18997 });
18998
18999 let Some((buffer, selection)) = buffer_and_selection else {
19000 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19001 };
19002
19003 let Some(project) = self.project.as_ref() else {
19004 return Task::ready(Err(anyhow!("editor does not have project")));
19005 };
19006
19007 project.update(cx, |project, cx| {
19008 project.get_permalink_to_line(&buffer, selection, cx)
19009 })
19010 }
19011
19012 pub fn copy_permalink_to_line(
19013 &mut self,
19014 _: &CopyPermalinkToLine,
19015 window: &mut Window,
19016 cx: &mut Context<Self>,
19017 ) {
19018 let permalink_task = self.get_permalink_to_line(cx);
19019 let workspace = self.workspace();
19020
19021 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19022 Ok(permalink) => {
19023 cx.update(|_, cx| {
19024 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19025 })
19026 .ok();
19027 }
19028 Err(err) => {
19029 let message = format!("Failed to copy permalink: {err}");
19030
19031 anyhow::Result::<()>::Err(err).log_err();
19032
19033 if let Some(workspace) = workspace {
19034 workspace
19035 .update_in(cx, |workspace, _, cx| {
19036 struct CopyPermalinkToLine;
19037
19038 workspace.show_toast(
19039 Toast::new(
19040 NotificationId::unique::<CopyPermalinkToLine>(),
19041 message,
19042 ),
19043 cx,
19044 )
19045 })
19046 .ok();
19047 }
19048 }
19049 })
19050 .detach();
19051 }
19052
19053 pub fn copy_file_location(
19054 &mut self,
19055 _: &CopyFileLocation,
19056 _: &mut Window,
19057 cx: &mut Context<Self>,
19058 ) {
19059 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19060 if let Some(file) = self.target_file(cx) {
19061 if let Some(path) = file.path().to_str() {
19062 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19063 }
19064 }
19065 }
19066
19067 pub fn open_permalink_to_line(
19068 &mut self,
19069 _: &OpenPermalinkToLine,
19070 window: &mut Window,
19071 cx: &mut Context<Self>,
19072 ) {
19073 let permalink_task = self.get_permalink_to_line(cx);
19074 let workspace = self.workspace();
19075
19076 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19077 Ok(permalink) => {
19078 cx.update(|_, cx| {
19079 cx.open_url(permalink.as_ref());
19080 })
19081 .ok();
19082 }
19083 Err(err) => {
19084 let message = format!("Failed to open permalink: {err}");
19085
19086 anyhow::Result::<()>::Err(err).log_err();
19087
19088 if let Some(workspace) = workspace {
19089 workspace
19090 .update(cx, |workspace, cx| {
19091 struct OpenPermalinkToLine;
19092
19093 workspace.show_toast(
19094 Toast::new(
19095 NotificationId::unique::<OpenPermalinkToLine>(),
19096 message,
19097 ),
19098 cx,
19099 )
19100 })
19101 .ok();
19102 }
19103 }
19104 })
19105 .detach();
19106 }
19107
19108 pub fn insert_uuid_v4(
19109 &mut self,
19110 _: &InsertUuidV4,
19111 window: &mut Window,
19112 cx: &mut Context<Self>,
19113 ) {
19114 self.insert_uuid(UuidVersion::V4, window, cx);
19115 }
19116
19117 pub fn insert_uuid_v7(
19118 &mut self,
19119 _: &InsertUuidV7,
19120 window: &mut Window,
19121 cx: &mut Context<Self>,
19122 ) {
19123 self.insert_uuid(UuidVersion::V7, window, cx);
19124 }
19125
19126 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19127 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19128 self.transact(window, cx, |this, window, cx| {
19129 let edits = this
19130 .selections
19131 .all::<Point>(cx)
19132 .into_iter()
19133 .map(|selection| {
19134 let uuid = match version {
19135 UuidVersion::V4 => uuid::Uuid::new_v4(),
19136 UuidVersion::V7 => uuid::Uuid::now_v7(),
19137 };
19138
19139 (selection.range(), uuid.to_string())
19140 });
19141 this.edit(edits, cx);
19142 this.refresh_edit_prediction(true, false, window, cx);
19143 });
19144 }
19145
19146 pub fn open_selections_in_multibuffer(
19147 &mut self,
19148 _: &OpenSelectionsInMultibuffer,
19149 window: &mut Window,
19150 cx: &mut Context<Self>,
19151 ) {
19152 let multibuffer = self.buffer.read(cx);
19153
19154 let Some(buffer) = multibuffer.as_singleton() else {
19155 return;
19156 };
19157
19158 let Some(workspace) = self.workspace() else {
19159 return;
19160 };
19161
19162 let title = multibuffer.title(cx).to_string();
19163
19164 let locations = self
19165 .selections
19166 .all_anchors(cx)
19167 .into_iter()
19168 .map(|selection| Location {
19169 buffer: buffer.clone(),
19170 range: selection.start.text_anchor..selection.end.text_anchor,
19171 })
19172 .collect::<Vec<_>>();
19173
19174 cx.spawn_in(window, async move |_, cx| {
19175 workspace.update_in(cx, |workspace, window, cx| {
19176 Self::open_locations_in_multibuffer(
19177 workspace,
19178 locations,
19179 format!("Selections for '{title}'"),
19180 false,
19181 MultibufferSelectionMode::All,
19182 window,
19183 cx,
19184 );
19185 })
19186 })
19187 .detach();
19188 }
19189
19190 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19191 /// last highlight added will be used.
19192 ///
19193 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19194 pub fn highlight_rows<T: 'static>(
19195 &mut self,
19196 range: Range<Anchor>,
19197 color: Hsla,
19198 options: RowHighlightOptions,
19199 cx: &mut Context<Self>,
19200 ) {
19201 let snapshot = self.buffer().read(cx).snapshot(cx);
19202 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19203 let ix = row_highlights.binary_search_by(|highlight| {
19204 Ordering::Equal
19205 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19206 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19207 });
19208
19209 if let Err(mut ix) = ix {
19210 let index = post_inc(&mut self.highlight_order);
19211
19212 // If this range intersects with the preceding highlight, then merge it with
19213 // the preceding highlight. Otherwise insert a new highlight.
19214 let mut merged = false;
19215 if ix > 0 {
19216 let prev_highlight = &mut row_highlights[ix - 1];
19217 if prev_highlight
19218 .range
19219 .end
19220 .cmp(&range.start, &snapshot)
19221 .is_ge()
19222 {
19223 ix -= 1;
19224 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19225 prev_highlight.range.end = range.end;
19226 }
19227 merged = true;
19228 prev_highlight.index = index;
19229 prev_highlight.color = color;
19230 prev_highlight.options = options;
19231 }
19232 }
19233
19234 if !merged {
19235 row_highlights.insert(
19236 ix,
19237 RowHighlight {
19238 range: range.clone(),
19239 index,
19240 color,
19241 options,
19242 type_id: TypeId::of::<T>(),
19243 },
19244 );
19245 }
19246
19247 // If any of the following highlights intersect with this one, merge them.
19248 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19249 let highlight = &row_highlights[ix];
19250 if next_highlight
19251 .range
19252 .start
19253 .cmp(&highlight.range.end, &snapshot)
19254 .is_le()
19255 {
19256 if next_highlight
19257 .range
19258 .end
19259 .cmp(&highlight.range.end, &snapshot)
19260 .is_gt()
19261 {
19262 row_highlights[ix].range.end = next_highlight.range.end;
19263 }
19264 row_highlights.remove(ix + 1);
19265 } else {
19266 break;
19267 }
19268 }
19269 }
19270 }
19271
19272 /// Remove any highlighted row ranges of the given type that intersect the
19273 /// given ranges.
19274 pub fn remove_highlighted_rows<T: 'static>(
19275 &mut self,
19276 ranges_to_remove: Vec<Range<Anchor>>,
19277 cx: &mut Context<Self>,
19278 ) {
19279 let snapshot = self.buffer().read(cx).snapshot(cx);
19280 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19281 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19282 row_highlights.retain(|highlight| {
19283 while let Some(range_to_remove) = ranges_to_remove.peek() {
19284 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19285 Ordering::Less | Ordering::Equal => {
19286 ranges_to_remove.next();
19287 }
19288 Ordering::Greater => {
19289 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19290 Ordering::Less | Ordering::Equal => {
19291 return false;
19292 }
19293 Ordering::Greater => break,
19294 }
19295 }
19296 }
19297 }
19298
19299 true
19300 })
19301 }
19302
19303 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19304 pub fn clear_row_highlights<T: 'static>(&mut self) {
19305 self.highlighted_rows.remove(&TypeId::of::<T>());
19306 }
19307
19308 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19309 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19310 self.highlighted_rows
19311 .get(&TypeId::of::<T>())
19312 .map_or(&[] as &[_], |vec| vec.as_slice())
19313 .iter()
19314 .map(|highlight| (highlight.range.clone(), highlight.color))
19315 }
19316
19317 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19318 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19319 /// Allows to ignore certain kinds of highlights.
19320 pub fn highlighted_display_rows(
19321 &self,
19322 window: &mut Window,
19323 cx: &mut App,
19324 ) -> BTreeMap<DisplayRow, LineHighlight> {
19325 let snapshot = self.snapshot(window, cx);
19326 let mut used_highlight_orders = HashMap::default();
19327 self.highlighted_rows
19328 .iter()
19329 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19330 .fold(
19331 BTreeMap::<DisplayRow, LineHighlight>::new(),
19332 |mut unique_rows, highlight| {
19333 let start = highlight.range.start.to_display_point(&snapshot);
19334 let end = highlight.range.end.to_display_point(&snapshot);
19335 let start_row = start.row().0;
19336 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19337 && end.column() == 0
19338 {
19339 end.row().0.saturating_sub(1)
19340 } else {
19341 end.row().0
19342 };
19343 for row in start_row..=end_row {
19344 let used_index =
19345 used_highlight_orders.entry(row).or_insert(highlight.index);
19346 if highlight.index >= *used_index {
19347 *used_index = highlight.index;
19348 unique_rows.insert(
19349 DisplayRow(row),
19350 LineHighlight {
19351 include_gutter: highlight.options.include_gutter,
19352 border: None,
19353 background: highlight.color.into(),
19354 type_id: Some(highlight.type_id),
19355 },
19356 );
19357 }
19358 }
19359 unique_rows
19360 },
19361 )
19362 }
19363
19364 pub fn highlighted_display_row_for_autoscroll(
19365 &self,
19366 snapshot: &DisplaySnapshot,
19367 ) -> Option<DisplayRow> {
19368 self.highlighted_rows
19369 .values()
19370 .flat_map(|highlighted_rows| highlighted_rows.iter())
19371 .filter_map(|highlight| {
19372 if highlight.options.autoscroll {
19373 Some(highlight.range.start.to_display_point(snapshot).row())
19374 } else {
19375 None
19376 }
19377 })
19378 .min()
19379 }
19380
19381 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19382 self.highlight_background::<SearchWithinRange>(
19383 ranges,
19384 |colors| colors.colors().editor_document_highlight_read_background,
19385 cx,
19386 )
19387 }
19388
19389 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19390 self.breadcrumb_header = Some(new_header);
19391 }
19392
19393 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19394 self.clear_background_highlights::<SearchWithinRange>(cx);
19395 }
19396
19397 pub fn highlight_background<T: 'static>(
19398 &mut self,
19399 ranges: &[Range<Anchor>],
19400 color_fetcher: fn(&Theme) -> Hsla,
19401 cx: &mut Context<Self>,
19402 ) {
19403 self.background_highlights.insert(
19404 HighlightKey::Type(TypeId::of::<T>()),
19405 (color_fetcher, Arc::from(ranges)),
19406 );
19407 self.scrollbar_marker_state.dirty = true;
19408 cx.notify();
19409 }
19410
19411 pub fn highlight_background_key<T: 'static>(
19412 &mut self,
19413 key: usize,
19414 ranges: &[Range<Anchor>],
19415 color_fetcher: fn(&Theme) -> Hsla,
19416 cx: &mut Context<Self>,
19417 ) {
19418 self.background_highlights.insert(
19419 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19420 (color_fetcher, Arc::from(ranges)),
19421 );
19422 self.scrollbar_marker_state.dirty = true;
19423 cx.notify();
19424 }
19425
19426 pub fn clear_background_highlights<T: 'static>(
19427 &mut self,
19428 cx: &mut Context<Self>,
19429 ) -> Option<BackgroundHighlight> {
19430 let text_highlights = self
19431 .background_highlights
19432 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19433 if !text_highlights.1.is_empty() {
19434 self.scrollbar_marker_state.dirty = true;
19435 cx.notify();
19436 }
19437 Some(text_highlights)
19438 }
19439
19440 pub fn highlight_gutter<T: 'static>(
19441 &mut self,
19442 ranges: impl Into<Vec<Range<Anchor>>>,
19443 color_fetcher: fn(&App) -> Hsla,
19444 cx: &mut Context<Self>,
19445 ) {
19446 self.gutter_highlights
19447 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19448 cx.notify();
19449 }
19450
19451 pub fn clear_gutter_highlights<T: 'static>(
19452 &mut self,
19453 cx: &mut Context<Self>,
19454 ) -> Option<GutterHighlight> {
19455 cx.notify();
19456 self.gutter_highlights.remove(&TypeId::of::<T>())
19457 }
19458
19459 pub fn insert_gutter_highlight<T: 'static>(
19460 &mut self,
19461 range: Range<Anchor>,
19462 color_fetcher: fn(&App) -> Hsla,
19463 cx: &mut Context<Self>,
19464 ) {
19465 let snapshot = self.buffer().read(cx).snapshot(cx);
19466 let mut highlights = self
19467 .gutter_highlights
19468 .remove(&TypeId::of::<T>())
19469 .map(|(_, highlights)| highlights)
19470 .unwrap_or_default();
19471 let ix = highlights.binary_search_by(|highlight| {
19472 Ordering::Equal
19473 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19474 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19475 });
19476 if let Err(ix) = ix {
19477 highlights.insert(ix, range);
19478 }
19479 self.gutter_highlights
19480 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19481 }
19482
19483 pub fn remove_gutter_highlights<T: 'static>(
19484 &mut self,
19485 ranges_to_remove: Vec<Range<Anchor>>,
19486 cx: &mut Context<Self>,
19487 ) {
19488 let snapshot = self.buffer().read(cx).snapshot(cx);
19489 let Some((color_fetcher, mut gutter_highlights)) =
19490 self.gutter_highlights.remove(&TypeId::of::<T>())
19491 else {
19492 return;
19493 };
19494 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19495 gutter_highlights.retain(|highlight| {
19496 while let Some(range_to_remove) = ranges_to_remove.peek() {
19497 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19498 Ordering::Less | Ordering::Equal => {
19499 ranges_to_remove.next();
19500 }
19501 Ordering::Greater => {
19502 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19503 Ordering::Less | Ordering::Equal => {
19504 return false;
19505 }
19506 Ordering::Greater => break,
19507 }
19508 }
19509 }
19510 }
19511
19512 true
19513 });
19514 self.gutter_highlights
19515 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19516 }
19517
19518 #[cfg(feature = "test-support")]
19519 pub fn all_text_highlights(
19520 &self,
19521 window: &mut Window,
19522 cx: &mut Context<Self>,
19523 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19524 let snapshot = self.snapshot(window, cx);
19525 self.display_map.update(cx, |display_map, _| {
19526 display_map
19527 .all_text_highlights()
19528 .map(|highlight| {
19529 let (style, ranges) = highlight.as_ref();
19530 (
19531 *style,
19532 ranges
19533 .iter()
19534 .map(|range| range.clone().to_display_points(&snapshot))
19535 .collect(),
19536 )
19537 })
19538 .collect()
19539 })
19540 }
19541
19542 #[cfg(feature = "test-support")]
19543 pub fn all_text_background_highlights(
19544 &self,
19545 window: &mut Window,
19546 cx: &mut Context<Self>,
19547 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19548 let snapshot = self.snapshot(window, cx);
19549 let buffer = &snapshot.buffer_snapshot;
19550 let start = buffer.anchor_before(0);
19551 let end = buffer.anchor_after(buffer.len());
19552 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19553 }
19554
19555 #[cfg(feature = "test-support")]
19556 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19557 let snapshot = self.buffer().read(cx).snapshot(cx);
19558
19559 let highlights = self
19560 .background_highlights
19561 .get(&HighlightKey::Type(TypeId::of::<
19562 items::BufferSearchHighlights,
19563 >()));
19564
19565 if let Some((_color, ranges)) = highlights {
19566 ranges
19567 .iter()
19568 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19569 .collect_vec()
19570 } else {
19571 vec![]
19572 }
19573 }
19574
19575 fn document_highlights_for_position<'a>(
19576 &'a self,
19577 position: Anchor,
19578 buffer: &'a MultiBufferSnapshot,
19579 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19580 let read_highlights = self
19581 .background_highlights
19582 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19583 .map(|h| &h.1);
19584 let write_highlights = self
19585 .background_highlights
19586 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19587 .map(|h| &h.1);
19588 let left_position = position.bias_left(buffer);
19589 let right_position = position.bias_right(buffer);
19590 read_highlights
19591 .into_iter()
19592 .chain(write_highlights)
19593 .flat_map(move |ranges| {
19594 let start_ix = match ranges.binary_search_by(|probe| {
19595 let cmp = probe.end.cmp(&left_position, buffer);
19596 if cmp.is_ge() {
19597 Ordering::Greater
19598 } else {
19599 Ordering::Less
19600 }
19601 }) {
19602 Ok(i) | Err(i) => i,
19603 };
19604
19605 ranges[start_ix..]
19606 .iter()
19607 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19608 })
19609 }
19610
19611 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19612 self.background_highlights
19613 .get(&HighlightKey::Type(TypeId::of::<T>()))
19614 .map_or(false, |(_, highlights)| !highlights.is_empty())
19615 }
19616
19617 pub fn background_highlights_in_range(
19618 &self,
19619 search_range: Range<Anchor>,
19620 display_snapshot: &DisplaySnapshot,
19621 theme: &Theme,
19622 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19623 let mut results = Vec::new();
19624 for (color_fetcher, ranges) in self.background_highlights.values() {
19625 let color = color_fetcher(theme);
19626 let start_ix = match ranges.binary_search_by(|probe| {
19627 let cmp = probe
19628 .end
19629 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19630 if cmp.is_gt() {
19631 Ordering::Greater
19632 } else {
19633 Ordering::Less
19634 }
19635 }) {
19636 Ok(i) | Err(i) => i,
19637 };
19638 for range in &ranges[start_ix..] {
19639 if range
19640 .start
19641 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19642 .is_ge()
19643 {
19644 break;
19645 }
19646
19647 let start = range.start.to_display_point(display_snapshot);
19648 let end = range.end.to_display_point(display_snapshot);
19649 results.push((start..end, color))
19650 }
19651 }
19652 results
19653 }
19654
19655 pub fn background_highlight_row_ranges<T: 'static>(
19656 &self,
19657 search_range: Range<Anchor>,
19658 display_snapshot: &DisplaySnapshot,
19659 count: usize,
19660 ) -> Vec<RangeInclusive<DisplayPoint>> {
19661 let mut results = Vec::new();
19662 let Some((_, ranges)) = self
19663 .background_highlights
19664 .get(&HighlightKey::Type(TypeId::of::<T>()))
19665 else {
19666 return vec![];
19667 };
19668
19669 let start_ix = match ranges.binary_search_by(|probe| {
19670 let cmp = probe
19671 .end
19672 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19673 if cmp.is_gt() {
19674 Ordering::Greater
19675 } else {
19676 Ordering::Less
19677 }
19678 }) {
19679 Ok(i) | Err(i) => i,
19680 };
19681 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19682 if let (Some(start_display), Some(end_display)) = (start, end) {
19683 results.push(
19684 start_display.to_display_point(display_snapshot)
19685 ..=end_display.to_display_point(display_snapshot),
19686 );
19687 }
19688 };
19689 let mut start_row: Option<Point> = None;
19690 let mut end_row: Option<Point> = None;
19691 if ranges.len() > count {
19692 return Vec::new();
19693 }
19694 for range in &ranges[start_ix..] {
19695 if range
19696 .start
19697 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19698 .is_ge()
19699 {
19700 break;
19701 }
19702 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19703 if let Some(current_row) = &end_row {
19704 if end.row == current_row.row {
19705 continue;
19706 }
19707 }
19708 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19709 if start_row.is_none() {
19710 assert_eq!(end_row, None);
19711 start_row = Some(start);
19712 end_row = Some(end);
19713 continue;
19714 }
19715 if let Some(current_end) = end_row.as_mut() {
19716 if start.row > current_end.row + 1 {
19717 push_region(start_row, end_row);
19718 start_row = Some(start);
19719 end_row = Some(end);
19720 } else {
19721 // Merge two hunks.
19722 *current_end = end;
19723 }
19724 } else {
19725 unreachable!();
19726 }
19727 }
19728 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19729 push_region(start_row, end_row);
19730 results
19731 }
19732
19733 pub fn gutter_highlights_in_range(
19734 &self,
19735 search_range: Range<Anchor>,
19736 display_snapshot: &DisplaySnapshot,
19737 cx: &App,
19738 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19739 let mut results = Vec::new();
19740 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19741 let color = color_fetcher(cx);
19742 let start_ix = match ranges.binary_search_by(|probe| {
19743 let cmp = probe
19744 .end
19745 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19746 if cmp.is_gt() {
19747 Ordering::Greater
19748 } else {
19749 Ordering::Less
19750 }
19751 }) {
19752 Ok(i) | Err(i) => i,
19753 };
19754 for range in &ranges[start_ix..] {
19755 if range
19756 .start
19757 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19758 .is_ge()
19759 {
19760 break;
19761 }
19762
19763 let start = range.start.to_display_point(display_snapshot);
19764 let end = range.end.to_display_point(display_snapshot);
19765 results.push((start..end, color))
19766 }
19767 }
19768 results
19769 }
19770
19771 /// Get the text ranges corresponding to the redaction query
19772 pub fn redacted_ranges(
19773 &self,
19774 search_range: Range<Anchor>,
19775 display_snapshot: &DisplaySnapshot,
19776 cx: &App,
19777 ) -> Vec<Range<DisplayPoint>> {
19778 display_snapshot
19779 .buffer_snapshot
19780 .redacted_ranges(search_range, |file| {
19781 if let Some(file) = file {
19782 file.is_private()
19783 && EditorSettings::get(
19784 Some(SettingsLocation {
19785 worktree_id: file.worktree_id(cx),
19786 path: file.path().as_ref(),
19787 }),
19788 cx,
19789 )
19790 .redact_private_values
19791 } else {
19792 false
19793 }
19794 })
19795 .map(|range| {
19796 range.start.to_display_point(display_snapshot)
19797 ..range.end.to_display_point(display_snapshot)
19798 })
19799 .collect()
19800 }
19801
19802 pub fn highlight_text_key<T: 'static>(
19803 &mut self,
19804 key: usize,
19805 ranges: Vec<Range<Anchor>>,
19806 style: HighlightStyle,
19807 cx: &mut Context<Self>,
19808 ) {
19809 self.display_map.update(cx, |map, _| {
19810 map.highlight_text(
19811 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19812 ranges,
19813 style,
19814 );
19815 });
19816 cx.notify();
19817 }
19818
19819 pub fn highlight_text<T: 'static>(
19820 &mut self,
19821 ranges: Vec<Range<Anchor>>,
19822 style: HighlightStyle,
19823 cx: &mut Context<Self>,
19824 ) {
19825 self.display_map.update(cx, |map, _| {
19826 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19827 });
19828 cx.notify();
19829 }
19830
19831 pub(crate) fn highlight_inlays<T: 'static>(
19832 &mut self,
19833 highlights: Vec<InlayHighlight>,
19834 style: HighlightStyle,
19835 cx: &mut Context<Self>,
19836 ) {
19837 self.display_map.update(cx, |map, _| {
19838 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19839 });
19840 cx.notify();
19841 }
19842
19843 pub fn text_highlights<'a, T: 'static>(
19844 &'a self,
19845 cx: &'a App,
19846 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19847 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19848 }
19849
19850 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19851 let cleared = self
19852 .display_map
19853 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19854 if cleared {
19855 cx.notify();
19856 }
19857 }
19858
19859 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19860 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19861 && self.focus_handle.is_focused(window)
19862 }
19863
19864 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19865 self.show_cursor_when_unfocused = is_enabled;
19866 cx.notify();
19867 }
19868
19869 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19870 cx.notify();
19871 }
19872
19873 fn on_debug_session_event(
19874 &mut self,
19875 _session: Entity<Session>,
19876 event: &SessionEvent,
19877 cx: &mut Context<Self>,
19878 ) {
19879 match event {
19880 SessionEvent::InvalidateInlineValue => {
19881 self.refresh_inline_values(cx);
19882 }
19883 _ => {}
19884 }
19885 }
19886
19887 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19888 let Some(project) = self.project.clone() else {
19889 return;
19890 };
19891
19892 if !self.inline_value_cache.enabled {
19893 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19894 self.splice_inlays(&inlays, Vec::new(), cx);
19895 return;
19896 }
19897
19898 let current_execution_position = self
19899 .highlighted_rows
19900 .get(&TypeId::of::<ActiveDebugLine>())
19901 .and_then(|lines| lines.last().map(|line| line.range.end));
19902
19903 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19904 let inline_values = editor
19905 .update(cx, |editor, cx| {
19906 let Some(current_execution_position) = current_execution_position else {
19907 return Some(Task::ready(Ok(Vec::new())));
19908 };
19909
19910 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19911 let snapshot = buffer.snapshot(cx);
19912
19913 let excerpt = snapshot.excerpt_containing(
19914 current_execution_position..current_execution_position,
19915 )?;
19916
19917 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19918 })?;
19919
19920 let range =
19921 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19922
19923 project.inline_values(buffer, range, cx)
19924 })
19925 .ok()
19926 .flatten()?
19927 .await
19928 .context("refreshing debugger inlays")
19929 .log_err()?;
19930
19931 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19932
19933 for (buffer_id, inline_value) in inline_values
19934 .into_iter()
19935 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19936 {
19937 buffer_inline_values
19938 .entry(buffer_id)
19939 .or_default()
19940 .push(inline_value);
19941 }
19942
19943 editor
19944 .update(cx, |editor, cx| {
19945 let snapshot = editor.buffer.read(cx).snapshot(cx);
19946 let mut new_inlays = Vec::default();
19947
19948 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19949 let buffer_id = buffer_snapshot.remote_id();
19950 buffer_inline_values
19951 .get(&buffer_id)
19952 .into_iter()
19953 .flatten()
19954 .for_each(|hint| {
19955 let inlay = Inlay::debugger(
19956 post_inc(&mut editor.next_inlay_id),
19957 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19958 hint.text(),
19959 );
19960 if !inlay.text.chars().contains(&'\n') {
19961 new_inlays.push(inlay);
19962 }
19963 });
19964 }
19965
19966 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19967 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19968
19969 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19970 })
19971 .ok()?;
19972 Some(())
19973 });
19974 }
19975
19976 fn on_buffer_event(
19977 &mut self,
19978 multibuffer: &Entity<MultiBuffer>,
19979 event: &multi_buffer::Event,
19980 window: &mut Window,
19981 cx: &mut Context<Self>,
19982 ) {
19983 match event {
19984 multi_buffer::Event::Edited {
19985 singleton_buffer_edited,
19986 edited_buffer,
19987 } => {
19988 self.scrollbar_marker_state.dirty = true;
19989 self.active_indent_guides_state.dirty = true;
19990 self.refresh_active_diagnostics(cx);
19991 self.refresh_code_actions(window, cx);
19992 self.refresh_selected_text_highlights(true, window, cx);
19993 self.refresh_single_line_folds(window, cx);
19994 refresh_matching_bracket_highlights(self, window, cx);
19995 if self.has_active_edit_prediction() {
19996 self.update_visible_edit_prediction(window, cx);
19997 }
19998 if let Some(project) = self.project.as_ref() {
19999 if let Some(edited_buffer) = edited_buffer {
20000 project.update(cx, |project, cx| {
20001 self.registered_buffers
20002 .entry(edited_buffer.read(cx).remote_id())
20003 .or_insert_with(|| {
20004 project
20005 .register_buffer_with_language_servers(&edited_buffer, cx)
20006 });
20007 });
20008 }
20009 }
20010 cx.emit(EditorEvent::BufferEdited);
20011 cx.emit(SearchEvent::MatchesInvalidated);
20012
20013 if let Some(buffer) = edited_buffer {
20014 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20015 }
20016
20017 if *singleton_buffer_edited {
20018 if let Some(buffer) = edited_buffer {
20019 if buffer.read(cx).file().is_none() {
20020 cx.emit(EditorEvent::TitleChanged);
20021 }
20022 }
20023 if let Some(project) = &self.project {
20024 #[allow(clippy::mutable_key_type)]
20025 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20026 multibuffer
20027 .all_buffers()
20028 .into_iter()
20029 .filter_map(|buffer| {
20030 buffer.update(cx, |buffer, cx| {
20031 let language = buffer.language()?;
20032 let should_discard = project.update(cx, |project, cx| {
20033 project.is_local()
20034 && !project.has_language_servers_for(buffer, cx)
20035 });
20036 should_discard.not().then_some(language.clone())
20037 })
20038 })
20039 .collect::<HashSet<_>>()
20040 });
20041 if !languages_affected.is_empty() {
20042 self.refresh_inlay_hints(
20043 InlayHintRefreshReason::BufferEdited(languages_affected),
20044 cx,
20045 );
20046 }
20047 }
20048 }
20049
20050 let Some(project) = &self.project else { return };
20051 let (telemetry, is_via_ssh) = {
20052 let project = project.read(cx);
20053 let telemetry = project.client().telemetry().clone();
20054 let is_via_ssh = project.is_via_ssh();
20055 (telemetry, is_via_ssh)
20056 };
20057 refresh_linked_ranges(self, window, cx);
20058 telemetry.log_edit_event("editor", is_via_ssh);
20059 }
20060 multi_buffer::Event::ExcerptsAdded {
20061 buffer,
20062 predecessor,
20063 excerpts,
20064 } => {
20065 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20066 let buffer_id = buffer.read(cx).remote_id();
20067 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
20068 if let Some(project) = &self.project {
20069 update_uncommitted_diff_for_buffer(
20070 cx.entity(),
20071 project,
20072 [buffer.clone()],
20073 self.buffer.clone(),
20074 cx,
20075 )
20076 .detach();
20077 }
20078 }
20079 self.update_lsp_data(false, Some(buffer_id), window, cx);
20080 cx.emit(EditorEvent::ExcerptsAdded {
20081 buffer: buffer.clone(),
20082 predecessor: *predecessor,
20083 excerpts: excerpts.clone(),
20084 });
20085 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20086 }
20087 multi_buffer::Event::ExcerptsRemoved {
20088 ids,
20089 removed_buffer_ids,
20090 } => {
20091 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20092 let buffer = self.buffer.read(cx);
20093 self.registered_buffers
20094 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20095 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20096 cx.emit(EditorEvent::ExcerptsRemoved {
20097 ids: ids.clone(),
20098 removed_buffer_ids: removed_buffer_ids.clone(),
20099 });
20100 }
20101 multi_buffer::Event::ExcerptsEdited {
20102 excerpt_ids,
20103 buffer_ids,
20104 } => {
20105 self.display_map.update(cx, |map, cx| {
20106 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20107 });
20108 cx.emit(EditorEvent::ExcerptsEdited {
20109 ids: excerpt_ids.clone(),
20110 });
20111 }
20112 multi_buffer::Event::ExcerptsExpanded { ids } => {
20113 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20114 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20115 }
20116 multi_buffer::Event::Reparsed(buffer_id) => {
20117 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20118 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20119
20120 cx.emit(EditorEvent::Reparsed(*buffer_id));
20121 }
20122 multi_buffer::Event::DiffHunksToggled => {
20123 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20124 }
20125 multi_buffer::Event::LanguageChanged(buffer_id) => {
20126 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20127 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20128 cx.emit(EditorEvent::Reparsed(*buffer_id));
20129 cx.notify();
20130 }
20131 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20132 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20133 multi_buffer::Event::FileHandleChanged
20134 | multi_buffer::Event::Reloaded
20135 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20136 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20137 multi_buffer::Event::DiagnosticsUpdated => {
20138 self.update_diagnostics_state(window, cx);
20139 }
20140 _ => {}
20141 };
20142 }
20143
20144 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20145 if !self.diagnostics_enabled() {
20146 return;
20147 }
20148 self.refresh_active_diagnostics(cx);
20149 self.refresh_inline_diagnostics(true, window, cx);
20150 self.scrollbar_marker_state.dirty = true;
20151 cx.notify();
20152 }
20153
20154 pub fn start_temporary_diff_override(&mut self) {
20155 self.load_diff_task.take();
20156 self.temporary_diff_override = true;
20157 }
20158
20159 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20160 self.temporary_diff_override = false;
20161 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20162 self.buffer.update(cx, |buffer, cx| {
20163 buffer.set_all_diff_hunks_collapsed(cx);
20164 });
20165
20166 if let Some(project) = self.project.clone() {
20167 self.load_diff_task = Some(
20168 update_uncommitted_diff_for_buffer(
20169 cx.entity(),
20170 &project,
20171 self.buffer.read(cx).all_buffers(),
20172 self.buffer.clone(),
20173 cx,
20174 )
20175 .shared(),
20176 );
20177 }
20178 }
20179
20180 fn on_display_map_changed(
20181 &mut self,
20182 _: Entity<DisplayMap>,
20183 _: &mut Window,
20184 cx: &mut Context<Self>,
20185 ) {
20186 cx.notify();
20187 }
20188
20189 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20190 if self.diagnostics_enabled() {
20191 let new_severity = EditorSettings::get_global(cx)
20192 .diagnostics_max_severity
20193 .unwrap_or(DiagnosticSeverity::Hint);
20194 self.set_max_diagnostics_severity(new_severity, cx);
20195 }
20196 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20197 self.update_edit_prediction_settings(cx);
20198 self.refresh_edit_prediction(true, false, window, cx);
20199 self.refresh_inline_values(cx);
20200 self.refresh_inlay_hints(
20201 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20202 self.selections.newest_anchor().head(),
20203 &self.buffer.read(cx).snapshot(cx),
20204 cx,
20205 )),
20206 cx,
20207 );
20208
20209 let old_cursor_shape = self.cursor_shape;
20210
20211 {
20212 let editor_settings = EditorSettings::get_global(cx);
20213 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20214 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20215 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20216 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20217 }
20218
20219 if old_cursor_shape != self.cursor_shape {
20220 cx.emit(EditorEvent::CursorShapeChanged);
20221 }
20222
20223 let project_settings = ProjectSettings::get_global(cx);
20224 self.serialize_dirty_buffers =
20225 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20226
20227 if self.mode.is_full() {
20228 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20229 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20230 if self.show_inline_diagnostics != show_inline_diagnostics {
20231 self.show_inline_diagnostics = show_inline_diagnostics;
20232 self.refresh_inline_diagnostics(false, window, cx);
20233 }
20234
20235 if self.git_blame_inline_enabled != inline_blame_enabled {
20236 self.toggle_git_blame_inline_internal(false, window, cx);
20237 }
20238
20239 let minimap_settings = EditorSettings::get_global(cx).minimap;
20240 if self.minimap_visibility != MinimapVisibility::Disabled {
20241 if self.minimap_visibility.settings_visibility()
20242 != minimap_settings.minimap_enabled()
20243 {
20244 self.set_minimap_visibility(
20245 MinimapVisibility::for_mode(self.mode(), cx),
20246 window,
20247 cx,
20248 );
20249 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20250 minimap_entity.update(cx, |minimap_editor, cx| {
20251 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20252 })
20253 }
20254 }
20255 }
20256
20257 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20258 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20259 }) {
20260 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20261 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20262 }
20263 self.refresh_colors(false, None, window, cx);
20264 }
20265
20266 cx.notify();
20267 }
20268
20269 pub fn set_searchable(&mut self, searchable: bool) {
20270 self.searchable = searchable;
20271 }
20272
20273 pub fn searchable(&self) -> bool {
20274 self.searchable
20275 }
20276
20277 fn open_proposed_changes_editor(
20278 &mut self,
20279 _: &OpenProposedChangesEditor,
20280 window: &mut Window,
20281 cx: &mut Context<Self>,
20282 ) {
20283 let Some(workspace) = self.workspace() else {
20284 cx.propagate();
20285 return;
20286 };
20287
20288 let selections = self.selections.all::<usize>(cx);
20289 let multi_buffer = self.buffer.read(cx);
20290 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20291 let mut new_selections_by_buffer = HashMap::default();
20292 for selection in selections {
20293 for (buffer, range, _) in
20294 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20295 {
20296 let mut range = range.to_point(buffer);
20297 range.start.column = 0;
20298 range.end.column = buffer.line_len(range.end.row);
20299 new_selections_by_buffer
20300 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20301 .or_insert(Vec::new())
20302 .push(range)
20303 }
20304 }
20305
20306 let proposed_changes_buffers = new_selections_by_buffer
20307 .into_iter()
20308 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20309 .collect::<Vec<_>>();
20310 let proposed_changes_editor = cx.new(|cx| {
20311 ProposedChangesEditor::new(
20312 "Proposed changes",
20313 proposed_changes_buffers,
20314 self.project.clone(),
20315 window,
20316 cx,
20317 )
20318 });
20319
20320 window.defer(cx, move |window, cx| {
20321 workspace.update(cx, |workspace, cx| {
20322 workspace.active_pane().update(cx, |pane, cx| {
20323 pane.add_item(
20324 Box::new(proposed_changes_editor),
20325 true,
20326 true,
20327 None,
20328 window,
20329 cx,
20330 );
20331 });
20332 });
20333 });
20334 }
20335
20336 pub fn open_excerpts_in_split(
20337 &mut self,
20338 _: &OpenExcerptsSplit,
20339 window: &mut Window,
20340 cx: &mut Context<Self>,
20341 ) {
20342 self.open_excerpts_common(None, true, window, cx)
20343 }
20344
20345 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20346 self.open_excerpts_common(None, false, window, cx)
20347 }
20348
20349 fn open_excerpts_common(
20350 &mut self,
20351 jump_data: Option<JumpData>,
20352 split: bool,
20353 window: &mut Window,
20354 cx: &mut Context<Self>,
20355 ) {
20356 let Some(workspace) = self.workspace() else {
20357 cx.propagate();
20358 return;
20359 };
20360
20361 if self.buffer.read(cx).is_singleton() {
20362 cx.propagate();
20363 return;
20364 }
20365
20366 let mut new_selections_by_buffer = HashMap::default();
20367 match &jump_data {
20368 Some(JumpData::MultiBufferPoint {
20369 excerpt_id,
20370 position,
20371 anchor,
20372 line_offset_from_top,
20373 }) => {
20374 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20375 if let Some(buffer) = multi_buffer_snapshot
20376 .buffer_id_for_excerpt(*excerpt_id)
20377 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20378 {
20379 let buffer_snapshot = buffer.read(cx).snapshot();
20380 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20381 language::ToPoint::to_point(anchor, &buffer_snapshot)
20382 } else {
20383 buffer_snapshot.clip_point(*position, Bias::Left)
20384 };
20385 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20386 new_selections_by_buffer.insert(
20387 buffer,
20388 (
20389 vec![jump_to_offset..jump_to_offset],
20390 Some(*line_offset_from_top),
20391 ),
20392 );
20393 }
20394 }
20395 Some(JumpData::MultiBufferRow {
20396 row,
20397 line_offset_from_top,
20398 }) => {
20399 let point = MultiBufferPoint::new(row.0, 0);
20400 if let Some((buffer, buffer_point, _)) =
20401 self.buffer.read(cx).point_to_buffer_point(point, cx)
20402 {
20403 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20404 new_selections_by_buffer
20405 .entry(buffer)
20406 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20407 .0
20408 .push(buffer_offset..buffer_offset)
20409 }
20410 }
20411 None => {
20412 let selections = self.selections.all::<usize>(cx);
20413 let multi_buffer = self.buffer.read(cx);
20414 for selection in selections {
20415 for (snapshot, range, _, anchor) in multi_buffer
20416 .snapshot(cx)
20417 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20418 {
20419 if let Some(anchor) = anchor {
20420 // selection is in a deleted hunk
20421 let Some(buffer_id) = anchor.buffer_id else {
20422 continue;
20423 };
20424 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20425 continue;
20426 };
20427 let offset = text::ToOffset::to_offset(
20428 &anchor.text_anchor,
20429 &buffer_handle.read(cx).snapshot(),
20430 );
20431 let range = offset..offset;
20432 new_selections_by_buffer
20433 .entry(buffer_handle)
20434 .or_insert((Vec::new(), None))
20435 .0
20436 .push(range)
20437 } else {
20438 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20439 else {
20440 continue;
20441 };
20442 new_selections_by_buffer
20443 .entry(buffer_handle)
20444 .or_insert((Vec::new(), None))
20445 .0
20446 .push(range)
20447 }
20448 }
20449 }
20450 }
20451 }
20452
20453 new_selections_by_buffer
20454 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20455
20456 if new_selections_by_buffer.is_empty() {
20457 return;
20458 }
20459
20460 // We defer the pane interaction because we ourselves are a workspace item
20461 // and activating a new item causes the pane to call a method on us reentrantly,
20462 // which panics if we're on the stack.
20463 window.defer(cx, move |window, cx| {
20464 workspace.update(cx, |workspace, cx| {
20465 let pane = if split {
20466 workspace.adjacent_pane(window, cx)
20467 } else {
20468 workspace.active_pane().clone()
20469 };
20470
20471 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20472 let editor = buffer
20473 .read(cx)
20474 .file()
20475 .is_none()
20476 .then(|| {
20477 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20478 // so `workspace.open_project_item` will never find them, always opening a new editor.
20479 // Instead, we try to activate the existing editor in the pane first.
20480 let (editor, pane_item_index) =
20481 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20482 let editor = item.downcast::<Editor>()?;
20483 let singleton_buffer =
20484 editor.read(cx).buffer().read(cx).as_singleton()?;
20485 if singleton_buffer == buffer {
20486 Some((editor, i))
20487 } else {
20488 None
20489 }
20490 })?;
20491 pane.update(cx, |pane, cx| {
20492 pane.activate_item(pane_item_index, true, true, window, cx)
20493 });
20494 Some(editor)
20495 })
20496 .flatten()
20497 .unwrap_or_else(|| {
20498 workspace.open_project_item::<Self>(
20499 pane.clone(),
20500 buffer,
20501 true,
20502 true,
20503 window,
20504 cx,
20505 )
20506 });
20507
20508 editor.update(cx, |editor, cx| {
20509 let autoscroll = match scroll_offset {
20510 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20511 None => Autoscroll::newest(),
20512 };
20513 let nav_history = editor.nav_history.take();
20514 editor.change_selections(
20515 SelectionEffects::scroll(autoscroll),
20516 window,
20517 cx,
20518 |s| {
20519 s.select_ranges(ranges);
20520 },
20521 );
20522 editor.nav_history = nav_history;
20523 });
20524 }
20525 })
20526 });
20527 }
20528
20529 // For now, don't allow opening excerpts in buffers that aren't backed by
20530 // regular project files.
20531 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20532 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20533 }
20534
20535 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20536 let snapshot = self.buffer.read(cx).read(cx);
20537 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20538 Some(
20539 ranges
20540 .iter()
20541 .map(move |range| {
20542 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20543 })
20544 .collect(),
20545 )
20546 }
20547
20548 fn selection_replacement_ranges(
20549 &self,
20550 range: Range<OffsetUtf16>,
20551 cx: &mut App,
20552 ) -> Vec<Range<OffsetUtf16>> {
20553 let selections = self.selections.all::<OffsetUtf16>(cx);
20554 let newest_selection = selections
20555 .iter()
20556 .max_by_key(|selection| selection.id)
20557 .unwrap();
20558 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20559 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20560 let snapshot = self.buffer.read(cx).read(cx);
20561 selections
20562 .into_iter()
20563 .map(|mut selection| {
20564 selection.start.0 =
20565 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20566 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20567 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20568 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20569 })
20570 .collect()
20571 }
20572
20573 fn report_editor_event(
20574 &self,
20575 reported_event: ReportEditorEvent,
20576 file_extension: Option<String>,
20577 cx: &App,
20578 ) {
20579 if cfg!(any(test, feature = "test-support")) {
20580 return;
20581 }
20582
20583 let Some(project) = &self.project else { return };
20584
20585 // If None, we are in a file without an extension
20586 let file = self
20587 .buffer
20588 .read(cx)
20589 .as_singleton()
20590 .and_then(|b| b.read(cx).file());
20591 let file_extension = file_extension.or(file
20592 .as_ref()
20593 .and_then(|file| Path::new(file.file_name(cx)).extension())
20594 .and_then(|e| e.to_str())
20595 .map(|a| a.to_string()));
20596
20597 let vim_mode = vim_enabled(cx);
20598
20599 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20600 let copilot_enabled = edit_predictions_provider
20601 == language::language_settings::EditPredictionProvider::Copilot;
20602 let copilot_enabled_for_language = self
20603 .buffer
20604 .read(cx)
20605 .language_settings(cx)
20606 .show_edit_predictions;
20607
20608 let project = project.read(cx);
20609 let event_type = reported_event.event_type();
20610
20611 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20612 telemetry::event!(
20613 event_type,
20614 type = if auto_saved {"autosave"} else {"manual"},
20615 file_extension,
20616 vim_mode,
20617 copilot_enabled,
20618 copilot_enabled_for_language,
20619 edit_predictions_provider,
20620 is_via_ssh = project.is_via_ssh(),
20621 );
20622 } else {
20623 telemetry::event!(
20624 event_type,
20625 file_extension,
20626 vim_mode,
20627 copilot_enabled,
20628 copilot_enabled_for_language,
20629 edit_predictions_provider,
20630 is_via_ssh = project.is_via_ssh(),
20631 );
20632 };
20633 }
20634
20635 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20636 /// with each line being an array of {text, highlight} objects.
20637 fn copy_highlight_json(
20638 &mut self,
20639 _: &CopyHighlightJson,
20640 window: &mut Window,
20641 cx: &mut Context<Self>,
20642 ) {
20643 #[derive(Serialize)]
20644 struct Chunk<'a> {
20645 text: String,
20646 highlight: Option<&'a str>,
20647 }
20648
20649 let snapshot = self.buffer.read(cx).snapshot(cx);
20650 let range = self
20651 .selected_text_range(false, window, cx)
20652 .and_then(|selection| {
20653 if selection.range.is_empty() {
20654 None
20655 } else {
20656 Some(selection.range)
20657 }
20658 })
20659 .unwrap_or_else(|| 0..snapshot.len());
20660
20661 let chunks = snapshot.chunks(range, true);
20662 let mut lines = Vec::new();
20663 let mut line: VecDeque<Chunk> = VecDeque::new();
20664
20665 let Some(style) = self.style.as_ref() else {
20666 return;
20667 };
20668
20669 for chunk in chunks {
20670 let highlight = chunk
20671 .syntax_highlight_id
20672 .and_then(|id| id.name(&style.syntax));
20673 let mut chunk_lines = chunk.text.split('\n').peekable();
20674 while let Some(text) = chunk_lines.next() {
20675 let mut merged_with_last_token = false;
20676 if let Some(last_token) = line.back_mut() {
20677 if last_token.highlight == highlight {
20678 last_token.text.push_str(text);
20679 merged_with_last_token = true;
20680 }
20681 }
20682
20683 if !merged_with_last_token {
20684 line.push_back(Chunk {
20685 text: text.into(),
20686 highlight,
20687 });
20688 }
20689
20690 if chunk_lines.peek().is_some() {
20691 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20692 line.pop_front();
20693 }
20694 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20695 line.pop_back();
20696 }
20697
20698 lines.push(mem::take(&mut line));
20699 }
20700 }
20701 }
20702
20703 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20704 return;
20705 };
20706 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20707 }
20708
20709 pub fn open_context_menu(
20710 &mut self,
20711 _: &OpenContextMenu,
20712 window: &mut Window,
20713 cx: &mut Context<Self>,
20714 ) {
20715 self.request_autoscroll(Autoscroll::newest(), cx);
20716 let position = self.selections.newest_display(cx).start;
20717 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20718 }
20719
20720 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20721 &self.inlay_hint_cache
20722 }
20723
20724 pub fn replay_insert_event(
20725 &mut self,
20726 text: &str,
20727 relative_utf16_range: Option<Range<isize>>,
20728 window: &mut Window,
20729 cx: &mut Context<Self>,
20730 ) {
20731 if !self.input_enabled {
20732 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20733 return;
20734 }
20735 if let Some(relative_utf16_range) = relative_utf16_range {
20736 let selections = self.selections.all::<OffsetUtf16>(cx);
20737 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20738 let new_ranges = selections.into_iter().map(|range| {
20739 let start = OffsetUtf16(
20740 range
20741 .head()
20742 .0
20743 .saturating_add_signed(relative_utf16_range.start),
20744 );
20745 let end = OffsetUtf16(
20746 range
20747 .head()
20748 .0
20749 .saturating_add_signed(relative_utf16_range.end),
20750 );
20751 start..end
20752 });
20753 s.select_ranges(new_ranges);
20754 });
20755 }
20756
20757 self.handle_input(text, window, cx);
20758 }
20759
20760 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20761 let Some(provider) = self.semantics_provider.as_ref() else {
20762 return false;
20763 };
20764
20765 let mut supports = false;
20766 self.buffer().update(cx, |this, cx| {
20767 this.for_each_buffer(|buffer| {
20768 supports |= provider.supports_inlay_hints(buffer, cx);
20769 });
20770 });
20771
20772 supports
20773 }
20774
20775 pub fn is_focused(&self, window: &Window) -> bool {
20776 self.focus_handle.is_focused(window)
20777 }
20778
20779 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20780 cx.emit(EditorEvent::Focused);
20781
20782 if let Some(descendant) = self
20783 .last_focused_descendant
20784 .take()
20785 .and_then(|descendant| descendant.upgrade())
20786 {
20787 window.focus(&descendant);
20788 } else {
20789 if let Some(blame) = self.blame.as_ref() {
20790 blame.update(cx, GitBlame::focus)
20791 }
20792
20793 self.blink_manager.update(cx, BlinkManager::enable);
20794 self.show_cursor_names(window, cx);
20795 self.buffer.update(cx, |buffer, cx| {
20796 buffer.finalize_last_transaction(cx);
20797 if self.leader_id.is_none() {
20798 buffer.set_active_selections(
20799 &self.selections.disjoint_anchors(),
20800 self.selections.line_mode,
20801 self.cursor_shape,
20802 cx,
20803 );
20804 }
20805 });
20806 }
20807 }
20808
20809 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20810 cx.emit(EditorEvent::FocusedIn)
20811 }
20812
20813 fn handle_focus_out(
20814 &mut self,
20815 event: FocusOutEvent,
20816 _window: &mut Window,
20817 cx: &mut Context<Self>,
20818 ) {
20819 if event.blurred != self.focus_handle {
20820 self.last_focused_descendant = Some(event.blurred);
20821 }
20822 self.selection_drag_state = SelectionDragState::None;
20823 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20824 }
20825
20826 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20827 self.blink_manager.update(cx, BlinkManager::disable);
20828 self.buffer
20829 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20830
20831 if let Some(blame) = self.blame.as_ref() {
20832 blame.update(cx, GitBlame::blur)
20833 }
20834 if !self.hover_state.focused(window, cx) {
20835 hide_hover(self, cx);
20836 }
20837 if !self
20838 .context_menu
20839 .borrow()
20840 .as_ref()
20841 .is_some_and(|context_menu| context_menu.focused(window, cx))
20842 {
20843 self.hide_context_menu(window, cx);
20844 }
20845 self.discard_edit_prediction(false, cx);
20846 cx.emit(EditorEvent::Blurred);
20847 cx.notify();
20848 }
20849
20850 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20851 let mut pending: String = window
20852 .pending_input_keystrokes()
20853 .into_iter()
20854 .flatten()
20855 .filter_map(|keystroke| {
20856 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20857 keystroke.key_char.clone()
20858 } else {
20859 None
20860 }
20861 })
20862 .collect();
20863
20864 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20865 pending = "".to_string();
20866 }
20867
20868 let existing_pending = self
20869 .text_highlights::<PendingInput>(cx)
20870 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20871 if existing_pending.is_none() && pending.is_empty() {
20872 return;
20873 }
20874 let transaction =
20875 self.transact(window, cx, |this, window, cx| {
20876 let selections = this.selections.all::<usize>(cx);
20877 let edits = selections
20878 .iter()
20879 .map(|selection| (selection.end..selection.end, pending.clone()));
20880 this.edit(edits, cx);
20881 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20882 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20883 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20884 }));
20885 });
20886 if let Some(existing_ranges) = existing_pending {
20887 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20888 this.edit(edits, cx);
20889 }
20890 });
20891
20892 let snapshot = self.snapshot(window, cx);
20893 let ranges = self
20894 .selections
20895 .all::<usize>(cx)
20896 .into_iter()
20897 .map(|selection| {
20898 snapshot.buffer_snapshot.anchor_after(selection.end)
20899 ..snapshot
20900 .buffer_snapshot
20901 .anchor_before(selection.end + pending.len())
20902 })
20903 .collect();
20904
20905 if pending.is_empty() {
20906 self.clear_highlights::<PendingInput>(cx);
20907 } else {
20908 self.highlight_text::<PendingInput>(
20909 ranges,
20910 HighlightStyle {
20911 underline: Some(UnderlineStyle {
20912 thickness: px(1.),
20913 color: None,
20914 wavy: false,
20915 }),
20916 ..Default::default()
20917 },
20918 cx,
20919 );
20920 }
20921
20922 self.ime_transaction = self.ime_transaction.or(transaction);
20923 if let Some(transaction) = self.ime_transaction {
20924 self.buffer.update(cx, |buffer, cx| {
20925 buffer.group_until_transaction(transaction, cx);
20926 });
20927 }
20928
20929 if self.text_highlights::<PendingInput>(cx).is_none() {
20930 self.ime_transaction.take();
20931 }
20932 }
20933
20934 pub fn register_action_renderer(
20935 &mut self,
20936 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20937 ) -> Subscription {
20938 let id = self.next_editor_action_id.post_inc();
20939 self.editor_actions
20940 .borrow_mut()
20941 .insert(id, Box::new(listener));
20942
20943 let editor_actions = self.editor_actions.clone();
20944 Subscription::new(move || {
20945 editor_actions.borrow_mut().remove(&id);
20946 })
20947 }
20948
20949 pub fn register_action<A: Action>(
20950 &mut self,
20951 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20952 ) -> Subscription {
20953 let id = self.next_editor_action_id.post_inc();
20954 let listener = Arc::new(listener);
20955 self.editor_actions.borrow_mut().insert(
20956 id,
20957 Box::new(move |_, window, _| {
20958 let listener = listener.clone();
20959 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20960 let action = action.downcast_ref().unwrap();
20961 if phase == DispatchPhase::Bubble {
20962 listener(action, window, cx)
20963 }
20964 })
20965 }),
20966 );
20967
20968 let editor_actions = self.editor_actions.clone();
20969 Subscription::new(move || {
20970 editor_actions.borrow_mut().remove(&id);
20971 })
20972 }
20973
20974 pub fn file_header_size(&self) -> u32 {
20975 FILE_HEADER_HEIGHT
20976 }
20977
20978 pub fn restore(
20979 &mut self,
20980 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20981 window: &mut Window,
20982 cx: &mut Context<Self>,
20983 ) {
20984 let workspace = self.workspace();
20985 let project = self.project.as_ref();
20986 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20987 let mut tasks = Vec::new();
20988 for (buffer_id, changes) in revert_changes {
20989 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20990 buffer.update(cx, |buffer, cx| {
20991 buffer.edit(
20992 changes
20993 .into_iter()
20994 .map(|(range, text)| (range, text.to_string())),
20995 None,
20996 cx,
20997 );
20998 });
20999
21000 if let Some(project) =
21001 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21002 {
21003 project.update(cx, |project, cx| {
21004 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21005 })
21006 }
21007 }
21008 }
21009 tasks
21010 });
21011 cx.spawn_in(window, async move |_, cx| {
21012 for (buffer, task) in save_tasks {
21013 let result = task.await;
21014 if result.is_err() {
21015 let Some(path) = buffer
21016 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21017 .ok()
21018 else {
21019 continue;
21020 };
21021 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21022 let Some(task) = cx
21023 .update_window_entity(&workspace, |workspace, window, cx| {
21024 workspace
21025 .open_path_preview(path, None, false, false, false, window, cx)
21026 })
21027 .ok()
21028 else {
21029 continue;
21030 };
21031 task.await.log_err();
21032 }
21033 }
21034 }
21035 })
21036 .detach();
21037 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21038 selections.refresh()
21039 });
21040 }
21041
21042 pub fn to_pixel_point(
21043 &self,
21044 source: multi_buffer::Anchor,
21045 editor_snapshot: &EditorSnapshot,
21046 window: &mut Window,
21047 ) -> Option<gpui::Point<Pixels>> {
21048 let source_point = source.to_display_point(editor_snapshot);
21049 self.display_to_pixel_point(source_point, editor_snapshot, window)
21050 }
21051
21052 pub fn display_to_pixel_point(
21053 &self,
21054 source: DisplayPoint,
21055 editor_snapshot: &EditorSnapshot,
21056 window: &mut Window,
21057 ) -> Option<gpui::Point<Pixels>> {
21058 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21059 let text_layout_details = self.text_layout_details(window);
21060 let scroll_top = text_layout_details
21061 .scroll_anchor
21062 .scroll_position(editor_snapshot)
21063 .y;
21064
21065 if source.row().as_f32() < scroll_top.floor() {
21066 return None;
21067 }
21068 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21069 let source_y = line_height * (source.row().as_f32() - scroll_top);
21070 Some(gpui::Point::new(source_x, source_y))
21071 }
21072
21073 pub fn has_visible_completions_menu(&self) -> bool {
21074 !self.edit_prediction_preview_is_active()
21075 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
21076 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21077 })
21078 }
21079
21080 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21081 if self.mode.is_minimap() {
21082 return;
21083 }
21084 self.addons
21085 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21086 }
21087
21088 pub fn unregister_addon<T: Addon>(&mut self) {
21089 self.addons.remove(&std::any::TypeId::of::<T>());
21090 }
21091
21092 pub fn addon<T: Addon>(&self) -> Option<&T> {
21093 let type_id = std::any::TypeId::of::<T>();
21094 self.addons
21095 .get(&type_id)
21096 .and_then(|item| item.to_any().downcast_ref::<T>())
21097 }
21098
21099 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21100 let type_id = std::any::TypeId::of::<T>();
21101 self.addons
21102 .get_mut(&type_id)
21103 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21104 }
21105
21106 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21107 let text_layout_details = self.text_layout_details(window);
21108 let style = &text_layout_details.editor_style;
21109 let font_id = window.text_system().resolve_font(&style.text.font());
21110 let font_size = style.text.font_size.to_pixels(window.rem_size());
21111 let line_height = style.text.line_height_in_pixels(window.rem_size());
21112 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21113 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21114
21115 CharacterDimensions {
21116 em_width,
21117 em_advance,
21118 line_height,
21119 }
21120 }
21121
21122 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21123 self.load_diff_task.clone()
21124 }
21125
21126 fn read_metadata_from_db(
21127 &mut self,
21128 item_id: u64,
21129 workspace_id: WorkspaceId,
21130 window: &mut Window,
21131 cx: &mut Context<Editor>,
21132 ) {
21133 if self.is_singleton(cx)
21134 && !self.mode.is_minimap()
21135 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21136 {
21137 let buffer_snapshot = OnceCell::new();
21138
21139 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21140 if !folds.is_empty() {
21141 let snapshot =
21142 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21143 self.fold_ranges(
21144 folds
21145 .into_iter()
21146 .map(|(start, end)| {
21147 snapshot.clip_offset(start, Bias::Left)
21148 ..snapshot.clip_offset(end, Bias::Right)
21149 })
21150 .collect(),
21151 false,
21152 window,
21153 cx,
21154 );
21155 }
21156 }
21157
21158 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21159 if !selections.is_empty() {
21160 let snapshot =
21161 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21162 // skip adding the initial selection to selection history
21163 self.selection_history.mode = SelectionHistoryMode::Skipping;
21164 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21165 s.select_ranges(selections.into_iter().map(|(start, end)| {
21166 snapshot.clip_offset(start, Bias::Left)
21167 ..snapshot.clip_offset(end, Bias::Right)
21168 }));
21169 });
21170 self.selection_history.mode = SelectionHistoryMode::Normal;
21171 }
21172 };
21173 }
21174
21175 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21176 }
21177
21178 fn update_lsp_data(
21179 &mut self,
21180 ignore_cache: bool,
21181 for_buffer: Option<BufferId>,
21182 window: &mut Window,
21183 cx: &mut Context<'_, Self>,
21184 ) {
21185 self.pull_diagnostics(for_buffer, window, cx);
21186 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21187 }
21188}
21189
21190fn vim_enabled(cx: &App) -> bool {
21191 cx.global::<SettingsStore>()
21192 .raw_user_settings()
21193 .get("vim_mode")
21194 == Some(&serde_json::Value::Bool(true))
21195}
21196
21197fn process_completion_for_edit(
21198 completion: &Completion,
21199 intent: CompletionIntent,
21200 buffer: &Entity<Buffer>,
21201 cursor_position: &text::Anchor,
21202 cx: &mut Context<Editor>,
21203) -> CompletionEdit {
21204 let buffer = buffer.read(cx);
21205 let buffer_snapshot = buffer.snapshot();
21206 let (snippet, new_text) = if completion.is_snippet() {
21207 // Workaround for typescript language server issues so that methods don't expand within
21208 // strings and functions with type expressions. The previous point is used because the query
21209 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21210 let mut snippet_source = completion.new_text.clone();
21211 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21212 previous_point.column = previous_point.column.saturating_sub(1);
21213 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21214 if scope.prefers_label_for_snippet_in_completion() {
21215 if let Some(label) = completion.label() {
21216 if matches!(
21217 completion.kind(),
21218 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21219 ) {
21220 snippet_source = label;
21221 }
21222 }
21223 }
21224 }
21225 match Snippet::parse(&snippet_source).log_err() {
21226 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21227 None => (None, completion.new_text.clone()),
21228 }
21229 } else {
21230 (None, completion.new_text.clone())
21231 };
21232
21233 let mut range_to_replace = {
21234 let replace_range = &completion.replace_range;
21235 if let CompletionSource::Lsp {
21236 insert_range: Some(insert_range),
21237 ..
21238 } = &completion.source
21239 {
21240 debug_assert_eq!(
21241 insert_range.start, replace_range.start,
21242 "insert_range and replace_range should start at the same position"
21243 );
21244 debug_assert!(
21245 insert_range
21246 .start
21247 .cmp(&cursor_position, &buffer_snapshot)
21248 .is_le(),
21249 "insert_range should start before or at cursor position"
21250 );
21251 debug_assert!(
21252 replace_range
21253 .start
21254 .cmp(&cursor_position, &buffer_snapshot)
21255 .is_le(),
21256 "replace_range should start before or at cursor position"
21257 );
21258
21259 let should_replace = match intent {
21260 CompletionIntent::CompleteWithInsert => false,
21261 CompletionIntent::CompleteWithReplace => true,
21262 CompletionIntent::Complete | CompletionIntent::Compose => {
21263 let insert_mode =
21264 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21265 .completions
21266 .lsp_insert_mode;
21267 match insert_mode {
21268 LspInsertMode::Insert => false,
21269 LspInsertMode::Replace => true,
21270 LspInsertMode::ReplaceSubsequence => {
21271 let mut text_to_replace = buffer.chars_for_range(
21272 buffer.anchor_before(replace_range.start)
21273 ..buffer.anchor_after(replace_range.end),
21274 );
21275 let mut current_needle = text_to_replace.next();
21276 for haystack_ch in completion.label.text.chars() {
21277 if let Some(needle_ch) = current_needle {
21278 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21279 current_needle = text_to_replace.next();
21280 }
21281 }
21282 }
21283 current_needle.is_none()
21284 }
21285 LspInsertMode::ReplaceSuffix => {
21286 if replace_range
21287 .end
21288 .cmp(&cursor_position, &buffer_snapshot)
21289 .is_gt()
21290 {
21291 let range_after_cursor = *cursor_position..replace_range.end;
21292 let text_after_cursor = buffer
21293 .text_for_range(
21294 buffer.anchor_before(range_after_cursor.start)
21295 ..buffer.anchor_after(range_after_cursor.end),
21296 )
21297 .collect::<String>()
21298 .to_ascii_lowercase();
21299 completion
21300 .label
21301 .text
21302 .to_ascii_lowercase()
21303 .ends_with(&text_after_cursor)
21304 } else {
21305 true
21306 }
21307 }
21308 }
21309 }
21310 };
21311
21312 if should_replace {
21313 replace_range.clone()
21314 } else {
21315 insert_range.clone()
21316 }
21317 } else {
21318 replace_range.clone()
21319 }
21320 };
21321
21322 if range_to_replace
21323 .end
21324 .cmp(&cursor_position, &buffer_snapshot)
21325 .is_lt()
21326 {
21327 range_to_replace.end = *cursor_position;
21328 }
21329
21330 CompletionEdit {
21331 new_text,
21332 replace_range: range_to_replace.to_offset(&buffer),
21333 snippet,
21334 }
21335}
21336
21337struct CompletionEdit {
21338 new_text: String,
21339 replace_range: Range<usize>,
21340 snippet: Option<Snippet>,
21341}
21342
21343fn insert_extra_newline_brackets(
21344 buffer: &MultiBufferSnapshot,
21345 range: Range<usize>,
21346 language: &language::LanguageScope,
21347) -> bool {
21348 let leading_whitespace_len = buffer
21349 .reversed_chars_at(range.start)
21350 .take_while(|c| c.is_whitespace() && *c != '\n')
21351 .map(|c| c.len_utf8())
21352 .sum::<usize>();
21353 let trailing_whitespace_len = buffer
21354 .chars_at(range.end)
21355 .take_while(|c| c.is_whitespace() && *c != '\n')
21356 .map(|c| c.len_utf8())
21357 .sum::<usize>();
21358 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21359
21360 language.brackets().any(|(pair, enabled)| {
21361 let pair_start = pair.start.trim_end();
21362 let pair_end = pair.end.trim_start();
21363
21364 enabled
21365 && pair.newline
21366 && buffer.contains_str_at(range.end, pair_end)
21367 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21368 })
21369}
21370
21371fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21372 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21373 [(buffer, range, _)] => (*buffer, range.clone()),
21374 _ => return false,
21375 };
21376 let pair = {
21377 let mut result: Option<BracketMatch> = None;
21378
21379 for pair in buffer
21380 .all_bracket_ranges(range.clone())
21381 .filter(move |pair| {
21382 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21383 })
21384 {
21385 let len = pair.close_range.end - pair.open_range.start;
21386
21387 if let Some(existing) = &result {
21388 let existing_len = existing.close_range.end - existing.open_range.start;
21389 if len > existing_len {
21390 continue;
21391 }
21392 }
21393
21394 result = Some(pair);
21395 }
21396
21397 result
21398 };
21399 let Some(pair) = pair else {
21400 return false;
21401 };
21402 pair.newline_only
21403 && buffer
21404 .chars_for_range(pair.open_range.end..range.start)
21405 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21406 .all(|c| c.is_whitespace() && c != '\n')
21407}
21408
21409fn update_uncommitted_diff_for_buffer(
21410 editor: Entity<Editor>,
21411 project: &Entity<Project>,
21412 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21413 buffer: Entity<MultiBuffer>,
21414 cx: &mut App,
21415) -> Task<()> {
21416 let mut tasks = Vec::new();
21417 project.update(cx, |project, cx| {
21418 for buffer in buffers {
21419 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21420 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21421 }
21422 }
21423 });
21424 cx.spawn(async move |cx| {
21425 let diffs = future::join_all(tasks).await;
21426 if editor
21427 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21428 .unwrap_or(false)
21429 {
21430 return;
21431 }
21432
21433 buffer
21434 .update(cx, |buffer, cx| {
21435 for diff in diffs.into_iter().flatten() {
21436 buffer.add_diff(diff, cx);
21437 }
21438 })
21439 .ok();
21440 })
21441}
21442
21443fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21444 let tab_size = tab_size.get() as usize;
21445 let mut width = offset;
21446
21447 for ch in text.chars() {
21448 width += if ch == '\t' {
21449 tab_size - (width % tab_size)
21450 } else {
21451 1
21452 };
21453 }
21454
21455 width - offset
21456}
21457
21458#[cfg(test)]
21459mod tests {
21460 use super::*;
21461
21462 #[test]
21463 fn test_string_size_with_expanded_tabs() {
21464 let nz = |val| NonZeroU32::new(val).unwrap();
21465 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21466 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21467 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21468 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21469 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21470 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21471 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21472 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21473 }
21474}
21475
21476/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21477struct WordBreakingTokenizer<'a> {
21478 input: &'a str,
21479}
21480
21481impl<'a> WordBreakingTokenizer<'a> {
21482 fn new(input: &'a str) -> Self {
21483 Self { input }
21484 }
21485}
21486
21487fn is_char_ideographic(ch: char) -> bool {
21488 use unicode_script::Script::*;
21489 use unicode_script::UnicodeScript;
21490 matches!(ch.script(), Han | Tangut | Yi)
21491}
21492
21493fn is_grapheme_ideographic(text: &str) -> bool {
21494 text.chars().any(is_char_ideographic)
21495}
21496
21497fn is_grapheme_whitespace(text: &str) -> bool {
21498 text.chars().any(|x| x.is_whitespace())
21499}
21500
21501fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21502 text.chars().next().map_or(false, |ch| {
21503 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21504 })
21505}
21506
21507#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21508enum WordBreakToken<'a> {
21509 Word { token: &'a str, grapheme_len: usize },
21510 InlineWhitespace { token: &'a str, grapheme_len: usize },
21511 Newline,
21512}
21513
21514impl<'a> Iterator for WordBreakingTokenizer<'a> {
21515 /// Yields a span, the count of graphemes in the token, and whether it was
21516 /// whitespace. Note that it also breaks at word boundaries.
21517 type Item = WordBreakToken<'a>;
21518
21519 fn next(&mut self) -> Option<Self::Item> {
21520 use unicode_segmentation::UnicodeSegmentation;
21521 if self.input.is_empty() {
21522 return None;
21523 }
21524
21525 let mut iter = self.input.graphemes(true).peekable();
21526 let mut offset = 0;
21527 let mut grapheme_len = 0;
21528 if let Some(first_grapheme) = iter.next() {
21529 let is_newline = first_grapheme == "\n";
21530 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21531 offset += first_grapheme.len();
21532 grapheme_len += 1;
21533 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21534 if let Some(grapheme) = iter.peek().copied() {
21535 if should_stay_with_preceding_ideograph(grapheme) {
21536 offset += grapheme.len();
21537 grapheme_len += 1;
21538 }
21539 }
21540 } else {
21541 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21542 let mut next_word_bound = words.peek().copied();
21543 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21544 next_word_bound = words.next();
21545 }
21546 while let Some(grapheme) = iter.peek().copied() {
21547 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21548 break;
21549 };
21550 if is_grapheme_whitespace(grapheme) != is_whitespace
21551 || (grapheme == "\n") != is_newline
21552 {
21553 break;
21554 };
21555 offset += grapheme.len();
21556 grapheme_len += 1;
21557 iter.next();
21558 }
21559 }
21560 let token = &self.input[..offset];
21561 self.input = &self.input[offset..];
21562 if token == "\n" {
21563 Some(WordBreakToken::Newline)
21564 } else if is_whitespace {
21565 Some(WordBreakToken::InlineWhitespace {
21566 token,
21567 grapheme_len,
21568 })
21569 } else {
21570 Some(WordBreakToken::Word {
21571 token,
21572 grapheme_len,
21573 })
21574 }
21575 } else {
21576 None
21577 }
21578 }
21579}
21580
21581#[test]
21582fn test_word_breaking_tokenizer() {
21583 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21584 ("", &[]),
21585 (" ", &[whitespace(" ", 2)]),
21586 ("Ʒ", &[word("Ʒ", 1)]),
21587 ("Ǽ", &[word("Ǽ", 1)]),
21588 ("⋑", &[word("⋑", 1)]),
21589 ("⋑⋑", &[word("⋑⋑", 2)]),
21590 (
21591 "原理,进而",
21592 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21593 ),
21594 (
21595 "hello world",
21596 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21597 ),
21598 (
21599 "hello, world",
21600 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21601 ),
21602 (
21603 " hello world",
21604 &[
21605 whitespace(" ", 2),
21606 word("hello", 5),
21607 whitespace(" ", 1),
21608 word("world", 5),
21609 ],
21610 ),
21611 (
21612 "这是什么 \n 钢笔",
21613 &[
21614 word("这", 1),
21615 word("是", 1),
21616 word("什", 1),
21617 word("么", 1),
21618 whitespace(" ", 1),
21619 newline(),
21620 whitespace(" ", 1),
21621 word("钢", 1),
21622 word("笔", 1),
21623 ],
21624 ),
21625 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21626 ];
21627
21628 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21629 WordBreakToken::Word {
21630 token,
21631 grapheme_len,
21632 }
21633 }
21634
21635 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21636 WordBreakToken::InlineWhitespace {
21637 token,
21638 grapheme_len,
21639 }
21640 }
21641
21642 fn newline() -> WordBreakToken<'static> {
21643 WordBreakToken::Newline
21644 }
21645
21646 for (input, result) in tests {
21647 assert_eq!(
21648 WordBreakingTokenizer::new(input)
21649 .collect::<Vec<_>>()
21650 .as_slice(),
21651 *result,
21652 );
21653 }
21654}
21655
21656fn wrap_with_prefix(
21657 first_line_prefix: String,
21658 subsequent_lines_prefix: String,
21659 unwrapped_text: String,
21660 wrap_column: usize,
21661 tab_size: NonZeroU32,
21662 preserve_existing_whitespace: bool,
21663) -> String {
21664 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21665 let subsequent_lines_prefix_len =
21666 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21667 let mut wrapped_text = String::new();
21668 let mut current_line = first_line_prefix.clone();
21669 let mut is_first_line = true;
21670
21671 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21672 let mut current_line_len = first_line_prefix_len;
21673 let mut in_whitespace = false;
21674 for token in tokenizer {
21675 let have_preceding_whitespace = in_whitespace;
21676 match token {
21677 WordBreakToken::Word {
21678 token,
21679 grapheme_len,
21680 } => {
21681 in_whitespace = false;
21682 let current_prefix_len = if is_first_line {
21683 first_line_prefix_len
21684 } else {
21685 subsequent_lines_prefix_len
21686 };
21687 if current_line_len + grapheme_len > wrap_column
21688 && current_line_len != current_prefix_len
21689 {
21690 wrapped_text.push_str(current_line.trim_end());
21691 wrapped_text.push('\n');
21692 is_first_line = false;
21693 current_line = subsequent_lines_prefix.clone();
21694 current_line_len = subsequent_lines_prefix_len;
21695 }
21696 current_line.push_str(token);
21697 current_line_len += grapheme_len;
21698 }
21699 WordBreakToken::InlineWhitespace {
21700 mut token,
21701 mut grapheme_len,
21702 } => {
21703 in_whitespace = true;
21704 if have_preceding_whitespace && !preserve_existing_whitespace {
21705 continue;
21706 }
21707 if !preserve_existing_whitespace {
21708 token = " ";
21709 grapheme_len = 1;
21710 }
21711 let current_prefix_len = if is_first_line {
21712 first_line_prefix_len
21713 } else {
21714 subsequent_lines_prefix_len
21715 };
21716 if current_line_len + grapheme_len > wrap_column {
21717 wrapped_text.push_str(current_line.trim_end());
21718 wrapped_text.push('\n');
21719 is_first_line = false;
21720 current_line = subsequent_lines_prefix.clone();
21721 current_line_len = subsequent_lines_prefix_len;
21722 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21723 current_line.push_str(token);
21724 current_line_len += grapheme_len;
21725 }
21726 }
21727 WordBreakToken::Newline => {
21728 in_whitespace = true;
21729 let current_prefix_len = if is_first_line {
21730 first_line_prefix_len
21731 } else {
21732 subsequent_lines_prefix_len
21733 };
21734 if preserve_existing_whitespace {
21735 wrapped_text.push_str(current_line.trim_end());
21736 wrapped_text.push('\n');
21737 is_first_line = false;
21738 current_line = subsequent_lines_prefix.clone();
21739 current_line_len = subsequent_lines_prefix_len;
21740 } else if have_preceding_whitespace {
21741 continue;
21742 } else if current_line_len + 1 > wrap_column
21743 && current_line_len != current_prefix_len
21744 {
21745 wrapped_text.push_str(current_line.trim_end());
21746 wrapped_text.push('\n');
21747 is_first_line = false;
21748 current_line = subsequent_lines_prefix.clone();
21749 current_line_len = subsequent_lines_prefix_len;
21750 } else if current_line_len != current_prefix_len {
21751 current_line.push(' ');
21752 current_line_len += 1;
21753 }
21754 }
21755 }
21756 }
21757
21758 if !current_line.is_empty() {
21759 wrapped_text.push_str(¤t_line);
21760 }
21761 wrapped_text
21762}
21763
21764#[test]
21765fn test_wrap_with_prefix() {
21766 assert_eq!(
21767 wrap_with_prefix(
21768 "# ".to_string(),
21769 "# ".to_string(),
21770 "abcdefg".to_string(),
21771 4,
21772 NonZeroU32::new(4).unwrap(),
21773 false,
21774 ),
21775 "# abcdefg"
21776 );
21777 assert_eq!(
21778 wrap_with_prefix(
21779 "".to_string(),
21780 "".to_string(),
21781 "\thello world".to_string(),
21782 8,
21783 NonZeroU32::new(4).unwrap(),
21784 false,
21785 ),
21786 "hello\nworld"
21787 );
21788 assert_eq!(
21789 wrap_with_prefix(
21790 "// ".to_string(),
21791 "// ".to_string(),
21792 "xx \nyy zz aa bb cc".to_string(),
21793 12,
21794 NonZeroU32::new(4).unwrap(),
21795 false,
21796 ),
21797 "// xx yy zz\n// aa bb cc"
21798 );
21799 assert_eq!(
21800 wrap_with_prefix(
21801 String::new(),
21802 String::new(),
21803 "这是什么 \n 钢笔".to_string(),
21804 3,
21805 NonZeroU32::new(4).unwrap(),
21806 false,
21807 ),
21808 "这是什\n么 钢\n笔"
21809 );
21810}
21811
21812pub trait CollaborationHub {
21813 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21814 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21815 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21816}
21817
21818impl CollaborationHub for Entity<Project> {
21819 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21820 self.read(cx).collaborators()
21821 }
21822
21823 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21824 self.read(cx).user_store().read(cx).participant_indices()
21825 }
21826
21827 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21828 let this = self.read(cx);
21829 let user_ids = this.collaborators().values().map(|c| c.user_id);
21830 this.user_store().read(cx).participant_names(user_ids, cx)
21831 }
21832}
21833
21834pub trait SemanticsProvider {
21835 fn hover(
21836 &self,
21837 buffer: &Entity<Buffer>,
21838 position: text::Anchor,
21839 cx: &mut App,
21840 ) -> Option<Task<Vec<project::Hover>>>;
21841
21842 fn inline_values(
21843 &self,
21844 buffer_handle: Entity<Buffer>,
21845 range: Range<text::Anchor>,
21846 cx: &mut App,
21847 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21848
21849 fn inlay_hints(
21850 &self,
21851 buffer_handle: Entity<Buffer>,
21852 range: Range<text::Anchor>,
21853 cx: &mut App,
21854 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21855
21856 fn resolve_inlay_hint(
21857 &self,
21858 hint: InlayHint,
21859 buffer_handle: Entity<Buffer>,
21860 server_id: LanguageServerId,
21861 cx: &mut App,
21862 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21863
21864 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21865
21866 fn document_highlights(
21867 &self,
21868 buffer: &Entity<Buffer>,
21869 position: text::Anchor,
21870 cx: &mut App,
21871 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21872
21873 fn definitions(
21874 &self,
21875 buffer: &Entity<Buffer>,
21876 position: text::Anchor,
21877 kind: GotoDefinitionKind,
21878 cx: &mut App,
21879 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21880
21881 fn range_for_rename(
21882 &self,
21883 buffer: &Entity<Buffer>,
21884 position: text::Anchor,
21885 cx: &mut App,
21886 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21887
21888 fn perform_rename(
21889 &self,
21890 buffer: &Entity<Buffer>,
21891 position: text::Anchor,
21892 new_name: String,
21893 cx: &mut App,
21894 ) -> Option<Task<Result<ProjectTransaction>>>;
21895}
21896
21897pub trait CompletionProvider {
21898 fn completions(
21899 &self,
21900 excerpt_id: ExcerptId,
21901 buffer: &Entity<Buffer>,
21902 buffer_position: text::Anchor,
21903 trigger: CompletionContext,
21904 window: &mut Window,
21905 cx: &mut Context<Editor>,
21906 ) -> Task<Result<Vec<CompletionResponse>>>;
21907
21908 fn resolve_completions(
21909 &self,
21910 _buffer: Entity<Buffer>,
21911 _completion_indices: Vec<usize>,
21912 _completions: Rc<RefCell<Box<[Completion]>>>,
21913 _cx: &mut Context<Editor>,
21914 ) -> Task<Result<bool>> {
21915 Task::ready(Ok(false))
21916 }
21917
21918 fn apply_additional_edits_for_completion(
21919 &self,
21920 _buffer: Entity<Buffer>,
21921 _completions: Rc<RefCell<Box<[Completion]>>>,
21922 _completion_index: usize,
21923 _push_to_history: bool,
21924 _cx: &mut Context<Editor>,
21925 ) -> Task<Result<Option<language::Transaction>>> {
21926 Task::ready(Ok(None))
21927 }
21928
21929 fn is_completion_trigger(
21930 &self,
21931 buffer: &Entity<Buffer>,
21932 position: language::Anchor,
21933 text: &str,
21934 trigger_in_words: bool,
21935 menu_is_open: bool,
21936 cx: &mut Context<Editor>,
21937 ) -> bool;
21938
21939 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21940
21941 fn sort_completions(&self) -> bool {
21942 true
21943 }
21944
21945 fn filter_completions(&self) -> bool {
21946 true
21947 }
21948}
21949
21950pub trait CodeActionProvider {
21951 fn id(&self) -> Arc<str>;
21952
21953 fn code_actions(
21954 &self,
21955 buffer: &Entity<Buffer>,
21956 range: Range<text::Anchor>,
21957 window: &mut Window,
21958 cx: &mut App,
21959 ) -> Task<Result<Vec<CodeAction>>>;
21960
21961 fn apply_code_action(
21962 &self,
21963 buffer_handle: Entity<Buffer>,
21964 action: CodeAction,
21965 excerpt_id: ExcerptId,
21966 push_to_history: bool,
21967 window: &mut Window,
21968 cx: &mut App,
21969 ) -> Task<Result<ProjectTransaction>>;
21970}
21971
21972impl CodeActionProvider for Entity<Project> {
21973 fn id(&self) -> Arc<str> {
21974 "project".into()
21975 }
21976
21977 fn code_actions(
21978 &self,
21979 buffer: &Entity<Buffer>,
21980 range: Range<text::Anchor>,
21981 _window: &mut Window,
21982 cx: &mut App,
21983 ) -> Task<Result<Vec<CodeAction>>> {
21984 self.update(cx, |project, cx| {
21985 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21986 let code_actions = project.code_actions(buffer, range, None, cx);
21987 cx.background_spawn(async move {
21988 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21989 Ok(code_lens_actions
21990 .context("code lens fetch")?
21991 .into_iter()
21992 .chain(code_actions.context("code action fetch")?)
21993 .collect())
21994 })
21995 })
21996 }
21997
21998 fn apply_code_action(
21999 &self,
22000 buffer_handle: Entity<Buffer>,
22001 action: CodeAction,
22002 _excerpt_id: ExcerptId,
22003 push_to_history: bool,
22004 _window: &mut Window,
22005 cx: &mut App,
22006 ) -> Task<Result<ProjectTransaction>> {
22007 self.update(cx, |project, cx| {
22008 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22009 })
22010 }
22011}
22012
22013fn snippet_completions(
22014 project: &Project,
22015 buffer: &Entity<Buffer>,
22016 buffer_position: text::Anchor,
22017 cx: &mut App,
22018) -> Task<Result<CompletionResponse>> {
22019 let languages = buffer.read(cx).languages_at(buffer_position);
22020 let snippet_store = project.snippets().read(cx);
22021
22022 let scopes: Vec<_> = languages
22023 .iter()
22024 .filter_map(|language| {
22025 let language_name = language.lsp_id();
22026 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22027
22028 if snippets.is_empty() {
22029 None
22030 } else {
22031 Some((language.default_scope(), snippets))
22032 }
22033 })
22034 .collect();
22035
22036 if scopes.is_empty() {
22037 return Task::ready(Ok(CompletionResponse {
22038 completions: vec![],
22039 is_incomplete: false,
22040 }));
22041 }
22042
22043 let snapshot = buffer.read(cx).text_snapshot();
22044 let chars: String = snapshot
22045 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22046 .collect();
22047 let executor = cx.background_executor().clone();
22048
22049 cx.background_spawn(async move {
22050 let mut is_incomplete = false;
22051 let mut completions: Vec<Completion> = Vec::new();
22052 for (scope, snippets) in scopes.into_iter() {
22053 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22054 let mut last_word = chars
22055 .chars()
22056 .take_while(|c| classifier.is_word(*c))
22057 .collect::<String>();
22058 last_word = last_word.chars().rev().collect();
22059
22060 if last_word.is_empty() {
22061 return Ok(CompletionResponse {
22062 completions: vec![],
22063 is_incomplete: true,
22064 });
22065 }
22066
22067 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22068 let to_lsp = |point: &text::Anchor| {
22069 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22070 point_to_lsp(end)
22071 };
22072 let lsp_end = to_lsp(&buffer_position);
22073
22074 let candidates = snippets
22075 .iter()
22076 .enumerate()
22077 .flat_map(|(ix, snippet)| {
22078 snippet
22079 .prefix
22080 .iter()
22081 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
22082 })
22083 .collect::<Vec<StringMatchCandidate>>();
22084
22085 const MAX_RESULTS: usize = 100;
22086 let mut matches = fuzzy::match_strings(
22087 &candidates,
22088 &last_word,
22089 last_word.chars().any(|c| c.is_uppercase()),
22090 true,
22091 MAX_RESULTS,
22092 &Default::default(),
22093 executor.clone(),
22094 )
22095 .await;
22096
22097 if matches.len() >= MAX_RESULTS {
22098 is_incomplete = true;
22099 }
22100
22101 // Remove all candidates where the query's start does not match the start of any word in the candidate
22102 if let Some(query_start) = last_word.chars().next() {
22103 matches.retain(|string_match| {
22104 split_words(&string_match.string).any(|word| {
22105 // Check that the first codepoint of the word as lowercase matches the first
22106 // codepoint of the query as lowercase
22107 word.chars()
22108 .flat_map(|codepoint| codepoint.to_lowercase())
22109 .zip(query_start.to_lowercase())
22110 .all(|(word_cp, query_cp)| word_cp == query_cp)
22111 })
22112 });
22113 }
22114
22115 let matched_strings = matches
22116 .into_iter()
22117 .map(|m| m.string)
22118 .collect::<HashSet<_>>();
22119
22120 completions.extend(snippets.iter().filter_map(|snippet| {
22121 let matching_prefix = snippet
22122 .prefix
22123 .iter()
22124 .find(|prefix| matched_strings.contains(*prefix))?;
22125 let start = as_offset - last_word.len();
22126 let start = snapshot.anchor_before(start);
22127 let range = start..buffer_position;
22128 let lsp_start = to_lsp(&start);
22129 let lsp_range = lsp::Range {
22130 start: lsp_start,
22131 end: lsp_end,
22132 };
22133 Some(Completion {
22134 replace_range: range,
22135 new_text: snippet.body.clone(),
22136 source: CompletionSource::Lsp {
22137 insert_range: None,
22138 server_id: LanguageServerId(usize::MAX),
22139 resolved: true,
22140 lsp_completion: Box::new(lsp::CompletionItem {
22141 label: snippet.prefix.first().unwrap().clone(),
22142 kind: Some(CompletionItemKind::SNIPPET),
22143 label_details: snippet.description.as_ref().map(|description| {
22144 lsp::CompletionItemLabelDetails {
22145 detail: Some(description.clone()),
22146 description: None,
22147 }
22148 }),
22149 insert_text_format: Some(InsertTextFormat::SNIPPET),
22150 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22151 lsp::InsertReplaceEdit {
22152 new_text: snippet.body.clone(),
22153 insert: lsp_range,
22154 replace: lsp_range,
22155 },
22156 )),
22157 filter_text: Some(snippet.body.clone()),
22158 sort_text: Some(char::MAX.to_string()),
22159 ..lsp::CompletionItem::default()
22160 }),
22161 lsp_defaults: None,
22162 },
22163 label: CodeLabel {
22164 text: matching_prefix.clone(),
22165 runs: Vec::new(),
22166 filter_range: 0..matching_prefix.len(),
22167 },
22168 icon_path: None,
22169 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22170 single_line: snippet.name.clone().into(),
22171 plain_text: snippet
22172 .description
22173 .clone()
22174 .map(|description| description.into()),
22175 }),
22176 insert_text_mode: None,
22177 confirm: None,
22178 })
22179 }))
22180 }
22181
22182 Ok(CompletionResponse {
22183 completions,
22184 is_incomplete,
22185 })
22186 })
22187}
22188
22189impl CompletionProvider for Entity<Project> {
22190 fn completions(
22191 &self,
22192 _excerpt_id: ExcerptId,
22193 buffer: &Entity<Buffer>,
22194 buffer_position: text::Anchor,
22195 options: CompletionContext,
22196 _window: &mut Window,
22197 cx: &mut Context<Editor>,
22198 ) -> Task<Result<Vec<CompletionResponse>>> {
22199 self.update(cx, |project, cx| {
22200 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22201 let project_completions = project.completions(buffer, buffer_position, options, cx);
22202 cx.background_spawn(async move {
22203 let mut responses = project_completions.await?;
22204 let snippets = snippets.await?;
22205 if !snippets.completions.is_empty() {
22206 responses.push(snippets);
22207 }
22208 Ok(responses)
22209 })
22210 })
22211 }
22212
22213 fn resolve_completions(
22214 &self,
22215 buffer: Entity<Buffer>,
22216 completion_indices: Vec<usize>,
22217 completions: Rc<RefCell<Box<[Completion]>>>,
22218 cx: &mut Context<Editor>,
22219 ) -> Task<Result<bool>> {
22220 self.update(cx, |project, cx| {
22221 project.lsp_store().update(cx, |lsp_store, cx| {
22222 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22223 })
22224 })
22225 }
22226
22227 fn apply_additional_edits_for_completion(
22228 &self,
22229 buffer: Entity<Buffer>,
22230 completions: Rc<RefCell<Box<[Completion]>>>,
22231 completion_index: usize,
22232 push_to_history: bool,
22233 cx: &mut Context<Editor>,
22234 ) -> Task<Result<Option<language::Transaction>>> {
22235 self.update(cx, |project, cx| {
22236 project.lsp_store().update(cx, |lsp_store, cx| {
22237 lsp_store.apply_additional_edits_for_completion(
22238 buffer,
22239 completions,
22240 completion_index,
22241 push_to_history,
22242 cx,
22243 )
22244 })
22245 })
22246 }
22247
22248 fn is_completion_trigger(
22249 &self,
22250 buffer: &Entity<Buffer>,
22251 position: language::Anchor,
22252 text: &str,
22253 trigger_in_words: bool,
22254 menu_is_open: bool,
22255 cx: &mut Context<Editor>,
22256 ) -> bool {
22257 let mut chars = text.chars();
22258 let char = if let Some(char) = chars.next() {
22259 char
22260 } else {
22261 return false;
22262 };
22263 if chars.next().is_some() {
22264 return false;
22265 }
22266
22267 let buffer = buffer.read(cx);
22268 let snapshot = buffer.snapshot();
22269 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22270 return false;
22271 }
22272 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22273 if trigger_in_words && classifier.is_word(char) {
22274 return true;
22275 }
22276
22277 buffer.completion_triggers().contains(text)
22278 }
22279}
22280
22281impl SemanticsProvider for Entity<Project> {
22282 fn hover(
22283 &self,
22284 buffer: &Entity<Buffer>,
22285 position: text::Anchor,
22286 cx: &mut App,
22287 ) -> Option<Task<Vec<project::Hover>>> {
22288 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22289 }
22290
22291 fn document_highlights(
22292 &self,
22293 buffer: &Entity<Buffer>,
22294 position: text::Anchor,
22295 cx: &mut App,
22296 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22297 Some(self.update(cx, |project, cx| {
22298 project.document_highlights(buffer, position, cx)
22299 }))
22300 }
22301
22302 fn definitions(
22303 &self,
22304 buffer: &Entity<Buffer>,
22305 position: text::Anchor,
22306 kind: GotoDefinitionKind,
22307 cx: &mut App,
22308 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22309 Some(self.update(cx, |project, cx| match kind {
22310 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22311 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22312 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22313 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22314 }))
22315 }
22316
22317 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22318 self.update(cx, |project, cx| {
22319 if project
22320 .active_debug_session(cx)
22321 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22322 {
22323 return true;
22324 }
22325
22326 buffer.update(cx, |buffer, cx| {
22327 project.any_language_server_supports_inlay_hints(buffer, cx)
22328 })
22329 })
22330 }
22331
22332 fn inline_values(
22333 &self,
22334 buffer_handle: Entity<Buffer>,
22335 range: Range<text::Anchor>,
22336 cx: &mut App,
22337 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22338 self.update(cx, |project, cx| {
22339 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22340
22341 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22342 })
22343 }
22344
22345 fn inlay_hints(
22346 &self,
22347 buffer_handle: Entity<Buffer>,
22348 range: Range<text::Anchor>,
22349 cx: &mut App,
22350 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22351 Some(self.update(cx, |project, cx| {
22352 project.inlay_hints(buffer_handle, range, cx)
22353 }))
22354 }
22355
22356 fn resolve_inlay_hint(
22357 &self,
22358 hint: InlayHint,
22359 buffer_handle: Entity<Buffer>,
22360 server_id: LanguageServerId,
22361 cx: &mut App,
22362 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22363 Some(self.update(cx, |project, cx| {
22364 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22365 }))
22366 }
22367
22368 fn range_for_rename(
22369 &self,
22370 buffer: &Entity<Buffer>,
22371 position: text::Anchor,
22372 cx: &mut App,
22373 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22374 Some(self.update(cx, |project, cx| {
22375 let buffer = buffer.clone();
22376 let task = project.prepare_rename(buffer.clone(), position, cx);
22377 cx.spawn(async move |_, cx| {
22378 Ok(match task.await? {
22379 PrepareRenameResponse::Success(range) => Some(range),
22380 PrepareRenameResponse::InvalidPosition => None,
22381 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22382 // Fallback on using TreeSitter info to determine identifier range
22383 buffer.read_with(cx, |buffer, _| {
22384 let snapshot = buffer.snapshot();
22385 let (range, kind) = snapshot.surrounding_word(position, false);
22386 if kind != Some(CharKind::Word) {
22387 return None;
22388 }
22389 Some(
22390 snapshot.anchor_before(range.start)
22391 ..snapshot.anchor_after(range.end),
22392 )
22393 })?
22394 }
22395 })
22396 })
22397 }))
22398 }
22399
22400 fn perform_rename(
22401 &self,
22402 buffer: &Entity<Buffer>,
22403 position: text::Anchor,
22404 new_name: String,
22405 cx: &mut App,
22406 ) -> Option<Task<Result<ProjectTransaction>>> {
22407 Some(self.update(cx, |project, cx| {
22408 project.perform_rename(buffer.clone(), position, new_name, cx)
22409 }))
22410 }
22411}
22412
22413fn inlay_hint_settings(
22414 location: Anchor,
22415 snapshot: &MultiBufferSnapshot,
22416 cx: &mut Context<Editor>,
22417) -> InlayHintSettings {
22418 let file = snapshot.file_at(location);
22419 let language = snapshot.language_at(location).map(|l| l.name());
22420 language_settings(language, file, cx).inlay_hints
22421}
22422
22423fn consume_contiguous_rows(
22424 contiguous_row_selections: &mut Vec<Selection<Point>>,
22425 selection: &Selection<Point>,
22426 display_map: &DisplaySnapshot,
22427 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22428) -> (MultiBufferRow, MultiBufferRow) {
22429 contiguous_row_selections.push(selection.clone());
22430 let start_row = starting_row(selection, display_map);
22431 let mut end_row = ending_row(selection, display_map);
22432
22433 while let Some(next_selection) = selections.peek() {
22434 if next_selection.start.row <= end_row.0 {
22435 end_row = ending_row(next_selection, display_map);
22436 contiguous_row_selections.push(selections.next().unwrap().clone());
22437 } else {
22438 break;
22439 }
22440 }
22441 (start_row, end_row)
22442}
22443
22444fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22445 if selection.start.column > 0 {
22446 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22447 } else {
22448 MultiBufferRow(selection.start.row)
22449 }
22450}
22451
22452fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22453 if next_selection.end.column > 0 || next_selection.is_empty() {
22454 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22455 } else {
22456 MultiBufferRow(next_selection.end.row)
22457 }
22458}
22459
22460impl EditorSnapshot {
22461 pub fn remote_selections_in_range<'a>(
22462 &'a self,
22463 range: &'a Range<Anchor>,
22464 collaboration_hub: &dyn CollaborationHub,
22465 cx: &'a App,
22466 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22467 let participant_names = collaboration_hub.user_names(cx);
22468 let participant_indices = collaboration_hub.user_participant_indices(cx);
22469 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22470 let collaborators_by_replica_id = collaborators_by_peer_id
22471 .values()
22472 .map(|collaborator| (collaborator.replica_id, collaborator))
22473 .collect::<HashMap<_, _>>();
22474 self.buffer_snapshot
22475 .selections_in_range(range, false)
22476 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22477 if replica_id == AGENT_REPLICA_ID {
22478 Some(RemoteSelection {
22479 replica_id,
22480 selection,
22481 cursor_shape,
22482 line_mode,
22483 collaborator_id: CollaboratorId::Agent,
22484 user_name: Some("Agent".into()),
22485 color: cx.theme().players().agent(),
22486 })
22487 } else {
22488 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22489 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22490 let user_name = participant_names.get(&collaborator.user_id).cloned();
22491 Some(RemoteSelection {
22492 replica_id,
22493 selection,
22494 cursor_shape,
22495 line_mode,
22496 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22497 user_name,
22498 color: if let Some(index) = participant_index {
22499 cx.theme().players().color_for_participant(index.0)
22500 } else {
22501 cx.theme().players().absent()
22502 },
22503 })
22504 }
22505 })
22506 }
22507
22508 pub fn hunks_for_ranges(
22509 &self,
22510 ranges: impl IntoIterator<Item = Range<Point>>,
22511 ) -> Vec<MultiBufferDiffHunk> {
22512 let mut hunks = Vec::new();
22513 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22514 HashMap::default();
22515 for query_range in ranges {
22516 let query_rows =
22517 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22518 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22519 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22520 ) {
22521 // Include deleted hunks that are adjacent to the query range, because
22522 // otherwise they would be missed.
22523 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22524 if hunk.status().is_deleted() {
22525 intersects_range |= hunk.row_range.start == query_rows.end;
22526 intersects_range |= hunk.row_range.end == query_rows.start;
22527 }
22528 if intersects_range {
22529 if !processed_buffer_rows
22530 .entry(hunk.buffer_id)
22531 .or_default()
22532 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22533 {
22534 continue;
22535 }
22536 hunks.push(hunk);
22537 }
22538 }
22539 }
22540
22541 hunks
22542 }
22543
22544 fn display_diff_hunks_for_rows<'a>(
22545 &'a self,
22546 display_rows: Range<DisplayRow>,
22547 folded_buffers: &'a HashSet<BufferId>,
22548 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22549 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22550 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22551
22552 self.buffer_snapshot
22553 .diff_hunks_in_range(buffer_start..buffer_end)
22554 .filter_map(|hunk| {
22555 if folded_buffers.contains(&hunk.buffer_id) {
22556 return None;
22557 }
22558
22559 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22560 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22561
22562 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22563 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22564
22565 let display_hunk = if hunk_display_start.column() != 0 {
22566 DisplayDiffHunk::Folded {
22567 display_row: hunk_display_start.row(),
22568 }
22569 } else {
22570 let mut end_row = hunk_display_end.row();
22571 if hunk_display_end.column() > 0 {
22572 end_row.0 += 1;
22573 }
22574 let is_created_file = hunk.is_created_file();
22575 DisplayDiffHunk::Unfolded {
22576 status: hunk.status(),
22577 diff_base_byte_range: hunk.diff_base_byte_range,
22578 display_row_range: hunk_display_start.row()..end_row,
22579 multi_buffer_range: Anchor::range_in_buffer(
22580 hunk.excerpt_id,
22581 hunk.buffer_id,
22582 hunk.buffer_range,
22583 ),
22584 is_created_file,
22585 }
22586 };
22587
22588 Some(display_hunk)
22589 })
22590 }
22591
22592 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22593 self.display_snapshot.buffer_snapshot.language_at(position)
22594 }
22595
22596 pub fn is_focused(&self) -> bool {
22597 self.is_focused
22598 }
22599
22600 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22601 self.placeholder_text.as_ref()
22602 }
22603
22604 pub fn scroll_position(&self) -> gpui::Point<f32> {
22605 self.scroll_anchor.scroll_position(&self.display_snapshot)
22606 }
22607
22608 fn gutter_dimensions(
22609 &self,
22610 font_id: FontId,
22611 font_size: Pixels,
22612 max_line_number_width: Pixels,
22613 cx: &App,
22614 ) -> Option<GutterDimensions> {
22615 if !self.show_gutter {
22616 return None;
22617 }
22618
22619 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22620 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22621
22622 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22623 matches!(
22624 ProjectSettings::get_global(cx).git.git_gutter,
22625 Some(GitGutterSetting::TrackedFiles)
22626 )
22627 });
22628 let gutter_settings = EditorSettings::get_global(cx).gutter;
22629 let show_line_numbers = self
22630 .show_line_numbers
22631 .unwrap_or(gutter_settings.line_numbers);
22632 let line_gutter_width = if show_line_numbers {
22633 // Avoid flicker-like gutter resizes when the line number gains another digit by
22634 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22635 let min_width_for_number_on_gutter =
22636 ch_advance * gutter_settings.min_line_number_digits as f32;
22637 max_line_number_width.max(min_width_for_number_on_gutter)
22638 } else {
22639 0.0.into()
22640 };
22641
22642 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22643 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22644
22645 let git_blame_entries_width =
22646 self.git_blame_gutter_max_author_length
22647 .map(|max_author_length| {
22648 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22649 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22650
22651 /// The number of characters to dedicate to gaps and margins.
22652 const SPACING_WIDTH: usize = 4;
22653
22654 let max_char_count = max_author_length.min(renderer.max_author_length())
22655 + ::git::SHORT_SHA_LENGTH
22656 + MAX_RELATIVE_TIMESTAMP.len()
22657 + SPACING_WIDTH;
22658
22659 ch_advance * max_char_count
22660 });
22661
22662 let is_singleton = self.buffer_snapshot.is_singleton();
22663
22664 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22665 left_padding += if !is_singleton {
22666 ch_width * 4.0
22667 } else if show_runnables || show_breakpoints {
22668 ch_width * 3.0
22669 } else if show_git_gutter && show_line_numbers {
22670 ch_width * 2.0
22671 } else if show_git_gutter || show_line_numbers {
22672 ch_width
22673 } else {
22674 px(0.)
22675 };
22676
22677 let shows_folds = is_singleton && gutter_settings.folds;
22678
22679 let right_padding = if shows_folds && show_line_numbers {
22680 ch_width * 4.0
22681 } else if shows_folds || (!is_singleton && show_line_numbers) {
22682 ch_width * 3.0
22683 } else if show_line_numbers {
22684 ch_width
22685 } else {
22686 px(0.)
22687 };
22688
22689 Some(GutterDimensions {
22690 left_padding,
22691 right_padding,
22692 width: line_gutter_width + left_padding + right_padding,
22693 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22694 git_blame_entries_width,
22695 })
22696 }
22697
22698 pub fn render_crease_toggle(
22699 &self,
22700 buffer_row: MultiBufferRow,
22701 row_contains_cursor: bool,
22702 editor: Entity<Editor>,
22703 window: &mut Window,
22704 cx: &mut App,
22705 ) -> Option<AnyElement> {
22706 let folded = self.is_line_folded(buffer_row);
22707 let mut is_foldable = false;
22708
22709 if let Some(crease) = self
22710 .crease_snapshot
22711 .query_row(buffer_row, &self.buffer_snapshot)
22712 {
22713 is_foldable = true;
22714 match crease {
22715 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22716 if let Some(render_toggle) = render_toggle {
22717 let toggle_callback =
22718 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22719 if folded {
22720 editor.update(cx, |editor, cx| {
22721 editor.fold_at(buffer_row, window, cx)
22722 });
22723 } else {
22724 editor.update(cx, |editor, cx| {
22725 editor.unfold_at(buffer_row, window, cx)
22726 });
22727 }
22728 });
22729 return Some((render_toggle)(
22730 buffer_row,
22731 folded,
22732 toggle_callback,
22733 window,
22734 cx,
22735 ));
22736 }
22737 }
22738 }
22739 }
22740
22741 is_foldable |= self.starts_indent(buffer_row);
22742
22743 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22744 Some(
22745 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22746 .toggle_state(folded)
22747 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22748 if folded {
22749 this.unfold_at(buffer_row, window, cx);
22750 } else {
22751 this.fold_at(buffer_row, window, cx);
22752 }
22753 }))
22754 .into_any_element(),
22755 )
22756 } else {
22757 None
22758 }
22759 }
22760
22761 pub fn render_crease_trailer(
22762 &self,
22763 buffer_row: MultiBufferRow,
22764 window: &mut Window,
22765 cx: &mut App,
22766 ) -> Option<AnyElement> {
22767 let folded = self.is_line_folded(buffer_row);
22768 if let Crease::Inline { render_trailer, .. } = self
22769 .crease_snapshot
22770 .query_row(buffer_row, &self.buffer_snapshot)?
22771 {
22772 let render_trailer = render_trailer.as_ref()?;
22773 Some(render_trailer(buffer_row, folded, window, cx))
22774 } else {
22775 None
22776 }
22777 }
22778}
22779
22780impl Deref for EditorSnapshot {
22781 type Target = DisplaySnapshot;
22782
22783 fn deref(&self) -> &Self::Target {
22784 &self.display_snapshot
22785 }
22786}
22787
22788#[derive(Clone, Debug, PartialEq, Eq)]
22789pub enum EditorEvent {
22790 InputIgnored {
22791 text: Arc<str>,
22792 },
22793 InputHandled {
22794 utf16_range_to_replace: Option<Range<isize>>,
22795 text: Arc<str>,
22796 },
22797 ExcerptsAdded {
22798 buffer: Entity<Buffer>,
22799 predecessor: ExcerptId,
22800 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22801 },
22802 ExcerptsRemoved {
22803 ids: Vec<ExcerptId>,
22804 removed_buffer_ids: Vec<BufferId>,
22805 },
22806 BufferFoldToggled {
22807 ids: Vec<ExcerptId>,
22808 folded: bool,
22809 },
22810 ExcerptsEdited {
22811 ids: Vec<ExcerptId>,
22812 },
22813 ExcerptsExpanded {
22814 ids: Vec<ExcerptId>,
22815 },
22816 BufferEdited,
22817 Edited {
22818 transaction_id: clock::Lamport,
22819 },
22820 Reparsed(BufferId),
22821 Focused,
22822 FocusedIn,
22823 Blurred,
22824 DirtyChanged,
22825 Saved,
22826 TitleChanged,
22827 DiffBaseChanged,
22828 SelectionsChanged {
22829 local: bool,
22830 },
22831 ScrollPositionChanged {
22832 local: bool,
22833 autoscroll: bool,
22834 },
22835 Closed,
22836 TransactionUndone {
22837 transaction_id: clock::Lamport,
22838 },
22839 TransactionBegun {
22840 transaction_id: clock::Lamport,
22841 },
22842 Reloaded,
22843 CursorShapeChanged,
22844 PushedToNavHistory {
22845 anchor: Anchor,
22846 is_deactivate: bool,
22847 },
22848}
22849
22850impl EventEmitter<EditorEvent> for Editor {}
22851
22852impl Focusable for Editor {
22853 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22854 self.focus_handle.clone()
22855 }
22856}
22857
22858impl Render for Editor {
22859 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22860 let settings = ThemeSettings::get_global(cx);
22861
22862 let mut text_style = match self.mode {
22863 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22864 color: cx.theme().colors().editor_foreground,
22865 font_family: settings.ui_font.family.clone(),
22866 font_features: settings.ui_font.features.clone(),
22867 font_fallbacks: settings.ui_font.fallbacks.clone(),
22868 font_size: rems(0.875).into(),
22869 font_weight: settings.ui_font.weight,
22870 line_height: relative(settings.buffer_line_height.value()),
22871 ..Default::default()
22872 },
22873 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22874 color: cx.theme().colors().editor_foreground,
22875 font_family: settings.buffer_font.family.clone(),
22876 font_features: settings.buffer_font.features.clone(),
22877 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22878 font_size: settings.buffer_font_size(cx).into(),
22879 font_weight: settings.buffer_font.weight,
22880 line_height: relative(settings.buffer_line_height.value()),
22881 ..Default::default()
22882 },
22883 };
22884 if let Some(text_style_refinement) = &self.text_style_refinement {
22885 text_style.refine(text_style_refinement)
22886 }
22887
22888 let background = match self.mode {
22889 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22890 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22891 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22892 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22893 };
22894
22895 EditorElement::new(
22896 &cx.entity(),
22897 EditorStyle {
22898 background,
22899 border: cx.theme().colors().border,
22900 local_player: cx.theme().players().local(),
22901 text: text_style,
22902 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22903 syntax: cx.theme().syntax().clone(),
22904 status: cx.theme().status().clone(),
22905 inlay_hints_style: make_inlay_hints_style(cx),
22906 edit_prediction_styles: make_suggestion_styles(cx),
22907 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22908 show_underlines: self.diagnostics_enabled(),
22909 },
22910 )
22911 }
22912}
22913
22914impl EntityInputHandler for Editor {
22915 fn text_for_range(
22916 &mut self,
22917 range_utf16: Range<usize>,
22918 adjusted_range: &mut Option<Range<usize>>,
22919 _: &mut Window,
22920 cx: &mut Context<Self>,
22921 ) -> Option<String> {
22922 let snapshot = self.buffer.read(cx).read(cx);
22923 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22924 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22925 if (start.0..end.0) != range_utf16 {
22926 adjusted_range.replace(start.0..end.0);
22927 }
22928 Some(snapshot.text_for_range(start..end).collect())
22929 }
22930
22931 fn selected_text_range(
22932 &mut self,
22933 ignore_disabled_input: bool,
22934 _: &mut Window,
22935 cx: &mut Context<Self>,
22936 ) -> Option<UTF16Selection> {
22937 // Prevent the IME menu from appearing when holding down an alphabetic key
22938 // while input is disabled.
22939 if !ignore_disabled_input && !self.input_enabled {
22940 return None;
22941 }
22942
22943 let selection = self.selections.newest::<OffsetUtf16>(cx);
22944 let range = selection.range();
22945
22946 Some(UTF16Selection {
22947 range: range.start.0..range.end.0,
22948 reversed: selection.reversed,
22949 })
22950 }
22951
22952 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22953 let snapshot = self.buffer.read(cx).read(cx);
22954 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22955 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22956 }
22957
22958 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22959 self.clear_highlights::<InputComposition>(cx);
22960 self.ime_transaction.take();
22961 }
22962
22963 fn replace_text_in_range(
22964 &mut self,
22965 range_utf16: Option<Range<usize>>,
22966 text: &str,
22967 window: &mut Window,
22968 cx: &mut Context<Self>,
22969 ) {
22970 if !self.input_enabled {
22971 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22972 return;
22973 }
22974
22975 self.transact(window, cx, |this, window, cx| {
22976 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22977 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22978 Some(this.selection_replacement_ranges(range_utf16, cx))
22979 } else {
22980 this.marked_text_ranges(cx)
22981 };
22982
22983 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22984 let newest_selection_id = this.selections.newest_anchor().id;
22985 this.selections
22986 .all::<OffsetUtf16>(cx)
22987 .iter()
22988 .zip(ranges_to_replace.iter())
22989 .find_map(|(selection, range)| {
22990 if selection.id == newest_selection_id {
22991 Some(
22992 (range.start.0 as isize - selection.head().0 as isize)
22993 ..(range.end.0 as isize - selection.head().0 as isize),
22994 )
22995 } else {
22996 None
22997 }
22998 })
22999 });
23000
23001 cx.emit(EditorEvent::InputHandled {
23002 utf16_range_to_replace: range_to_replace,
23003 text: text.into(),
23004 });
23005
23006 if let Some(new_selected_ranges) = new_selected_ranges {
23007 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23008 selections.select_ranges(new_selected_ranges)
23009 });
23010 this.backspace(&Default::default(), window, cx);
23011 }
23012
23013 this.handle_input(text, window, cx);
23014 });
23015
23016 if let Some(transaction) = self.ime_transaction {
23017 self.buffer.update(cx, |buffer, cx| {
23018 buffer.group_until_transaction(transaction, cx);
23019 });
23020 }
23021
23022 self.unmark_text(window, cx);
23023 }
23024
23025 fn replace_and_mark_text_in_range(
23026 &mut self,
23027 range_utf16: Option<Range<usize>>,
23028 text: &str,
23029 new_selected_range_utf16: Option<Range<usize>>,
23030 window: &mut Window,
23031 cx: &mut Context<Self>,
23032 ) {
23033 if !self.input_enabled {
23034 return;
23035 }
23036
23037 let transaction = self.transact(window, cx, |this, window, cx| {
23038 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23039 let snapshot = this.buffer.read(cx).read(cx);
23040 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23041 for marked_range in &mut marked_ranges {
23042 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23043 marked_range.start.0 += relative_range_utf16.start;
23044 marked_range.start =
23045 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23046 marked_range.end =
23047 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23048 }
23049 }
23050 Some(marked_ranges)
23051 } else if let Some(range_utf16) = range_utf16 {
23052 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23053 Some(this.selection_replacement_ranges(range_utf16, cx))
23054 } else {
23055 None
23056 };
23057
23058 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23059 let newest_selection_id = this.selections.newest_anchor().id;
23060 this.selections
23061 .all::<OffsetUtf16>(cx)
23062 .iter()
23063 .zip(ranges_to_replace.iter())
23064 .find_map(|(selection, range)| {
23065 if selection.id == newest_selection_id {
23066 Some(
23067 (range.start.0 as isize - selection.head().0 as isize)
23068 ..(range.end.0 as isize - selection.head().0 as isize),
23069 )
23070 } else {
23071 None
23072 }
23073 })
23074 });
23075
23076 cx.emit(EditorEvent::InputHandled {
23077 utf16_range_to_replace: range_to_replace,
23078 text: text.into(),
23079 });
23080
23081 if let Some(ranges) = ranges_to_replace {
23082 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23083 s.select_ranges(ranges)
23084 });
23085 }
23086
23087 let marked_ranges = {
23088 let snapshot = this.buffer.read(cx).read(cx);
23089 this.selections
23090 .disjoint_anchors()
23091 .iter()
23092 .map(|selection| {
23093 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23094 })
23095 .collect::<Vec<_>>()
23096 };
23097
23098 if text.is_empty() {
23099 this.unmark_text(window, cx);
23100 } else {
23101 this.highlight_text::<InputComposition>(
23102 marked_ranges.clone(),
23103 HighlightStyle {
23104 underline: Some(UnderlineStyle {
23105 thickness: px(1.),
23106 color: None,
23107 wavy: false,
23108 }),
23109 ..Default::default()
23110 },
23111 cx,
23112 );
23113 }
23114
23115 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23116 let use_autoclose = this.use_autoclose;
23117 let use_auto_surround = this.use_auto_surround;
23118 this.set_use_autoclose(false);
23119 this.set_use_auto_surround(false);
23120 this.handle_input(text, window, cx);
23121 this.set_use_autoclose(use_autoclose);
23122 this.set_use_auto_surround(use_auto_surround);
23123
23124 if let Some(new_selected_range) = new_selected_range_utf16 {
23125 let snapshot = this.buffer.read(cx).read(cx);
23126 let new_selected_ranges = marked_ranges
23127 .into_iter()
23128 .map(|marked_range| {
23129 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23130 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23131 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23132 snapshot.clip_offset_utf16(new_start, Bias::Left)
23133 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23134 })
23135 .collect::<Vec<_>>();
23136
23137 drop(snapshot);
23138 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23139 selections.select_ranges(new_selected_ranges)
23140 });
23141 }
23142 });
23143
23144 self.ime_transaction = self.ime_transaction.or(transaction);
23145 if let Some(transaction) = self.ime_transaction {
23146 self.buffer.update(cx, |buffer, cx| {
23147 buffer.group_until_transaction(transaction, cx);
23148 });
23149 }
23150
23151 if self.text_highlights::<InputComposition>(cx).is_none() {
23152 self.ime_transaction.take();
23153 }
23154 }
23155
23156 fn bounds_for_range(
23157 &mut self,
23158 range_utf16: Range<usize>,
23159 element_bounds: gpui::Bounds<Pixels>,
23160 window: &mut Window,
23161 cx: &mut Context<Self>,
23162 ) -> Option<gpui::Bounds<Pixels>> {
23163 let text_layout_details = self.text_layout_details(window);
23164 let CharacterDimensions {
23165 em_width,
23166 em_advance,
23167 line_height,
23168 } = self.character_dimensions(window);
23169
23170 let snapshot = self.snapshot(window, cx);
23171 let scroll_position = snapshot.scroll_position();
23172 let scroll_left = scroll_position.x * em_advance;
23173
23174 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23175 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23176 + self.gutter_dimensions.full_width();
23177 let y = line_height * (start.row().as_f32() - scroll_position.y);
23178
23179 Some(Bounds {
23180 origin: element_bounds.origin + point(x, y),
23181 size: size(em_width, line_height),
23182 })
23183 }
23184
23185 fn character_index_for_point(
23186 &mut self,
23187 point: gpui::Point<Pixels>,
23188 _window: &mut Window,
23189 _cx: &mut Context<Self>,
23190 ) -> Option<usize> {
23191 let position_map = self.last_position_map.as_ref()?;
23192 if !position_map.text_hitbox.contains(&point) {
23193 return None;
23194 }
23195 let display_point = position_map.point_for_position(point).previous_valid;
23196 let anchor = position_map
23197 .snapshot
23198 .display_point_to_anchor(display_point, Bias::Left);
23199 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23200 Some(utf16_offset.0)
23201 }
23202}
23203
23204trait SelectionExt {
23205 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23206 fn spanned_rows(
23207 &self,
23208 include_end_if_at_line_start: bool,
23209 map: &DisplaySnapshot,
23210 ) -> Range<MultiBufferRow>;
23211}
23212
23213impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23214 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23215 let start = self
23216 .start
23217 .to_point(&map.buffer_snapshot)
23218 .to_display_point(map);
23219 let end = self
23220 .end
23221 .to_point(&map.buffer_snapshot)
23222 .to_display_point(map);
23223 if self.reversed {
23224 end..start
23225 } else {
23226 start..end
23227 }
23228 }
23229
23230 fn spanned_rows(
23231 &self,
23232 include_end_if_at_line_start: bool,
23233 map: &DisplaySnapshot,
23234 ) -> Range<MultiBufferRow> {
23235 let start = self.start.to_point(&map.buffer_snapshot);
23236 let mut end = self.end.to_point(&map.buffer_snapshot);
23237 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23238 end.row -= 1;
23239 }
23240
23241 let buffer_start = map.prev_line_boundary(start).0;
23242 let buffer_end = map.next_line_boundary(end).0;
23243 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23244 }
23245}
23246
23247impl<T: InvalidationRegion> InvalidationStack<T> {
23248 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23249 where
23250 S: Clone + ToOffset,
23251 {
23252 while let Some(region) = self.last() {
23253 let all_selections_inside_invalidation_ranges =
23254 if selections.len() == region.ranges().len() {
23255 selections
23256 .iter()
23257 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23258 .all(|(selection, invalidation_range)| {
23259 let head = selection.head().to_offset(buffer);
23260 invalidation_range.start <= head && invalidation_range.end >= head
23261 })
23262 } else {
23263 false
23264 };
23265
23266 if all_selections_inside_invalidation_ranges {
23267 break;
23268 } else {
23269 self.pop();
23270 }
23271 }
23272 }
23273}
23274
23275impl<T> Default for InvalidationStack<T> {
23276 fn default() -> Self {
23277 Self(Default::default())
23278 }
23279}
23280
23281impl<T> Deref for InvalidationStack<T> {
23282 type Target = Vec<T>;
23283
23284 fn deref(&self) -> &Self::Target {
23285 &self.0
23286 }
23287}
23288
23289impl<T> DerefMut for InvalidationStack<T> {
23290 fn deref_mut(&mut self) -> &mut Self::Target {
23291 &mut self.0
23292 }
23293}
23294
23295impl InvalidationRegion for SnippetState {
23296 fn ranges(&self) -> &[Range<Anchor>] {
23297 &self.ranges[self.active_index]
23298 }
23299}
23300
23301fn edit_prediction_edit_text(
23302 current_snapshot: &BufferSnapshot,
23303 edits: &[(Range<Anchor>, String)],
23304 edit_preview: &EditPreview,
23305 include_deletions: bool,
23306 cx: &App,
23307) -> HighlightedText {
23308 let edits = edits
23309 .iter()
23310 .map(|(anchor, text)| {
23311 (
23312 anchor.start.text_anchor..anchor.end.text_anchor,
23313 text.clone(),
23314 )
23315 })
23316 .collect::<Vec<_>>();
23317
23318 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23319}
23320
23321fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23322 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23323 // Just show the raw edit text with basic styling
23324 let mut text = String::new();
23325 let mut highlights = Vec::new();
23326
23327 let insertion_highlight_style = HighlightStyle {
23328 color: Some(cx.theme().colors().text),
23329 ..Default::default()
23330 };
23331
23332 for (_, edit_text) in edits {
23333 let start_offset = text.len();
23334 text.push_str(edit_text);
23335 let end_offset = text.len();
23336
23337 if start_offset < end_offset {
23338 highlights.push((start_offset..end_offset, insertion_highlight_style));
23339 }
23340 }
23341
23342 HighlightedText {
23343 text: text.into(),
23344 highlights,
23345 }
23346}
23347
23348pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23349 match severity {
23350 lsp::DiagnosticSeverity::ERROR => colors.error,
23351 lsp::DiagnosticSeverity::WARNING => colors.warning,
23352 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23353 lsp::DiagnosticSeverity::HINT => colors.info,
23354 _ => colors.ignored,
23355 }
23356}
23357
23358pub fn styled_runs_for_code_label<'a>(
23359 label: &'a CodeLabel,
23360 syntax_theme: &'a theme::SyntaxTheme,
23361) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23362 let fade_out = HighlightStyle {
23363 fade_out: Some(0.35),
23364 ..Default::default()
23365 };
23366
23367 let mut prev_end = label.filter_range.end;
23368 label
23369 .runs
23370 .iter()
23371 .enumerate()
23372 .flat_map(move |(ix, (range, highlight_id))| {
23373 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23374 style
23375 } else {
23376 return Default::default();
23377 };
23378 let mut muted_style = style;
23379 muted_style.highlight(fade_out);
23380
23381 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23382 if range.start >= label.filter_range.end {
23383 if range.start > prev_end {
23384 runs.push((prev_end..range.start, fade_out));
23385 }
23386 runs.push((range.clone(), muted_style));
23387 } else if range.end <= label.filter_range.end {
23388 runs.push((range.clone(), style));
23389 } else {
23390 runs.push((range.start..label.filter_range.end, style));
23391 runs.push((label.filter_range.end..range.end, muted_style));
23392 }
23393 prev_end = cmp::max(prev_end, range.end);
23394
23395 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23396 runs.push((prev_end..label.text.len(), fade_out));
23397 }
23398
23399 runs
23400 })
23401}
23402
23403pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23404 let mut prev_index = 0;
23405 let mut prev_codepoint: Option<char> = None;
23406 text.char_indices()
23407 .chain([(text.len(), '\0')])
23408 .filter_map(move |(index, codepoint)| {
23409 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23410 let is_boundary = index == text.len()
23411 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23412 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23413 if is_boundary {
23414 let chunk = &text[prev_index..index];
23415 prev_index = index;
23416 Some(chunk)
23417 } else {
23418 None
23419 }
23420 })
23421}
23422
23423pub trait RangeToAnchorExt: Sized {
23424 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23425
23426 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23427 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23428 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23429 }
23430}
23431
23432impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23433 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23434 let start_offset = self.start.to_offset(snapshot);
23435 let end_offset = self.end.to_offset(snapshot);
23436 if start_offset == end_offset {
23437 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23438 } else {
23439 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23440 }
23441 }
23442}
23443
23444pub trait RowExt {
23445 fn as_f32(&self) -> f32;
23446
23447 fn next_row(&self) -> Self;
23448
23449 fn previous_row(&self) -> Self;
23450
23451 fn minus(&self, other: Self) -> u32;
23452}
23453
23454impl RowExt for DisplayRow {
23455 fn as_f32(&self) -> f32 {
23456 self.0 as f32
23457 }
23458
23459 fn next_row(&self) -> Self {
23460 Self(self.0 + 1)
23461 }
23462
23463 fn previous_row(&self) -> Self {
23464 Self(self.0.saturating_sub(1))
23465 }
23466
23467 fn minus(&self, other: Self) -> u32 {
23468 self.0 - other.0
23469 }
23470}
23471
23472impl RowExt for MultiBufferRow {
23473 fn as_f32(&self) -> f32 {
23474 self.0 as f32
23475 }
23476
23477 fn next_row(&self) -> Self {
23478 Self(self.0 + 1)
23479 }
23480
23481 fn previous_row(&self) -> Self {
23482 Self(self.0.saturating_sub(1))
23483 }
23484
23485 fn minus(&self, other: Self) -> u32 {
23486 self.0 - other.0
23487 }
23488}
23489
23490trait RowRangeExt {
23491 type Row;
23492
23493 fn len(&self) -> usize;
23494
23495 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23496}
23497
23498impl RowRangeExt for Range<MultiBufferRow> {
23499 type Row = MultiBufferRow;
23500
23501 fn len(&self) -> usize {
23502 (self.end.0 - self.start.0) as usize
23503 }
23504
23505 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23506 (self.start.0..self.end.0).map(MultiBufferRow)
23507 }
23508}
23509
23510impl RowRangeExt for Range<DisplayRow> {
23511 type Row = DisplayRow;
23512
23513 fn len(&self) -> usize {
23514 (self.end.0 - self.start.0) as usize
23515 }
23516
23517 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23518 (self.start.0..self.end.0).map(DisplayRow)
23519 }
23520}
23521
23522/// If select range has more than one line, we
23523/// just point the cursor to range.start.
23524fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23525 if range.start.row == range.end.row {
23526 range
23527 } else {
23528 range.start..range.start
23529 }
23530}
23531pub struct KillRing(ClipboardItem);
23532impl Global for KillRing {}
23533
23534const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23535
23536enum BreakpointPromptEditAction {
23537 Log,
23538 Condition,
23539 HitCondition,
23540}
23541
23542struct BreakpointPromptEditor {
23543 pub(crate) prompt: Entity<Editor>,
23544 editor: WeakEntity<Editor>,
23545 breakpoint_anchor: Anchor,
23546 breakpoint: Breakpoint,
23547 edit_action: BreakpointPromptEditAction,
23548 block_ids: HashSet<CustomBlockId>,
23549 editor_margins: Arc<Mutex<EditorMargins>>,
23550 _subscriptions: Vec<Subscription>,
23551}
23552
23553impl BreakpointPromptEditor {
23554 const MAX_LINES: u8 = 4;
23555
23556 fn new(
23557 editor: WeakEntity<Editor>,
23558 breakpoint_anchor: Anchor,
23559 breakpoint: Breakpoint,
23560 edit_action: BreakpointPromptEditAction,
23561 window: &mut Window,
23562 cx: &mut Context<Self>,
23563 ) -> Self {
23564 let base_text = match edit_action {
23565 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23566 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23567 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23568 }
23569 .map(|msg| msg.to_string())
23570 .unwrap_or_default();
23571
23572 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23573 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23574
23575 let prompt = cx.new(|cx| {
23576 let mut prompt = Editor::new(
23577 EditorMode::AutoHeight {
23578 min_lines: 1,
23579 max_lines: Some(Self::MAX_LINES as usize),
23580 },
23581 buffer,
23582 None,
23583 window,
23584 cx,
23585 );
23586 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23587 prompt.set_show_cursor_when_unfocused(false, cx);
23588 prompt.set_placeholder_text(
23589 match edit_action {
23590 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23591 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23592 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23593 },
23594 cx,
23595 );
23596
23597 prompt
23598 });
23599
23600 Self {
23601 prompt,
23602 editor,
23603 breakpoint_anchor,
23604 breakpoint,
23605 edit_action,
23606 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23607 block_ids: Default::default(),
23608 _subscriptions: vec![],
23609 }
23610 }
23611
23612 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23613 self.block_ids.extend(block_ids)
23614 }
23615
23616 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23617 if let Some(editor) = self.editor.upgrade() {
23618 let message = self
23619 .prompt
23620 .read(cx)
23621 .buffer
23622 .read(cx)
23623 .as_singleton()
23624 .expect("A multi buffer in breakpoint prompt isn't possible")
23625 .read(cx)
23626 .as_rope()
23627 .to_string();
23628
23629 editor.update(cx, |editor, cx| {
23630 editor.edit_breakpoint_at_anchor(
23631 self.breakpoint_anchor,
23632 self.breakpoint.clone(),
23633 match self.edit_action {
23634 BreakpointPromptEditAction::Log => {
23635 BreakpointEditAction::EditLogMessage(message.into())
23636 }
23637 BreakpointPromptEditAction::Condition => {
23638 BreakpointEditAction::EditCondition(message.into())
23639 }
23640 BreakpointPromptEditAction::HitCondition => {
23641 BreakpointEditAction::EditHitCondition(message.into())
23642 }
23643 },
23644 cx,
23645 );
23646
23647 editor.remove_blocks(self.block_ids.clone(), None, cx);
23648 cx.focus_self(window);
23649 });
23650 }
23651 }
23652
23653 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23654 self.editor
23655 .update(cx, |editor, cx| {
23656 editor.remove_blocks(self.block_ids.clone(), None, cx);
23657 window.focus(&editor.focus_handle);
23658 })
23659 .log_err();
23660 }
23661
23662 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23663 let settings = ThemeSettings::get_global(cx);
23664 let text_style = TextStyle {
23665 color: if self.prompt.read(cx).read_only(cx) {
23666 cx.theme().colors().text_disabled
23667 } else {
23668 cx.theme().colors().text
23669 },
23670 font_family: settings.buffer_font.family.clone(),
23671 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23672 font_size: settings.buffer_font_size(cx).into(),
23673 font_weight: settings.buffer_font.weight,
23674 line_height: relative(settings.buffer_line_height.value()),
23675 ..Default::default()
23676 };
23677 EditorElement::new(
23678 &self.prompt,
23679 EditorStyle {
23680 background: cx.theme().colors().editor_background,
23681 local_player: cx.theme().players().local(),
23682 text: text_style,
23683 ..Default::default()
23684 },
23685 )
23686 }
23687}
23688
23689impl Render for BreakpointPromptEditor {
23690 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23691 let editor_margins = *self.editor_margins.lock();
23692 let gutter_dimensions = editor_margins.gutter;
23693 h_flex()
23694 .key_context("Editor")
23695 .bg(cx.theme().colors().editor_background)
23696 .border_y_1()
23697 .border_color(cx.theme().status().info_border)
23698 .size_full()
23699 .py(window.line_height() / 2.5)
23700 .on_action(cx.listener(Self::confirm))
23701 .on_action(cx.listener(Self::cancel))
23702 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23703 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23704 }
23705}
23706
23707impl Focusable for BreakpointPromptEditor {
23708 fn focus_handle(&self, cx: &App) -> FocusHandle {
23709 self.prompt.focus_handle(cx)
23710 }
23711}
23712
23713fn all_edits_insertions_or_deletions(
23714 edits: &Vec<(Range<Anchor>, String)>,
23715 snapshot: &MultiBufferSnapshot,
23716) -> bool {
23717 let mut all_insertions = true;
23718 let mut all_deletions = true;
23719
23720 for (range, new_text) in edits.iter() {
23721 let range_is_empty = range.to_offset(&snapshot).is_empty();
23722 let text_is_empty = new_text.is_empty();
23723
23724 if range_is_empty != text_is_empty {
23725 if range_is_empty {
23726 all_deletions = false;
23727 } else {
23728 all_insertions = false;
23729 }
23730 } else {
23731 return false;
23732 }
23733
23734 if !all_insertions && !all_deletions {
23735 return false;
23736 }
23737 }
23738 all_insertions || all_deletions
23739}
23740
23741struct MissingEditPredictionKeybindingTooltip;
23742
23743impl Render for MissingEditPredictionKeybindingTooltip {
23744 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23745 ui::tooltip_container(window, cx, |container, _, cx| {
23746 container
23747 .flex_shrink_0()
23748 .max_w_80()
23749 .min_h(rems_from_px(124.))
23750 .justify_between()
23751 .child(
23752 v_flex()
23753 .flex_1()
23754 .text_ui_sm(cx)
23755 .child(Label::new("Conflict with Accept Keybinding"))
23756 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23757 )
23758 .child(
23759 h_flex()
23760 .pb_1()
23761 .gap_1()
23762 .items_end()
23763 .w_full()
23764 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23765 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23766 }))
23767 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23768 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23769 })),
23770 )
23771 })
23772 }
23773}
23774
23775#[derive(Debug, Clone, Copy, PartialEq)]
23776pub struct LineHighlight {
23777 pub background: Background,
23778 pub border: Option<gpui::Hsla>,
23779 pub include_gutter: bool,
23780 pub type_id: Option<TypeId>,
23781}
23782
23783struct LineManipulationResult {
23784 pub new_text: String,
23785 pub line_count_before: usize,
23786 pub line_count_after: usize,
23787}
23788
23789fn render_diff_hunk_controls(
23790 row: u32,
23791 status: &DiffHunkStatus,
23792 hunk_range: Range<Anchor>,
23793 is_created_file: bool,
23794 line_height: Pixels,
23795 editor: &Entity<Editor>,
23796 _window: &mut Window,
23797 cx: &mut App,
23798) -> AnyElement {
23799 h_flex()
23800 .h(line_height)
23801 .mr_1()
23802 .gap_1()
23803 .px_0p5()
23804 .pb_1()
23805 .border_x_1()
23806 .border_b_1()
23807 .border_color(cx.theme().colors().border_variant)
23808 .rounded_b_lg()
23809 .bg(cx.theme().colors().editor_background)
23810 .gap_1()
23811 .block_mouse_except_scroll()
23812 .shadow_md()
23813 .child(if status.has_secondary_hunk() {
23814 Button::new(("stage", row as u64), "Stage")
23815 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23816 .tooltip({
23817 let focus_handle = editor.focus_handle(cx);
23818 move |window, cx| {
23819 Tooltip::for_action_in(
23820 "Stage Hunk",
23821 &::git::ToggleStaged,
23822 &focus_handle,
23823 window,
23824 cx,
23825 )
23826 }
23827 })
23828 .on_click({
23829 let editor = editor.clone();
23830 move |_event, _window, cx| {
23831 editor.update(cx, |editor, cx| {
23832 editor.stage_or_unstage_diff_hunks(
23833 true,
23834 vec![hunk_range.start..hunk_range.start],
23835 cx,
23836 );
23837 });
23838 }
23839 })
23840 } else {
23841 Button::new(("unstage", row as u64), "Unstage")
23842 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23843 .tooltip({
23844 let focus_handle = editor.focus_handle(cx);
23845 move |window, cx| {
23846 Tooltip::for_action_in(
23847 "Unstage Hunk",
23848 &::git::ToggleStaged,
23849 &focus_handle,
23850 window,
23851 cx,
23852 )
23853 }
23854 })
23855 .on_click({
23856 let editor = editor.clone();
23857 move |_event, _window, cx| {
23858 editor.update(cx, |editor, cx| {
23859 editor.stage_or_unstage_diff_hunks(
23860 false,
23861 vec![hunk_range.start..hunk_range.start],
23862 cx,
23863 );
23864 });
23865 }
23866 })
23867 })
23868 .child(
23869 Button::new(("restore", row as u64), "Restore")
23870 .tooltip({
23871 let focus_handle = editor.focus_handle(cx);
23872 move |window, cx| {
23873 Tooltip::for_action_in(
23874 "Restore Hunk",
23875 &::git::Restore,
23876 &focus_handle,
23877 window,
23878 cx,
23879 )
23880 }
23881 })
23882 .on_click({
23883 let editor = editor.clone();
23884 move |_event, window, cx| {
23885 editor.update(cx, |editor, cx| {
23886 let snapshot = editor.snapshot(window, cx);
23887 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23888 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23889 });
23890 }
23891 })
23892 .disabled(is_created_file),
23893 )
23894 .when(
23895 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23896 |el| {
23897 el.child(
23898 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23899 .shape(IconButtonShape::Square)
23900 .icon_size(IconSize::Small)
23901 // .disabled(!has_multiple_hunks)
23902 .tooltip({
23903 let focus_handle = editor.focus_handle(cx);
23904 move |window, cx| {
23905 Tooltip::for_action_in(
23906 "Next Hunk",
23907 &GoToHunk,
23908 &focus_handle,
23909 window,
23910 cx,
23911 )
23912 }
23913 })
23914 .on_click({
23915 let editor = editor.clone();
23916 move |_event, window, cx| {
23917 editor.update(cx, |editor, cx| {
23918 let snapshot = editor.snapshot(window, cx);
23919 let position =
23920 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23921 editor.go_to_hunk_before_or_after_position(
23922 &snapshot,
23923 position,
23924 Direction::Next,
23925 window,
23926 cx,
23927 );
23928 editor.expand_selected_diff_hunks(cx);
23929 });
23930 }
23931 }),
23932 )
23933 .child(
23934 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23935 .shape(IconButtonShape::Square)
23936 .icon_size(IconSize::Small)
23937 // .disabled(!has_multiple_hunks)
23938 .tooltip({
23939 let focus_handle = editor.focus_handle(cx);
23940 move |window, cx| {
23941 Tooltip::for_action_in(
23942 "Previous Hunk",
23943 &GoToPreviousHunk,
23944 &focus_handle,
23945 window,
23946 cx,
23947 )
23948 }
23949 })
23950 .on_click({
23951 let editor = editor.clone();
23952 move |_event, window, cx| {
23953 editor.update(cx, |editor, cx| {
23954 let snapshot = editor.snapshot(window, cx);
23955 let point =
23956 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23957 editor.go_to_hunk_before_or_after_position(
23958 &snapshot,
23959 point,
23960 Direction::Prev,
23961 window,
23962 cx,
23963 );
23964 editor.expand_selected_diff_hunks(cx);
23965 });
23966 }
23967 }),
23968 )
23969 },
23970 )
23971 .into_any_element()
23972}