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, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
152 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
153 ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{
164 DiagnosticSeverity, GitGutterSetting, GoToDiagnosticSeverityFilter, ProjectSettings,
165 },
166};
167use rand::{seq::SliceRandom, thread_rng};
168use rpc::{ErrorCode, ErrorExt, proto::PeerId};
169use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
170use selections_collection::{
171 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
172};
173use serde::{Deserialize, Serialize};
174use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
175use smallvec::{SmallVec, smallvec};
176use snippet::Snippet;
177use std::{
178 any::TypeId,
179 borrow::Cow,
180 cell::OnceCell,
181 cell::RefCell,
182 cmp::{self, Ordering, Reverse},
183 iter::Peekable,
184 mem,
185 num::NonZeroU32,
186 ops::Not,
187 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
188 path::{Path, PathBuf},
189 rc::Rc,
190 sync::Arc,
191 time::{Duration, Instant},
192};
193use sum_tree::TreeMap;
194use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
196use theme::{
197 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::SearchEvent,
212};
213
214use crate::{
215 code_context_menus::CompletionsMenuSource,
216 editor_settings::MultiCursorModifier,
217 hover_links::{find_url, find_url_from_range},
218 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
219};
220
221pub const FILE_HEADER_HEIGHT: u32 = 2;
222pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
223pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
224const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
225const MAX_LINE_LEN: usize = 1024;
226const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
227const MAX_SELECTION_HISTORY_LEN: usize = 1024;
228pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
229#[doc(hidden)]
230pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
231const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
232
233pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
235pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
236
237pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
238pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
239pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
240
241pub type RenderDiffHunkControlsFn = Arc<
242 dyn Fn(
243 u32,
244 &DiffHunkStatus,
245 Range<Anchor>,
246 bool,
247 Pixels,
248 &Entity<Editor>,
249 &mut Window,
250 &mut App,
251 ) -> AnyElement,
252>;
253
254enum ReportEditorEvent {
255 Saved { auto_saved: bool },
256 EditorOpened,
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::Closed => "Editor Closed",
266 }
267 }
268}
269
270struct InlineValueCache {
271 enabled: bool,
272 inlays: Vec<InlayId>,
273 refresh_task: Task<Option<()>>,
274}
275
276impl InlineValueCache {
277 fn new(enabled: bool) -> Self {
278 Self {
279 enabled,
280 inlays: Vec::new(),
281 refresh_task: Task::ready(None),
282 }
283 }
284}
285
286#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
287pub enum InlayId {
288 EditPrediction(usize),
289 DebuggerValue(usize),
290 // LSP
291 Hint(usize),
292 Color(usize),
293}
294
295impl InlayId {
296 fn id(&self) -> usize {
297 match self {
298 Self::EditPrediction(id) => *id,
299 Self::DebuggerValue(id) => *id,
300 Self::Hint(id) => *id,
301 Self::Color(id) => *id,
302 }
303 }
304}
305
306pub enum ActiveDebugLine {}
307pub enum DebugStackFrameLine {}
308enum DocumentHighlightRead {}
309enum DocumentHighlightWrite {}
310enum InputComposition {}
311pub enum PendingInput {}
312enum SelectedTextHighlight {}
313
314pub enum ConflictsOuter {}
315pub enum ConflictsOurs {}
316pub enum ConflictsTheirs {}
317pub enum ConflictsOursMarker {}
318pub enum ConflictsTheirsMarker {}
319
320#[derive(Debug, Copy, Clone, PartialEq, Eq)]
321pub enum Navigated {
322 Yes,
323 No,
324}
325
326impl Navigated {
327 pub fn from_bool(yes: bool) -> Navigated {
328 if yes { Navigated::Yes } else { Navigated::No }
329 }
330}
331
332#[derive(Debug, Clone, PartialEq, Eq)]
333enum DisplayDiffHunk {
334 Folded {
335 display_row: DisplayRow,
336 },
337 Unfolded {
338 is_created_file: bool,
339 diff_base_byte_range: Range<usize>,
340 display_row_range: Range<DisplayRow>,
341 multi_buffer_range: Range<Anchor>,
342 status: DiffHunkStatus,
343 },
344}
345
346pub enum HideMouseCursorOrigin {
347 TypingAction,
348 MovementAction,
349}
350
351pub fn init_settings(cx: &mut App) {
352 EditorSettings::register(cx);
353}
354
355pub fn init(cx: &mut App) {
356 init_settings(cx);
357
358 cx.set_global(GlobalBlameRenderer(Arc::new(())));
359
360 workspace::register_project_item::<Editor>(cx);
361 workspace::FollowableViewRegistry::register::<Editor>(cx);
362 workspace::register_serializable_item::<Editor>(cx);
363
364 cx.observe_new(
365 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
366 workspace.register_action(Editor::new_file);
367 workspace.register_action(Editor::new_file_vertical);
368 workspace.register_action(Editor::new_file_horizontal);
369 workspace.register_action(Editor::cancel_language_server_work);
370 workspace.register_action(Editor::toggle_focus);
371 },
372 )
373 .detach();
374
375 cx.on_action(move |_: &workspace::NewFile, cx| {
376 let app_state = workspace::AppState::global(cx);
377 if let Some(app_state) = app_state.upgrade() {
378 workspace::open_new(
379 Default::default(),
380 app_state,
381 cx,
382 |workspace, window, cx| {
383 Editor::new_file(workspace, &Default::default(), window, cx)
384 },
385 )
386 .detach();
387 }
388 });
389 cx.on_action(move |_: &workspace::NewWindow, cx| {
390 let app_state = workspace::AppState::global(cx);
391 if let Some(app_state) = app_state.upgrade() {
392 workspace::open_new(
393 Default::default(),
394 app_state,
395 cx,
396 |workspace, window, cx| {
397 cx.activate(true);
398 Editor::new_file(workspace, &Default::default(), window, cx)
399 },
400 )
401 .detach();
402 }
403 });
404}
405
406pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
407 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
408}
409
410pub trait DiagnosticRenderer {
411 fn render_group(
412 &self,
413 diagnostic_group: Vec<DiagnosticEntry<Point>>,
414 buffer_id: BufferId,
415 snapshot: EditorSnapshot,
416 editor: WeakEntity<Editor>,
417 cx: &mut App,
418 ) -> Vec<BlockProperties<Anchor>>;
419
420 fn render_hover(
421 &self,
422 diagnostic_group: Vec<DiagnosticEntry<Point>>,
423 range: Range<Point>,
424 buffer_id: BufferId,
425 cx: &mut App,
426 ) -> Option<Entity<markdown::Markdown>>;
427
428 fn open_link(
429 &self,
430 editor: &mut Editor,
431 link: SharedString,
432 window: &mut Window,
433 cx: &mut Context<Editor>,
434 );
435}
436
437pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
438
439impl GlobalDiagnosticRenderer {
440 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
441 cx.try_global::<Self>().map(|g| g.0.clone())
442 }
443}
444
445impl gpui::Global for GlobalDiagnosticRenderer {}
446pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
447 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
448}
449
450pub struct SearchWithinRange;
451
452trait InvalidationRegion {
453 fn ranges(&self) -> &[Range<Anchor>];
454}
455
456#[derive(Clone, Debug, PartialEq)]
457pub enum SelectPhase {
458 Begin {
459 position: DisplayPoint,
460 add: bool,
461 click_count: usize,
462 },
463 BeginColumnar {
464 position: DisplayPoint,
465 reset: bool,
466 mode: ColumnarMode,
467 goal_column: u32,
468 },
469 Extend {
470 position: DisplayPoint,
471 click_count: usize,
472 },
473 Update {
474 position: DisplayPoint,
475 goal_column: u32,
476 scroll_delta: gpui::Point<f32>,
477 },
478 End,
479}
480
481#[derive(Clone, Debug, PartialEq)]
482pub enum ColumnarMode {
483 FromMouse,
484 FromSelection,
485}
486
487#[derive(Clone, Debug)]
488pub enum SelectMode {
489 Character,
490 Word(Range<Anchor>),
491 Line(Range<Anchor>),
492 All,
493}
494
495#[derive(Clone, PartialEq, Eq, Debug)]
496pub enum EditorMode {
497 SingleLine,
498 AutoHeight {
499 min_lines: usize,
500 max_lines: Option<usize>,
501 },
502 Full {
503 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
504 scale_ui_elements_with_buffer_font_size: bool,
505 /// When set to `true`, the editor will render a background for the active line.
506 show_active_line_background: bool,
507 /// When set to `true`, the editor's height will be determined by its content.
508 sized_by_content: bool,
509 },
510 Minimap {
511 parent: WeakEntity<Editor>,
512 },
513}
514
515impl EditorMode {
516 pub fn full() -> Self {
517 Self::Full {
518 scale_ui_elements_with_buffer_font_size: true,
519 show_active_line_background: true,
520 sized_by_content: false,
521 }
522 }
523
524 #[inline]
525 pub fn is_full(&self) -> bool {
526 matches!(self, Self::Full { .. })
527 }
528
529 #[inline]
530 pub fn is_single_line(&self) -> bool {
531 matches!(self, Self::SingleLine { .. })
532 }
533
534 #[inline]
535 fn is_minimap(&self) -> bool {
536 matches!(self, Self::Minimap { .. })
537 }
538}
539
540#[derive(Copy, Clone, Debug)]
541pub enum SoftWrap {
542 /// Prefer not to wrap at all.
543 ///
544 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
545 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
546 GitDiff,
547 /// Prefer a single line generally, unless an overly long line is encountered.
548 None,
549 /// Soft wrap lines that exceed the editor width.
550 EditorWidth,
551 /// Soft wrap lines at the preferred line length.
552 Column(u32),
553 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
554 Bounded(u32),
555}
556
557#[derive(Clone)]
558pub struct EditorStyle {
559 pub background: Hsla,
560 pub border: Hsla,
561 pub local_player: PlayerColor,
562 pub text: TextStyle,
563 pub scrollbar_width: Pixels,
564 pub syntax: Arc<SyntaxTheme>,
565 pub status: StatusColors,
566 pub inlay_hints_style: HighlightStyle,
567 pub edit_prediction_styles: EditPredictionStyles,
568 pub unnecessary_code_fade: f32,
569 pub show_underlines: bool,
570}
571
572impl Default for EditorStyle {
573 fn default() -> Self {
574 Self {
575 background: Hsla::default(),
576 border: Hsla::default(),
577 local_player: PlayerColor::default(),
578 text: TextStyle::default(),
579 scrollbar_width: Pixels::default(),
580 syntax: Default::default(),
581 // HACK: Status colors don't have a real default.
582 // We should look into removing the status colors from the editor
583 // style and retrieve them directly from the theme.
584 status: StatusColors::dark(),
585 inlay_hints_style: HighlightStyle::default(),
586 edit_prediction_styles: EditPredictionStyles {
587 insertion: HighlightStyle::default(),
588 whitespace: HighlightStyle::default(),
589 },
590 unnecessary_code_fade: Default::default(),
591 show_underlines: true,
592 }
593 }
594}
595
596pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
597 let show_background = language_settings::language_settings(None, None, cx)
598 .inlay_hints
599 .show_background;
600
601 HighlightStyle {
602 color: Some(cx.theme().status().hint),
603 background_color: show_background.then(|| cx.theme().status().hint_background),
604 ..HighlightStyle::default()
605 }
606}
607
608pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
609 EditPredictionStyles {
610 insertion: HighlightStyle {
611 color: Some(cx.theme().status().predictive),
612 ..HighlightStyle::default()
613 },
614 whitespace: HighlightStyle {
615 background_color: Some(cx.theme().status().created_background),
616 ..HighlightStyle::default()
617 },
618 }
619}
620
621type CompletionId = usize;
622
623pub(crate) enum EditDisplayMode {
624 TabAccept,
625 DiffPopover,
626 Inline,
627}
628
629enum EditPrediction {
630 Edit {
631 edits: Vec<(Range<Anchor>, String)>,
632 edit_preview: Option<EditPreview>,
633 display_mode: EditDisplayMode,
634 snapshot: BufferSnapshot,
635 },
636 Move {
637 target: Anchor,
638 snapshot: BufferSnapshot,
639 },
640}
641
642struct EditPredictionState {
643 inlay_ids: Vec<InlayId>,
644 completion: EditPrediction,
645 completion_id: Option<SharedString>,
646 invalidation_range: Range<Anchor>,
647}
648
649enum EditPredictionSettings {
650 Disabled,
651 Enabled {
652 show_in_menu: bool,
653 preview_requires_modifier: bool,
654 },
655}
656
657enum EditPredictionHighlight {}
658
659#[derive(Debug, Clone)]
660struct InlineDiagnostic {
661 message: SharedString,
662 group_id: usize,
663 is_primary: bool,
664 start: Point,
665 severity: lsp::DiagnosticSeverity,
666}
667
668pub enum MenuEditPredictionsPolicy {
669 Never,
670 ByProvider,
671}
672
673pub enum EditPredictionPreview {
674 /// Modifier is not pressed
675 Inactive { released_too_fast: bool },
676 /// Modifier pressed
677 Active {
678 since: Instant,
679 previous_scroll_position: Option<ScrollAnchor>,
680 },
681}
682
683impl EditPredictionPreview {
684 pub fn released_too_fast(&self) -> bool {
685 match self {
686 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
687 EditPredictionPreview::Active { .. } => false,
688 }
689 }
690
691 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
692 if let EditPredictionPreview::Active {
693 previous_scroll_position,
694 ..
695 } = self
696 {
697 *previous_scroll_position = scroll_position;
698 }
699 }
700}
701
702pub struct ContextMenuOptions {
703 pub min_entries_visible: usize,
704 pub max_entries_visible: usize,
705 pub placement: Option<ContextMenuPlacement>,
706}
707
708#[derive(Debug, Clone, PartialEq, Eq)]
709pub enum ContextMenuPlacement {
710 Above,
711 Below,
712}
713
714#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
715struct EditorActionId(usize);
716
717impl EditorActionId {
718 pub fn post_inc(&mut self) -> Self {
719 let answer = self.0;
720
721 *self = Self(answer + 1);
722
723 Self(answer)
724 }
725}
726
727// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
728// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
729
730type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
731type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
732
733#[derive(Default)]
734struct ScrollbarMarkerState {
735 scrollbar_size: Size<Pixels>,
736 dirty: bool,
737 markers: Arc<[PaintQuad]>,
738 pending_refresh: Option<Task<Result<()>>>,
739}
740
741impl ScrollbarMarkerState {
742 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
743 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
744 }
745}
746
747#[derive(Clone, Copy, PartialEq, Eq)]
748pub enum MinimapVisibility {
749 Disabled,
750 Enabled {
751 /// The configuration currently present in the users settings.
752 setting_configuration: bool,
753 /// Whether to override the currently set visibility from the users setting.
754 toggle_override: bool,
755 },
756}
757
758impl MinimapVisibility {
759 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
760 if mode.is_full() {
761 Self::Enabled {
762 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
763 toggle_override: false,
764 }
765 } else {
766 Self::Disabled
767 }
768 }
769
770 fn hidden(&self) -> Self {
771 match *self {
772 Self::Enabled {
773 setting_configuration,
774 ..
775 } => Self::Enabled {
776 setting_configuration,
777 toggle_override: setting_configuration,
778 },
779 Self::Disabled => Self::Disabled,
780 }
781 }
782
783 fn disabled(&self) -> bool {
784 matches!(*self, Self::Disabled)
785 }
786
787 fn settings_visibility(&self) -> bool {
788 match *self {
789 Self::Enabled {
790 setting_configuration,
791 ..
792 } => setting_configuration,
793 _ => false,
794 }
795 }
796
797 fn visible(&self) -> bool {
798 match *self {
799 Self::Enabled {
800 setting_configuration,
801 toggle_override,
802 } => setting_configuration ^ toggle_override,
803 _ => false,
804 }
805 }
806
807 fn toggle_visibility(&self) -> Self {
808 match *self {
809 Self::Enabled {
810 toggle_override,
811 setting_configuration,
812 } => Self::Enabled {
813 setting_configuration,
814 toggle_override: !toggle_override,
815 },
816 Self::Disabled => Self::Disabled,
817 }
818 }
819}
820
821#[derive(Clone, Debug)]
822struct RunnableTasks {
823 templates: Vec<(TaskSourceKind, TaskTemplate)>,
824 offset: multi_buffer::Anchor,
825 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
826 column: u32,
827 // Values of all named captures, including those starting with '_'
828 extra_variables: HashMap<String, String>,
829 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
830 context_range: Range<BufferOffset>,
831}
832
833impl RunnableTasks {
834 fn resolve<'a>(
835 &'a self,
836 cx: &'a task::TaskContext,
837 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
838 self.templates.iter().filter_map(|(kind, template)| {
839 template
840 .resolve_task(&kind.to_id_base(), cx)
841 .map(|task| (kind.clone(), task))
842 })
843 }
844}
845
846#[derive(Clone)]
847pub struct ResolvedTasks {
848 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
849 position: Anchor,
850}
851
852#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
853struct BufferOffset(usize);
854
855// Addons allow storing per-editor state in other crates (e.g. Vim)
856pub trait Addon: 'static {
857 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
858
859 fn render_buffer_header_controls(
860 &self,
861 _: &ExcerptInfo,
862 _: &Window,
863 _: &App,
864 ) -> Option<AnyElement> {
865 None
866 }
867
868 fn to_any(&self) -> &dyn std::any::Any;
869
870 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
871 None
872 }
873}
874
875struct ChangeLocation {
876 current: Option<Vec<Anchor>>,
877 original: Vec<Anchor>,
878}
879impl ChangeLocation {
880 fn locations(&self) -> &[Anchor] {
881 self.current.as_ref().unwrap_or(&self.original)
882 }
883}
884
885/// A set of caret positions, registered when the editor was edited.
886pub struct ChangeList {
887 changes: Vec<ChangeLocation>,
888 /// Currently "selected" change.
889 position: Option<usize>,
890}
891
892impl ChangeList {
893 pub fn new() -> Self {
894 Self {
895 changes: Vec::new(),
896 position: None,
897 }
898 }
899
900 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
901 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
902 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
903 if self.changes.is_empty() {
904 return None;
905 }
906
907 let prev = self.position.unwrap_or(self.changes.len());
908 let next = if direction == Direction::Prev {
909 prev.saturating_sub(count)
910 } else {
911 (prev + count).min(self.changes.len() - 1)
912 };
913 self.position = Some(next);
914 self.changes.get(next).map(|change| change.locations())
915 }
916
917 /// Adds a new change to the list, resetting the change list position.
918 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
919 self.position.take();
920 if let Some(last) = self.changes.last_mut()
921 && group
922 {
923 last.current = Some(new_positions)
924 } else {
925 self.changes.push(ChangeLocation {
926 original: new_positions,
927 current: None,
928 });
929 }
930 }
931
932 pub fn last(&self) -> Option<&[Anchor]> {
933 self.changes.last().map(|change| change.locations())
934 }
935
936 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
937 self.changes.last().map(|change| change.original.as_slice())
938 }
939
940 pub fn invert_last_group(&mut self) {
941 if let Some(last) = self.changes.last_mut()
942 && let Some(current) = last.current.as_mut()
943 {
944 mem::swap(&mut last.original, current);
945 }
946 }
947}
948
949#[derive(Clone)]
950struct InlineBlamePopoverState {
951 scroll_handle: ScrollHandle,
952 commit_message: Option<ParsedCommitMessage>,
953 markdown: Entity<Markdown>,
954}
955
956struct InlineBlamePopover {
957 position: gpui::Point<Pixels>,
958 hide_task: Option<Task<()>>,
959 popover_bounds: Option<Bounds<Pixels>>,
960 popover_state: InlineBlamePopoverState,
961 keyboard_grace: bool,
962}
963
964enum SelectionDragState {
965 /// State when no drag related activity is detected.
966 None,
967 /// State when the mouse is down on a selection that is about to be dragged.
968 ReadyToDrag {
969 selection: Selection<Anchor>,
970 click_position: gpui::Point<Pixels>,
971 mouse_down_time: Instant,
972 },
973 /// State when the mouse is dragging the selection in the editor.
974 Dragging {
975 selection: Selection<Anchor>,
976 drop_cursor: Selection<Anchor>,
977 hide_drop_cursor: bool,
978 },
979}
980
981enum ColumnarSelectionState {
982 FromMouse {
983 selection_tail: Anchor,
984 display_point: Option<DisplayPoint>,
985 },
986 FromSelection {
987 selection_tail: Anchor,
988 },
989}
990
991/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
992/// a breakpoint on them.
993#[derive(Clone, Copy, Debug, PartialEq, Eq)]
994struct PhantomBreakpointIndicator {
995 display_row: DisplayRow,
996 /// There's a small debounce between hovering over the line and showing the indicator.
997 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
998 is_active: bool,
999 collides_with_existing_breakpoint: bool,
1000}
1001
1002/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1003///
1004/// See the [module level documentation](self) for more information.
1005pub struct Editor {
1006 focus_handle: FocusHandle,
1007 last_focused_descendant: Option<WeakFocusHandle>,
1008 /// The text buffer being edited
1009 buffer: Entity<MultiBuffer>,
1010 /// Map of how text in the buffer should be displayed.
1011 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1012 pub display_map: Entity<DisplayMap>,
1013 pub selections: SelectionsCollection,
1014 pub scroll_manager: ScrollManager,
1015 /// When inline assist editors are linked, they all render cursors because
1016 /// typing enters text into each of them, even the ones that aren't focused.
1017 pub(crate) show_cursor_when_unfocused: bool,
1018 columnar_selection_state: Option<ColumnarSelectionState>,
1019 add_selections_state: Option<AddSelectionsState>,
1020 select_next_state: Option<SelectNextState>,
1021 select_prev_state: Option<SelectNextState>,
1022 selection_history: SelectionHistory,
1023 defer_selection_effects: bool,
1024 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1025 autoclose_regions: Vec<AutocloseRegion>,
1026 snippet_stack: InvalidationStack<SnippetState>,
1027 select_syntax_node_history: SelectSyntaxNodeHistory,
1028 ime_transaction: Option<TransactionId>,
1029 pub diagnostics_max_severity: DiagnosticSeverity,
1030 active_diagnostics: ActiveDiagnostic,
1031 show_inline_diagnostics: bool,
1032 inline_diagnostics_update: Task<()>,
1033 inline_diagnostics_enabled: bool,
1034 diagnostics_enabled: bool,
1035 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1036 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1037 hard_wrap: Option<usize>,
1038 project: Option<Entity<Project>>,
1039 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1040 completion_provider: Option<Rc<dyn CompletionProvider>>,
1041 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1042 blink_manager: Entity<BlinkManager>,
1043 show_cursor_names: bool,
1044 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1045 pub show_local_selections: bool,
1046 mode: EditorMode,
1047 show_breadcrumbs: bool,
1048 show_gutter: bool,
1049 show_scrollbars: ScrollbarAxes,
1050 minimap_visibility: MinimapVisibility,
1051 offset_content: bool,
1052 disable_expand_excerpt_buttons: bool,
1053 show_line_numbers: Option<bool>,
1054 use_relative_line_numbers: Option<bool>,
1055 show_git_diff_gutter: Option<bool>,
1056 show_code_actions: Option<bool>,
1057 show_runnables: Option<bool>,
1058 show_breakpoints: Option<bool>,
1059 show_wrap_guides: Option<bool>,
1060 show_indent_guides: Option<bool>,
1061 placeholder_text: Option<Arc<str>>,
1062 highlight_order: usize,
1063 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1064 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1065 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1066 scrollbar_marker_state: ScrollbarMarkerState,
1067 active_indent_guides_state: ActiveIndentGuidesState,
1068 nav_history: Option<ItemNavHistory>,
1069 context_menu: RefCell<Option<CodeContextMenu>>,
1070 context_menu_options: Option<ContextMenuOptions>,
1071 mouse_context_menu: Option<MouseContextMenu>,
1072 completion_tasks: Vec<(CompletionId, Task<()>)>,
1073 inline_blame_popover: Option<InlineBlamePopover>,
1074 inline_blame_popover_show_task: Option<Task<()>>,
1075 signature_help_state: SignatureHelpState,
1076 auto_signature_help: Option<bool>,
1077 find_all_references_task_sources: Vec<Anchor>,
1078 next_completion_id: CompletionId,
1079 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1080 code_actions_task: Option<Task<Result<()>>>,
1081 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1082 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1083 document_highlights_task: Option<Task<()>>,
1084 linked_editing_range_task: Option<Task<Option<()>>>,
1085 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1086 pending_rename: Option<RenameState>,
1087 searchable: bool,
1088 cursor_shape: CursorShape,
1089 current_line_highlight: Option<CurrentLineHighlight>,
1090 collapse_matches: bool,
1091 autoindent_mode: Option<AutoindentMode>,
1092 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1093 input_enabled: bool,
1094 use_modal_editing: bool,
1095 read_only: bool,
1096 leader_id: Option<CollaboratorId>,
1097 remote_id: Option<ViewId>,
1098 pub hover_state: HoverState,
1099 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1100 gutter_hovered: bool,
1101 hovered_link_state: Option<HoveredLinkState>,
1102 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1103 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1104 active_edit_prediction: Option<EditPredictionState>,
1105 /// Used to prevent flickering as the user types while the menu is open
1106 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1107 edit_prediction_settings: EditPredictionSettings,
1108 edit_predictions_hidden_for_vim_mode: bool,
1109 show_edit_predictions_override: Option<bool>,
1110 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1111 edit_prediction_preview: EditPredictionPreview,
1112 edit_prediction_indent_conflict: bool,
1113 edit_prediction_requires_modifier_in_indent_conflict: bool,
1114 inlay_hint_cache: InlayHintCache,
1115 next_inlay_id: usize,
1116 _subscriptions: Vec<Subscription>,
1117 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1118 gutter_dimensions: GutterDimensions,
1119 style: Option<EditorStyle>,
1120 text_style_refinement: Option<TextStyleRefinement>,
1121 next_editor_action_id: EditorActionId,
1122 editor_actions: Rc<
1123 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1124 >,
1125 use_autoclose: bool,
1126 use_auto_surround: bool,
1127 auto_replace_emoji_shortcode: bool,
1128 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1129 show_git_blame_gutter: bool,
1130 show_git_blame_inline: bool,
1131 show_git_blame_inline_delay_task: Option<Task<()>>,
1132 git_blame_inline_enabled: bool,
1133 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1134 serialize_dirty_buffers: bool,
1135 show_selection_menu: Option<bool>,
1136 blame: Option<Entity<GitBlame>>,
1137 blame_subscription: Option<Subscription>,
1138 custom_context_menu: Option<
1139 Box<
1140 dyn 'static
1141 + Fn(
1142 &mut Self,
1143 DisplayPoint,
1144 &mut Window,
1145 &mut Context<Self>,
1146 ) -> Option<Entity<ui::ContextMenu>>,
1147 >,
1148 >,
1149 last_bounds: Option<Bounds<Pixels>>,
1150 last_position_map: Option<Rc<PositionMap>>,
1151 expect_bounds_change: Option<Bounds<Pixels>>,
1152 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1153 tasks_update_task: Option<Task<()>>,
1154 breakpoint_store: Option<Entity<BreakpointStore>>,
1155 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1156 hovered_diff_hunk_row: Option<DisplayRow>,
1157 pull_diagnostics_task: Task<()>,
1158 in_project_search: bool,
1159 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1160 breadcrumb_header: Option<String>,
1161 focused_block: Option<FocusedBlock>,
1162 next_scroll_position: NextScrollCursorCenterTopBottom,
1163 addons: HashMap<TypeId, Box<dyn Addon>>,
1164 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1165 load_diff_task: Option<Shared<Task<()>>>,
1166 /// Whether we are temporarily displaying a diff other than git's
1167 temporary_diff_override: bool,
1168 selection_mark_mode: bool,
1169 toggle_fold_multiple_buffers: Task<()>,
1170 _scroll_cursor_center_top_bottom_task: Task<()>,
1171 serialize_selections: Task<()>,
1172 serialize_folds: Task<()>,
1173 mouse_cursor_hidden: bool,
1174 minimap: Option<Entity<Self>>,
1175 hide_mouse_mode: HideMouseMode,
1176 pub change_list: ChangeList,
1177 inline_value_cache: InlineValueCache,
1178 selection_drag_state: SelectionDragState,
1179 next_color_inlay_id: usize,
1180 colors: Option<LspColorData>,
1181 folding_newlines: Task<()>,
1182}
1183
1184#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1185enum NextScrollCursorCenterTopBottom {
1186 #[default]
1187 Center,
1188 Top,
1189 Bottom,
1190}
1191
1192impl NextScrollCursorCenterTopBottom {
1193 fn next(&self) -> Self {
1194 match self {
1195 Self::Center => Self::Top,
1196 Self::Top => Self::Bottom,
1197 Self::Bottom => Self::Center,
1198 }
1199 }
1200}
1201
1202#[derive(Clone)]
1203pub struct EditorSnapshot {
1204 pub mode: EditorMode,
1205 show_gutter: bool,
1206 show_line_numbers: Option<bool>,
1207 show_git_diff_gutter: Option<bool>,
1208 show_code_actions: Option<bool>,
1209 show_runnables: Option<bool>,
1210 show_breakpoints: Option<bool>,
1211 git_blame_gutter_max_author_length: Option<usize>,
1212 pub display_snapshot: DisplaySnapshot,
1213 pub placeholder_text: Option<Arc<str>>,
1214 is_focused: bool,
1215 scroll_anchor: ScrollAnchor,
1216 ongoing_scroll: OngoingScroll,
1217 current_line_highlight: CurrentLineHighlight,
1218 gutter_hovered: bool,
1219}
1220
1221#[derive(Default, Debug, Clone, Copy)]
1222pub struct GutterDimensions {
1223 pub left_padding: Pixels,
1224 pub right_padding: Pixels,
1225 pub width: Pixels,
1226 pub margin: Pixels,
1227 pub git_blame_entries_width: Option<Pixels>,
1228}
1229
1230impl GutterDimensions {
1231 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1232 Self {
1233 margin: Self::default_gutter_margin(font_id, font_size, cx),
1234 ..Default::default()
1235 }
1236 }
1237
1238 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1239 -cx.text_system().descent(font_id, font_size)
1240 }
1241 /// The full width of the space taken up by the gutter.
1242 pub fn full_width(&self) -> Pixels {
1243 self.margin + self.width
1244 }
1245
1246 /// The width of the space reserved for the fold indicators,
1247 /// use alongside 'justify_end' and `gutter_width` to
1248 /// right align content with the line numbers
1249 pub fn fold_area_width(&self) -> Pixels {
1250 self.margin + self.right_padding
1251 }
1252}
1253
1254struct CharacterDimensions {
1255 em_width: Pixels,
1256 em_advance: Pixels,
1257 line_height: Pixels,
1258}
1259
1260#[derive(Debug)]
1261pub struct RemoteSelection {
1262 pub replica_id: ReplicaId,
1263 pub selection: Selection<Anchor>,
1264 pub cursor_shape: CursorShape,
1265 pub collaborator_id: CollaboratorId,
1266 pub line_mode: bool,
1267 pub user_name: Option<SharedString>,
1268 pub color: PlayerColor,
1269}
1270
1271#[derive(Clone, Debug)]
1272struct SelectionHistoryEntry {
1273 selections: Arc<[Selection<Anchor>]>,
1274 select_next_state: Option<SelectNextState>,
1275 select_prev_state: Option<SelectNextState>,
1276 add_selections_state: Option<AddSelectionsState>,
1277}
1278
1279#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1280enum SelectionHistoryMode {
1281 Normal,
1282 Undoing,
1283 Redoing,
1284 Skipping,
1285}
1286
1287#[derive(Clone, PartialEq, Eq, Hash)]
1288struct HoveredCursor {
1289 replica_id: u16,
1290 selection_id: usize,
1291}
1292
1293impl Default for SelectionHistoryMode {
1294 fn default() -> Self {
1295 Self::Normal
1296 }
1297}
1298
1299#[derive(Debug)]
1300/// SelectionEffects controls the side-effects of updating the selection.
1301///
1302/// The default behaviour does "what you mostly want":
1303/// - it pushes to the nav history if the cursor moved by >10 lines
1304/// - it re-triggers completion requests
1305/// - it scrolls to fit
1306///
1307/// You might want to modify these behaviours. For example when doing a "jump"
1308/// like go to definition, we always want to add to nav history; but when scrolling
1309/// in vim mode we never do.
1310///
1311/// Similarly, you might want to disable scrolling if you don't want the viewport to
1312/// move.
1313#[derive(Clone)]
1314pub struct SelectionEffects {
1315 nav_history: Option<bool>,
1316 completions: bool,
1317 scroll: Option<Autoscroll>,
1318}
1319
1320impl Default for SelectionEffects {
1321 fn default() -> Self {
1322 Self {
1323 nav_history: None,
1324 completions: true,
1325 scroll: Some(Autoscroll::fit()),
1326 }
1327 }
1328}
1329impl SelectionEffects {
1330 pub fn scroll(scroll: Autoscroll) -> Self {
1331 Self {
1332 scroll: Some(scroll),
1333 ..Default::default()
1334 }
1335 }
1336
1337 pub fn no_scroll() -> Self {
1338 Self {
1339 scroll: None,
1340 ..Default::default()
1341 }
1342 }
1343
1344 pub fn completions(self, completions: bool) -> Self {
1345 Self {
1346 completions,
1347 ..self
1348 }
1349 }
1350
1351 pub fn nav_history(self, nav_history: bool) -> Self {
1352 Self {
1353 nav_history: Some(nav_history),
1354 ..self
1355 }
1356 }
1357}
1358
1359struct DeferredSelectionEffectsState {
1360 changed: bool,
1361 effects: SelectionEffects,
1362 old_cursor_position: Anchor,
1363 history_entry: SelectionHistoryEntry,
1364}
1365
1366#[derive(Default)]
1367struct SelectionHistory {
1368 #[allow(clippy::type_complexity)]
1369 selections_by_transaction:
1370 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1371 mode: SelectionHistoryMode,
1372 undo_stack: VecDeque<SelectionHistoryEntry>,
1373 redo_stack: VecDeque<SelectionHistoryEntry>,
1374}
1375
1376impl SelectionHistory {
1377 #[track_caller]
1378 fn insert_transaction(
1379 &mut self,
1380 transaction_id: TransactionId,
1381 selections: Arc<[Selection<Anchor>]>,
1382 ) {
1383 if selections.is_empty() {
1384 log::error!(
1385 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1386 std::panic::Location::caller()
1387 );
1388 return;
1389 }
1390 self.selections_by_transaction
1391 .insert(transaction_id, (selections, None));
1392 }
1393
1394 #[allow(clippy::type_complexity)]
1395 fn transaction(
1396 &self,
1397 transaction_id: TransactionId,
1398 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1399 self.selections_by_transaction.get(&transaction_id)
1400 }
1401
1402 #[allow(clippy::type_complexity)]
1403 fn transaction_mut(
1404 &mut self,
1405 transaction_id: TransactionId,
1406 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1407 self.selections_by_transaction.get_mut(&transaction_id)
1408 }
1409
1410 fn push(&mut self, entry: SelectionHistoryEntry) {
1411 if !entry.selections.is_empty() {
1412 match self.mode {
1413 SelectionHistoryMode::Normal => {
1414 self.push_undo(entry);
1415 self.redo_stack.clear();
1416 }
1417 SelectionHistoryMode::Undoing => self.push_redo(entry),
1418 SelectionHistoryMode::Redoing => self.push_undo(entry),
1419 SelectionHistoryMode::Skipping => {}
1420 }
1421 }
1422 }
1423
1424 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1425 if self
1426 .undo_stack
1427 .back()
1428 .is_none_or(|e| e.selections != entry.selections)
1429 {
1430 self.undo_stack.push_back(entry);
1431 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1432 self.undo_stack.pop_front();
1433 }
1434 }
1435 }
1436
1437 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1438 if self
1439 .redo_stack
1440 .back()
1441 .is_none_or(|e| e.selections != entry.selections)
1442 {
1443 self.redo_stack.push_back(entry);
1444 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1445 self.redo_stack.pop_front();
1446 }
1447 }
1448 }
1449}
1450
1451#[derive(Clone, Copy)]
1452pub struct RowHighlightOptions {
1453 pub autoscroll: bool,
1454 pub include_gutter: bool,
1455}
1456
1457impl Default for RowHighlightOptions {
1458 fn default() -> Self {
1459 Self {
1460 autoscroll: Default::default(),
1461 include_gutter: true,
1462 }
1463 }
1464}
1465
1466struct RowHighlight {
1467 index: usize,
1468 range: Range<Anchor>,
1469 color: Hsla,
1470 options: RowHighlightOptions,
1471 type_id: TypeId,
1472}
1473
1474#[derive(Clone, Debug)]
1475struct AddSelectionsState {
1476 groups: Vec<AddSelectionsGroup>,
1477}
1478
1479#[derive(Clone, Debug)]
1480struct AddSelectionsGroup {
1481 above: bool,
1482 stack: Vec<usize>,
1483}
1484
1485#[derive(Clone)]
1486struct SelectNextState {
1487 query: AhoCorasick,
1488 wordwise: bool,
1489 done: bool,
1490}
1491
1492impl std::fmt::Debug for SelectNextState {
1493 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1494 f.debug_struct(std::any::type_name::<Self>())
1495 .field("wordwise", &self.wordwise)
1496 .field("done", &self.done)
1497 .finish()
1498 }
1499}
1500
1501#[derive(Debug)]
1502struct AutocloseRegion {
1503 selection_id: usize,
1504 range: Range<Anchor>,
1505 pair: BracketPair,
1506}
1507
1508#[derive(Debug)]
1509struct SnippetState {
1510 ranges: Vec<Vec<Range<Anchor>>>,
1511 active_index: usize,
1512 choices: Vec<Option<Vec<String>>>,
1513}
1514
1515#[doc(hidden)]
1516pub struct RenameState {
1517 pub range: Range<Anchor>,
1518 pub old_name: Arc<str>,
1519 pub editor: Entity<Editor>,
1520 block_id: CustomBlockId,
1521}
1522
1523struct InvalidationStack<T>(Vec<T>);
1524
1525struct RegisteredEditPredictionProvider {
1526 provider: Arc<dyn EditPredictionProviderHandle>,
1527 _subscription: Subscription,
1528}
1529
1530#[derive(Debug, PartialEq, Eq)]
1531pub struct ActiveDiagnosticGroup {
1532 pub active_range: Range<Anchor>,
1533 pub active_message: String,
1534 pub group_id: usize,
1535 pub blocks: HashSet<CustomBlockId>,
1536}
1537
1538#[derive(Debug, PartialEq, Eq)]
1539
1540pub(crate) enum ActiveDiagnostic {
1541 None,
1542 All,
1543 Group(ActiveDiagnosticGroup),
1544}
1545
1546#[derive(Serialize, Deserialize, Clone, Debug)]
1547pub struct ClipboardSelection {
1548 /// The number of bytes in this selection.
1549 pub len: usize,
1550 /// Whether this was a full-line selection.
1551 pub is_entire_line: bool,
1552 /// The indentation of the first line when this content was originally copied.
1553 pub first_line_indent: u32,
1554}
1555
1556// selections, scroll behavior, was newest selection reversed
1557type SelectSyntaxNodeHistoryState = (
1558 Box<[Selection<usize>]>,
1559 SelectSyntaxNodeScrollBehavior,
1560 bool,
1561);
1562
1563#[derive(Default)]
1564struct SelectSyntaxNodeHistory {
1565 stack: Vec<SelectSyntaxNodeHistoryState>,
1566 // disable temporarily to allow changing selections without losing the stack
1567 pub disable_clearing: bool,
1568}
1569
1570impl SelectSyntaxNodeHistory {
1571 pub fn try_clear(&mut self) {
1572 if !self.disable_clearing {
1573 self.stack.clear();
1574 }
1575 }
1576
1577 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1578 self.stack.push(selection);
1579 }
1580
1581 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1582 self.stack.pop()
1583 }
1584}
1585
1586enum SelectSyntaxNodeScrollBehavior {
1587 CursorTop,
1588 FitSelection,
1589 CursorBottom,
1590}
1591
1592#[derive(Debug)]
1593pub(crate) struct NavigationData {
1594 cursor_anchor: Anchor,
1595 cursor_position: Point,
1596 scroll_anchor: ScrollAnchor,
1597 scroll_top_row: u32,
1598}
1599
1600#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1601pub enum GotoDefinitionKind {
1602 Symbol,
1603 Declaration,
1604 Type,
1605 Implementation,
1606}
1607
1608#[derive(Debug, Clone)]
1609enum InlayHintRefreshReason {
1610 ModifiersChanged(bool),
1611 Toggle(bool),
1612 SettingsChange(InlayHintSettings),
1613 NewLinesShown,
1614 BufferEdited(HashSet<Arc<Language>>),
1615 RefreshRequested,
1616 ExcerptsRemoved(Vec<ExcerptId>),
1617}
1618
1619impl InlayHintRefreshReason {
1620 fn description(&self) -> &'static str {
1621 match self {
1622 Self::ModifiersChanged(_) => "modifiers changed",
1623 Self::Toggle(_) => "toggle",
1624 Self::SettingsChange(_) => "settings change",
1625 Self::NewLinesShown => "new lines shown",
1626 Self::BufferEdited(_) => "buffer edited",
1627 Self::RefreshRequested => "refresh requested",
1628 Self::ExcerptsRemoved(_) => "excerpts removed",
1629 }
1630 }
1631}
1632
1633pub enum FormatTarget {
1634 Buffers(HashSet<Entity<Buffer>>),
1635 Ranges(Vec<Range<MultiBufferPoint>>),
1636}
1637
1638pub(crate) struct FocusedBlock {
1639 id: BlockId,
1640 focus_handle: WeakFocusHandle,
1641}
1642
1643#[derive(Clone)]
1644enum JumpData {
1645 MultiBufferRow {
1646 row: MultiBufferRow,
1647 line_offset_from_top: u32,
1648 },
1649 MultiBufferPoint {
1650 excerpt_id: ExcerptId,
1651 position: Point,
1652 anchor: text::Anchor,
1653 line_offset_from_top: u32,
1654 },
1655}
1656
1657pub enum MultibufferSelectionMode {
1658 First,
1659 All,
1660}
1661
1662#[derive(Clone, Copy, Debug, Default)]
1663pub struct RewrapOptions {
1664 pub override_language_settings: bool,
1665 pub preserve_existing_whitespace: bool,
1666}
1667
1668impl Editor {
1669 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1670 let buffer = cx.new(|cx| Buffer::local("", cx));
1671 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1672 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1673 }
1674
1675 pub fn multi_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::full(), buffer, None, window, cx)
1679 }
1680
1681 pub fn auto_height(
1682 min_lines: usize,
1683 max_lines: usize,
1684 window: &mut Window,
1685 cx: &mut Context<Self>,
1686 ) -> Self {
1687 let buffer = cx.new(|cx| Buffer::local("", cx));
1688 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1689 Self::new(
1690 EditorMode::AutoHeight {
1691 min_lines,
1692 max_lines: Some(max_lines),
1693 },
1694 buffer,
1695 None,
1696 window,
1697 cx,
1698 )
1699 }
1700
1701 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1702 /// The editor grows as tall as needed to fit its content.
1703 pub fn auto_height_unbounded(
1704 min_lines: usize,
1705 window: &mut Window,
1706 cx: &mut Context<Self>,
1707 ) -> Self {
1708 let buffer = cx.new(|cx| Buffer::local("", cx));
1709 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1710 Self::new(
1711 EditorMode::AutoHeight {
1712 min_lines,
1713 max_lines: None,
1714 },
1715 buffer,
1716 None,
1717 window,
1718 cx,
1719 )
1720 }
1721
1722 pub fn for_buffer(
1723 buffer: Entity<Buffer>,
1724 project: Option<Entity<Project>>,
1725 window: &mut Window,
1726 cx: &mut Context<Self>,
1727 ) -> Self {
1728 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1729 Self::new(EditorMode::full(), buffer, project, window, cx)
1730 }
1731
1732 pub fn for_multibuffer(
1733 buffer: Entity<MultiBuffer>,
1734 project: Option<Entity<Project>>,
1735 window: &mut Window,
1736 cx: &mut Context<Self>,
1737 ) -> Self {
1738 Self::new(EditorMode::full(), buffer, project, window, cx)
1739 }
1740
1741 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1742 let mut clone = Self::new(
1743 self.mode.clone(),
1744 self.buffer.clone(),
1745 self.project.clone(),
1746 window,
1747 cx,
1748 );
1749 self.display_map.update(cx, |display_map, cx| {
1750 let snapshot = display_map.snapshot(cx);
1751 clone.display_map.update(cx, |display_map, cx| {
1752 display_map.set_state(&snapshot, cx);
1753 });
1754 });
1755 clone.folds_did_change(cx);
1756 clone.selections.clone_state(&self.selections);
1757 clone.scroll_manager.clone_state(&self.scroll_manager);
1758 clone.searchable = self.searchable;
1759 clone.read_only = self.read_only;
1760 clone
1761 }
1762
1763 pub fn new(
1764 mode: EditorMode,
1765 buffer: Entity<MultiBuffer>,
1766 project: Option<Entity<Project>>,
1767 window: &mut Window,
1768 cx: &mut Context<Self>,
1769 ) -> Self {
1770 Editor::new_internal(mode, buffer, project, None, window, cx)
1771 }
1772
1773 fn new_internal(
1774 mode: EditorMode,
1775 buffer: Entity<MultiBuffer>,
1776 project: Option<Entity<Project>>,
1777 display_map: Option<Entity<DisplayMap>>,
1778 window: &mut Window,
1779 cx: &mut Context<Self>,
1780 ) -> Self {
1781 debug_assert!(
1782 display_map.is_none() || mode.is_minimap(),
1783 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1784 );
1785
1786 let full_mode = mode.is_full();
1787 let is_minimap = mode.is_minimap();
1788 let diagnostics_max_severity = if full_mode {
1789 EditorSettings::get_global(cx)
1790 .diagnostics_max_severity
1791 .unwrap_or(DiagnosticSeverity::Hint)
1792 } else {
1793 DiagnosticSeverity::Off
1794 };
1795 let style = window.text_style();
1796 let font_size = style.font_size.to_pixels(window.rem_size());
1797 let editor = cx.entity().downgrade();
1798 let fold_placeholder = FoldPlaceholder {
1799 constrain_width: true,
1800 render: Arc::new(move |fold_id, fold_range, cx| {
1801 let editor = editor.clone();
1802 div()
1803 .id(fold_id)
1804 .bg(cx.theme().colors().ghost_element_background)
1805 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1806 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1807 .rounded_xs()
1808 .size_full()
1809 .cursor_pointer()
1810 .child("⋯")
1811 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1812 .on_click(move |_, _window, cx| {
1813 editor
1814 .update(cx, |editor, cx| {
1815 editor.unfold_ranges(
1816 &[fold_range.start..fold_range.end],
1817 true,
1818 false,
1819 cx,
1820 );
1821 cx.stop_propagation();
1822 })
1823 .ok();
1824 })
1825 .into_any()
1826 }),
1827 merge_adjacent: true,
1828 ..FoldPlaceholder::default()
1829 };
1830 let display_map = display_map.unwrap_or_else(|| {
1831 cx.new(|cx| {
1832 DisplayMap::new(
1833 buffer.clone(),
1834 style.font(),
1835 font_size,
1836 None,
1837 FILE_HEADER_HEIGHT,
1838 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1839 fold_placeholder,
1840 diagnostics_max_severity,
1841 cx,
1842 )
1843 })
1844 });
1845
1846 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1847
1848 let blink_manager = cx.new(|cx| {
1849 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1850 if is_minimap {
1851 blink_manager.disable(cx);
1852 }
1853 blink_manager
1854 });
1855
1856 let soft_wrap_mode_override =
1857 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1858
1859 let mut project_subscriptions = Vec::new();
1860 if full_mode && let Some(project) = project.as_ref() {
1861 project_subscriptions.push(cx.subscribe_in(
1862 project,
1863 window,
1864 |editor, _, event, window, cx| match event {
1865 project::Event::RefreshCodeLens => {
1866 // we always query lens with actions, without storing them, always refreshing them
1867 }
1868 project::Event::RefreshInlayHints => {
1869 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1870 }
1871 project::Event::LanguageServerAdded(..)
1872 | project::Event::LanguageServerRemoved(..) => {
1873 if editor.tasks_update_task.is_none() {
1874 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1875 }
1876 }
1877 project::Event::SnippetEdit(id, snippet_edits) => {
1878 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1879 let focus_handle = editor.focus_handle(cx);
1880 if focus_handle.is_focused(window) {
1881 let snapshot = buffer.read(cx).snapshot();
1882 for (range, snippet) in snippet_edits {
1883 let editor_range =
1884 language::range_from_lsp(*range).to_offset(&snapshot);
1885 editor
1886 .insert_snippet(
1887 &[editor_range],
1888 snippet.clone(),
1889 window,
1890 cx,
1891 )
1892 .ok();
1893 }
1894 }
1895 }
1896 }
1897 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1898 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1899 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1900 }
1901 }
1902
1903 project::Event::EntryRenamed(transaction) => {
1904 let Some(workspace) = editor.workspace() else {
1905 return;
1906 };
1907 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1908 else {
1909 return;
1910 };
1911 if active_editor.entity_id() == cx.entity_id() {
1912 let edited_buffers_already_open = {
1913 let other_editors: Vec<Entity<Editor>> = workspace
1914 .read(cx)
1915 .panes()
1916 .iter()
1917 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1918 .filter(|editor| editor.entity_id() != cx.entity_id())
1919 .collect();
1920
1921 transaction.0.keys().all(|buffer| {
1922 other_editors.iter().any(|editor| {
1923 let multi_buffer = editor.read(cx).buffer();
1924 multi_buffer.read(cx).is_singleton()
1925 && multi_buffer.read(cx).as_singleton().map_or(
1926 false,
1927 |singleton| {
1928 singleton.entity_id() == buffer.entity_id()
1929 },
1930 )
1931 })
1932 })
1933 };
1934
1935 if !edited_buffers_already_open {
1936 let workspace = workspace.downgrade();
1937 let transaction = transaction.clone();
1938 cx.defer_in(window, move |_, window, cx| {
1939 cx.spawn_in(window, async move |editor, cx| {
1940 Self::open_project_transaction(
1941 &editor,
1942 workspace,
1943 transaction,
1944 "Rename".to_string(),
1945 cx,
1946 )
1947 .await
1948 .ok()
1949 })
1950 .detach();
1951 });
1952 }
1953 }
1954 }
1955
1956 _ => {}
1957 },
1958 ));
1959 if let Some(task_inventory) = project
1960 .read(cx)
1961 .task_store()
1962 .read(cx)
1963 .task_inventory()
1964 .cloned()
1965 {
1966 project_subscriptions.push(cx.observe_in(
1967 &task_inventory,
1968 window,
1969 |editor, _, window, cx| {
1970 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1971 },
1972 ));
1973 };
1974
1975 project_subscriptions.push(cx.subscribe_in(
1976 &project.read(cx).breakpoint_store(),
1977 window,
1978 |editor, _, event, window, cx| match event {
1979 BreakpointStoreEvent::ClearDebugLines => {
1980 editor.clear_row_highlights::<ActiveDebugLine>();
1981 editor.refresh_inline_values(cx);
1982 }
1983 BreakpointStoreEvent::SetDebugLine => {
1984 if editor.go_to_active_debug_line(window, cx) {
1985 cx.stop_propagation();
1986 }
1987
1988 editor.refresh_inline_values(cx);
1989 }
1990 _ => {}
1991 },
1992 ));
1993 let git_store = project.read(cx).git_store().clone();
1994 let project = project.clone();
1995 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1996 if let GitStoreEvent::RepositoryUpdated(
1997 _,
1998 RepositoryEvent::Updated {
1999 new_instance: true, ..
2000 },
2001 _,
2002 ) = event
2003 {
2004 this.load_diff_task = Some(
2005 update_uncommitted_diff_for_buffer(
2006 cx.entity(),
2007 &project,
2008 this.buffer.read(cx).all_buffers(),
2009 this.buffer.clone(),
2010 cx,
2011 )
2012 .shared(),
2013 );
2014 }
2015 }));
2016 }
2017
2018 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2019
2020 let inlay_hint_settings =
2021 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2022 let focus_handle = cx.focus_handle();
2023 if !is_minimap {
2024 cx.on_focus(&focus_handle, window, Self::handle_focus)
2025 .detach();
2026 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2027 .detach();
2028 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2029 .detach();
2030 cx.on_blur(&focus_handle, window, Self::handle_blur)
2031 .detach();
2032 cx.observe_pending_input(window, Self::observe_pending_input)
2033 .detach();
2034 }
2035
2036 let show_indent_guides =
2037 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2038 Some(false)
2039 } else {
2040 None
2041 };
2042
2043 let breakpoint_store = match (&mode, project.as_ref()) {
2044 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2045 _ => None,
2046 };
2047
2048 let mut code_action_providers = Vec::new();
2049 let mut load_uncommitted_diff = None;
2050 if let Some(project) = project.clone() {
2051 load_uncommitted_diff = Some(
2052 update_uncommitted_diff_for_buffer(
2053 cx.entity(),
2054 &project,
2055 buffer.read(cx).all_buffers(),
2056 buffer.clone(),
2057 cx,
2058 )
2059 .shared(),
2060 );
2061 code_action_providers.push(Rc::new(project) as Rc<_>);
2062 }
2063
2064 let mut editor = Self {
2065 focus_handle,
2066 show_cursor_when_unfocused: false,
2067 last_focused_descendant: None,
2068 buffer: buffer.clone(),
2069 display_map: display_map.clone(),
2070 selections,
2071 scroll_manager: ScrollManager::new(cx),
2072 columnar_selection_state: None,
2073 add_selections_state: None,
2074 select_next_state: None,
2075 select_prev_state: None,
2076 selection_history: SelectionHistory::default(),
2077 defer_selection_effects: false,
2078 deferred_selection_effects_state: None,
2079 autoclose_regions: Vec::new(),
2080 snippet_stack: InvalidationStack::default(),
2081 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2082 ime_transaction: None,
2083 active_diagnostics: ActiveDiagnostic::None,
2084 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2085 inline_diagnostics_update: Task::ready(()),
2086 inline_diagnostics: Vec::new(),
2087 soft_wrap_mode_override,
2088 diagnostics_max_severity,
2089 hard_wrap: None,
2090 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2091 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2092 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2093 project,
2094 blink_manager: blink_manager.clone(),
2095 show_local_selections: true,
2096 show_scrollbars: ScrollbarAxes {
2097 horizontal: full_mode,
2098 vertical: full_mode,
2099 },
2100 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2101 offset_content: !matches!(mode, EditorMode::SingleLine),
2102 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2103 show_gutter: full_mode,
2104 show_line_numbers: (!full_mode).then_some(false),
2105 use_relative_line_numbers: None,
2106 disable_expand_excerpt_buttons: !full_mode,
2107 show_git_diff_gutter: None,
2108 show_code_actions: None,
2109 show_runnables: None,
2110 show_breakpoints: None,
2111 show_wrap_guides: None,
2112 show_indent_guides,
2113 placeholder_text: None,
2114 highlight_order: 0,
2115 highlighted_rows: HashMap::default(),
2116 background_highlights: TreeMap::default(),
2117 gutter_highlights: TreeMap::default(),
2118 scrollbar_marker_state: ScrollbarMarkerState::default(),
2119 active_indent_guides_state: ActiveIndentGuidesState::default(),
2120 nav_history: None,
2121 context_menu: RefCell::new(None),
2122 context_menu_options: None,
2123 mouse_context_menu: None,
2124 completion_tasks: Vec::new(),
2125 inline_blame_popover: None,
2126 inline_blame_popover_show_task: None,
2127 signature_help_state: SignatureHelpState::default(),
2128 auto_signature_help: None,
2129 find_all_references_task_sources: Vec::new(),
2130 next_completion_id: 0,
2131 next_inlay_id: 0,
2132 code_action_providers,
2133 available_code_actions: None,
2134 code_actions_task: None,
2135 quick_selection_highlight_task: None,
2136 debounced_selection_highlight_task: None,
2137 document_highlights_task: None,
2138 linked_editing_range_task: None,
2139 pending_rename: None,
2140 searchable: !is_minimap,
2141 cursor_shape: EditorSettings::get_global(cx)
2142 .cursor_shape
2143 .unwrap_or_default(),
2144 current_line_highlight: None,
2145 autoindent_mode: Some(AutoindentMode::EachLine),
2146 collapse_matches: false,
2147 workspace: None,
2148 input_enabled: !is_minimap,
2149 use_modal_editing: full_mode,
2150 read_only: is_minimap,
2151 use_autoclose: true,
2152 use_auto_surround: true,
2153 auto_replace_emoji_shortcode: false,
2154 jsx_tag_auto_close_enabled_in_any_buffer: false,
2155 leader_id: None,
2156 remote_id: None,
2157 hover_state: HoverState::default(),
2158 pending_mouse_down: None,
2159 hovered_link_state: None,
2160 edit_prediction_provider: None,
2161 active_edit_prediction: None,
2162 stale_edit_prediction_in_menu: None,
2163 edit_prediction_preview: EditPredictionPreview::Inactive {
2164 released_too_fast: false,
2165 },
2166 inline_diagnostics_enabled: full_mode,
2167 diagnostics_enabled: full_mode,
2168 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2169 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2170 gutter_hovered: false,
2171 pixel_position_of_newest_cursor: None,
2172 last_bounds: None,
2173 last_position_map: None,
2174 expect_bounds_change: None,
2175 gutter_dimensions: GutterDimensions::default(),
2176 style: None,
2177 show_cursor_names: false,
2178 hovered_cursors: HashMap::default(),
2179 next_editor_action_id: EditorActionId::default(),
2180 editor_actions: Rc::default(),
2181 edit_predictions_hidden_for_vim_mode: false,
2182 show_edit_predictions_override: None,
2183 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2184 edit_prediction_settings: EditPredictionSettings::Disabled,
2185 edit_prediction_indent_conflict: false,
2186 edit_prediction_requires_modifier_in_indent_conflict: true,
2187 custom_context_menu: None,
2188 show_git_blame_gutter: false,
2189 show_git_blame_inline: false,
2190 show_selection_menu: None,
2191 show_git_blame_inline_delay_task: None,
2192 git_blame_inline_enabled: full_mode
2193 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2194 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2195 serialize_dirty_buffers: !is_minimap
2196 && ProjectSettings::get_global(cx)
2197 .session
2198 .restore_unsaved_buffers,
2199 blame: None,
2200 blame_subscription: None,
2201 tasks: BTreeMap::default(),
2202
2203 breakpoint_store,
2204 gutter_breakpoint_indicator: (None, None),
2205 hovered_diff_hunk_row: None,
2206 _subscriptions: (!is_minimap)
2207 .then(|| {
2208 vec![
2209 cx.observe(&buffer, Self::on_buffer_changed),
2210 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2211 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2212 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2213 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2214 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2215 cx.observe_window_activation(window, |editor, window, cx| {
2216 let active = window.is_window_active();
2217 editor.blink_manager.update(cx, |blink_manager, cx| {
2218 if active {
2219 blink_manager.enable(cx);
2220 } else {
2221 blink_manager.disable(cx);
2222 }
2223 });
2224 if active {
2225 editor.show_mouse_cursor(cx);
2226 }
2227 }),
2228 ]
2229 })
2230 .unwrap_or_default(),
2231 tasks_update_task: None,
2232 pull_diagnostics_task: Task::ready(()),
2233 colors: None,
2234 next_color_inlay_id: 0,
2235 linked_edit_ranges: Default::default(),
2236 in_project_search: false,
2237 previous_search_ranges: None,
2238 breadcrumb_header: None,
2239 focused_block: None,
2240 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2241 addons: HashMap::default(),
2242 registered_buffers: HashMap::default(),
2243 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2244 selection_mark_mode: false,
2245 toggle_fold_multiple_buffers: Task::ready(()),
2246 serialize_selections: Task::ready(()),
2247 serialize_folds: Task::ready(()),
2248 text_style_refinement: None,
2249 load_diff_task: load_uncommitted_diff,
2250 temporary_diff_override: false,
2251 mouse_cursor_hidden: false,
2252 minimap: None,
2253 hide_mouse_mode: EditorSettings::get_global(cx)
2254 .hide_mouse
2255 .unwrap_or_default(),
2256 change_list: ChangeList::new(),
2257 mode,
2258 selection_drag_state: SelectionDragState::None,
2259 folding_newlines: Task::ready(()),
2260 };
2261
2262 if is_minimap {
2263 return editor;
2264 }
2265
2266 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2267 editor
2268 ._subscriptions
2269 .push(cx.observe(breakpoints, |_, _, cx| {
2270 cx.notify();
2271 }));
2272 }
2273 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2274 editor._subscriptions.extend(project_subscriptions);
2275
2276 editor._subscriptions.push(cx.subscribe_in(
2277 &cx.entity(),
2278 window,
2279 |editor, _, e: &EditorEvent, window, cx| match e {
2280 EditorEvent::ScrollPositionChanged { local, .. } => {
2281 if *local {
2282 let new_anchor = editor.scroll_manager.anchor();
2283 let snapshot = editor.snapshot(window, cx);
2284 editor.update_restoration_data(cx, move |data| {
2285 data.scroll_position = (
2286 new_anchor.top_row(&snapshot.buffer_snapshot),
2287 new_anchor.offset,
2288 );
2289 });
2290 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2291 editor.inline_blame_popover.take();
2292 }
2293 }
2294 EditorEvent::Edited { .. } => {
2295 if !vim_enabled(cx) {
2296 let (map, selections) = editor.selections.all_adjusted_display(cx);
2297 let pop_state = editor
2298 .change_list
2299 .last()
2300 .map(|previous| {
2301 previous.len() == selections.len()
2302 && previous.iter().enumerate().all(|(ix, p)| {
2303 p.to_display_point(&map).row()
2304 == selections[ix].head().row()
2305 })
2306 })
2307 .unwrap_or(false);
2308 let new_positions = selections
2309 .into_iter()
2310 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2311 .collect();
2312 editor
2313 .change_list
2314 .push_to_change_list(pop_state, new_positions);
2315 }
2316 }
2317 _ => (),
2318 },
2319 ));
2320
2321 if let Some(dap_store) = editor
2322 .project
2323 .as_ref()
2324 .map(|project| project.read(cx).dap_store())
2325 {
2326 let weak_editor = cx.weak_entity();
2327
2328 editor
2329 ._subscriptions
2330 .push(
2331 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2332 let session_entity = cx.entity();
2333 weak_editor
2334 .update(cx, |editor, cx| {
2335 editor._subscriptions.push(
2336 cx.subscribe(&session_entity, Self::on_debug_session_event),
2337 );
2338 })
2339 .ok();
2340 }),
2341 );
2342
2343 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2344 editor
2345 ._subscriptions
2346 .push(cx.subscribe(&session, Self::on_debug_session_event));
2347 }
2348 }
2349
2350 // skip adding the initial selection to selection history
2351 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2352 editor.end_selection(window, cx);
2353 editor.selection_history.mode = SelectionHistoryMode::Normal;
2354
2355 editor.scroll_manager.show_scrollbars(window, cx);
2356 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2357
2358 if full_mode {
2359 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2360 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2361
2362 if editor.git_blame_inline_enabled {
2363 editor.start_git_blame_inline(false, window, cx);
2364 }
2365
2366 editor.go_to_active_debug_line(window, cx);
2367
2368 if let Some(buffer) = buffer.read(cx).as_singleton()
2369 && let Some(project) = editor.project()
2370 {
2371 let handle = project.update(cx, |project, cx| {
2372 project.register_buffer_with_language_servers(&buffer, cx)
2373 });
2374 editor
2375 .registered_buffers
2376 .insert(buffer.read(cx).remote_id(), handle);
2377 }
2378
2379 editor.minimap =
2380 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2381 editor.colors = Some(LspColorData::new(cx));
2382 editor.update_lsp_data(false, None, window, cx);
2383 }
2384
2385 if editor.mode.is_full() {
2386 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2387 }
2388
2389 editor
2390 }
2391
2392 pub fn deploy_mouse_context_menu(
2393 &mut self,
2394 position: gpui::Point<Pixels>,
2395 context_menu: Entity<ContextMenu>,
2396 window: &mut Window,
2397 cx: &mut Context<Self>,
2398 ) {
2399 self.mouse_context_menu = Some(MouseContextMenu::new(
2400 self,
2401 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2402 context_menu,
2403 window,
2404 cx,
2405 ));
2406 }
2407
2408 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2409 self.mouse_context_menu
2410 .as_ref()
2411 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2412 }
2413
2414 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2415 if self
2416 .selections
2417 .pending
2418 .as_ref()
2419 .is_some_and(|pending_selection| {
2420 let snapshot = self.buffer().read(cx).snapshot(cx);
2421 pending_selection
2422 .selection
2423 .range()
2424 .includes(range, &snapshot)
2425 })
2426 {
2427 return true;
2428 }
2429
2430 self.selections
2431 .disjoint_in_range::<usize>(range.clone(), cx)
2432 .into_iter()
2433 .any(|selection| {
2434 // This is needed to cover a corner case, if we just check for an existing
2435 // selection in the fold range, having a cursor at the start of the fold
2436 // marks it as selected. Non-empty selections don't cause this.
2437 let length = selection.end - selection.start;
2438 length > 0
2439 })
2440 }
2441
2442 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2443 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2444 }
2445
2446 fn key_context_internal(
2447 &self,
2448 has_active_edit_prediction: bool,
2449 window: &Window,
2450 cx: &App,
2451 ) -> KeyContext {
2452 let mut key_context = KeyContext::new_with_defaults();
2453 key_context.add("Editor");
2454 let mode = match self.mode {
2455 EditorMode::SingleLine => "single_line",
2456 EditorMode::AutoHeight { .. } => "auto_height",
2457 EditorMode::Minimap { .. } => "minimap",
2458 EditorMode::Full { .. } => "full",
2459 };
2460
2461 if EditorSettings::jupyter_enabled(cx) {
2462 key_context.add("jupyter");
2463 }
2464
2465 key_context.set("mode", mode);
2466 if self.pending_rename.is_some() {
2467 key_context.add("renaming");
2468 }
2469
2470 match self.context_menu.borrow().as_ref() {
2471 Some(CodeContextMenu::Completions(menu)) => {
2472 if menu.visible() {
2473 key_context.add("menu");
2474 key_context.add("showing_completions");
2475 }
2476 }
2477 Some(CodeContextMenu::CodeActions(menu)) => {
2478 if menu.visible() {
2479 key_context.add("menu");
2480 key_context.add("showing_code_actions")
2481 }
2482 }
2483 None => {}
2484 }
2485
2486 if self.signature_help_state.has_multiple_signatures() {
2487 key_context.add("showing_signature_help");
2488 }
2489
2490 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2491 if !self.focus_handle(cx).contains_focused(window, cx)
2492 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2493 {
2494 for addon in self.addons.values() {
2495 addon.extend_key_context(&mut key_context, cx)
2496 }
2497 }
2498
2499 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2500 if let Some(extension) = singleton_buffer
2501 .read(cx)
2502 .file()
2503 .and_then(|file| file.path().extension()?.to_str())
2504 {
2505 key_context.set("extension", extension.to_string());
2506 }
2507 } else {
2508 key_context.add("multibuffer");
2509 }
2510
2511 if has_active_edit_prediction {
2512 if self.edit_prediction_in_conflict() {
2513 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2514 } else {
2515 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2516 key_context.add("copilot_suggestion");
2517 }
2518 }
2519
2520 if self.selection_mark_mode {
2521 key_context.add("selection_mode");
2522 }
2523
2524 key_context
2525 }
2526
2527 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2528 if self.mouse_cursor_hidden {
2529 self.mouse_cursor_hidden = false;
2530 cx.notify();
2531 }
2532 }
2533
2534 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2535 let hide_mouse_cursor = match origin {
2536 HideMouseCursorOrigin::TypingAction => {
2537 matches!(
2538 self.hide_mouse_mode,
2539 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2540 )
2541 }
2542 HideMouseCursorOrigin::MovementAction => {
2543 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2544 }
2545 };
2546 if self.mouse_cursor_hidden != hide_mouse_cursor {
2547 self.mouse_cursor_hidden = hide_mouse_cursor;
2548 cx.notify();
2549 }
2550 }
2551
2552 pub fn edit_prediction_in_conflict(&self) -> bool {
2553 if !self.show_edit_predictions_in_menu() {
2554 return false;
2555 }
2556
2557 let showing_completions = self
2558 .context_menu
2559 .borrow()
2560 .as_ref()
2561 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2562
2563 showing_completions
2564 || self.edit_prediction_requires_modifier()
2565 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2566 // bindings to insert tab characters.
2567 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2568 }
2569
2570 pub fn accept_edit_prediction_keybind(
2571 &self,
2572 accept_partial: bool,
2573 window: &Window,
2574 cx: &App,
2575 ) -> AcceptEditPredictionBinding {
2576 let key_context = self.key_context_internal(true, window, cx);
2577 let in_conflict = self.edit_prediction_in_conflict();
2578
2579 let bindings = if accept_partial {
2580 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2581 } else {
2582 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2583 };
2584
2585 // TODO: if the binding contains multiple keystrokes, display all of them, not
2586 // just the first one.
2587 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2588 !in_conflict
2589 || binding
2590 .keystrokes()
2591 .first()
2592 .is_some_and(|keystroke| keystroke.display_modifiers.modified())
2593 }))
2594 }
2595
2596 pub fn new_file(
2597 workspace: &mut Workspace,
2598 _: &workspace::NewFile,
2599 window: &mut Window,
2600 cx: &mut Context<Workspace>,
2601 ) {
2602 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2603 "Failed to create buffer",
2604 window,
2605 cx,
2606 |e, _, _| match e.error_code() {
2607 ErrorCode::RemoteUpgradeRequired => Some(format!(
2608 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2609 e.error_tag("required").unwrap_or("the latest version")
2610 )),
2611 _ => None,
2612 },
2613 );
2614 }
2615
2616 pub fn new_in_workspace(
2617 workspace: &mut Workspace,
2618 window: &mut Window,
2619 cx: &mut Context<Workspace>,
2620 ) -> Task<Result<Entity<Editor>>> {
2621 let project = workspace.project().clone();
2622 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2623
2624 cx.spawn_in(window, async move |workspace, cx| {
2625 let buffer = create.await?;
2626 workspace.update_in(cx, |workspace, window, cx| {
2627 let editor =
2628 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2629 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2630 editor
2631 })
2632 })
2633 }
2634
2635 fn new_file_vertical(
2636 workspace: &mut Workspace,
2637 _: &workspace::NewFileSplitVertical,
2638 window: &mut Window,
2639 cx: &mut Context<Workspace>,
2640 ) {
2641 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2642 }
2643
2644 fn new_file_horizontal(
2645 workspace: &mut Workspace,
2646 _: &workspace::NewFileSplitHorizontal,
2647 window: &mut Window,
2648 cx: &mut Context<Workspace>,
2649 ) {
2650 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2651 }
2652
2653 fn new_file_in_direction(
2654 workspace: &mut Workspace,
2655 direction: SplitDirection,
2656 window: &mut Window,
2657 cx: &mut Context<Workspace>,
2658 ) {
2659 let project = workspace.project().clone();
2660 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2661
2662 cx.spawn_in(window, async move |workspace, cx| {
2663 let buffer = create.await?;
2664 workspace.update_in(cx, move |workspace, window, cx| {
2665 workspace.split_item(
2666 direction,
2667 Box::new(
2668 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2669 ),
2670 window,
2671 cx,
2672 )
2673 })?;
2674 anyhow::Ok(())
2675 })
2676 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2677 match e.error_code() {
2678 ErrorCode::RemoteUpgradeRequired => Some(format!(
2679 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2680 e.error_tag("required").unwrap_or("the latest version")
2681 )),
2682 _ => None,
2683 }
2684 });
2685 }
2686
2687 pub fn leader_id(&self) -> Option<CollaboratorId> {
2688 self.leader_id
2689 }
2690
2691 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2692 &self.buffer
2693 }
2694
2695 pub fn project(&self) -> Option<&Entity<Project>> {
2696 self.project.as_ref()
2697 }
2698
2699 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2700 self.workspace.as_ref()?.0.upgrade()
2701 }
2702
2703 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2704 self.buffer().read(cx).title(cx)
2705 }
2706
2707 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2708 let git_blame_gutter_max_author_length = self
2709 .render_git_blame_gutter(cx)
2710 .then(|| {
2711 if let Some(blame) = self.blame.as_ref() {
2712 let max_author_length =
2713 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2714 Some(max_author_length)
2715 } else {
2716 None
2717 }
2718 })
2719 .flatten();
2720
2721 EditorSnapshot {
2722 mode: self.mode.clone(),
2723 show_gutter: self.show_gutter,
2724 show_line_numbers: self.show_line_numbers,
2725 show_git_diff_gutter: self.show_git_diff_gutter,
2726 show_code_actions: self.show_code_actions,
2727 show_runnables: self.show_runnables,
2728 show_breakpoints: self.show_breakpoints,
2729 git_blame_gutter_max_author_length,
2730 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2731 scroll_anchor: self.scroll_manager.anchor(),
2732 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2733 placeholder_text: self.placeholder_text.clone(),
2734 is_focused: self.focus_handle.is_focused(window),
2735 current_line_highlight: self
2736 .current_line_highlight
2737 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2738 gutter_hovered: self.gutter_hovered,
2739 }
2740 }
2741
2742 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2743 self.buffer.read(cx).language_at(point, cx)
2744 }
2745
2746 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2747 self.buffer.read(cx).read(cx).file_at(point).cloned()
2748 }
2749
2750 pub fn active_excerpt(
2751 &self,
2752 cx: &App,
2753 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2754 self.buffer
2755 .read(cx)
2756 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2757 }
2758
2759 pub fn mode(&self) -> &EditorMode {
2760 &self.mode
2761 }
2762
2763 pub fn set_mode(&mut self, mode: EditorMode) {
2764 self.mode = mode;
2765 }
2766
2767 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2768 self.collaboration_hub.as_deref()
2769 }
2770
2771 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2772 self.collaboration_hub = Some(hub);
2773 }
2774
2775 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2776 self.in_project_search = in_project_search;
2777 }
2778
2779 pub fn set_custom_context_menu(
2780 &mut self,
2781 f: impl 'static
2782 + Fn(
2783 &mut Self,
2784 DisplayPoint,
2785 &mut Window,
2786 &mut Context<Self>,
2787 ) -> Option<Entity<ui::ContextMenu>>,
2788 ) {
2789 self.custom_context_menu = Some(Box::new(f))
2790 }
2791
2792 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2793 self.completion_provider = provider;
2794 }
2795
2796 #[cfg(any(test, feature = "test-support"))]
2797 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2798 self.completion_provider.clone()
2799 }
2800
2801 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2802 self.semantics_provider.clone()
2803 }
2804
2805 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2806 self.semantics_provider = provider;
2807 }
2808
2809 pub fn set_edit_prediction_provider<T>(
2810 &mut self,
2811 provider: Option<Entity<T>>,
2812 window: &mut Window,
2813 cx: &mut Context<Self>,
2814 ) where
2815 T: EditPredictionProvider,
2816 {
2817 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2818 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2819 if this.focus_handle.is_focused(window) {
2820 this.update_visible_edit_prediction(window, cx);
2821 }
2822 }),
2823 provider: Arc::new(provider),
2824 });
2825 self.update_edit_prediction_settings(cx);
2826 self.refresh_edit_prediction(false, false, window, cx);
2827 }
2828
2829 pub fn placeholder_text(&self) -> Option<&str> {
2830 self.placeholder_text.as_deref()
2831 }
2832
2833 pub fn set_placeholder_text(
2834 &mut self,
2835 placeholder_text: impl Into<Arc<str>>,
2836 cx: &mut Context<Self>,
2837 ) {
2838 let placeholder_text = Some(placeholder_text.into());
2839 if self.placeholder_text != placeholder_text {
2840 self.placeholder_text = placeholder_text;
2841 cx.notify();
2842 }
2843 }
2844
2845 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2846 self.cursor_shape = cursor_shape;
2847
2848 // Disrupt blink for immediate user feedback that the cursor shape has changed
2849 self.blink_manager.update(cx, BlinkManager::show_cursor);
2850
2851 cx.notify();
2852 }
2853
2854 pub fn set_current_line_highlight(
2855 &mut self,
2856 current_line_highlight: Option<CurrentLineHighlight>,
2857 ) {
2858 self.current_line_highlight = current_line_highlight;
2859 }
2860
2861 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2862 self.collapse_matches = collapse_matches;
2863 }
2864
2865 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2866 let buffers = self.buffer.read(cx).all_buffers();
2867 let Some(project) = self.project.as_ref() else {
2868 return;
2869 };
2870 project.update(cx, |project, cx| {
2871 for buffer in buffers {
2872 self.registered_buffers
2873 .entry(buffer.read(cx).remote_id())
2874 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2875 }
2876 })
2877 }
2878
2879 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2880 if self.collapse_matches {
2881 return range.start..range.start;
2882 }
2883 range.clone()
2884 }
2885
2886 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2887 if self.display_map.read(cx).clip_at_line_ends != clip {
2888 self.display_map
2889 .update(cx, |map, _| map.clip_at_line_ends = clip);
2890 }
2891 }
2892
2893 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2894 self.input_enabled = input_enabled;
2895 }
2896
2897 pub fn set_edit_predictions_hidden_for_vim_mode(
2898 &mut self,
2899 hidden: bool,
2900 window: &mut Window,
2901 cx: &mut Context<Self>,
2902 ) {
2903 if hidden != self.edit_predictions_hidden_for_vim_mode {
2904 self.edit_predictions_hidden_for_vim_mode = hidden;
2905 if hidden {
2906 self.update_visible_edit_prediction(window, cx);
2907 } else {
2908 self.refresh_edit_prediction(true, false, window, cx);
2909 }
2910 }
2911 }
2912
2913 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2914 self.menu_edit_predictions_policy = value;
2915 }
2916
2917 pub fn set_autoindent(&mut self, autoindent: bool) {
2918 if autoindent {
2919 self.autoindent_mode = Some(AutoindentMode::EachLine);
2920 } else {
2921 self.autoindent_mode = None;
2922 }
2923 }
2924
2925 pub fn read_only(&self, cx: &App) -> bool {
2926 self.read_only || self.buffer.read(cx).read_only()
2927 }
2928
2929 pub fn set_read_only(&mut self, read_only: bool) {
2930 self.read_only = read_only;
2931 }
2932
2933 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2934 self.use_autoclose = autoclose;
2935 }
2936
2937 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2938 self.use_auto_surround = auto_surround;
2939 }
2940
2941 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2942 self.auto_replace_emoji_shortcode = auto_replace;
2943 }
2944
2945 pub fn toggle_edit_predictions(
2946 &mut self,
2947 _: &ToggleEditPrediction,
2948 window: &mut Window,
2949 cx: &mut Context<Self>,
2950 ) {
2951 if self.show_edit_predictions_override.is_some() {
2952 self.set_show_edit_predictions(None, window, cx);
2953 } else {
2954 let show_edit_predictions = !self.edit_predictions_enabled();
2955 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2956 }
2957 }
2958
2959 pub fn set_show_edit_predictions(
2960 &mut self,
2961 show_edit_predictions: Option<bool>,
2962 window: &mut Window,
2963 cx: &mut Context<Self>,
2964 ) {
2965 self.show_edit_predictions_override = show_edit_predictions;
2966 self.update_edit_prediction_settings(cx);
2967
2968 if let Some(false) = show_edit_predictions {
2969 self.discard_edit_prediction(false, cx);
2970 } else {
2971 self.refresh_edit_prediction(false, true, window, cx);
2972 }
2973 }
2974
2975 fn edit_predictions_disabled_in_scope(
2976 &self,
2977 buffer: &Entity<Buffer>,
2978 buffer_position: language::Anchor,
2979 cx: &App,
2980 ) -> bool {
2981 let snapshot = buffer.read(cx).snapshot();
2982 let settings = snapshot.settings_at(buffer_position, cx);
2983
2984 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2985 return false;
2986 };
2987
2988 scope.override_name().is_some_and(|scope_name| {
2989 settings
2990 .edit_predictions_disabled_in
2991 .iter()
2992 .any(|s| s == scope_name)
2993 })
2994 }
2995
2996 pub fn set_use_modal_editing(&mut self, to: bool) {
2997 self.use_modal_editing = to;
2998 }
2999
3000 pub fn use_modal_editing(&self) -> bool {
3001 self.use_modal_editing
3002 }
3003
3004 fn selections_did_change(
3005 &mut self,
3006 local: bool,
3007 old_cursor_position: &Anchor,
3008 effects: SelectionEffects,
3009 window: &mut Window,
3010 cx: &mut Context<Self>,
3011 ) {
3012 window.invalidate_character_coordinates();
3013
3014 // Copy selections to primary selection buffer
3015 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3016 if local {
3017 let selections = self.selections.all::<usize>(cx);
3018 let buffer_handle = self.buffer.read(cx).read(cx);
3019
3020 let mut text = String::new();
3021 for (index, selection) in selections.iter().enumerate() {
3022 let text_for_selection = buffer_handle
3023 .text_for_range(selection.start..selection.end)
3024 .collect::<String>();
3025
3026 text.push_str(&text_for_selection);
3027 if index != selections.len() - 1 {
3028 text.push('\n');
3029 }
3030 }
3031
3032 if !text.is_empty() {
3033 cx.write_to_primary(ClipboardItem::new_string(text));
3034 }
3035 }
3036
3037 let selection_anchors = self.selections.disjoint_anchors();
3038
3039 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3040 self.buffer.update(cx, |buffer, cx| {
3041 buffer.set_active_selections(
3042 &selection_anchors,
3043 self.selections.line_mode,
3044 self.cursor_shape,
3045 cx,
3046 )
3047 });
3048 }
3049 let display_map = self
3050 .display_map
3051 .update(cx, |display_map, cx| display_map.snapshot(cx));
3052 let buffer = &display_map.buffer_snapshot;
3053 if self.selections.count() == 1 {
3054 self.add_selections_state = None;
3055 }
3056 self.select_next_state = None;
3057 self.select_prev_state = None;
3058 self.select_syntax_node_history.try_clear();
3059 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3060 self.snippet_stack.invalidate(&selection_anchors, buffer);
3061 self.take_rename(false, window, cx);
3062
3063 let newest_selection = self.selections.newest_anchor();
3064 let new_cursor_position = newest_selection.head();
3065 let selection_start = newest_selection.start;
3066
3067 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3068 self.push_to_nav_history(
3069 *old_cursor_position,
3070 Some(new_cursor_position.to_point(buffer)),
3071 false,
3072 effects.nav_history == Some(true),
3073 cx,
3074 );
3075 }
3076
3077 if local {
3078 if let Some(buffer_id) = new_cursor_position.buffer_id
3079 && !self.registered_buffers.contains_key(&buffer_id)
3080 && let Some(project) = self.project.as_ref()
3081 {
3082 project.update(cx, |project, cx| {
3083 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3084 return;
3085 };
3086 self.registered_buffers.insert(
3087 buffer_id,
3088 project.register_buffer_with_language_servers(&buffer, cx),
3089 );
3090 })
3091 }
3092
3093 let mut context_menu = self.context_menu.borrow_mut();
3094 let completion_menu = match context_menu.as_ref() {
3095 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3096 Some(CodeContextMenu::CodeActions(_)) => {
3097 *context_menu = None;
3098 None
3099 }
3100 None => None,
3101 };
3102 let completion_position = completion_menu.map(|menu| menu.initial_position);
3103 drop(context_menu);
3104
3105 if effects.completions
3106 && let Some(completion_position) = completion_position
3107 {
3108 let start_offset = selection_start.to_offset(buffer);
3109 let position_matches = start_offset == completion_position.to_offset(buffer);
3110 let continue_showing = if position_matches {
3111 if self.snippet_stack.is_empty() {
3112 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3113 } else {
3114 // Snippet choices can be shown even when the cursor is in whitespace.
3115 // Dismissing the menu with actions like backspace is handled by
3116 // invalidation regions.
3117 true
3118 }
3119 } else {
3120 false
3121 };
3122
3123 if continue_showing {
3124 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3125 } else {
3126 self.hide_context_menu(window, cx);
3127 }
3128 }
3129
3130 hide_hover(self, cx);
3131
3132 if old_cursor_position.to_display_point(&display_map).row()
3133 != new_cursor_position.to_display_point(&display_map).row()
3134 {
3135 self.available_code_actions.take();
3136 }
3137 self.refresh_code_actions(window, cx);
3138 self.refresh_document_highlights(cx);
3139 self.refresh_selected_text_highlights(false, window, cx);
3140 refresh_matching_bracket_highlights(self, window, cx);
3141 self.update_visible_edit_prediction(window, cx);
3142 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3143 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3144 self.inline_blame_popover.take();
3145 if self.git_blame_inline_enabled {
3146 self.start_inline_blame_timer(window, cx);
3147 }
3148 }
3149
3150 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3151 cx.emit(EditorEvent::SelectionsChanged { local });
3152
3153 let selections = &self.selections.disjoint;
3154 if selections.len() == 1 {
3155 cx.emit(SearchEvent::ActiveMatchChanged)
3156 }
3157 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3158 let inmemory_selections = selections
3159 .iter()
3160 .map(|s| {
3161 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3162 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3163 })
3164 .collect();
3165 self.update_restoration_data(cx, |data| {
3166 data.selections = inmemory_selections;
3167 });
3168
3169 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3170 && let Some(workspace_id) =
3171 self.workspace.as_ref().and_then(|workspace| workspace.1)
3172 {
3173 let snapshot = self.buffer().read(cx).snapshot(cx);
3174 let selections = selections.clone();
3175 let background_executor = cx.background_executor().clone();
3176 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3177 self.serialize_selections = cx.background_spawn(async move {
3178 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3179 let db_selections = selections
3180 .iter()
3181 .map(|selection| {
3182 (
3183 selection.start.to_offset(&snapshot),
3184 selection.end.to_offset(&snapshot),
3185 )
3186 })
3187 .collect();
3188
3189 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3190 .await
3191 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3192 .log_err();
3193 });
3194 }
3195 }
3196
3197 cx.notify();
3198 }
3199
3200 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3201 use text::ToOffset as _;
3202 use text::ToPoint as _;
3203
3204 if self.mode.is_minimap()
3205 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3206 {
3207 return;
3208 }
3209
3210 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3211 return;
3212 };
3213
3214 let snapshot = singleton.read(cx).snapshot();
3215 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3216 let display_snapshot = display_map.snapshot(cx);
3217
3218 display_snapshot
3219 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3220 .map(|fold| {
3221 fold.range.start.text_anchor.to_point(&snapshot)
3222 ..fold.range.end.text_anchor.to_point(&snapshot)
3223 })
3224 .collect()
3225 });
3226 self.update_restoration_data(cx, |data| {
3227 data.folds = inmemory_folds;
3228 });
3229
3230 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3231 return;
3232 };
3233 let background_executor = cx.background_executor().clone();
3234 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3235 let db_folds = self.display_map.update(cx, |display_map, cx| {
3236 display_map
3237 .snapshot(cx)
3238 .folds_in_range(0..snapshot.len())
3239 .map(|fold| {
3240 (
3241 fold.range.start.text_anchor.to_offset(&snapshot),
3242 fold.range.end.text_anchor.to_offset(&snapshot),
3243 )
3244 })
3245 .collect()
3246 });
3247 self.serialize_folds = cx.background_spawn(async move {
3248 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3249 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3250 .await
3251 .with_context(|| {
3252 format!(
3253 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3254 )
3255 })
3256 .log_err();
3257 });
3258 }
3259
3260 pub fn sync_selections(
3261 &mut self,
3262 other: Entity<Editor>,
3263 cx: &mut Context<Self>,
3264 ) -> gpui::Subscription {
3265 let other_selections = other.read(cx).selections.disjoint.to_vec();
3266 self.selections.change_with(cx, |selections| {
3267 selections.select_anchors(other_selections);
3268 });
3269
3270 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3271 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3272 let other_selections = other.read(cx).selections.disjoint.to_vec();
3273 if other_selections.is_empty() {
3274 return;
3275 }
3276 this.selections.change_with(cx, |selections| {
3277 selections.select_anchors(other_selections);
3278 });
3279 }
3280 });
3281
3282 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3283 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3284 let these_selections = this.selections.disjoint.to_vec();
3285 if these_selections.is_empty() {
3286 return;
3287 }
3288 other.update(cx, |other_editor, cx| {
3289 other_editor.selections.change_with(cx, |selections| {
3290 selections.select_anchors(these_selections);
3291 })
3292 });
3293 }
3294 });
3295
3296 Subscription::join(other_subscription, this_subscription)
3297 }
3298
3299 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3300 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3301 /// effects of selection change occur at the end of the transaction.
3302 pub fn change_selections<R>(
3303 &mut self,
3304 effects: SelectionEffects,
3305 window: &mut Window,
3306 cx: &mut Context<Self>,
3307 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3308 ) -> R {
3309 if let Some(state) = &mut self.deferred_selection_effects_state {
3310 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3311 state.effects.completions = effects.completions;
3312 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3313 let (changed, result) = self.selections.change_with(cx, change);
3314 state.changed |= changed;
3315 return result;
3316 }
3317 let mut state = DeferredSelectionEffectsState {
3318 changed: false,
3319 effects,
3320 old_cursor_position: self.selections.newest_anchor().head(),
3321 history_entry: SelectionHistoryEntry {
3322 selections: self.selections.disjoint_anchors(),
3323 select_next_state: self.select_next_state.clone(),
3324 select_prev_state: self.select_prev_state.clone(),
3325 add_selections_state: self.add_selections_state.clone(),
3326 },
3327 };
3328 let (changed, result) = self.selections.change_with(cx, change);
3329 state.changed = state.changed || changed;
3330 if self.defer_selection_effects {
3331 self.deferred_selection_effects_state = Some(state);
3332 } else {
3333 self.apply_selection_effects(state, window, cx);
3334 }
3335 result
3336 }
3337
3338 /// Defers the effects of selection change, so that the effects of multiple calls to
3339 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3340 /// to selection history and the state of popovers based on selection position aren't
3341 /// erroneously updated.
3342 pub fn with_selection_effects_deferred<R>(
3343 &mut self,
3344 window: &mut Window,
3345 cx: &mut Context<Self>,
3346 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3347 ) -> R {
3348 let already_deferred = self.defer_selection_effects;
3349 self.defer_selection_effects = true;
3350 let result = update(self, window, cx);
3351 if !already_deferred {
3352 self.defer_selection_effects = false;
3353 if let Some(state) = self.deferred_selection_effects_state.take() {
3354 self.apply_selection_effects(state, window, cx);
3355 }
3356 }
3357 result
3358 }
3359
3360 fn apply_selection_effects(
3361 &mut self,
3362 state: DeferredSelectionEffectsState,
3363 window: &mut Window,
3364 cx: &mut Context<Self>,
3365 ) {
3366 if state.changed {
3367 self.selection_history.push(state.history_entry);
3368
3369 if let Some(autoscroll) = state.effects.scroll {
3370 self.request_autoscroll(autoscroll, cx);
3371 }
3372
3373 let old_cursor_position = &state.old_cursor_position;
3374
3375 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3376
3377 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3378 self.show_signature_help(&ShowSignatureHelp, window, cx);
3379 }
3380 }
3381 }
3382
3383 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3384 where
3385 I: IntoIterator<Item = (Range<S>, T)>,
3386 S: ToOffset,
3387 T: Into<Arc<str>>,
3388 {
3389 if self.read_only(cx) {
3390 return;
3391 }
3392
3393 self.buffer
3394 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3395 }
3396
3397 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3398 where
3399 I: IntoIterator<Item = (Range<S>, T)>,
3400 S: ToOffset,
3401 T: Into<Arc<str>>,
3402 {
3403 if self.read_only(cx) {
3404 return;
3405 }
3406
3407 self.buffer.update(cx, |buffer, cx| {
3408 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3409 });
3410 }
3411
3412 pub fn edit_with_block_indent<I, S, T>(
3413 &mut self,
3414 edits: I,
3415 original_indent_columns: Vec<Option<u32>>,
3416 cx: &mut Context<Self>,
3417 ) where
3418 I: IntoIterator<Item = (Range<S>, T)>,
3419 S: ToOffset,
3420 T: Into<Arc<str>>,
3421 {
3422 if self.read_only(cx) {
3423 return;
3424 }
3425
3426 self.buffer.update(cx, |buffer, cx| {
3427 buffer.edit(
3428 edits,
3429 Some(AutoindentMode::Block {
3430 original_indent_columns,
3431 }),
3432 cx,
3433 )
3434 });
3435 }
3436
3437 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3438 self.hide_context_menu(window, cx);
3439
3440 match phase {
3441 SelectPhase::Begin {
3442 position,
3443 add,
3444 click_count,
3445 } => self.begin_selection(position, add, click_count, window, cx),
3446 SelectPhase::BeginColumnar {
3447 position,
3448 goal_column,
3449 reset,
3450 mode,
3451 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3452 SelectPhase::Extend {
3453 position,
3454 click_count,
3455 } => self.extend_selection(position, click_count, window, cx),
3456 SelectPhase::Update {
3457 position,
3458 goal_column,
3459 scroll_delta,
3460 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3461 SelectPhase::End => self.end_selection(window, cx),
3462 }
3463 }
3464
3465 fn extend_selection(
3466 &mut self,
3467 position: DisplayPoint,
3468 click_count: usize,
3469 window: &mut Window,
3470 cx: &mut Context<Self>,
3471 ) {
3472 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3473 let tail = self.selections.newest::<usize>(cx).tail();
3474 self.begin_selection(position, false, click_count, window, cx);
3475
3476 let position = position.to_offset(&display_map, Bias::Left);
3477 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3478
3479 let mut pending_selection = self
3480 .selections
3481 .pending_anchor()
3482 .expect("extend_selection not called with pending selection");
3483 if position >= tail {
3484 pending_selection.start = tail_anchor;
3485 } else {
3486 pending_selection.end = tail_anchor;
3487 pending_selection.reversed = true;
3488 }
3489
3490 let mut pending_mode = self.selections.pending_mode().unwrap();
3491 match &mut pending_mode {
3492 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3493 _ => {}
3494 }
3495
3496 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3497 SelectionEffects::scroll(Autoscroll::fit())
3498 } else {
3499 SelectionEffects::no_scroll()
3500 };
3501
3502 self.change_selections(effects, window, cx, |s| {
3503 s.set_pending(pending_selection, pending_mode)
3504 });
3505 }
3506
3507 fn begin_selection(
3508 &mut self,
3509 position: DisplayPoint,
3510 add: bool,
3511 click_count: usize,
3512 window: &mut Window,
3513 cx: &mut Context<Self>,
3514 ) {
3515 if !self.focus_handle.is_focused(window) {
3516 self.last_focused_descendant = None;
3517 window.focus(&self.focus_handle);
3518 }
3519
3520 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3521 let buffer = &display_map.buffer_snapshot;
3522 let position = display_map.clip_point(position, Bias::Left);
3523
3524 let start;
3525 let end;
3526 let mode;
3527 let mut auto_scroll;
3528 match click_count {
3529 1 => {
3530 start = buffer.anchor_before(position.to_point(&display_map));
3531 end = start;
3532 mode = SelectMode::Character;
3533 auto_scroll = true;
3534 }
3535 2 => {
3536 let position = display_map
3537 .clip_point(position, Bias::Left)
3538 .to_offset(&display_map, Bias::Left);
3539 let (range, _) = buffer.surrounding_word(position, false);
3540 start = buffer.anchor_before(range.start);
3541 end = buffer.anchor_before(range.end);
3542 mode = SelectMode::Word(start..end);
3543 auto_scroll = true;
3544 }
3545 3 => {
3546 let position = display_map
3547 .clip_point(position, Bias::Left)
3548 .to_point(&display_map);
3549 let line_start = display_map.prev_line_boundary(position).0;
3550 let next_line_start = buffer.clip_point(
3551 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3552 Bias::Left,
3553 );
3554 start = buffer.anchor_before(line_start);
3555 end = buffer.anchor_before(next_line_start);
3556 mode = SelectMode::Line(start..end);
3557 auto_scroll = true;
3558 }
3559 _ => {
3560 start = buffer.anchor_before(0);
3561 end = buffer.anchor_before(buffer.len());
3562 mode = SelectMode::All;
3563 auto_scroll = false;
3564 }
3565 }
3566 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3567
3568 let point_to_delete: Option<usize> = {
3569 let selected_points: Vec<Selection<Point>> =
3570 self.selections.disjoint_in_range(start..end, cx);
3571
3572 if !add || click_count > 1 {
3573 None
3574 } else if !selected_points.is_empty() {
3575 Some(selected_points[0].id)
3576 } else {
3577 let clicked_point_already_selected =
3578 self.selections.disjoint.iter().find(|selection| {
3579 selection.start.to_point(buffer) == start.to_point(buffer)
3580 || selection.end.to_point(buffer) == end.to_point(buffer)
3581 });
3582
3583 clicked_point_already_selected.map(|selection| selection.id)
3584 }
3585 };
3586
3587 let selections_count = self.selections.count();
3588 let effects = if auto_scroll {
3589 SelectionEffects::default()
3590 } else {
3591 SelectionEffects::no_scroll()
3592 };
3593
3594 self.change_selections(effects, window, cx, |s| {
3595 if let Some(point_to_delete) = point_to_delete {
3596 s.delete(point_to_delete);
3597
3598 if selections_count == 1 {
3599 s.set_pending_anchor_range(start..end, mode);
3600 }
3601 } else {
3602 if !add {
3603 s.clear_disjoint();
3604 }
3605
3606 s.set_pending_anchor_range(start..end, mode);
3607 }
3608 });
3609 }
3610
3611 fn begin_columnar_selection(
3612 &mut self,
3613 position: DisplayPoint,
3614 goal_column: u32,
3615 reset: bool,
3616 mode: ColumnarMode,
3617 window: &mut Window,
3618 cx: &mut Context<Self>,
3619 ) {
3620 if !self.focus_handle.is_focused(window) {
3621 self.last_focused_descendant = None;
3622 window.focus(&self.focus_handle);
3623 }
3624
3625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3626
3627 if reset {
3628 let pointer_position = display_map
3629 .buffer_snapshot
3630 .anchor_before(position.to_point(&display_map));
3631
3632 self.change_selections(
3633 SelectionEffects::scroll(Autoscroll::newest()),
3634 window,
3635 cx,
3636 |s| {
3637 s.clear_disjoint();
3638 s.set_pending_anchor_range(
3639 pointer_position..pointer_position,
3640 SelectMode::Character,
3641 );
3642 },
3643 );
3644 };
3645
3646 let tail = self.selections.newest::<Point>(cx).tail();
3647 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3648 self.columnar_selection_state = match mode {
3649 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3650 selection_tail: selection_anchor,
3651 display_point: if reset {
3652 if position.column() != goal_column {
3653 Some(DisplayPoint::new(position.row(), goal_column))
3654 } else {
3655 None
3656 }
3657 } else {
3658 None
3659 },
3660 }),
3661 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3662 selection_tail: selection_anchor,
3663 }),
3664 };
3665
3666 if !reset {
3667 self.select_columns(position, goal_column, &display_map, window, cx);
3668 }
3669 }
3670
3671 fn update_selection(
3672 &mut self,
3673 position: DisplayPoint,
3674 goal_column: u32,
3675 scroll_delta: gpui::Point<f32>,
3676 window: &mut Window,
3677 cx: &mut Context<Self>,
3678 ) {
3679 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3680
3681 if self.columnar_selection_state.is_some() {
3682 self.select_columns(position, goal_column, &display_map, window, cx);
3683 } else if let Some(mut pending) = self.selections.pending_anchor() {
3684 let buffer = &display_map.buffer_snapshot;
3685 let head;
3686 let tail;
3687 let mode = self.selections.pending_mode().unwrap();
3688 match &mode {
3689 SelectMode::Character => {
3690 head = position.to_point(&display_map);
3691 tail = pending.tail().to_point(buffer);
3692 }
3693 SelectMode::Word(original_range) => {
3694 let offset = display_map
3695 .clip_point(position, Bias::Left)
3696 .to_offset(&display_map, Bias::Left);
3697 let original_range = original_range.to_offset(buffer);
3698
3699 let head_offset = if buffer.is_inside_word(offset, false)
3700 || original_range.contains(&offset)
3701 {
3702 let (word_range, _) = buffer.surrounding_word(offset, false);
3703 if word_range.start < original_range.start {
3704 word_range.start
3705 } else {
3706 word_range.end
3707 }
3708 } else {
3709 offset
3710 };
3711
3712 head = head_offset.to_point(buffer);
3713 if head_offset <= original_range.start {
3714 tail = original_range.end.to_point(buffer);
3715 } else {
3716 tail = original_range.start.to_point(buffer);
3717 }
3718 }
3719 SelectMode::Line(original_range) => {
3720 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3721
3722 let position = display_map
3723 .clip_point(position, Bias::Left)
3724 .to_point(&display_map);
3725 let line_start = display_map.prev_line_boundary(position).0;
3726 let next_line_start = buffer.clip_point(
3727 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3728 Bias::Left,
3729 );
3730
3731 if line_start < original_range.start {
3732 head = line_start
3733 } else {
3734 head = next_line_start
3735 }
3736
3737 if head <= original_range.start {
3738 tail = original_range.end;
3739 } else {
3740 tail = original_range.start;
3741 }
3742 }
3743 SelectMode::All => {
3744 return;
3745 }
3746 };
3747
3748 if head < tail {
3749 pending.start = buffer.anchor_before(head);
3750 pending.end = buffer.anchor_before(tail);
3751 pending.reversed = true;
3752 } else {
3753 pending.start = buffer.anchor_before(tail);
3754 pending.end = buffer.anchor_before(head);
3755 pending.reversed = false;
3756 }
3757
3758 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3759 s.set_pending(pending, mode);
3760 });
3761 } else {
3762 log::error!("update_selection dispatched with no pending selection");
3763 return;
3764 }
3765
3766 self.apply_scroll_delta(scroll_delta, window, cx);
3767 cx.notify();
3768 }
3769
3770 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3771 self.columnar_selection_state.take();
3772 if self.selections.pending_anchor().is_some() {
3773 let selections = self.selections.all::<usize>(cx);
3774 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3775 s.select(selections);
3776 s.clear_pending();
3777 });
3778 }
3779 }
3780
3781 fn select_columns(
3782 &mut self,
3783 head: DisplayPoint,
3784 goal_column: u32,
3785 display_map: &DisplaySnapshot,
3786 window: &mut Window,
3787 cx: &mut Context<Self>,
3788 ) {
3789 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3790 return;
3791 };
3792
3793 let tail = match columnar_state {
3794 ColumnarSelectionState::FromMouse {
3795 selection_tail,
3796 display_point,
3797 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3798 ColumnarSelectionState::FromSelection { selection_tail } => {
3799 selection_tail.to_display_point(display_map)
3800 }
3801 };
3802
3803 let start_row = cmp::min(tail.row(), head.row());
3804 let end_row = cmp::max(tail.row(), head.row());
3805 let start_column = cmp::min(tail.column(), goal_column);
3806 let end_column = cmp::max(tail.column(), goal_column);
3807 let reversed = start_column < tail.column();
3808
3809 let selection_ranges = (start_row.0..=end_row.0)
3810 .map(DisplayRow)
3811 .filter_map(|row| {
3812 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3813 || start_column <= display_map.line_len(row))
3814 && !display_map.is_block_line(row)
3815 {
3816 let start = display_map
3817 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3818 .to_point(display_map);
3819 let end = display_map
3820 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3821 .to_point(display_map);
3822 if reversed {
3823 Some(end..start)
3824 } else {
3825 Some(start..end)
3826 }
3827 } else {
3828 None
3829 }
3830 })
3831 .collect::<Vec<_>>();
3832
3833 let ranges = match columnar_state {
3834 ColumnarSelectionState::FromMouse { .. } => {
3835 let mut non_empty_ranges = selection_ranges
3836 .iter()
3837 .filter(|selection_range| selection_range.start != selection_range.end)
3838 .peekable();
3839 if non_empty_ranges.peek().is_some() {
3840 non_empty_ranges.cloned().collect()
3841 } else {
3842 selection_ranges
3843 }
3844 }
3845 _ => selection_ranges,
3846 };
3847
3848 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3849 s.select_ranges(ranges);
3850 });
3851 cx.notify();
3852 }
3853
3854 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3855 self.selections
3856 .all_adjusted(cx)
3857 .iter()
3858 .any(|selection| !selection.is_empty())
3859 }
3860
3861 pub fn has_pending_nonempty_selection(&self) -> bool {
3862 let pending_nonempty_selection = match self.selections.pending_anchor() {
3863 Some(Selection { start, end, .. }) => start != end,
3864 None => false,
3865 };
3866
3867 pending_nonempty_selection
3868 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3869 }
3870
3871 pub fn has_pending_selection(&self) -> bool {
3872 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3873 }
3874
3875 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3876 self.selection_mark_mode = false;
3877 self.selection_drag_state = SelectionDragState::None;
3878
3879 if self.clear_expanded_diff_hunks(cx) {
3880 cx.notify();
3881 return;
3882 }
3883 if self.dismiss_menus_and_popups(true, window, cx) {
3884 return;
3885 }
3886
3887 if self.mode.is_full()
3888 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3889 {
3890 return;
3891 }
3892
3893 cx.propagate();
3894 }
3895
3896 pub fn dismiss_menus_and_popups(
3897 &mut self,
3898 is_user_requested: bool,
3899 window: &mut Window,
3900 cx: &mut Context<Self>,
3901 ) -> bool {
3902 if self.take_rename(false, window, cx).is_some() {
3903 return true;
3904 }
3905
3906 if hide_hover(self, cx) {
3907 return true;
3908 }
3909
3910 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3911 return true;
3912 }
3913
3914 if self.hide_context_menu(window, cx).is_some() {
3915 return true;
3916 }
3917
3918 if self.mouse_context_menu.take().is_some() {
3919 return true;
3920 }
3921
3922 if is_user_requested && self.discard_edit_prediction(true, cx) {
3923 return true;
3924 }
3925
3926 if self.snippet_stack.pop().is_some() {
3927 return true;
3928 }
3929
3930 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3931 self.dismiss_diagnostics(cx);
3932 return true;
3933 }
3934
3935 false
3936 }
3937
3938 fn linked_editing_ranges_for(
3939 &self,
3940 selection: Range<text::Anchor>,
3941 cx: &App,
3942 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3943 if self.linked_edit_ranges.is_empty() {
3944 return None;
3945 }
3946 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3947 selection.end.buffer_id.and_then(|end_buffer_id| {
3948 if selection.start.buffer_id != Some(end_buffer_id) {
3949 return None;
3950 }
3951 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3952 let snapshot = buffer.read(cx).snapshot();
3953 self.linked_edit_ranges
3954 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3955 .map(|ranges| (ranges, snapshot, buffer))
3956 })?;
3957 use text::ToOffset as TO;
3958 // find offset from the start of current range to current cursor position
3959 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3960
3961 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3962 let start_difference = start_offset - start_byte_offset;
3963 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3964 let end_difference = end_offset - start_byte_offset;
3965 // Current range has associated linked ranges.
3966 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3967 for range in linked_ranges.iter() {
3968 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3969 let end_offset = start_offset + end_difference;
3970 let start_offset = start_offset + start_difference;
3971 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3972 continue;
3973 }
3974 if self.selections.disjoint_anchor_ranges().any(|s| {
3975 if s.start.buffer_id != selection.start.buffer_id
3976 || s.end.buffer_id != selection.end.buffer_id
3977 {
3978 return false;
3979 }
3980 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3981 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3982 }) {
3983 continue;
3984 }
3985 let start = buffer_snapshot.anchor_after(start_offset);
3986 let end = buffer_snapshot.anchor_after(end_offset);
3987 linked_edits
3988 .entry(buffer.clone())
3989 .or_default()
3990 .push(start..end);
3991 }
3992 Some(linked_edits)
3993 }
3994
3995 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3996 let text: Arc<str> = text.into();
3997
3998 if self.read_only(cx) {
3999 return;
4000 }
4001
4002 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4003
4004 let selections = self.selections.all_adjusted(cx);
4005 let mut bracket_inserted = false;
4006 let mut edits = Vec::new();
4007 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4008 let mut new_selections = Vec::with_capacity(selections.len());
4009 let mut new_autoclose_regions = Vec::new();
4010 let snapshot = self.buffer.read(cx).read(cx);
4011 let mut clear_linked_edit_ranges = false;
4012
4013 for (selection, autoclose_region) in
4014 self.selections_with_autoclose_regions(selections, &snapshot)
4015 {
4016 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4017 // Determine if the inserted text matches the opening or closing
4018 // bracket of any of this language's bracket pairs.
4019 let mut bracket_pair = None;
4020 let mut is_bracket_pair_start = false;
4021 let mut is_bracket_pair_end = false;
4022 if !text.is_empty() {
4023 let mut bracket_pair_matching_end = None;
4024 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4025 // and they are removing the character that triggered IME popup.
4026 for (pair, enabled) in scope.brackets() {
4027 if !pair.close && !pair.surround {
4028 continue;
4029 }
4030
4031 if enabled && pair.start.ends_with(text.as_ref()) {
4032 let prefix_len = pair.start.len() - text.len();
4033 let preceding_text_matches_prefix = prefix_len == 0
4034 || (selection.start.column >= (prefix_len as u32)
4035 && snapshot.contains_str_at(
4036 Point::new(
4037 selection.start.row,
4038 selection.start.column - (prefix_len as u32),
4039 ),
4040 &pair.start[..prefix_len],
4041 ));
4042 if preceding_text_matches_prefix {
4043 bracket_pair = Some(pair.clone());
4044 is_bracket_pair_start = true;
4045 break;
4046 }
4047 }
4048 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4049 {
4050 // take first bracket pair matching end, but don't break in case a later bracket
4051 // pair matches start
4052 bracket_pair_matching_end = Some(pair.clone());
4053 }
4054 }
4055 if let Some(end) = bracket_pair_matching_end
4056 && bracket_pair.is_none()
4057 {
4058 bracket_pair = Some(end);
4059 is_bracket_pair_end = true;
4060 }
4061 }
4062
4063 if let Some(bracket_pair) = bracket_pair {
4064 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4065 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4066 let auto_surround =
4067 self.use_auto_surround && snapshot_settings.use_auto_surround;
4068 if selection.is_empty() {
4069 if is_bracket_pair_start {
4070 // If the inserted text is a suffix of an opening bracket and the
4071 // selection is preceded by the rest of the opening bracket, then
4072 // insert the closing bracket.
4073 let following_text_allows_autoclose = snapshot
4074 .chars_at(selection.start)
4075 .next()
4076 .is_none_or(|c| scope.should_autoclose_before(c));
4077
4078 let preceding_text_allows_autoclose = selection.start.column == 0
4079 || snapshot
4080 .reversed_chars_at(selection.start)
4081 .next()
4082 .is_none_or(|c| {
4083 bracket_pair.start != bracket_pair.end
4084 || !snapshot
4085 .char_classifier_at(selection.start)
4086 .is_word(c)
4087 });
4088
4089 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4090 && bracket_pair.start.len() == 1
4091 {
4092 let target = bracket_pair.start.chars().next().unwrap();
4093 let current_line_count = snapshot
4094 .reversed_chars_at(selection.start)
4095 .take_while(|&c| c != '\n')
4096 .filter(|&c| c == target)
4097 .count();
4098 current_line_count % 2 == 1
4099 } else {
4100 false
4101 };
4102
4103 if autoclose
4104 && bracket_pair.close
4105 && following_text_allows_autoclose
4106 && preceding_text_allows_autoclose
4107 && !is_closing_quote
4108 {
4109 let anchor = snapshot.anchor_before(selection.end);
4110 new_selections.push((selection.map(|_| anchor), text.len()));
4111 new_autoclose_regions.push((
4112 anchor,
4113 text.len(),
4114 selection.id,
4115 bracket_pair.clone(),
4116 ));
4117 edits.push((
4118 selection.range(),
4119 format!("{}{}", text, bracket_pair.end).into(),
4120 ));
4121 bracket_inserted = true;
4122 continue;
4123 }
4124 }
4125
4126 if let Some(region) = autoclose_region {
4127 // If the selection is followed by an auto-inserted closing bracket,
4128 // then don't insert that closing bracket again; just move the selection
4129 // past the closing bracket.
4130 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4131 && text.as_ref() == region.pair.end.as_str()
4132 && snapshot.contains_str_at(region.range.end, text.as_ref());
4133 if should_skip {
4134 let anchor = snapshot.anchor_after(selection.end);
4135 new_selections
4136 .push((selection.map(|_| anchor), region.pair.end.len()));
4137 continue;
4138 }
4139 }
4140
4141 let always_treat_brackets_as_autoclosed = snapshot
4142 .language_settings_at(selection.start, cx)
4143 .always_treat_brackets_as_autoclosed;
4144 if always_treat_brackets_as_autoclosed
4145 && is_bracket_pair_end
4146 && snapshot.contains_str_at(selection.end, text.as_ref())
4147 {
4148 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4149 // and the inserted text is a closing bracket and the selection is followed
4150 // by the closing bracket then move the selection past the closing bracket.
4151 let anchor = snapshot.anchor_after(selection.end);
4152 new_selections.push((selection.map(|_| anchor), text.len()));
4153 continue;
4154 }
4155 }
4156 // If an opening bracket is 1 character long and is typed while
4157 // text is selected, then surround that text with the bracket pair.
4158 else if auto_surround
4159 && bracket_pair.surround
4160 && is_bracket_pair_start
4161 && bracket_pair.start.chars().count() == 1
4162 {
4163 edits.push((selection.start..selection.start, text.clone()));
4164 edits.push((
4165 selection.end..selection.end,
4166 bracket_pair.end.as_str().into(),
4167 ));
4168 bracket_inserted = true;
4169 new_selections.push((
4170 Selection {
4171 id: selection.id,
4172 start: snapshot.anchor_after(selection.start),
4173 end: snapshot.anchor_before(selection.end),
4174 reversed: selection.reversed,
4175 goal: selection.goal,
4176 },
4177 0,
4178 ));
4179 continue;
4180 }
4181 }
4182 }
4183
4184 if self.auto_replace_emoji_shortcode
4185 && selection.is_empty()
4186 && text.as_ref().ends_with(':')
4187 && let Some(possible_emoji_short_code) =
4188 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4189 && !possible_emoji_short_code.is_empty()
4190 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4191 {
4192 let emoji_shortcode_start = Point::new(
4193 selection.start.row,
4194 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4195 );
4196
4197 // Remove shortcode from buffer
4198 edits.push((
4199 emoji_shortcode_start..selection.start,
4200 "".to_string().into(),
4201 ));
4202 new_selections.push((
4203 Selection {
4204 id: selection.id,
4205 start: snapshot.anchor_after(emoji_shortcode_start),
4206 end: snapshot.anchor_before(selection.start),
4207 reversed: selection.reversed,
4208 goal: selection.goal,
4209 },
4210 0,
4211 ));
4212
4213 // Insert emoji
4214 let selection_start_anchor = snapshot.anchor_after(selection.start);
4215 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4216 edits.push((selection.start..selection.end, emoji.to_string().into()));
4217
4218 continue;
4219 }
4220
4221 // If not handling any auto-close operation, then just replace the selected
4222 // text with the given input and move the selection to the end of the
4223 // newly inserted text.
4224 let anchor = snapshot.anchor_after(selection.end);
4225 if !self.linked_edit_ranges.is_empty() {
4226 let start_anchor = snapshot.anchor_before(selection.start);
4227
4228 let is_word_char = text.chars().next().is_none_or(|char| {
4229 let classifier = snapshot
4230 .char_classifier_at(start_anchor.to_offset(&snapshot))
4231 .ignore_punctuation(true);
4232 classifier.is_word(char)
4233 });
4234
4235 if is_word_char {
4236 if let Some(ranges) = self
4237 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4238 {
4239 for (buffer, edits) in ranges {
4240 linked_edits
4241 .entry(buffer.clone())
4242 .or_default()
4243 .extend(edits.into_iter().map(|range| (range, text.clone())));
4244 }
4245 }
4246 } else {
4247 clear_linked_edit_ranges = true;
4248 }
4249 }
4250
4251 new_selections.push((selection.map(|_| anchor), 0));
4252 edits.push((selection.start..selection.end, text.clone()));
4253 }
4254
4255 drop(snapshot);
4256
4257 self.transact(window, cx, |this, window, cx| {
4258 if clear_linked_edit_ranges {
4259 this.linked_edit_ranges.clear();
4260 }
4261 let initial_buffer_versions =
4262 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4263
4264 this.buffer.update(cx, |buffer, cx| {
4265 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4266 });
4267 for (buffer, edits) in linked_edits {
4268 buffer.update(cx, |buffer, cx| {
4269 let snapshot = buffer.snapshot();
4270 let edits = edits
4271 .into_iter()
4272 .map(|(range, text)| {
4273 use text::ToPoint as TP;
4274 let end_point = TP::to_point(&range.end, &snapshot);
4275 let start_point = TP::to_point(&range.start, &snapshot);
4276 (start_point..end_point, text)
4277 })
4278 .sorted_by_key(|(range, _)| range.start);
4279 buffer.edit(edits, None, cx);
4280 })
4281 }
4282 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4283 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4284 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4285 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4286 .zip(new_selection_deltas)
4287 .map(|(selection, delta)| Selection {
4288 id: selection.id,
4289 start: selection.start + delta,
4290 end: selection.end + delta,
4291 reversed: selection.reversed,
4292 goal: SelectionGoal::None,
4293 })
4294 .collect::<Vec<_>>();
4295
4296 let mut i = 0;
4297 for (position, delta, selection_id, pair) in new_autoclose_regions {
4298 let position = position.to_offset(&map.buffer_snapshot) + delta;
4299 let start = map.buffer_snapshot.anchor_before(position);
4300 let end = map.buffer_snapshot.anchor_after(position);
4301 while let Some(existing_state) = this.autoclose_regions.get(i) {
4302 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4303 Ordering::Less => i += 1,
4304 Ordering::Greater => break,
4305 Ordering::Equal => {
4306 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4307 Ordering::Less => i += 1,
4308 Ordering::Equal => break,
4309 Ordering::Greater => break,
4310 }
4311 }
4312 }
4313 }
4314 this.autoclose_regions.insert(
4315 i,
4316 AutocloseRegion {
4317 selection_id,
4318 range: start..end,
4319 pair,
4320 },
4321 );
4322 }
4323
4324 let had_active_edit_prediction = this.has_active_edit_prediction();
4325 this.change_selections(
4326 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4327 window,
4328 cx,
4329 |s| s.select(new_selections),
4330 );
4331
4332 if !bracket_inserted
4333 && let Some(on_type_format_task) =
4334 this.trigger_on_type_formatting(text.to_string(), window, cx)
4335 {
4336 on_type_format_task.detach_and_log_err(cx);
4337 }
4338
4339 let editor_settings = EditorSettings::get_global(cx);
4340 if bracket_inserted
4341 && (editor_settings.auto_signature_help
4342 || editor_settings.show_signature_help_after_edits)
4343 {
4344 this.show_signature_help(&ShowSignatureHelp, window, cx);
4345 }
4346
4347 let trigger_in_words =
4348 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4349 if this.hard_wrap.is_some() {
4350 let latest: Range<Point> = this.selections.newest(cx).range();
4351 if latest.is_empty()
4352 && this
4353 .buffer()
4354 .read(cx)
4355 .snapshot(cx)
4356 .line_len(MultiBufferRow(latest.start.row))
4357 == latest.start.column
4358 {
4359 this.rewrap_impl(
4360 RewrapOptions {
4361 override_language_settings: true,
4362 preserve_existing_whitespace: true,
4363 },
4364 cx,
4365 )
4366 }
4367 }
4368 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4369 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4370 this.refresh_edit_prediction(true, false, window, cx);
4371 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4372 });
4373 }
4374
4375 fn find_possible_emoji_shortcode_at_position(
4376 snapshot: &MultiBufferSnapshot,
4377 position: Point,
4378 ) -> Option<String> {
4379 let mut chars = Vec::new();
4380 let mut found_colon = false;
4381 for char in snapshot.reversed_chars_at(position).take(100) {
4382 // Found a possible emoji shortcode in the middle of the buffer
4383 if found_colon {
4384 if char.is_whitespace() {
4385 chars.reverse();
4386 return Some(chars.iter().collect());
4387 }
4388 // If the previous character is not a whitespace, we are in the middle of a word
4389 // and we only want to complete the shortcode if the word is made up of other emojis
4390 let mut containing_word = String::new();
4391 for ch in snapshot
4392 .reversed_chars_at(position)
4393 .skip(chars.len() + 1)
4394 .take(100)
4395 {
4396 if ch.is_whitespace() {
4397 break;
4398 }
4399 containing_word.push(ch);
4400 }
4401 let containing_word = containing_word.chars().rev().collect::<String>();
4402 if util::word_consists_of_emojis(containing_word.as_str()) {
4403 chars.reverse();
4404 return Some(chars.iter().collect());
4405 }
4406 }
4407
4408 if char.is_whitespace() || !char.is_ascii() {
4409 return None;
4410 }
4411 if char == ':' {
4412 found_colon = true;
4413 } else {
4414 chars.push(char);
4415 }
4416 }
4417 // Found a possible emoji shortcode at the beginning of the buffer
4418 chars.reverse();
4419 Some(chars.iter().collect())
4420 }
4421
4422 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4423 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4424 self.transact(window, cx, |this, window, cx| {
4425 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4426 let selections = this.selections.all::<usize>(cx);
4427 let multi_buffer = this.buffer.read(cx);
4428 let buffer = multi_buffer.snapshot(cx);
4429 selections
4430 .iter()
4431 .map(|selection| {
4432 let start_point = selection.start.to_point(&buffer);
4433 let mut existing_indent =
4434 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4435 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4436 let start = selection.start;
4437 let end = selection.end;
4438 let selection_is_empty = start == end;
4439 let language_scope = buffer.language_scope_at(start);
4440 let (
4441 comment_delimiter,
4442 doc_delimiter,
4443 insert_extra_newline,
4444 indent_on_newline,
4445 indent_on_extra_newline,
4446 ) = if let Some(language) = &language_scope {
4447 let mut insert_extra_newline =
4448 insert_extra_newline_brackets(&buffer, start..end, language)
4449 || insert_extra_newline_tree_sitter(&buffer, start..end);
4450
4451 // Comment extension on newline is allowed only for cursor selections
4452 let comment_delimiter = maybe!({
4453 if !selection_is_empty {
4454 return None;
4455 }
4456
4457 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4458 return None;
4459 }
4460
4461 let delimiters = language.line_comment_prefixes();
4462 let max_len_of_delimiter =
4463 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4464 let (snapshot, range) =
4465 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4466
4467 let num_of_whitespaces = snapshot
4468 .chars_for_range(range.clone())
4469 .take_while(|c| c.is_whitespace())
4470 .count();
4471 let comment_candidate = snapshot
4472 .chars_for_range(range.clone())
4473 .skip(num_of_whitespaces)
4474 .take(max_len_of_delimiter)
4475 .collect::<String>();
4476 let (delimiter, trimmed_len) = delimiters
4477 .iter()
4478 .filter_map(|delimiter| {
4479 let prefix = delimiter.trim_end();
4480 if comment_candidate.starts_with(prefix) {
4481 Some((delimiter, prefix.len()))
4482 } else {
4483 None
4484 }
4485 })
4486 .max_by_key(|(_, len)| *len)?;
4487
4488 if let Some(BlockCommentConfig {
4489 start: block_start, ..
4490 }) = language.block_comment()
4491 {
4492 let block_start_trimmed = block_start.trim_end();
4493 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4494 let line_content = snapshot
4495 .chars_for_range(range)
4496 .skip(num_of_whitespaces)
4497 .take(block_start_trimmed.len())
4498 .collect::<String>();
4499
4500 if line_content.starts_with(block_start_trimmed) {
4501 return None;
4502 }
4503 }
4504 }
4505
4506 let cursor_is_placed_after_comment_marker =
4507 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4508 if cursor_is_placed_after_comment_marker {
4509 Some(delimiter.clone())
4510 } else {
4511 None
4512 }
4513 });
4514
4515 let mut indent_on_newline = IndentSize::spaces(0);
4516 let mut indent_on_extra_newline = IndentSize::spaces(0);
4517
4518 let doc_delimiter = maybe!({
4519 if !selection_is_empty {
4520 return None;
4521 }
4522
4523 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4524 return None;
4525 }
4526
4527 let BlockCommentConfig {
4528 start: start_tag,
4529 end: end_tag,
4530 prefix: delimiter,
4531 tab_size: len,
4532 } = language.documentation_comment()?;
4533 let is_within_block_comment = buffer
4534 .language_scope_at(start_point)
4535 .is_some_and(|scope| scope.override_name() == Some("comment"));
4536 if !is_within_block_comment {
4537 return None;
4538 }
4539
4540 let (snapshot, range) =
4541 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4542
4543 let num_of_whitespaces = snapshot
4544 .chars_for_range(range.clone())
4545 .take_while(|c| c.is_whitespace())
4546 .count();
4547
4548 // 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.
4549 let column = start_point.column;
4550 let cursor_is_after_start_tag = {
4551 let start_tag_len = start_tag.len();
4552 let start_tag_line = snapshot
4553 .chars_for_range(range.clone())
4554 .skip(num_of_whitespaces)
4555 .take(start_tag_len)
4556 .collect::<String>();
4557 if start_tag_line.starts_with(start_tag.as_ref()) {
4558 num_of_whitespaces + start_tag_len <= column as usize
4559 } else {
4560 false
4561 }
4562 };
4563
4564 let cursor_is_after_delimiter = {
4565 let delimiter_trim = delimiter.trim_end();
4566 let delimiter_line = snapshot
4567 .chars_for_range(range.clone())
4568 .skip(num_of_whitespaces)
4569 .take(delimiter_trim.len())
4570 .collect::<String>();
4571 if delimiter_line.starts_with(delimiter_trim) {
4572 num_of_whitespaces + delimiter_trim.len() <= column as usize
4573 } else {
4574 false
4575 }
4576 };
4577
4578 let cursor_is_before_end_tag_if_exists = {
4579 let mut char_position = 0u32;
4580 let mut end_tag_offset = None;
4581
4582 'outer: for chunk in snapshot.text_for_range(range) {
4583 if let Some(byte_pos) = chunk.find(&**end_tag) {
4584 let chars_before_match =
4585 chunk[..byte_pos].chars().count() as u32;
4586 end_tag_offset =
4587 Some(char_position + chars_before_match);
4588 break 'outer;
4589 }
4590 char_position += chunk.chars().count() as u32;
4591 }
4592
4593 if let Some(end_tag_offset) = end_tag_offset {
4594 let cursor_is_before_end_tag = column <= end_tag_offset;
4595 if cursor_is_after_start_tag {
4596 if cursor_is_before_end_tag {
4597 insert_extra_newline = true;
4598 }
4599 let cursor_is_at_start_of_end_tag =
4600 column == end_tag_offset;
4601 if cursor_is_at_start_of_end_tag {
4602 indent_on_extra_newline.len = *len;
4603 }
4604 }
4605 cursor_is_before_end_tag
4606 } else {
4607 true
4608 }
4609 };
4610
4611 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4612 && cursor_is_before_end_tag_if_exists
4613 {
4614 if cursor_is_after_start_tag {
4615 indent_on_newline.len = *len;
4616 }
4617 Some(delimiter.clone())
4618 } else {
4619 None
4620 }
4621 });
4622
4623 (
4624 comment_delimiter,
4625 doc_delimiter,
4626 insert_extra_newline,
4627 indent_on_newline,
4628 indent_on_extra_newline,
4629 )
4630 } else {
4631 (
4632 None,
4633 None,
4634 false,
4635 IndentSize::default(),
4636 IndentSize::default(),
4637 )
4638 };
4639
4640 let prevent_auto_indent = doc_delimiter.is_some();
4641 let delimiter = comment_delimiter.or(doc_delimiter);
4642
4643 let capacity_for_delimiter =
4644 delimiter.as_deref().map(str::len).unwrap_or_default();
4645 let mut new_text = String::with_capacity(
4646 1 + capacity_for_delimiter
4647 + existing_indent.len as usize
4648 + indent_on_newline.len as usize
4649 + indent_on_extra_newline.len as usize,
4650 );
4651 new_text.push('\n');
4652 new_text.extend(existing_indent.chars());
4653 new_text.extend(indent_on_newline.chars());
4654
4655 if let Some(delimiter) = &delimiter {
4656 new_text.push_str(delimiter);
4657 }
4658
4659 if insert_extra_newline {
4660 new_text.push('\n');
4661 new_text.extend(existing_indent.chars());
4662 new_text.extend(indent_on_extra_newline.chars());
4663 }
4664
4665 let anchor = buffer.anchor_after(end);
4666 let new_selection = selection.map(|_| anchor);
4667 (
4668 ((start..end, new_text), prevent_auto_indent),
4669 (insert_extra_newline, new_selection),
4670 )
4671 })
4672 .unzip()
4673 };
4674
4675 let mut auto_indent_edits = Vec::new();
4676 let mut edits = Vec::new();
4677 for (edit, prevent_auto_indent) in edits_with_flags {
4678 if prevent_auto_indent {
4679 edits.push(edit);
4680 } else {
4681 auto_indent_edits.push(edit);
4682 }
4683 }
4684 if !edits.is_empty() {
4685 this.edit(edits, cx);
4686 }
4687 if !auto_indent_edits.is_empty() {
4688 this.edit_with_autoindent(auto_indent_edits, cx);
4689 }
4690
4691 let buffer = this.buffer.read(cx).snapshot(cx);
4692 let new_selections = selection_info
4693 .into_iter()
4694 .map(|(extra_newline_inserted, new_selection)| {
4695 let mut cursor = new_selection.end.to_point(&buffer);
4696 if extra_newline_inserted {
4697 cursor.row -= 1;
4698 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4699 }
4700 new_selection.map(|_| cursor)
4701 })
4702 .collect();
4703
4704 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4705 this.refresh_edit_prediction(true, false, window, cx);
4706 });
4707 }
4708
4709 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4710 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4711
4712 let buffer = self.buffer.read(cx);
4713 let snapshot = buffer.snapshot(cx);
4714
4715 let mut edits = Vec::new();
4716 let mut rows = Vec::new();
4717
4718 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4719 let cursor = selection.head();
4720 let row = cursor.row;
4721
4722 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4723
4724 let newline = "\n".to_string();
4725 edits.push((start_of_line..start_of_line, newline));
4726
4727 rows.push(row + rows_inserted as u32);
4728 }
4729
4730 self.transact(window, cx, |editor, window, cx| {
4731 editor.edit(edits, cx);
4732
4733 editor.change_selections(Default::default(), window, cx, |s| {
4734 let mut index = 0;
4735 s.move_cursors_with(|map, _, _| {
4736 let row = rows[index];
4737 index += 1;
4738
4739 let point = Point::new(row, 0);
4740 let boundary = map.next_line_boundary(point).1;
4741 let clipped = map.clip_point(boundary, Bias::Left);
4742
4743 (clipped, SelectionGoal::None)
4744 });
4745 });
4746
4747 let mut indent_edits = Vec::new();
4748 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4749 for row in rows {
4750 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4751 for (row, indent) in indents {
4752 if indent.len == 0 {
4753 continue;
4754 }
4755
4756 let text = match indent.kind {
4757 IndentKind::Space => " ".repeat(indent.len as usize),
4758 IndentKind::Tab => "\t".repeat(indent.len as usize),
4759 };
4760 let point = Point::new(row.0, 0);
4761 indent_edits.push((point..point, text));
4762 }
4763 }
4764 editor.edit(indent_edits, cx);
4765 });
4766 }
4767
4768 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4769 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4770
4771 let buffer = self.buffer.read(cx);
4772 let snapshot = buffer.snapshot(cx);
4773
4774 let mut edits = Vec::new();
4775 let mut rows = Vec::new();
4776 let mut rows_inserted = 0;
4777
4778 for selection in self.selections.all_adjusted(cx) {
4779 let cursor = selection.head();
4780 let row = cursor.row;
4781
4782 let point = Point::new(row + 1, 0);
4783 let start_of_line = snapshot.clip_point(point, Bias::Left);
4784
4785 let newline = "\n".to_string();
4786 edits.push((start_of_line..start_of_line, newline));
4787
4788 rows_inserted += 1;
4789 rows.push(row + rows_inserted);
4790 }
4791
4792 self.transact(window, cx, |editor, window, cx| {
4793 editor.edit(edits, cx);
4794
4795 editor.change_selections(Default::default(), window, cx, |s| {
4796 let mut index = 0;
4797 s.move_cursors_with(|map, _, _| {
4798 let row = rows[index];
4799 index += 1;
4800
4801 let point = Point::new(row, 0);
4802 let boundary = map.next_line_boundary(point).1;
4803 let clipped = map.clip_point(boundary, Bias::Left);
4804
4805 (clipped, SelectionGoal::None)
4806 });
4807 });
4808
4809 let mut indent_edits = Vec::new();
4810 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4811 for row in rows {
4812 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4813 for (row, indent) in indents {
4814 if indent.len == 0 {
4815 continue;
4816 }
4817
4818 let text = match indent.kind {
4819 IndentKind::Space => " ".repeat(indent.len as usize),
4820 IndentKind::Tab => "\t".repeat(indent.len as usize),
4821 };
4822 let point = Point::new(row.0, 0);
4823 indent_edits.push((point..point, text));
4824 }
4825 }
4826 editor.edit(indent_edits, cx);
4827 });
4828 }
4829
4830 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4831 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4832 original_indent_columns: Vec::new(),
4833 });
4834 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4835 }
4836
4837 fn insert_with_autoindent_mode(
4838 &mut self,
4839 text: &str,
4840 autoindent_mode: Option<AutoindentMode>,
4841 window: &mut Window,
4842 cx: &mut Context<Self>,
4843 ) {
4844 if self.read_only(cx) {
4845 return;
4846 }
4847
4848 let text: Arc<str> = text.into();
4849 self.transact(window, cx, |this, window, cx| {
4850 let old_selections = this.selections.all_adjusted(cx);
4851 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4852 let anchors = {
4853 let snapshot = buffer.read(cx);
4854 old_selections
4855 .iter()
4856 .map(|s| {
4857 let anchor = snapshot.anchor_after(s.head());
4858 s.map(|_| anchor)
4859 })
4860 .collect::<Vec<_>>()
4861 };
4862 buffer.edit(
4863 old_selections
4864 .iter()
4865 .map(|s| (s.start..s.end, text.clone())),
4866 autoindent_mode,
4867 cx,
4868 );
4869 anchors
4870 });
4871
4872 this.change_selections(Default::default(), window, cx, |s| {
4873 s.select_anchors(selection_anchors);
4874 });
4875
4876 cx.notify();
4877 });
4878 }
4879
4880 fn trigger_completion_on_input(
4881 &mut self,
4882 text: &str,
4883 trigger_in_words: bool,
4884 window: &mut Window,
4885 cx: &mut Context<Self>,
4886 ) {
4887 let completions_source = self
4888 .context_menu
4889 .borrow()
4890 .as_ref()
4891 .and_then(|menu| match menu {
4892 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4893 CodeContextMenu::CodeActions(_) => None,
4894 });
4895
4896 match completions_source {
4897 Some(CompletionsMenuSource::Words) => {
4898 self.show_word_completions(&ShowWordCompletions, window, cx)
4899 }
4900 Some(CompletionsMenuSource::Normal)
4901 | Some(CompletionsMenuSource::SnippetChoices)
4902 | None
4903 if self.is_completion_trigger(
4904 text,
4905 trigger_in_words,
4906 completions_source.is_some(),
4907 cx,
4908 ) =>
4909 {
4910 self.show_completions(
4911 &ShowCompletions {
4912 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4913 },
4914 window,
4915 cx,
4916 )
4917 }
4918 _ => {
4919 self.hide_context_menu(window, cx);
4920 }
4921 }
4922 }
4923
4924 fn is_completion_trigger(
4925 &self,
4926 text: &str,
4927 trigger_in_words: bool,
4928 menu_is_open: bool,
4929 cx: &mut Context<Self>,
4930 ) -> bool {
4931 let position = self.selections.newest_anchor().head();
4932 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4933 return false;
4934 };
4935
4936 if let Some(completion_provider) = &self.completion_provider {
4937 completion_provider.is_completion_trigger(
4938 &buffer,
4939 position.text_anchor,
4940 text,
4941 trigger_in_words,
4942 menu_is_open,
4943 cx,
4944 )
4945 } else {
4946 false
4947 }
4948 }
4949
4950 /// If any empty selections is touching the start of its innermost containing autoclose
4951 /// region, expand it to select the brackets.
4952 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4953 let selections = self.selections.all::<usize>(cx);
4954 let buffer = self.buffer.read(cx).read(cx);
4955 let new_selections = self
4956 .selections_with_autoclose_regions(selections, &buffer)
4957 .map(|(mut selection, region)| {
4958 if !selection.is_empty() {
4959 return selection;
4960 }
4961
4962 if let Some(region) = region {
4963 let mut range = region.range.to_offset(&buffer);
4964 if selection.start == range.start && range.start >= region.pair.start.len() {
4965 range.start -= region.pair.start.len();
4966 if buffer.contains_str_at(range.start, ®ion.pair.start)
4967 && buffer.contains_str_at(range.end, ®ion.pair.end)
4968 {
4969 range.end += region.pair.end.len();
4970 selection.start = range.start;
4971 selection.end = range.end;
4972
4973 return selection;
4974 }
4975 }
4976 }
4977
4978 let always_treat_brackets_as_autoclosed = buffer
4979 .language_settings_at(selection.start, cx)
4980 .always_treat_brackets_as_autoclosed;
4981
4982 if !always_treat_brackets_as_autoclosed {
4983 return selection;
4984 }
4985
4986 if let Some(scope) = buffer.language_scope_at(selection.start) {
4987 for (pair, enabled) in scope.brackets() {
4988 if !enabled || !pair.close {
4989 continue;
4990 }
4991
4992 if buffer.contains_str_at(selection.start, &pair.end) {
4993 let pair_start_len = pair.start.len();
4994 if buffer.contains_str_at(
4995 selection.start.saturating_sub(pair_start_len),
4996 &pair.start,
4997 ) {
4998 selection.start -= pair_start_len;
4999 selection.end += pair.end.len();
5000
5001 return selection;
5002 }
5003 }
5004 }
5005 }
5006
5007 selection
5008 })
5009 .collect();
5010
5011 drop(buffer);
5012 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5013 selections.select(new_selections)
5014 });
5015 }
5016
5017 /// Iterate the given selections, and for each one, find the smallest surrounding
5018 /// autoclose region. This uses the ordering of the selections and the autoclose
5019 /// regions to avoid repeated comparisons.
5020 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5021 &'a self,
5022 selections: impl IntoIterator<Item = Selection<D>>,
5023 buffer: &'a MultiBufferSnapshot,
5024 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5025 let mut i = 0;
5026 let mut regions = self.autoclose_regions.as_slice();
5027 selections.into_iter().map(move |selection| {
5028 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5029
5030 let mut enclosing = None;
5031 while let Some(pair_state) = regions.get(i) {
5032 if pair_state.range.end.to_offset(buffer) < range.start {
5033 regions = ®ions[i + 1..];
5034 i = 0;
5035 } else if pair_state.range.start.to_offset(buffer) > range.end {
5036 break;
5037 } else {
5038 if pair_state.selection_id == selection.id {
5039 enclosing = Some(pair_state);
5040 }
5041 i += 1;
5042 }
5043 }
5044
5045 (selection, enclosing)
5046 })
5047 }
5048
5049 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5050 fn invalidate_autoclose_regions(
5051 &mut self,
5052 mut selections: &[Selection<Anchor>],
5053 buffer: &MultiBufferSnapshot,
5054 ) {
5055 self.autoclose_regions.retain(|state| {
5056 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5057 return false;
5058 }
5059
5060 let mut i = 0;
5061 while let Some(selection) = selections.get(i) {
5062 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5063 selections = &selections[1..];
5064 continue;
5065 }
5066 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5067 break;
5068 }
5069 if selection.id == state.selection_id {
5070 return true;
5071 } else {
5072 i += 1;
5073 }
5074 }
5075 false
5076 });
5077 }
5078
5079 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5080 let offset = position.to_offset(buffer);
5081 let (word_range, kind) = buffer.surrounding_word(offset, true);
5082 if offset > word_range.start && kind == Some(CharKind::Word) {
5083 Some(
5084 buffer
5085 .text_for_range(word_range.start..offset)
5086 .collect::<String>(),
5087 )
5088 } else {
5089 None
5090 }
5091 }
5092
5093 pub fn toggle_inline_values(
5094 &mut self,
5095 _: &ToggleInlineValues,
5096 _: &mut Window,
5097 cx: &mut Context<Self>,
5098 ) {
5099 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5100
5101 self.refresh_inline_values(cx);
5102 }
5103
5104 pub fn toggle_inlay_hints(
5105 &mut self,
5106 _: &ToggleInlayHints,
5107 _: &mut Window,
5108 cx: &mut Context<Self>,
5109 ) {
5110 self.refresh_inlay_hints(
5111 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5112 cx,
5113 );
5114 }
5115
5116 pub fn inlay_hints_enabled(&self) -> bool {
5117 self.inlay_hint_cache.enabled
5118 }
5119
5120 pub fn inline_values_enabled(&self) -> bool {
5121 self.inline_value_cache.enabled
5122 }
5123
5124 #[cfg(any(test, feature = "test-support"))]
5125 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5126 self.display_map
5127 .read(cx)
5128 .current_inlays()
5129 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5130 .cloned()
5131 .collect()
5132 }
5133
5134 #[cfg(any(test, feature = "test-support"))]
5135 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5136 self.display_map
5137 .read(cx)
5138 .current_inlays()
5139 .cloned()
5140 .collect()
5141 }
5142
5143 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5144 if self.semantics_provider.is_none() || !self.mode.is_full() {
5145 return;
5146 }
5147
5148 let reason_description = reason.description();
5149 let ignore_debounce = matches!(
5150 reason,
5151 InlayHintRefreshReason::SettingsChange(_)
5152 | InlayHintRefreshReason::Toggle(_)
5153 | InlayHintRefreshReason::ExcerptsRemoved(_)
5154 | InlayHintRefreshReason::ModifiersChanged(_)
5155 );
5156 let (invalidate_cache, required_languages) = match reason {
5157 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5158 match self.inlay_hint_cache.modifiers_override(enabled) {
5159 Some(enabled) => {
5160 if enabled {
5161 (InvalidationStrategy::RefreshRequested, None)
5162 } else {
5163 self.splice_inlays(
5164 &self
5165 .visible_inlay_hints(cx)
5166 .iter()
5167 .map(|inlay| inlay.id)
5168 .collect::<Vec<InlayId>>(),
5169 Vec::new(),
5170 cx,
5171 );
5172 return;
5173 }
5174 }
5175 None => return,
5176 }
5177 }
5178 InlayHintRefreshReason::Toggle(enabled) => {
5179 if self.inlay_hint_cache.toggle(enabled) {
5180 if enabled {
5181 (InvalidationStrategy::RefreshRequested, None)
5182 } else {
5183 self.splice_inlays(
5184 &self
5185 .visible_inlay_hints(cx)
5186 .iter()
5187 .map(|inlay| inlay.id)
5188 .collect::<Vec<InlayId>>(),
5189 Vec::new(),
5190 cx,
5191 );
5192 return;
5193 }
5194 } else {
5195 return;
5196 }
5197 }
5198 InlayHintRefreshReason::SettingsChange(new_settings) => {
5199 match self.inlay_hint_cache.update_settings(
5200 &self.buffer,
5201 new_settings,
5202 self.visible_inlay_hints(cx),
5203 cx,
5204 ) {
5205 ControlFlow::Break(Some(InlaySplice {
5206 to_remove,
5207 to_insert,
5208 })) => {
5209 self.splice_inlays(&to_remove, to_insert, cx);
5210 return;
5211 }
5212 ControlFlow::Break(None) => return,
5213 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5214 }
5215 }
5216 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5217 if let Some(InlaySplice {
5218 to_remove,
5219 to_insert,
5220 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5221 {
5222 self.splice_inlays(&to_remove, to_insert, cx);
5223 }
5224 self.display_map.update(cx, |display_map, _| {
5225 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5226 });
5227 return;
5228 }
5229 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5230 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5231 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5232 }
5233 InlayHintRefreshReason::RefreshRequested => {
5234 (InvalidationStrategy::RefreshRequested, None)
5235 }
5236 };
5237
5238 if let Some(InlaySplice {
5239 to_remove,
5240 to_insert,
5241 }) = self.inlay_hint_cache.spawn_hint_refresh(
5242 reason_description,
5243 self.visible_excerpts(required_languages.as_ref(), cx),
5244 invalidate_cache,
5245 ignore_debounce,
5246 cx,
5247 ) {
5248 self.splice_inlays(&to_remove, to_insert, cx);
5249 }
5250 }
5251
5252 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5253 self.display_map
5254 .read(cx)
5255 .current_inlays()
5256 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5257 .cloned()
5258 .collect()
5259 }
5260
5261 pub fn visible_excerpts(
5262 &self,
5263 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5264 cx: &mut Context<Editor>,
5265 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5266 let Some(project) = self.project() else {
5267 return HashMap::default();
5268 };
5269 let project = project.read(cx);
5270 let multi_buffer = self.buffer().read(cx);
5271 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5272 let multi_buffer_visible_start = self
5273 .scroll_manager
5274 .anchor()
5275 .anchor
5276 .to_point(&multi_buffer_snapshot);
5277 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5278 multi_buffer_visible_start
5279 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5280 Bias::Left,
5281 );
5282 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5283 multi_buffer_snapshot
5284 .range_to_buffer_ranges(multi_buffer_visible_range)
5285 .into_iter()
5286 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5287 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5288 let buffer_file = project::File::from_dyn(buffer.file())?;
5289 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5290 let worktree_entry = buffer_worktree
5291 .read(cx)
5292 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5293 if worktree_entry.is_ignored {
5294 return None;
5295 }
5296
5297 let language = buffer.language()?;
5298 if let Some(restrict_to_languages) = restrict_to_languages
5299 && !restrict_to_languages.contains(language)
5300 {
5301 return None;
5302 }
5303 Some((
5304 excerpt_id,
5305 (
5306 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5307 buffer.version().clone(),
5308 excerpt_visible_range,
5309 ),
5310 ))
5311 })
5312 .collect()
5313 }
5314
5315 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5316 TextLayoutDetails {
5317 text_system: window.text_system().clone(),
5318 editor_style: self.style.clone().unwrap(),
5319 rem_size: window.rem_size(),
5320 scroll_anchor: self.scroll_manager.anchor(),
5321 visible_rows: self.visible_line_count(),
5322 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5323 }
5324 }
5325
5326 pub fn splice_inlays(
5327 &self,
5328 to_remove: &[InlayId],
5329 to_insert: Vec<Inlay>,
5330 cx: &mut Context<Self>,
5331 ) {
5332 self.display_map.update(cx, |display_map, cx| {
5333 display_map.splice_inlays(to_remove, to_insert, cx)
5334 });
5335 cx.notify();
5336 }
5337
5338 fn trigger_on_type_formatting(
5339 &self,
5340 input: String,
5341 window: &mut Window,
5342 cx: &mut Context<Self>,
5343 ) -> Option<Task<Result<()>>> {
5344 if input.len() != 1 {
5345 return None;
5346 }
5347
5348 let project = self.project()?;
5349 let position = self.selections.newest_anchor().head();
5350 let (buffer, buffer_position) = self
5351 .buffer
5352 .read(cx)
5353 .text_anchor_for_position(position, cx)?;
5354
5355 let settings = language_settings::language_settings(
5356 buffer
5357 .read(cx)
5358 .language_at(buffer_position)
5359 .map(|l| l.name()),
5360 buffer.read(cx).file(),
5361 cx,
5362 );
5363 if !settings.use_on_type_format {
5364 return None;
5365 }
5366
5367 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5368 // hence we do LSP request & edit on host side only — add formats to host's history.
5369 let push_to_lsp_host_history = true;
5370 // If this is not the host, append its history with new edits.
5371 let push_to_client_history = project.read(cx).is_via_collab();
5372
5373 let on_type_formatting = project.update(cx, |project, cx| {
5374 project.on_type_format(
5375 buffer.clone(),
5376 buffer_position,
5377 input,
5378 push_to_lsp_host_history,
5379 cx,
5380 )
5381 });
5382 Some(cx.spawn_in(window, async move |editor, cx| {
5383 if let Some(transaction) = on_type_formatting.await? {
5384 if push_to_client_history {
5385 buffer
5386 .update(cx, |buffer, _| {
5387 buffer.push_transaction(transaction, Instant::now());
5388 buffer.finalize_last_transaction();
5389 })
5390 .ok();
5391 }
5392 editor.update(cx, |editor, cx| {
5393 editor.refresh_document_highlights(cx);
5394 })?;
5395 }
5396 Ok(())
5397 }))
5398 }
5399
5400 pub fn show_word_completions(
5401 &mut self,
5402 _: &ShowWordCompletions,
5403 window: &mut Window,
5404 cx: &mut Context<Self>,
5405 ) {
5406 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5407 }
5408
5409 pub fn show_completions(
5410 &mut self,
5411 options: &ShowCompletions,
5412 window: &mut Window,
5413 cx: &mut Context<Self>,
5414 ) {
5415 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5416 }
5417
5418 fn open_or_update_completions_menu(
5419 &mut self,
5420 requested_source: Option<CompletionsMenuSource>,
5421 trigger: Option<&str>,
5422 window: &mut Window,
5423 cx: &mut Context<Self>,
5424 ) {
5425 if self.pending_rename.is_some() {
5426 return;
5427 }
5428
5429 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5430
5431 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5432 // inserted and selected. To handle that case, the start of the selection is used so that
5433 // the menu starts with all choices.
5434 let position = self
5435 .selections
5436 .newest_anchor()
5437 .start
5438 .bias_right(&multibuffer_snapshot);
5439 if position.diff_base_anchor.is_some() {
5440 return;
5441 }
5442 let (buffer, buffer_position) =
5443 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5444 output
5445 } else {
5446 return;
5447 };
5448 let buffer_snapshot = buffer.read(cx).snapshot();
5449
5450 let query: Option<Arc<String>> =
5451 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5452
5453 drop(multibuffer_snapshot);
5454
5455 let provider = match requested_source {
5456 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5457 Some(CompletionsMenuSource::Words) => None,
5458 Some(CompletionsMenuSource::SnippetChoices) => {
5459 log::error!("bug: SnippetChoices requested_source is not handled");
5460 None
5461 }
5462 };
5463
5464 let sort_completions = provider
5465 .as_ref()
5466 .is_some_and(|provider| provider.sort_completions());
5467
5468 let filter_completions = provider
5469 .as_ref()
5470 .is_none_or(|provider| provider.filter_completions());
5471
5472 let trigger_kind = match trigger {
5473 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5474 CompletionTriggerKind::TRIGGER_CHARACTER
5475 }
5476 _ => CompletionTriggerKind::INVOKED,
5477 };
5478 let completion_context = CompletionContext {
5479 trigger_character: trigger.and_then(|trigger| {
5480 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5481 Some(String::from(trigger))
5482 } else {
5483 None
5484 }
5485 }),
5486 trigger_kind,
5487 };
5488
5489 // Hide the current completions menu when a trigger char is typed. Without this, cached
5490 // completions from before the trigger char may be reused (#32774). Snippet choices could
5491 // involve trigger chars, so this is skipped in that case.
5492 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5493 {
5494 let menu_is_open = matches!(
5495 self.context_menu.borrow().as_ref(),
5496 Some(CodeContextMenu::Completions(_))
5497 );
5498 if menu_is_open {
5499 self.hide_context_menu(window, cx);
5500 }
5501 }
5502
5503 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5504 if filter_completions {
5505 menu.filter(query.clone(), provider.clone(), window, cx);
5506 }
5507 // When `is_incomplete` is false, no need to re-query completions when the current query
5508 // is a suffix of the initial query.
5509 if !menu.is_incomplete {
5510 // If the new query is a suffix of the old query (typing more characters) and
5511 // the previous result was complete, the existing completions can be filtered.
5512 //
5513 // Note that this is always true for snippet completions.
5514 let query_matches = match (&menu.initial_query, &query) {
5515 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5516 (None, _) => true,
5517 _ => false,
5518 };
5519 if query_matches {
5520 let position_matches = if menu.initial_position == position {
5521 true
5522 } else {
5523 let snapshot = self.buffer.read(cx).read(cx);
5524 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5525 };
5526 if position_matches {
5527 return;
5528 }
5529 }
5530 }
5531 };
5532
5533 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5534 buffer_snapshot.surrounding_word(buffer_position, false)
5535 {
5536 let word_to_exclude = buffer_snapshot
5537 .text_for_range(word_range.clone())
5538 .collect::<String>();
5539 (
5540 buffer_snapshot.anchor_before(word_range.start)
5541 ..buffer_snapshot.anchor_after(buffer_position),
5542 Some(word_to_exclude),
5543 )
5544 } else {
5545 (buffer_position..buffer_position, None)
5546 };
5547
5548 let language = buffer_snapshot
5549 .language_at(buffer_position)
5550 .map(|language| language.name());
5551
5552 let completion_settings =
5553 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5554
5555 let show_completion_documentation = buffer_snapshot
5556 .settings_at(buffer_position, cx)
5557 .show_completion_documentation;
5558
5559 // The document can be large, so stay in reasonable bounds when searching for words,
5560 // otherwise completion pop-up might be slow to appear.
5561 const WORD_LOOKUP_ROWS: u32 = 5_000;
5562 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5563 let min_word_search = buffer_snapshot.clip_point(
5564 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5565 Bias::Left,
5566 );
5567 let max_word_search = buffer_snapshot.clip_point(
5568 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5569 Bias::Right,
5570 );
5571 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5572 ..buffer_snapshot.point_to_offset(max_word_search);
5573
5574 let skip_digits = query
5575 .as_ref()
5576 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5577
5578 let omit_word_completions = match &query {
5579 Some(query) => query.chars().count() < completion_settings.words_min_length,
5580 None => completion_settings.words_min_length != 0,
5581 };
5582
5583 let (mut words, provider_responses) = match &provider {
5584 Some(provider) => {
5585 let provider_responses = provider.completions(
5586 position.excerpt_id,
5587 &buffer,
5588 buffer_position,
5589 completion_context,
5590 window,
5591 cx,
5592 );
5593
5594 let words = match (omit_word_completions, completion_settings.words) {
5595 (true, _) | (_, WordsCompletionMode::Disabled) => {
5596 Task::ready(BTreeMap::default())
5597 }
5598 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5599 .background_spawn(async move {
5600 buffer_snapshot.words_in_range(WordsQuery {
5601 fuzzy_contents: None,
5602 range: word_search_range,
5603 skip_digits,
5604 })
5605 }),
5606 };
5607
5608 (words, provider_responses)
5609 }
5610 None => {
5611 let words = if omit_word_completions {
5612 Task::ready(BTreeMap::default())
5613 } else {
5614 cx.background_spawn(async move {
5615 buffer_snapshot.words_in_range(WordsQuery {
5616 fuzzy_contents: None,
5617 range: word_search_range,
5618 skip_digits,
5619 })
5620 })
5621 };
5622 (words, Task::ready(Ok(Vec::new())))
5623 }
5624 };
5625
5626 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5627
5628 let id = post_inc(&mut self.next_completion_id);
5629 let task = cx.spawn_in(window, async move |editor, cx| {
5630 let Ok(()) = editor.update(cx, |this, _| {
5631 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5632 }) else {
5633 return;
5634 };
5635
5636 // TODO: Ideally completions from different sources would be selectively re-queried, so
5637 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5638 let mut completions = Vec::new();
5639 let mut is_incomplete = false;
5640 let mut display_options: Option<CompletionDisplayOptions> = None;
5641 if let Some(provider_responses) = provider_responses.await.log_err()
5642 && !provider_responses.is_empty()
5643 {
5644 for response in provider_responses {
5645 completions.extend(response.completions);
5646 is_incomplete = is_incomplete || response.is_incomplete;
5647 match display_options.as_mut() {
5648 None => {
5649 display_options = Some(response.display_options);
5650 }
5651 Some(options) => options.merge(&response.display_options),
5652 }
5653 }
5654 if completion_settings.words == WordsCompletionMode::Fallback {
5655 words = Task::ready(BTreeMap::default());
5656 }
5657 }
5658 let display_options = display_options.unwrap_or_default();
5659
5660 let mut words = words.await;
5661 if let Some(word_to_exclude) = &word_to_exclude {
5662 words.remove(word_to_exclude);
5663 }
5664 for lsp_completion in &completions {
5665 words.remove(&lsp_completion.new_text);
5666 }
5667 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5668 replace_range: word_replace_range.clone(),
5669 new_text: word.clone(),
5670 label: CodeLabel::plain(word, None),
5671 icon_path: None,
5672 documentation: None,
5673 source: CompletionSource::BufferWord {
5674 word_range,
5675 resolved: false,
5676 },
5677 insert_text_mode: Some(InsertTextMode::AS_IS),
5678 confirm: None,
5679 }));
5680
5681 let menu = if completions.is_empty() {
5682 None
5683 } else {
5684 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5685 let languages = editor
5686 .workspace
5687 .as_ref()
5688 .and_then(|(workspace, _)| workspace.upgrade())
5689 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5690 let menu = CompletionsMenu::new(
5691 id,
5692 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5693 sort_completions,
5694 show_completion_documentation,
5695 position,
5696 query.clone(),
5697 is_incomplete,
5698 buffer.clone(),
5699 completions.into(),
5700 display_options,
5701 snippet_sort_order,
5702 languages,
5703 language,
5704 cx,
5705 );
5706
5707 let query = if filter_completions { query } else { None };
5708 let matches_task = if let Some(query) = query {
5709 menu.do_async_filtering(query, cx)
5710 } else {
5711 Task::ready(menu.unfiltered_matches())
5712 };
5713 (menu, matches_task)
5714 }) else {
5715 return;
5716 };
5717
5718 let matches = matches_task.await;
5719
5720 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5721 // Newer menu already set, so exit.
5722 if let Some(CodeContextMenu::Completions(prev_menu)) =
5723 editor.context_menu.borrow().as_ref()
5724 && prev_menu.id > id
5725 {
5726 return;
5727 };
5728
5729 // Only valid to take prev_menu because it the new menu is immediately set
5730 // below, or the menu is hidden.
5731 if let Some(CodeContextMenu::Completions(prev_menu)) =
5732 editor.context_menu.borrow_mut().take()
5733 {
5734 let position_matches =
5735 if prev_menu.initial_position == menu.initial_position {
5736 true
5737 } else {
5738 let snapshot = editor.buffer.read(cx).read(cx);
5739 prev_menu.initial_position.to_offset(&snapshot)
5740 == menu.initial_position.to_offset(&snapshot)
5741 };
5742 if position_matches {
5743 // Preserve markdown cache before `set_filter_results` because it will
5744 // try to populate the documentation cache.
5745 menu.preserve_markdown_cache(prev_menu);
5746 }
5747 };
5748
5749 menu.set_filter_results(matches, provider, window, cx);
5750 }) else {
5751 return;
5752 };
5753
5754 menu.visible().then_some(menu)
5755 };
5756
5757 editor
5758 .update_in(cx, |editor, window, cx| {
5759 if editor.focus_handle.is_focused(window)
5760 && let Some(menu) = menu
5761 {
5762 *editor.context_menu.borrow_mut() =
5763 Some(CodeContextMenu::Completions(menu));
5764
5765 crate::hover_popover::hide_hover(editor, cx);
5766 if editor.show_edit_predictions_in_menu() {
5767 editor.update_visible_edit_prediction(window, cx);
5768 } else {
5769 editor.discard_edit_prediction(false, cx);
5770 }
5771
5772 cx.notify();
5773 return;
5774 }
5775
5776 if editor.completion_tasks.len() <= 1 {
5777 // If there are no more completion tasks and the last menu was empty, we should hide it.
5778 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5779 // If it was already hidden and we don't show edit predictions in the menu,
5780 // we should also show the edit prediction when available.
5781 if was_hidden && editor.show_edit_predictions_in_menu() {
5782 editor.update_visible_edit_prediction(window, cx);
5783 }
5784 }
5785 })
5786 .ok();
5787 });
5788
5789 self.completion_tasks.push((id, task));
5790 }
5791
5792 #[cfg(feature = "test-support")]
5793 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5794 let menu = self.context_menu.borrow();
5795 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5796 let completions = menu.completions.borrow();
5797 Some(completions.to_vec())
5798 } else {
5799 None
5800 }
5801 }
5802
5803 pub fn with_completions_menu_matching_id<R>(
5804 &self,
5805 id: CompletionId,
5806 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5807 ) -> R {
5808 let mut context_menu = self.context_menu.borrow_mut();
5809 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5810 return f(None);
5811 };
5812 if completions_menu.id != id {
5813 return f(None);
5814 }
5815 f(Some(completions_menu))
5816 }
5817
5818 pub fn confirm_completion(
5819 &mut self,
5820 action: &ConfirmCompletion,
5821 window: &mut Window,
5822 cx: &mut Context<Self>,
5823 ) -> Option<Task<Result<()>>> {
5824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5825 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5826 }
5827
5828 pub fn confirm_completion_insert(
5829 &mut self,
5830 _: &ConfirmCompletionInsert,
5831 window: &mut Window,
5832 cx: &mut Context<Self>,
5833 ) -> Option<Task<Result<()>>> {
5834 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5835 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5836 }
5837
5838 pub fn confirm_completion_replace(
5839 &mut self,
5840 _: &ConfirmCompletionReplace,
5841 window: &mut Window,
5842 cx: &mut Context<Self>,
5843 ) -> Option<Task<Result<()>>> {
5844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5845 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5846 }
5847
5848 pub fn compose_completion(
5849 &mut self,
5850 action: &ComposeCompletion,
5851 window: &mut Window,
5852 cx: &mut Context<Self>,
5853 ) -> Option<Task<Result<()>>> {
5854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5855 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5856 }
5857
5858 fn do_completion(
5859 &mut self,
5860 item_ix: Option<usize>,
5861 intent: CompletionIntent,
5862 window: &mut Window,
5863 cx: &mut Context<Editor>,
5864 ) -> Option<Task<Result<()>>> {
5865 use language::ToOffset as _;
5866
5867 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5868 else {
5869 return None;
5870 };
5871
5872 let candidate_id = {
5873 let entries = completions_menu.entries.borrow();
5874 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5875 if self.show_edit_predictions_in_menu() {
5876 self.discard_edit_prediction(true, cx);
5877 }
5878 mat.candidate_id
5879 };
5880
5881 let completion = completions_menu
5882 .completions
5883 .borrow()
5884 .get(candidate_id)?
5885 .clone();
5886 cx.stop_propagation();
5887
5888 let buffer_handle = completions_menu.buffer.clone();
5889
5890 let CompletionEdit {
5891 new_text,
5892 snippet,
5893 replace_range,
5894 } = process_completion_for_edit(
5895 &completion,
5896 intent,
5897 &buffer_handle,
5898 &completions_menu.initial_position.text_anchor,
5899 cx,
5900 );
5901
5902 let buffer = buffer_handle.read(cx);
5903 let snapshot = self.buffer.read(cx).snapshot(cx);
5904 let newest_anchor = self.selections.newest_anchor();
5905 let replace_range_multibuffer = {
5906 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5907 let multibuffer_anchor = snapshot
5908 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5909 .unwrap()
5910 ..snapshot
5911 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5912 .unwrap();
5913 multibuffer_anchor.start.to_offset(&snapshot)
5914 ..multibuffer_anchor.end.to_offset(&snapshot)
5915 };
5916 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5917 return None;
5918 }
5919
5920 let old_text = buffer
5921 .text_for_range(replace_range.clone())
5922 .collect::<String>();
5923 let lookbehind = newest_anchor
5924 .start
5925 .text_anchor
5926 .to_offset(buffer)
5927 .saturating_sub(replace_range.start);
5928 let lookahead = replace_range
5929 .end
5930 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5931 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5932 let suffix = &old_text[lookbehind.min(old_text.len())..];
5933
5934 let selections = self.selections.all::<usize>(cx);
5935 let mut ranges = Vec::new();
5936 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5937
5938 for selection in &selections {
5939 let range = if selection.id == newest_anchor.id {
5940 replace_range_multibuffer.clone()
5941 } else {
5942 let mut range = selection.range();
5943
5944 // if prefix is present, don't duplicate it
5945 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5946 range.start = range.start.saturating_sub(lookbehind);
5947
5948 // if suffix is also present, mimic the newest cursor and replace it
5949 if selection.id != newest_anchor.id
5950 && snapshot.contains_str_at(range.end, suffix)
5951 {
5952 range.end += lookahead;
5953 }
5954 }
5955 range
5956 };
5957
5958 ranges.push(range.clone());
5959
5960 if !self.linked_edit_ranges.is_empty() {
5961 let start_anchor = snapshot.anchor_before(range.start);
5962 let end_anchor = snapshot.anchor_after(range.end);
5963 if let Some(ranges) = self
5964 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5965 {
5966 for (buffer, edits) in ranges {
5967 linked_edits
5968 .entry(buffer.clone())
5969 .or_default()
5970 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5971 }
5972 }
5973 }
5974 }
5975
5976 let common_prefix_len = old_text
5977 .chars()
5978 .zip(new_text.chars())
5979 .take_while(|(a, b)| a == b)
5980 .map(|(a, _)| a.len_utf8())
5981 .sum::<usize>();
5982
5983 cx.emit(EditorEvent::InputHandled {
5984 utf16_range_to_replace: None,
5985 text: new_text[common_prefix_len..].into(),
5986 });
5987
5988 self.transact(window, cx, |editor, window, cx| {
5989 if let Some(mut snippet) = snippet {
5990 snippet.text = new_text.to_string();
5991 editor
5992 .insert_snippet(&ranges, snippet, window, cx)
5993 .log_err();
5994 } else {
5995 editor.buffer.update(cx, |multi_buffer, cx| {
5996 let auto_indent = match completion.insert_text_mode {
5997 Some(InsertTextMode::AS_IS) => None,
5998 _ => editor.autoindent_mode.clone(),
5999 };
6000 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6001 multi_buffer.edit(edits, auto_indent, cx);
6002 });
6003 }
6004 for (buffer, edits) in linked_edits {
6005 buffer.update(cx, |buffer, cx| {
6006 let snapshot = buffer.snapshot();
6007 let edits = edits
6008 .into_iter()
6009 .map(|(range, text)| {
6010 use text::ToPoint as TP;
6011 let end_point = TP::to_point(&range.end, &snapshot);
6012 let start_point = TP::to_point(&range.start, &snapshot);
6013 (start_point..end_point, text)
6014 })
6015 .sorted_by_key(|(range, _)| range.start);
6016 buffer.edit(edits, None, cx);
6017 })
6018 }
6019
6020 editor.refresh_edit_prediction(true, false, window, cx);
6021 });
6022 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6023
6024 let show_new_completions_on_confirm = completion
6025 .confirm
6026 .as_ref()
6027 .is_some_and(|confirm| confirm(intent, window, cx));
6028 if show_new_completions_on_confirm {
6029 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6030 }
6031
6032 let provider = self.completion_provider.as_ref()?;
6033 drop(completion);
6034 let apply_edits = provider.apply_additional_edits_for_completion(
6035 buffer_handle,
6036 completions_menu.completions.clone(),
6037 candidate_id,
6038 true,
6039 cx,
6040 );
6041
6042 let editor_settings = EditorSettings::get_global(cx);
6043 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6044 // After the code completion is finished, users often want to know what signatures are needed.
6045 // so we should automatically call signature_help
6046 self.show_signature_help(&ShowSignatureHelp, window, cx);
6047 }
6048
6049 Some(cx.foreground_executor().spawn(async move {
6050 apply_edits.await?;
6051 Ok(())
6052 }))
6053 }
6054
6055 pub fn toggle_code_actions(
6056 &mut self,
6057 action: &ToggleCodeActions,
6058 window: &mut Window,
6059 cx: &mut Context<Self>,
6060 ) {
6061 let quick_launch = action.quick_launch;
6062 let mut context_menu = self.context_menu.borrow_mut();
6063 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6064 if code_actions.deployed_from == action.deployed_from {
6065 // Toggle if we're selecting the same one
6066 *context_menu = None;
6067 cx.notify();
6068 return;
6069 } else {
6070 // Otherwise, clear it and start a new one
6071 *context_menu = None;
6072 cx.notify();
6073 }
6074 }
6075 drop(context_menu);
6076 let snapshot = self.snapshot(window, cx);
6077 let deployed_from = action.deployed_from.clone();
6078 let action = action.clone();
6079 self.completion_tasks.clear();
6080 self.discard_edit_prediction(false, cx);
6081
6082 let multibuffer_point = match &action.deployed_from {
6083 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6084 DisplayPoint::new(*row, 0).to_point(&snapshot)
6085 }
6086 _ => self.selections.newest::<Point>(cx).head(),
6087 };
6088 let Some((buffer, buffer_row)) = snapshot
6089 .buffer_snapshot
6090 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6091 .and_then(|(buffer_snapshot, range)| {
6092 self.buffer()
6093 .read(cx)
6094 .buffer(buffer_snapshot.remote_id())
6095 .map(|buffer| (buffer, range.start.row))
6096 })
6097 else {
6098 return;
6099 };
6100 let buffer_id = buffer.read(cx).remote_id();
6101 let tasks = self
6102 .tasks
6103 .get(&(buffer_id, buffer_row))
6104 .map(|t| Arc::new(t.to_owned()));
6105
6106 if !self.focus_handle.is_focused(window) {
6107 return;
6108 }
6109 let project = self.project.clone();
6110
6111 let code_actions_task = match deployed_from {
6112 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6113 _ => self.code_actions(buffer_row, window, cx),
6114 };
6115
6116 let runnable_task = match deployed_from {
6117 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6118 _ => {
6119 let mut task_context_task = Task::ready(None);
6120 if let Some(tasks) = &tasks
6121 && let Some(project) = project
6122 {
6123 task_context_task =
6124 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6125 }
6126
6127 cx.spawn_in(window, {
6128 let buffer = buffer.clone();
6129 async move |editor, cx| {
6130 let task_context = task_context_task.await;
6131
6132 let resolved_tasks =
6133 tasks
6134 .zip(task_context.clone())
6135 .map(|(tasks, task_context)| ResolvedTasks {
6136 templates: tasks.resolve(&task_context).collect(),
6137 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6138 multibuffer_point.row,
6139 tasks.column,
6140 )),
6141 });
6142 let debug_scenarios = editor
6143 .update(cx, |editor, cx| {
6144 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6145 })?
6146 .await;
6147 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6148 }
6149 })
6150 }
6151 };
6152
6153 cx.spawn_in(window, async move |editor, cx| {
6154 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6155 let code_actions = code_actions_task.await;
6156 let spawn_straight_away = quick_launch
6157 && resolved_tasks
6158 .as_ref()
6159 .is_some_and(|tasks| tasks.templates.len() == 1)
6160 && code_actions
6161 .as_ref()
6162 .is_none_or(|actions| actions.is_empty())
6163 && debug_scenarios.is_empty();
6164
6165 editor.update_in(cx, |editor, window, cx| {
6166 crate::hover_popover::hide_hover(editor, cx);
6167 let actions = CodeActionContents::new(
6168 resolved_tasks,
6169 code_actions,
6170 debug_scenarios,
6171 task_context.unwrap_or_default(),
6172 );
6173
6174 // Don't show the menu if there are no actions available
6175 if actions.is_empty() {
6176 cx.notify();
6177 return Task::ready(Ok(()));
6178 }
6179
6180 *editor.context_menu.borrow_mut() =
6181 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6182 buffer,
6183 actions,
6184 selected_item: Default::default(),
6185 scroll_handle: UniformListScrollHandle::default(),
6186 deployed_from,
6187 }));
6188 cx.notify();
6189 if spawn_straight_away
6190 && let Some(task) = editor.confirm_code_action(
6191 &ConfirmCodeAction { item_ix: Some(0) },
6192 window,
6193 cx,
6194 )
6195 {
6196 return task;
6197 }
6198
6199 Task::ready(Ok(()))
6200 })
6201 })
6202 .detach_and_log_err(cx);
6203 }
6204
6205 fn debug_scenarios(
6206 &mut self,
6207 resolved_tasks: &Option<ResolvedTasks>,
6208 buffer: &Entity<Buffer>,
6209 cx: &mut App,
6210 ) -> Task<Vec<task::DebugScenario>> {
6211 maybe!({
6212 let project = self.project()?;
6213 let dap_store = project.read(cx).dap_store();
6214 let mut scenarios = vec![];
6215 let resolved_tasks = resolved_tasks.as_ref()?;
6216 let buffer = buffer.read(cx);
6217 let language = buffer.language()?;
6218 let file = buffer.file();
6219 let debug_adapter = language_settings(language.name().into(), file, cx)
6220 .debuggers
6221 .first()
6222 .map(SharedString::from)
6223 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6224
6225 dap_store.update(cx, |dap_store, cx| {
6226 for (_, task) in &resolved_tasks.templates {
6227 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6228 task.original_task().clone(),
6229 debug_adapter.clone().into(),
6230 task.display_label().to_owned().into(),
6231 cx,
6232 );
6233 scenarios.push(maybe_scenario);
6234 }
6235 });
6236 Some(cx.background_spawn(async move {
6237 futures::future::join_all(scenarios)
6238 .await
6239 .into_iter()
6240 .flatten()
6241 .collect::<Vec<_>>()
6242 }))
6243 })
6244 .unwrap_or_else(|| Task::ready(vec![]))
6245 }
6246
6247 fn code_actions(
6248 &mut self,
6249 buffer_row: u32,
6250 window: &mut Window,
6251 cx: &mut Context<Self>,
6252 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6253 let mut task = self.code_actions_task.take();
6254 cx.spawn_in(window, async move |editor, cx| {
6255 while let Some(prev_task) = task {
6256 prev_task.await.log_err();
6257 task = editor
6258 .update(cx, |this, _| this.code_actions_task.take())
6259 .ok()?;
6260 }
6261
6262 editor
6263 .update(cx, |editor, cx| {
6264 editor
6265 .available_code_actions
6266 .clone()
6267 .and_then(|(location, code_actions)| {
6268 let snapshot = location.buffer.read(cx).snapshot();
6269 let point_range = location.range.to_point(&snapshot);
6270 let point_range = point_range.start.row..=point_range.end.row;
6271 if point_range.contains(&buffer_row) {
6272 Some(code_actions)
6273 } else {
6274 None
6275 }
6276 })
6277 })
6278 .ok()
6279 .flatten()
6280 })
6281 }
6282
6283 pub fn confirm_code_action(
6284 &mut self,
6285 action: &ConfirmCodeAction,
6286 window: &mut Window,
6287 cx: &mut Context<Self>,
6288 ) -> Option<Task<Result<()>>> {
6289 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6290
6291 let actions_menu =
6292 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6293 menu
6294 } else {
6295 return None;
6296 };
6297
6298 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6299 let action = actions_menu.actions.get(action_ix)?;
6300 let title = action.label();
6301 let buffer = actions_menu.buffer;
6302 let workspace = self.workspace()?;
6303
6304 match action {
6305 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6306 workspace.update(cx, |workspace, cx| {
6307 workspace.schedule_resolved_task(
6308 task_source_kind,
6309 resolved_task,
6310 false,
6311 window,
6312 cx,
6313 );
6314
6315 Some(Task::ready(Ok(())))
6316 })
6317 }
6318 CodeActionsItem::CodeAction {
6319 excerpt_id,
6320 action,
6321 provider,
6322 } => {
6323 let apply_code_action =
6324 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6325 let workspace = workspace.downgrade();
6326 Some(cx.spawn_in(window, async move |editor, cx| {
6327 let project_transaction = apply_code_action.await?;
6328 Self::open_project_transaction(
6329 &editor,
6330 workspace,
6331 project_transaction,
6332 title,
6333 cx,
6334 )
6335 .await
6336 }))
6337 }
6338 CodeActionsItem::DebugScenario(scenario) => {
6339 let context = actions_menu.actions.context;
6340
6341 workspace.update(cx, |workspace, cx| {
6342 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6343 workspace.start_debug_session(
6344 scenario,
6345 context,
6346 Some(buffer),
6347 None,
6348 window,
6349 cx,
6350 );
6351 });
6352 Some(Task::ready(Ok(())))
6353 }
6354 }
6355 }
6356
6357 pub async fn open_project_transaction(
6358 editor: &WeakEntity<Editor>,
6359 workspace: WeakEntity<Workspace>,
6360 transaction: ProjectTransaction,
6361 title: String,
6362 cx: &mut AsyncWindowContext,
6363 ) -> Result<()> {
6364 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6365 cx.update(|_, cx| {
6366 entries.sort_unstable_by_key(|(buffer, _)| {
6367 buffer.read(cx).file().map(|f| f.path().clone())
6368 });
6369 })?;
6370
6371 // If the project transaction's edits are all contained within this editor, then
6372 // avoid opening a new editor to display them.
6373
6374 if let Some((buffer, transaction)) = entries.first() {
6375 if entries.len() == 1 {
6376 let excerpt = editor.update(cx, |editor, cx| {
6377 editor
6378 .buffer()
6379 .read(cx)
6380 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6381 })?;
6382 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6383 && excerpted_buffer == *buffer
6384 {
6385 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6386 let excerpt_range = excerpt_range.to_offset(buffer);
6387 buffer
6388 .edited_ranges_for_transaction::<usize>(transaction)
6389 .all(|range| {
6390 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6391 })
6392 })?;
6393
6394 if all_edits_within_excerpt {
6395 return Ok(());
6396 }
6397 }
6398 }
6399 } else {
6400 return Ok(());
6401 }
6402
6403 let mut ranges_to_highlight = Vec::new();
6404 let excerpt_buffer = cx.new(|cx| {
6405 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6406 for (buffer_handle, transaction) in &entries {
6407 let edited_ranges = buffer_handle
6408 .read(cx)
6409 .edited_ranges_for_transaction::<Point>(transaction)
6410 .collect::<Vec<_>>();
6411 let (ranges, _) = multibuffer.set_excerpts_for_path(
6412 PathKey::for_buffer(buffer_handle, cx),
6413 buffer_handle.clone(),
6414 edited_ranges,
6415 DEFAULT_MULTIBUFFER_CONTEXT,
6416 cx,
6417 );
6418
6419 ranges_to_highlight.extend(ranges);
6420 }
6421 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6422 multibuffer
6423 })?;
6424
6425 workspace.update_in(cx, |workspace, window, cx| {
6426 let project = workspace.project().clone();
6427 let editor =
6428 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6429 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6430 editor.update(cx, |editor, cx| {
6431 editor.highlight_background::<Self>(
6432 &ranges_to_highlight,
6433 |theme| theme.colors().editor_highlighted_line_background,
6434 cx,
6435 );
6436 });
6437 })?;
6438
6439 Ok(())
6440 }
6441
6442 pub fn clear_code_action_providers(&mut self) {
6443 self.code_action_providers.clear();
6444 self.available_code_actions.take();
6445 }
6446
6447 pub fn add_code_action_provider(
6448 &mut self,
6449 provider: Rc<dyn CodeActionProvider>,
6450 window: &mut Window,
6451 cx: &mut Context<Self>,
6452 ) {
6453 if self
6454 .code_action_providers
6455 .iter()
6456 .any(|existing_provider| existing_provider.id() == provider.id())
6457 {
6458 return;
6459 }
6460
6461 self.code_action_providers.push(provider);
6462 self.refresh_code_actions(window, cx);
6463 }
6464
6465 pub fn remove_code_action_provider(
6466 &mut self,
6467 id: Arc<str>,
6468 window: &mut Window,
6469 cx: &mut Context<Self>,
6470 ) {
6471 self.code_action_providers
6472 .retain(|provider| provider.id() != id);
6473 self.refresh_code_actions(window, cx);
6474 }
6475
6476 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6477 !self.code_action_providers.is_empty()
6478 && EditorSettings::get_global(cx).toolbar.code_actions
6479 }
6480
6481 pub fn has_available_code_actions(&self) -> bool {
6482 self.available_code_actions
6483 .as_ref()
6484 .is_some_and(|(_, actions)| !actions.is_empty())
6485 }
6486
6487 fn render_inline_code_actions(
6488 &self,
6489 icon_size: ui::IconSize,
6490 display_row: DisplayRow,
6491 is_active: bool,
6492 cx: &mut Context<Self>,
6493 ) -> AnyElement {
6494 let show_tooltip = !self.context_menu_visible();
6495 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6496 .icon_size(icon_size)
6497 .shape(ui::IconButtonShape::Square)
6498 .icon_color(ui::Color::Hidden)
6499 .toggle_state(is_active)
6500 .when(show_tooltip, |this| {
6501 this.tooltip({
6502 let focus_handle = self.focus_handle.clone();
6503 move |window, cx| {
6504 Tooltip::for_action_in(
6505 "Toggle Code Actions",
6506 &ToggleCodeActions {
6507 deployed_from: None,
6508 quick_launch: false,
6509 },
6510 &focus_handle,
6511 window,
6512 cx,
6513 )
6514 }
6515 })
6516 })
6517 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6518 window.focus(&editor.focus_handle(cx));
6519 editor.toggle_code_actions(
6520 &crate::actions::ToggleCodeActions {
6521 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6522 display_row,
6523 )),
6524 quick_launch: false,
6525 },
6526 window,
6527 cx,
6528 );
6529 }))
6530 .into_any_element()
6531 }
6532
6533 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6534 &self.context_menu
6535 }
6536
6537 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6538 let newest_selection = self.selections.newest_anchor().clone();
6539 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6540 let buffer = self.buffer.read(cx);
6541 if newest_selection.head().diff_base_anchor.is_some() {
6542 return None;
6543 }
6544 let (start_buffer, start) =
6545 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6546 let (end_buffer, end) =
6547 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6548 if start_buffer != end_buffer {
6549 return None;
6550 }
6551
6552 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6553 cx.background_executor()
6554 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6555 .await;
6556
6557 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6558 let providers = this.code_action_providers.clone();
6559 let tasks = this
6560 .code_action_providers
6561 .iter()
6562 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6563 .collect::<Vec<_>>();
6564 (providers, tasks)
6565 })?;
6566
6567 let mut actions = Vec::new();
6568 for (provider, provider_actions) in
6569 providers.into_iter().zip(future::join_all(tasks).await)
6570 {
6571 if let Some(provider_actions) = provider_actions.log_err() {
6572 actions.extend(provider_actions.into_iter().map(|action| {
6573 AvailableCodeAction {
6574 excerpt_id: newest_selection.start.excerpt_id,
6575 action,
6576 provider: provider.clone(),
6577 }
6578 }));
6579 }
6580 }
6581
6582 this.update(cx, |this, cx| {
6583 this.available_code_actions = if actions.is_empty() {
6584 None
6585 } else {
6586 Some((
6587 Location {
6588 buffer: start_buffer,
6589 range: start..end,
6590 },
6591 actions.into(),
6592 ))
6593 };
6594 cx.notify();
6595 })
6596 }));
6597 None
6598 }
6599
6600 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6601 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6602 self.show_git_blame_inline = false;
6603
6604 self.show_git_blame_inline_delay_task =
6605 Some(cx.spawn_in(window, async move |this, cx| {
6606 cx.background_executor().timer(delay).await;
6607
6608 this.update(cx, |this, cx| {
6609 this.show_git_blame_inline = true;
6610 cx.notify();
6611 })
6612 .log_err();
6613 }));
6614 }
6615 }
6616
6617 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6618 let snapshot = self.snapshot(window, cx);
6619 let cursor = self.selections.newest::<Point>(cx).head();
6620 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6621 else {
6622 return;
6623 };
6624
6625 let Some(blame) = self.blame.as_ref() else {
6626 return;
6627 };
6628
6629 let row_info = RowInfo {
6630 buffer_id: Some(buffer.remote_id()),
6631 buffer_row: Some(point.row),
6632 ..Default::default()
6633 };
6634 let Some(blame_entry) = blame
6635 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6636 .flatten()
6637 else {
6638 return;
6639 };
6640
6641 let anchor = self.selections.newest_anchor().head();
6642 let position = self.to_pixel_point(anchor, &snapshot, window);
6643 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6644 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6645 };
6646 }
6647
6648 fn show_blame_popover(
6649 &mut self,
6650 blame_entry: &BlameEntry,
6651 position: gpui::Point<Pixels>,
6652 ignore_timeout: bool,
6653 cx: &mut Context<Self>,
6654 ) {
6655 if let Some(state) = &mut self.inline_blame_popover {
6656 state.hide_task.take();
6657 } else {
6658 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6659 let blame_entry = blame_entry.clone();
6660 let show_task = cx.spawn(async move |editor, cx| {
6661 if !ignore_timeout {
6662 cx.background_executor()
6663 .timer(std::time::Duration::from_millis(blame_popover_delay))
6664 .await;
6665 }
6666 editor
6667 .update(cx, |editor, cx| {
6668 editor.inline_blame_popover_show_task.take();
6669 let Some(blame) = editor.blame.as_ref() else {
6670 return;
6671 };
6672 let blame = blame.read(cx);
6673 let details = blame.details_for_entry(&blame_entry);
6674 let markdown = cx.new(|cx| {
6675 Markdown::new(
6676 details
6677 .as_ref()
6678 .map(|message| message.message.clone())
6679 .unwrap_or_default(),
6680 None,
6681 None,
6682 cx,
6683 )
6684 });
6685 editor.inline_blame_popover = Some(InlineBlamePopover {
6686 position,
6687 hide_task: None,
6688 popover_bounds: None,
6689 popover_state: InlineBlamePopoverState {
6690 scroll_handle: ScrollHandle::new(),
6691 commit_message: details,
6692 markdown,
6693 },
6694 keyboard_grace: ignore_timeout,
6695 });
6696 cx.notify();
6697 })
6698 .ok();
6699 });
6700 self.inline_blame_popover_show_task = Some(show_task);
6701 }
6702 }
6703
6704 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6705 self.inline_blame_popover_show_task.take();
6706 if let Some(state) = &mut self.inline_blame_popover {
6707 let hide_task = cx.spawn(async move |editor, cx| {
6708 cx.background_executor()
6709 .timer(std::time::Duration::from_millis(100))
6710 .await;
6711 editor
6712 .update(cx, |editor, cx| {
6713 editor.inline_blame_popover.take();
6714 cx.notify();
6715 })
6716 .ok();
6717 });
6718 state.hide_task = Some(hide_task);
6719 }
6720 }
6721
6722 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6723 if self.pending_rename.is_some() {
6724 return None;
6725 }
6726
6727 let provider = self.semantics_provider.clone()?;
6728 let buffer = self.buffer.read(cx);
6729 let newest_selection = self.selections.newest_anchor().clone();
6730 let cursor_position = newest_selection.head();
6731 let (cursor_buffer, cursor_buffer_position) =
6732 buffer.text_anchor_for_position(cursor_position, cx)?;
6733 let (tail_buffer, tail_buffer_position) =
6734 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6735 if cursor_buffer != tail_buffer {
6736 return None;
6737 }
6738
6739 let snapshot = cursor_buffer.read(cx).snapshot();
6740 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6741 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6742 if start_word_range != end_word_range {
6743 self.document_highlights_task.take();
6744 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6745 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6746 return None;
6747 }
6748
6749 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6750 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6751 cx.background_executor()
6752 .timer(Duration::from_millis(debounce))
6753 .await;
6754
6755 let highlights = if let Some(highlights) = cx
6756 .update(|cx| {
6757 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6758 })
6759 .ok()
6760 .flatten()
6761 {
6762 highlights.await.log_err()
6763 } else {
6764 None
6765 };
6766
6767 if let Some(highlights) = highlights {
6768 this.update(cx, |this, cx| {
6769 if this.pending_rename.is_some() {
6770 return;
6771 }
6772
6773 let buffer = this.buffer.read(cx);
6774 if buffer
6775 .text_anchor_for_position(cursor_position, cx)
6776 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6777 {
6778 return;
6779 }
6780
6781 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6782 let mut write_ranges = Vec::new();
6783 let mut read_ranges = Vec::new();
6784 for highlight in highlights {
6785 let buffer_id = cursor_buffer.read(cx).remote_id();
6786 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6787 {
6788 let start = highlight
6789 .range
6790 .start
6791 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6792 let end = highlight
6793 .range
6794 .end
6795 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6796 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6797 continue;
6798 }
6799
6800 let range = Anchor {
6801 buffer_id: Some(buffer_id),
6802 excerpt_id,
6803 text_anchor: start,
6804 diff_base_anchor: None,
6805 }..Anchor {
6806 buffer_id: Some(buffer_id),
6807 excerpt_id,
6808 text_anchor: end,
6809 diff_base_anchor: None,
6810 };
6811 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6812 write_ranges.push(range);
6813 } else {
6814 read_ranges.push(range);
6815 }
6816 }
6817 }
6818
6819 this.highlight_background::<DocumentHighlightRead>(
6820 &read_ranges,
6821 |theme| theme.colors().editor_document_highlight_read_background,
6822 cx,
6823 );
6824 this.highlight_background::<DocumentHighlightWrite>(
6825 &write_ranges,
6826 |theme| theme.colors().editor_document_highlight_write_background,
6827 cx,
6828 );
6829 cx.notify();
6830 })
6831 .log_err();
6832 }
6833 }));
6834 None
6835 }
6836
6837 fn prepare_highlight_query_from_selection(
6838 &mut self,
6839 cx: &mut Context<Editor>,
6840 ) -> Option<(String, Range<Anchor>)> {
6841 if matches!(self.mode, EditorMode::SingleLine) {
6842 return None;
6843 }
6844 if !EditorSettings::get_global(cx).selection_highlight {
6845 return None;
6846 }
6847 if self.selections.count() != 1 || self.selections.line_mode {
6848 return None;
6849 }
6850 let selection = self.selections.newest::<Point>(cx);
6851 if selection.is_empty() || selection.start.row != selection.end.row {
6852 return None;
6853 }
6854 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6855 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6856 let query = multi_buffer_snapshot
6857 .text_for_range(selection_anchor_range.clone())
6858 .collect::<String>();
6859 if query.trim().is_empty() {
6860 return None;
6861 }
6862 Some((query, selection_anchor_range))
6863 }
6864
6865 fn update_selection_occurrence_highlights(
6866 &mut self,
6867 query_text: String,
6868 query_range: Range<Anchor>,
6869 multi_buffer_range_to_query: Range<Point>,
6870 use_debounce: bool,
6871 window: &mut Window,
6872 cx: &mut Context<Editor>,
6873 ) -> Task<()> {
6874 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6875 cx.spawn_in(window, async move |editor, cx| {
6876 if use_debounce {
6877 cx.background_executor()
6878 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6879 .await;
6880 }
6881 let match_task = cx.background_spawn(async move {
6882 let buffer_ranges = multi_buffer_snapshot
6883 .range_to_buffer_ranges(multi_buffer_range_to_query)
6884 .into_iter()
6885 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6886 let mut match_ranges = Vec::new();
6887 let Ok(regex) = project::search::SearchQuery::text(
6888 query_text.clone(),
6889 false,
6890 false,
6891 false,
6892 Default::default(),
6893 Default::default(),
6894 false,
6895 None,
6896 ) else {
6897 return Vec::default();
6898 };
6899 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6900 match_ranges.extend(
6901 regex
6902 .search(buffer_snapshot, Some(search_range.clone()))
6903 .await
6904 .into_iter()
6905 .filter_map(|match_range| {
6906 let match_start = buffer_snapshot
6907 .anchor_after(search_range.start + match_range.start);
6908 let match_end = buffer_snapshot
6909 .anchor_before(search_range.start + match_range.end);
6910 let match_anchor_range = Anchor::range_in_buffer(
6911 excerpt_id,
6912 buffer_snapshot.remote_id(),
6913 match_start..match_end,
6914 );
6915 (match_anchor_range != query_range).then_some(match_anchor_range)
6916 }),
6917 );
6918 }
6919 match_ranges
6920 });
6921 let match_ranges = match_task.await;
6922 editor
6923 .update_in(cx, |editor, _, cx| {
6924 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6925 if !match_ranges.is_empty() {
6926 editor.highlight_background::<SelectedTextHighlight>(
6927 &match_ranges,
6928 |theme| theme.colors().editor_document_highlight_bracket_background,
6929 cx,
6930 )
6931 }
6932 })
6933 .log_err();
6934 })
6935 }
6936
6937 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6938 struct NewlineFold;
6939 let type_id = std::any::TypeId::of::<NewlineFold>();
6940 if !self.mode.is_single_line() {
6941 return;
6942 }
6943 let snapshot = self.snapshot(window, cx);
6944 if snapshot.buffer_snapshot.max_point().row == 0 {
6945 return;
6946 }
6947 let task = cx.background_spawn(async move {
6948 let new_newlines = snapshot
6949 .buffer_chars_at(0)
6950 .filter_map(|(c, i)| {
6951 if c == '\n' {
6952 Some(
6953 snapshot.buffer_snapshot.anchor_after(i)
6954 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6955 )
6956 } else {
6957 None
6958 }
6959 })
6960 .collect::<Vec<_>>();
6961 let existing_newlines = snapshot
6962 .folds_in_range(0..snapshot.buffer_snapshot.len())
6963 .filter_map(|fold| {
6964 if fold.placeholder.type_tag == Some(type_id) {
6965 Some(fold.range.start..fold.range.end)
6966 } else {
6967 None
6968 }
6969 })
6970 .collect::<Vec<_>>();
6971
6972 (new_newlines, existing_newlines)
6973 });
6974 self.folding_newlines = cx.spawn(async move |this, cx| {
6975 let (new_newlines, existing_newlines) = task.await;
6976 if new_newlines == existing_newlines {
6977 return;
6978 }
6979 let placeholder = FoldPlaceholder {
6980 render: Arc::new(move |_, _, cx| {
6981 div()
6982 .bg(cx.theme().status().hint_background)
6983 .border_b_1()
6984 .size_full()
6985 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6986 .border_color(cx.theme().status().hint)
6987 .child("\\n")
6988 .into_any()
6989 }),
6990 constrain_width: false,
6991 merge_adjacent: false,
6992 type_tag: Some(type_id),
6993 };
6994 let creases = new_newlines
6995 .into_iter()
6996 .map(|range| Crease::simple(range, placeholder.clone()))
6997 .collect();
6998 this.update(cx, |this, cx| {
6999 this.display_map.update(cx, |display_map, cx| {
7000 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7001 display_map.fold(creases, cx);
7002 });
7003 })
7004 .ok();
7005 });
7006 }
7007
7008 fn refresh_selected_text_highlights(
7009 &mut self,
7010 on_buffer_edit: bool,
7011 window: &mut Window,
7012 cx: &mut Context<Editor>,
7013 ) {
7014 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7015 else {
7016 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7017 self.quick_selection_highlight_task.take();
7018 self.debounced_selection_highlight_task.take();
7019 return;
7020 };
7021 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7022 if on_buffer_edit
7023 || self
7024 .quick_selection_highlight_task
7025 .as_ref()
7026 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7027 {
7028 let multi_buffer_visible_start = self
7029 .scroll_manager
7030 .anchor()
7031 .anchor
7032 .to_point(&multi_buffer_snapshot);
7033 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7034 multi_buffer_visible_start
7035 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7036 Bias::Left,
7037 );
7038 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7039 self.quick_selection_highlight_task = Some((
7040 query_range.clone(),
7041 self.update_selection_occurrence_highlights(
7042 query_text.clone(),
7043 query_range.clone(),
7044 multi_buffer_visible_range,
7045 false,
7046 window,
7047 cx,
7048 ),
7049 ));
7050 }
7051 if on_buffer_edit
7052 || self
7053 .debounced_selection_highlight_task
7054 .as_ref()
7055 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7056 {
7057 let multi_buffer_start = multi_buffer_snapshot
7058 .anchor_before(0)
7059 .to_point(&multi_buffer_snapshot);
7060 let multi_buffer_end = multi_buffer_snapshot
7061 .anchor_after(multi_buffer_snapshot.len())
7062 .to_point(&multi_buffer_snapshot);
7063 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7064 self.debounced_selection_highlight_task = Some((
7065 query_range.clone(),
7066 self.update_selection_occurrence_highlights(
7067 query_text,
7068 query_range,
7069 multi_buffer_full_range,
7070 true,
7071 window,
7072 cx,
7073 ),
7074 ));
7075 }
7076 }
7077
7078 pub fn refresh_edit_prediction(
7079 &mut self,
7080 debounce: bool,
7081 user_requested: bool,
7082 window: &mut Window,
7083 cx: &mut Context<Self>,
7084 ) -> Option<()> {
7085 if DisableAiSettings::get_global(cx).disable_ai {
7086 return None;
7087 }
7088
7089 let provider = self.edit_prediction_provider()?;
7090 let cursor = self.selections.newest_anchor().head();
7091 let (buffer, cursor_buffer_position) =
7092 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7093
7094 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7095 self.discard_edit_prediction(false, cx);
7096 return None;
7097 }
7098
7099 if !user_requested
7100 && (!self.should_show_edit_predictions()
7101 || !self.is_focused(window)
7102 || buffer.read(cx).is_empty())
7103 {
7104 self.discard_edit_prediction(false, cx);
7105 return None;
7106 }
7107
7108 self.update_visible_edit_prediction(window, cx);
7109 provider.refresh(
7110 self.project.clone(),
7111 buffer,
7112 cursor_buffer_position,
7113 debounce,
7114 cx,
7115 );
7116 Some(())
7117 }
7118
7119 fn show_edit_predictions_in_menu(&self) -> bool {
7120 match self.edit_prediction_settings {
7121 EditPredictionSettings::Disabled => false,
7122 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7123 }
7124 }
7125
7126 pub fn edit_predictions_enabled(&self) -> bool {
7127 match self.edit_prediction_settings {
7128 EditPredictionSettings::Disabled => false,
7129 EditPredictionSettings::Enabled { .. } => true,
7130 }
7131 }
7132
7133 fn edit_prediction_requires_modifier(&self) -> bool {
7134 match self.edit_prediction_settings {
7135 EditPredictionSettings::Disabled => false,
7136 EditPredictionSettings::Enabled {
7137 preview_requires_modifier,
7138 ..
7139 } => preview_requires_modifier,
7140 }
7141 }
7142
7143 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7144 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7145 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7146 self.discard_edit_prediction(false, cx);
7147 } else {
7148 let selection = self.selections.newest_anchor();
7149 let cursor = selection.head();
7150
7151 if let Some((buffer, cursor_buffer_position)) =
7152 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7153 {
7154 self.edit_prediction_settings =
7155 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7156 }
7157 }
7158 }
7159
7160 fn edit_prediction_settings_at_position(
7161 &self,
7162 buffer: &Entity<Buffer>,
7163 buffer_position: language::Anchor,
7164 cx: &App,
7165 ) -> EditPredictionSettings {
7166 if !self.mode.is_full()
7167 || !self.show_edit_predictions_override.unwrap_or(true)
7168 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7169 {
7170 return EditPredictionSettings::Disabled;
7171 }
7172
7173 let buffer = buffer.read(cx);
7174
7175 let file = buffer.file();
7176
7177 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7178 return EditPredictionSettings::Disabled;
7179 };
7180
7181 let by_provider = matches!(
7182 self.menu_edit_predictions_policy,
7183 MenuEditPredictionsPolicy::ByProvider
7184 );
7185
7186 let show_in_menu = by_provider
7187 && self
7188 .edit_prediction_provider
7189 .as_ref()
7190 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7191
7192 let preview_requires_modifier =
7193 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7194
7195 EditPredictionSettings::Enabled {
7196 show_in_menu,
7197 preview_requires_modifier,
7198 }
7199 }
7200
7201 fn should_show_edit_predictions(&self) -> bool {
7202 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7203 }
7204
7205 pub fn edit_prediction_preview_is_active(&self) -> bool {
7206 matches!(
7207 self.edit_prediction_preview,
7208 EditPredictionPreview::Active { .. }
7209 )
7210 }
7211
7212 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7213 let cursor = self.selections.newest_anchor().head();
7214 if let Some((buffer, cursor_position)) =
7215 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7216 {
7217 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7218 } else {
7219 false
7220 }
7221 }
7222
7223 pub fn supports_minimap(&self, cx: &App) -> bool {
7224 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7225 }
7226
7227 fn edit_predictions_enabled_in_buffer(
7228 &self,
7229 buffer: &Entity<Buffer>,
7230 buffer_position: language::Anchor,
7231 cx: &App,
7232 ) -> bool {
7233 maybe!({
7234 if self.read_only(cx) {
7235 return Some(false);
7236 }
7237 let provider = self.edit_prediction_provider()?;
7238 if !provider.is_enabled(buffer, buffer_position, cx) {
7239 return Some(false);
7240 }
7241 let buffer = buffer.read(cx);
7242 let Some(file) = buffer.file() else {
7243 return Some(true);
7244 };
7245 let settings = all_language_settings(Some(file), cx);
7246 Some(settings.edit_predictions_enabled_for_file(file, cx))
7247 })
7248 .unwrap_or(false)
7249 }
7250
7251 fn cycle_edit_prediction(
7252 &mut self,
7253 direction: Direction,
7254 window: &mut Window,
7255 cx: &mut Context<Self>,
7256 ) -> Option<()> {
7257 let provider = self.edit_prediction_provider()?;
7258 let cursor = self.selections.newest_anchor().head();
7259 let (buffer, cursor_buffer_position) =
7260 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7261 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7262 return None;
7263 }
7264
7265 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7266 self.update_visible_edit_prediction(window, cx);
7267
7268 Some(())
7269 }
7270
7271 pub fn show_edit_prediction(
7272 &mut self,
7273 _: &ShowEditPrediction,
7274 window: &mut Window,
7275 cx: &mut Context<Self>,
7276 ) {
7277 if !self.has_active_edit_prediction() {
7278 self.refresh_edit_prediction(false, true, window, cx);
7279 return;
7280 }
7281
7282 self.update_visible_edit_prediction(window, cx);
7283 }
7284
7285 pub fn display_cursor_names(
7286 &mut self,
7287 _: &DisplayCursorNames,
7288 window: &mut Window,
7289 cx: &mut Context<Self>,
7290 ) {
7291 self.show_cursor_names(window, cx);
7292 }
7293
7294 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7295 self.show_cursor_names = true;
7296 cx.notify();
7297 cx.spawn_in(window, async move |this, cx| {
7298 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7299 this.update(cx, |this, cx| {
7300 this.show_cursor_names = false;
7301 cx.notify()
7302 })
7303 .ok()
7304 })
7305 .detach();
7306 }
7307
7308 pub fn next_edit_prediction(
7309 &mut self,
7310 _: &NextEditPrediction,
7311 window: &mut Window,
7312 cx: &mut Context<Self>,
7313 ) {
7314 if self.has_active_edit_prediction() {
7315 self.cycle_edit_prediction(Direction::Next, window, cx);
7316 } else {
7317 let is_copilot_disabled = self
7318 .refresh_edit_prediction(false, true, window, cx)
7319 .is_none();
7320 if is_copilot_disabled {
7321 cx.propagate();
7322 }
7323 }
7324 }
7325
7326 pub fn previous_edit_prediction(
7327 &mut self,
7328 _: &PreviousEditPrediction,
7329 window: &mut Window,
7330 cx: &mut Context<Self>,
7331 ) {
7332 if self.has_active_edit_prediction() {
7333 self.cycle_edit_prediction(Direction::Prev, window, cx);
7334 } else {
7335 let is_copilot_disabled = self
7336 .refresh_edit_prediction(false, true, window, cx)
7337 .is_none();
7338 if is_copilot_disabled {
7339 cx.propagate();
7340 }
7341 }
7342 }
7343
7344 pub fn accept_edit_prediction(
7345 &mut self,
7346 _: &AcceptEditPrediction,
7347 window: &mut Window,
7348 cx: &mut Context<Self>,
7349 ) {
7350 if self.show_edit_predictions_in_menu() {
7351 self.hide_context_menu(window, cx);
7352 }
7353
7354 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7355 return;
7356 };
7357
7358 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7359
7360 match &active_edit_prediction.completion {
7361 EditPrediction::Move { target, .. } => {
7362 let target = *target;
7363
7364 if let Some(position_map) = &self.last_position_map {
7365 if position_map
7366 .visible_row_range
7367 .contains(&target.to_display_point(&position_map.snapshot).row())
7368 || !self.edit_prediction_requires_modifier()
7369 {
7370 self.unfold_ranges(&[target..target], true, false, cx);
7371 // Note that this is also done in vim's handler of the Tab action.
7372 self.change_selections(
7373 SelectionEffects::scroll(Autoscroll::newest()),
7374 window,
7375 cx,
7376 |selections| {
7377 selections.select_anchor_ranges([target..target]);
7378 },
7379 );
7380 self.clear_row_highlights::<EditPredictionPreview>();
7381
7382 self.edit_prediction_preview
7383 .set_previous_scroll_position(None);
7384 } else {
7385 self.edit_prediction_preview
7386 .set_previous_scroll_position(Some(
7387 position_map.snapshot.scroll_anchor,
7388 ));
7389
7390 self.highlight_rows::<EditPredictionPreview>(
7391 target..target,
7392 cx.theme().colors().editor_highlighted_line_background,
7393 RowHighlightOptions {
7394 autoscroll: true,
7395 ..Default::default()
7396 },
7397 cx,
7398 );
7399 self.request_autoscroll(Autoscroll::fit(), cx);
7400 }
7401 }
7402 }
7403 EditPrediction::Edit { edits, .. } => {
7404 if let Some(provider) = self.edit_prediction_provider() {
7405 provider.accept(cx);
7406 }
7407
7408 // Store the transaction ID and selections before applying the edit
7409 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7410
7411 let snapshot = self.buffer.read(cx).snapshot(cx);
7412 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7413
7414 self.buffer.update(cx, |buffer, cx| {
7415 buffer.edit(edits.iter().cloned(), None, cx)
7416 });
7417
7418 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7419 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7420 });
7421
7422 let selections = self.selections.disjoint_anchors();
7423 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7424 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7425 if has_new_transaction {
7426 self.selection_history
7427 .insert_transaction(transaction_id_now, selections);
7428 }
7429 }
7430
7431 self.update_visible_edit_prediction(window, cx);
7432 if self.active_edit_prediction.is_none() {
7433 self.refresh_edit_prediction(true, true, window, cx);
7434 }
7435
7436 cx.notify();
7437 }
7438 }
7439
7440 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7441 }
7442
7443 pub fn accept_partial_edit_prediction(
7444 &mut self,
7445 _: &AcceptPartialEditPrediction,
7446 window: &mut Window,
7447 cx: &mut Context<Self>,
7448 ) {
7449 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7450 return;
7451 };
7452 if self.selections.count() != 1 {
7453 return;
7454 }
7455
7456 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7457
7458 match &active_edit_prediction.completion {
7459 EditPrediction::Move { target, .. } => {
7460 let target = *target;
7461 self.change_selections(
7462 SelectionEffects::scroll(Autoscroll::newest()),
7463 window,
7464 cx,
7465 |selections| {
7466 selections.select_anchor_ranges([target..target]);
7467 },
7468 );
7469 }
7470 EditPrediction::Edit { edits, .. } => {
7471 // Find an insertion that starts at the cursor position.
7472 let snapshot = self.buffer.read(cx).snapshot(cx);
7473 let cursor_offset = self.selections.newest::<usize>(cx).head();
7474 let insertion = edits.iter().find_map(|(range, text)| {
7475 let range = range.to_offset(&snapshot);
7476 if range.is_empty() && range.start == cursor_offset {
7477 Some(text)
7478 } else {
7479 None
7480 }
7481 });
7482
7483 if let Some(text) = insertion {
7484 let mut partial_completion = text
7485 .chars()
7486 .by_ref()
7487 .take_while(|c| c.is_alphabetic())
7488 .collect::<String>();
7489 if partial_completion.is_empty() {
7490 partial_completion = text
7491 .chars()
7492 .by_ref()
7493 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7494 .collect::<String>();
7495 }
7496
7497 cx.emit(EditorEvent::InputHandled {
7498 utf16_range_to_replace: None,
7499 text: partial_completion.clone().into(),
7500 });
7501
7502 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7503
7504 self.refresh_edit_prediction(true, true, window, cx);
7505 cx.notify();
7506 } else {
7507 self.accept_edit_prediction(&Default::default(), window, cx);
7508 }
7509 }
7510 }
7511 }
7512
7513 fn discard_edit_prediction(
7514 &mut self,
7515 should_report_edit_prediction_event: bool,
7516 cx: &mut Context<Self>,
7517 ) -> bool {
7518 if should_report_edit_prediction_event {
7519 let completion_id = self
7520 .active_edit_prediction
7521 .as_ref()
7522 .and_then(|active_completion| active_completion.completion_id.clone());
7523
7524 self.report_edit_prediction_event(completion_id, false, cx);
7525 }
7526
7527 if let Some(provider) = self.edit_prediction_provider() {
7528 provider.discard(cx);
7529 }
7530
7531 self.take_active_edit_prediction(cx)
7532 }
7533
7534 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7535 let Some(provider) = self.edit_prediction_provider() else {
7536 return;
7537 };
7538
7539 let Some((_, buffer, _)) = self
7540 .buffer
7541 .read(cx)
7542 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7543 else {
7544 return;
7545 };
7546
7547 let extension = buffer
7548 .read(cx)
7549 .file()
7550 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7551
7552 let event_type = match accepted {
7553 true => "Edit Prediction Accepted",
7554 false => "Edit Prediction Discarded",
7555 };
7556 telemetry::event!(
7557 event_type,
7558 provider = provider.name(),
7559 prediction_id = id,
7560 suggestion_accepted = accepted,
7561 file_extension = extension,
7562 );
7563 }
7564
7565 pub fn has_active_edit_prediction(&self) -> bool {
7566 self.active_edit_prediction.is_some()
7567 }
7568
7569 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7570 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7571 return false;
7572 };
7573
7574 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7575 self.clear_highlights::<EditPredictionHighlight>(cx);
7576 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7577 true
7578 }
7579
7580 /// Returns true when we're displaying the edit prediction popover below the cursor
7581 /// like we are not previewing and the LSP autocomplete menu is visible
7582 /// or we are in `when_holding_modifier` mode.
7583 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7584 if self.edit_prediction_preview_is_active()
7585 || !self.show_edit_predictions_in_menu()
7586 || !self.edit_predictions_enabled()
7587 {
7588 return false;
7589 }
7590
7591 if self.has_visible_completions_menu() {
7592 return true;
7593 }
7594
7595 has_completion && self.edit_prediction_requires_modifier()
7596 }
7597
7598 fn handle_modifiers_changed(
7599 &mut self,
7600 modifiers: Modifiers,
7601 position_map: &PositionMap,
7602 window: &mut Window,
7603 cx: &mut Context<Self>,
7604 ) {
7605 if self.show_edit_predictions_in_menu() {
7606 self.update_edit_prediction_preview(&modifiers, window, cx);
7607 }
7608
7609 self.update_selection_mode(&modifiers, position_map, window, cx);
7610
7611 let mouse_position = window.mouse_position();
7612 if !position_map.text_hitbox.is_hovered(window) {
7613 return;
7614 }
7615
7616 self.update_hovered_link(
7617 position_map.point_for_position(mouse_position),
7618 &position_map.snapshot,
7619 modifiers,
7620 window,
7621 cx,
7622 )
7623 }
7624
7625 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7626 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7627 if invert {
7628 match multi_cursor_setting {
7629 MultiCursorModifier::Alt => modifiers.alt,
7630 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7631 }
7632 } else {
7633 match multi_cursor_setting {
7634 MultiCursorModifier::Alt => modifiers.secondary(),
7635 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7636 }
7637 }
7638 }
7639
7640 fn columnar_selection_mode(
7641 modifiers: &Modifiers,
7642 cx: &mut Context<Self>,
7643 ) -> Option<ColumnarMode> {
7644 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7645 if Self::multi_cursor_modifier(false, modifiers, cx) {
7646 Some(ColumnarMode::FromMouse)
7647 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7648 Some(ColumnarMode::FromSelection)
7649 } else {
7650 None
7651 }
7652 } else {
7653 None
7654 }
7655 }
7656
7657 fn update_selection_mode(
7658 &mut self,
7659 modifiers: &Modifiers,
7660 position_map: &PositionMap,
7661 window: &mut Window,
7662 cx: &mut Context<Self>,
7663 ) {
7664 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7665 return;
7666 };
7667 if self.selections.pending.is_none() {
7668 return;
7669 }
7670
7671 let mouse_position = window.mouse_position();
7672 let point_for_position = position_map.point_for_position(mouse_position);
7673 let position = point_for_position.previous_valid;
7674
7675 self.select(
7676 SelectPhase::BeginColumnar {
7677 position,
7678 reset: false,
7679 mode,
7680 goal_column: point_for_position.exact_unclipped.column(),
7681 },
7682 window,
7683 cx,
7684 );
7685 }
7686
7687 fn update_edit_prediction_preview(
7688 &mut self,
7689 modifiers: &Modifiers,
7690 window: &mut Window,
7691 cx: &mut Context<Self>,
7692 ) {
7693 let mut modifiers_held = false;
7694 if let Some(accept_keystroke) = self
7695 .accept_edit_prediction_keybind(false, window, cx)
7696 .keystroke()
7697 {
7698 modifiers_held = modifiers_held
7699 || (&accept_keystroke.display_modifiers == modifiers
7700 && accept_keystroke.display_modifiers.modified());
7701 };
7702 if let Some(accept_partial_keystroke) = self
7703 .accept_edit_prediction_keybind(true, window, cx)
7704 .keystroke()
7705 {
7706 modifiers_held = modifiers_held
7707 || (&accept_partial_keystroke.display_modifiers == modifiers
7708 && accept_partial_keystroke.display_modifiers.modified());
7709 }
7710
7711 if modifiers_held {
7712 if matches!(
7713 self.edit_prediction_preview,
7714 EditPredictionPreview::Inactive { .. }
7715 ) {
7716 self.edit_prediction_preview = EditPredictionPreview::Active {
7717 previous_scroll_position: None,
7718 since: Instant::now(),
7719 };
7720
7721 self.update_visible_edit_prediction(window, cx);
7722 cx.notify();
7723 }
7724 } else if let EditPredictionPreview::Active {
7725 previous_scroll_position,
7726 since,
7727 } = self.edit_prediction_preview
7728 {
7729 if let (Some(previous_scroll_position), Some(position_map)) =
7730 (previous_scroll_position, self.last_position_map.as_ref())
7731 {
7732 self.set_scroll_position(
7733 previous_scroll_position
7734 .scroll_position(&position_map.snapshot.display_snapshot),
7735 window,
7736 cx,
7737 );
7738 }
7739
7740 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7741 released_too_fast: since.elapsed() < Duration::from_millis(200),
7742 };
7743 self.clear_row_highlights::<EditPredictionPreview>();
7744 self.update_visible_edit_prediction(window, cx);
7745 cx.notify();
7746 }
7747 }
7748
7749 fn update_visible_edit_prediction(
7750 &mut self,
7751 _window: &mut Window,
7752 cx: &mut Context<Self>,
7753 ) -> Option<()> {
7754 if DisableAiSettings::get_global(cx).disable_ai {
7755 return None;
7756 }
7757
7758 if self.ime_transaction.is_some() {
7759 self.discard_edit_prediction(false, cx);
7760 return None;
7761 }
7762
7763 let selection = self.selections.newest_anchor();
7764 let cursor = selection.head();
7765 let multibuffer = self.buffer.read(cx).snapshot(cx);
7766 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7767 let excerpt_id = cursor.excerpt_id;
7768
7769 let show_in_menu = self.show_edit_predictions_in_menu();
7770 let completions_menu_has_precedence = !show_in_menu
7771 && (self.context_menu.borrow().is_some()
7772 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7773
7774 if completions_menu_has_precedence
7775 || !offset_selection.is_empty()
7776 || self
7777 .active_edit_prediction
7778 .as_ref()
7779 .is_some_and(|completion| {
7780 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7781 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7782 !invalidation_range.contains(&offset_selection.head())
7783 })
7784 {
7785 self.discard_edit_prediction(false, cx);
7786 return None;
7787 }
7788
7789 self.take_active_edit_prediction(cx);
7790 let Some(provider) = self.edit_prediction_provider() else {
7791 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7792 return None;
7793 };
7794
7795 let (buffer, cursor_buffer_position) =
7796 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7797
7798 self.edit_prediction_settings =
7799 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7800
7801 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7802 self.discard_edit_prediction(false, cx);
7803 return None;
7804 };
7805
7806 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7807
7808 if self.edit_prediction_indent_conflict {
7809 let cursor_point = cursor.to_point(&multibuffer);
7810
7811 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7812
7813 if let Some((_, indent)) = indents.iter().next()
7814 && indent.len == cursor_point.column
7815 {
7816 self.edit_prediction_indent_conflict = false;
7817 }
7818 }
7819
7820 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7821 let edits = edit_prediction
7822 .edits
7823 .into_iter()
7824 .flat_map(|(range, new_text)| {
7825 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7826 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7827 Some((start..end, new_text))
7828 })
7829 .collect::<Vec<_>>();
7830 if edits.is_empty() {
7831 return None;
7832 }
7833
7834 let first_edit_start = edits.first().unwrap().0.start;
7835 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7836 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7837
7838 let last_edit_end = edits.last().unwrap().0.end;
7839 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7840 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7841
7842 let cursor_row = cursor.to_point(&multibuffer).row;
7843
7844 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7845
7846 let mut inlay_ids = Vec::new();
7847 let invalidation_row_range;
7848 let move_invalidation_row_range = if cursor_row < edit_start_row {
7849 Some(cursor_row..edit_end_row)
7850 } else if cursor_row > edit_end_row {
7851 Some(edit_start_row..cursor_row)
7852 } else {
7853 None
7854 };
7855 let supports_jump = self
7856 .edit_prediction_provider
7857 .as_ref()
7858 .map(|provider| provider.provider.supports_jump_to_edit())
7859 .unwrap_or(true);
7860
7861 let is_move = supports_jump
7862 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7863 let completion = if is_move {
7864 invalidation_row_range =
7865 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7866 let target = first_edit_start;
7867 EditPrediction::Move { target, snapshot }
7868 } else {
7869 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7870 && !self.edit_predictions_hidden_for_vim_mode;
7871
7872 if show_completions_in_buffer {
7873 if edits
7874 .iter()
7875 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7876 {
7877 let mut inlays = Vec::new();
7878 for (range, new_text) in &edits {
7879 let inlay = Inlay::edit_prediction(
7880 post_inc(&mut self.next_inlay_id),
7881 range.start,
7882 new_text.as_str(),
7883 );
7884 inlay_ids.push(inlay.id);
7885 inlays.push(inlay);
7886 }
7887
7888 self.splice_inlays(&[], inlays, cx);
7889 } else {
7890 let background_color = cx.theme().status().deleted_background;
7891 self.highlight_text::<EditPredictionHighlight>(
7892 edits.iter().map(|(range, _)| range.clone()).collect(),
7893 HighlightStyle {
7894 background_color: Some(background_color),
7895 ..Default::default()
7896 },
7897 cx,
7898 );
7899 }
7900 }
7901
7902 invalidation_row_range = edit_start_row..edit_end_row;
7903
7904 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7905 if provider.show_tab_accept_marker() {
7906 EditDisplayMode::TabAccept
7907 } else {
7908 EditDisplayMode::Inline
7909 }
7910 } else {
7911 EditDisplayMode::DiffPopover
7912 };
7913
7914 EditPrediction::Edit {
7915 edits,
7916 edit_preview: edit_prediction.edit_preview,
7917 display_mode,
7918 snapshot,
7919 }
7920 };
7921
7922 let invalidation_range = multibuffer
7923 .anchor_before(Point::new(invalidation_row_range.start, 0))
7924 ..multibuffer.anchor_after(Point::new(
7925 invalidation_row_range.end,
7926 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7927 ));
7928
7929 self.stale_edit_prediction_in_menu = None;
7930 self.active_edit_prediction = Some(EditPredictionState {
7931 inlay_ids,
7932 completion,
7933 completion_id: edit_prediction.id,
7934 invalidation_range,
7935 });
7936
7937 cx.notify();
7938
7939 Some(())
7940 }
7941
7942 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7943 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7944 }
7945
7946 fn clear_tasks(&mut self) {
7947 self.tasks.clear()
7948 }
7949
7950 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7951 if self.tasks.insert(key, value).is_some() {
7952 // This case should hopefully be rare, but just in case...
7953 log::error!(
7954 "multiple different run targets found on a single line, only the last target will be rendered"
7955 )
7956 }
7957 }
7958
7959 /// Get all display points of breakpoints that will be rendered within editor
7960 ///
7961 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7962 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7963 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7964 fn active_breakpoints(
7965 &self,
7966 range: Range<DisplayRow>,
7967 window: &mut Window,
7968 cx: &mut Context<Self>,
7969 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7970 let mut breakpoint_display_points = HashMap::default();
7971
7972 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7973 return breakpoint_display_points;
7974 };
7975
7976 let snapshot = self.snapshot(window, cx);
7977
7978 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7979 let Some(project) = self.project() else {
7980 return breakpoint_display_points;
7981 };
7982
7983 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7984 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7985
7986 for (buffer_snapshot, range, excerpt_id) in
7987 multi_buffer_snapshot.range_to_buffer_ranges(range)
7988 {
7989 let Some(buffer) = project
7990 .read(cx)
7991 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7992 else {
7993 continue;
7994 };
7995 let breakpoints = breakpoint_store.read(cx).breakpoints(
7996 &buffer,
7997 Some(
7998 buffer_snapshot.anchor_before(range.start)
7999 ..buffer_snapshot.anchor_after(range.end),
8000 ),
8001 buffer_snapshot,
8002 cx,
8003 );
8004 for (breakpoint, state) in breakpoints {
8005 let multi_buffer_anchor =
8006 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8007 let position = multi_buffer_anchor
8008 .to_point(multi_buffer_snapshot)
8009 .to_display_point(&snapshot);
8010
8011 breakpoint_display_points.insert(
8012 position.row(),
8013 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8014 );
8015 }
8016 }
8017
8018 breakpoint_display_points
8019 }
8020
8021 fn breakpoint_context_menu(
8022 &self,
8023 anchor: Anchor,
8024 window: &mut Window,
8025 cx: &mut Context<Self>,
8026 ) -> Entity<ui::ContextMenu> {
8027 let weak_editor = cx.weak_entity();
8028 let focus_handle = self.focus_handle(cx);
8029
8030 let row = self
8031 .buffer
8032 .read(cx)
8033 .snapshot(cx)
8034 .summary_for_anchor::<Point>(&anchor)
8035 .row;
8036
8037 let breakpoint = self
8038 .breakpoint_at_row(row, window, cx)
8039 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8040
8041 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8042 "Edit Log Breakpoint"
8043 } else {
8044 "Set Log Breakpoint"
8045 };
8046
8047 let condition_breakpoint_msg = if breakpoint
8048 .as_ref()
8049 .is_some_and(|bp| bp.1.condition.is_some())
8050 {
8051 "Edit Condition Breakpoint"
8052 } else {
8053 "Set Condition Breakpoint"
8054 };
8055
8056 let hit_condition_breakpoint_msg = if breakpoint
8057 .as_ref()
8058 .is_some_and(|bp| bp.1.hit_condition.is_some())
8059 {
8060 "Edit Hit Condition Breakpoint"
8061 } else {
8062 "Set Hit Condition Breakpoint"
8063 };
8064
8065 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8066 "Unset Breakpoint"
8067 } else {
8068 "Set Breakpoint"
8069 };
8070
8071 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8072
8073 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8074 BreakpointState::Enabled => Some("Disable"),
8075 BreakpointState::Disabled => Some("Enable"),
8076 });
8077
8078 let (anchor, breakpoint) =
8079 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8080
8081 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8082 menu.on_blur_subscription(Subscription::new(|| {}))
8083 .context(focus_handle)
8084 .when(run_to_cursor, |this| {
8085 let weak_editor = weak_editor.clone();
8086 this.entry("Run to cursor", None, move |window, cx| {
8087 weak_editor
8088 .update(cx, |editor, cx| {
8089 editor.change_selections(
8090 SelectionEffects::no_scroll(),
8091 window,
8092 cx,
8093 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8094 );
8095 })
8096 .ok();
8097
8098 window.dispatch_action(Box::new(RunToCursor), cx);
8099 })
8100 .separator()
8101 })
8102 .when_some(toggle_state_msg, |this, msg| {
8103 this.entry(msg, None, {
8104 let weak_editor = weak_editor.clone();
8105 let breakpoint = breakpoint.clone();
8106 move |_window, cx| {
8107 weak_editor
8108 .update(cx, |this, cx| {
8109 this.edit_breakpoint_at_anchor(
8110 anchor,
8111 breakpoint.as_ref().clone(),
8112 BreakpointEditAction::InvertState,
8113 cx,
8114 );
8115 })
8116 .log_err();
8117 }
8118 })
8119 })
8120 .entry(set_breakpoint_msg, None, {
8121 let weak_editor = weak_editor.clone();
8122 let breakpoint = breakpoint.clone();
8123 move |_window, cx| {
8124 weak_editor
8125 .update(cx, |this, cx| {
8126 this.edit_breakpoint_at_anchor(
8127 anchor,
8128 breakpoint.as_ref().clone(),
8129 BreakpointEditAction::Toggle,
8130 cx,
8131 );
8132 })
8133 .log_err();
8134 }
8135 })
8136 .entry(log_breakpoint_msg, None, {
8137 let breakpoint = breakpoint.clone();
8138 let weak_editor = weak_editor.clone();
8139 move |window, cx| {
8140 weak_editor
8141 .update(cx, |this, cx| {
8142 this.add_edit_breakpoint_block(
8143 anchor,
8144 breakpoint.as_ref(),
8145 BreakpointPromptEditAction::Log,
8146 window,
8147 cx,
8148 );
8149 })
8150 .log_err();
8151 }
8152 })
8153 .entry(condition_breakpoint_msg, None, {
8154 let breakpoint = breakpoint.clone();
8155 let weak_editor = weak_editor.clone();
8156 move |window, cx| {
8157 weak_editor
8158 .update(cx, |this, cx| {
8159 this.add_edit_breakpoint_block(
8160 anchor,
8161 breakpoint.as_ref(),
8162 BreakpointPromptEditAction::Condition,
8163 window,
8164 cx,
8165 );
8166 })
8167 .log_err();
8168 }
8169 })
8170 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8171 weak_editor
8172 .update(cx, |this, cx| {
8173 this.add_edit_breakpoint_block(
8174 anchor,
8175 breakpoint.as_ref(),
8176 BreakpointPromptEditAction::HitCondition,
8177 window,
8178 cx,
8179 );
8180 })
8181 .log_err();
8182 })
8183 })
8184 }
8185
8186 fn render_breakpoint(
8187 &self,
8188 position: Anchor,
8189 row: DisplayRow,
8190 breakpoint: &Breakpoint,
8191 state: Option<BreakpointSessionState>,
8192 cx: &mut Context<Self>,
8193 ) -> IconButton {
8194 let is_rejected = state.is_some_and(|s| !s.verified);
8195 // Is it a breakpoint that shows up when hovering over gutter?
8196 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8197 (false, false),
8198 |PhantomBreakpointIndicator {
8199 is_active,
8200 display_row,
8201 collides_with_existing_breakpoint,
8202 }| {
8203 (
8204 is_active && display_row == row,
8205 collides_with_existing_breakpoint,
8206 )
8207 },
8208 );
8209
8210 let (color, icon) = {
8211 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8212 (false, false) => ui::IconName::DebugBreakpoint,
8213 (true, false) => ui::IconName::DebugLogBreakpoint,
8214 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8215 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8216 };
8217
8218 let color = if is_phantom {
8219 Color::Hint
8220 } else if is_rejected {
8221 Color::Disabled
8222 } else {
8223 Color::Debugger
8224 };
8225
8226 (color, icon)
8227 };
8228
8229 let breakpoint = Arc::from(breakpoint.clone());
8230
8231 let alt_as_text = gpui::Keystroke {
8232 modifiers: Modifiers::secondary_key(),
8233 ..Default::default()
8234 };
8235 let primary_action_text = if breakpoint.is_disabled() {
8236 "Enable breakpoint"
8237 } else if is_phantom && !collides_with_existing {
8238 "Set breakpoint"
8239 } else {
8240 "Unset breakpoint"
8241 };
8242 let focus_handle = self.focus_handle.clone();
8243
8244 let meta = if is_rejected {
8245 SharedString::from("No executable code is associated with this line.")
8246 } else if collides_with_existing && !breakpoint.is_disabled() {
8247 SharedString::from(format!(
8248 "{alt_as_text}-click to disable,\nright-click for more options."
8249 ))
8250 } else {
8251 SharedString::from("Right-click for more options.")
8252 };
8253 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8254 .icon_size(IconSize::XSmall)
8255 .size(ui::ButtonSize::None)
8256 .when(is_rejected, |this| {
8257 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8258 })
8259 .icon_color(color)
8260 .style(ButtonStyle::Transparent)
8261 .on_click(cx.listener({
8262 move |editor, event: &ClickEvent, window, cx| {
8263 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8264 BreakpointEditAction::InvertState
8265 } else {
8266 BreakpointEditAction::Toggle
8267 };
8268
8269 window.focus(&editor.focus_handle(cx));
8270 editor.edit_breakpoint_at_anchor(
8271 position,
8272 breakpoint.as_ref().clone(),
8273 edit_action,
8274 cx,
8275 );
8276 }
8277 }))
8278 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8279 editor.set_breakpoint_context_menu(
8280 row,
8281 Some(position),
8282 event.position(),
8283 window,
8284 cx,
8285 );
8286 }))
8287 .tooltip(move |window, cx| {
8288 Tooltip::with_meta_in(
8289 primary_action_text,
8290 Some(&ToggleBreakpoint),
8291 meta.clone(),
8292 &focus_handle,
8293 window,
8294 cx,
8295 )
8296 })
8297 }
8298
8299 fn build_tasks_context(
8300 project: &Entity<Project>,
8301 buffer: &Entity<Buffer>,
8302 buffer_row: u32,
8303 tasks: &Arc<RunnableTasks>,
8304 cx: &mut Context<Self>,
8305 ) -> Task<Option<task::TaskContext>> {
8306 let position = Point::new(buffer_row, tasks.column);
8307 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8308 let location = Location {
8309 buffer: buffer.clone(),
8310 range: range_start..range_start,
8311 };
8312 // Fill in the environmental variables from the tree-sitter captures
8313 let mut captured_task_variables = TaskVariables::default();
8314 for (capture_name, value) in tasks.extra_variables.clone() {
8315 captured_task_variables.insert(
8316 task::VariableName::Custom(capture_name.into()),
8317 value.clone(),
8318 );
8319 }
8320 project.update(cx, |project, cx| {
8321 project.task_store().update(cx, |task_store, cx| {
8322 task_store.task_context_for_location(captured_task_variables, location, cx)
8323 })
8324 })
8325 }
8326
8327 pub fn spawn_nearest_task(
8328 &mut self,
8329 action: &SpawnNearestTask,
8330 window: &mut Window,
8331 cx: &mut Context<Self>,
8332 ) {
8333 let Some((workspace, _)) = self.workspace.clone() else {
8334 return;
8335 };
8336 let Some(project) = self.project.clone() else {
8337 return;
8338 };
8339
8340 // Try to find a closest, enclosing node using tree-sitter that has a task
8341 let Some((buffer, buffer_row, tasks)) = self
8342 .find_enclosing_node_task(cx)
8343 // Or find the task that's closest in row-distance.
8344 .or_else(|| self.find_closest_task(cx))
8345 else {
8346 return;
8347 };
8348
8349 let reveal_strategy = action.reveal;
8350 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8351 cx.spawn_in(window, async move |_, cx| {
8352 let context = task_context.await?;
8353 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8354
8355 let resolved = &mut resolved_task.resolved;
8356 resolved.reveal = reveal_strategy;
8357
8358 workspace
8359 .update_in(cx, |workspace, window, cx| {
8360 workspace.schedule_resolved_task(
8361 task_source_kind,
8362 resolved_task,
8363 false,
8364 window,
8365 cx,
8366 );
8367 })
8368 .ok()
8369 })
8370 .detach();
8371 }
8372
8373 fn find_closest_task(
8374 &mut self,
8375 cx: &mut Context<Self>,
8376 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8377 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8378
8379 let ((buffer_id, row), tasks) = self
8380 .tasks
8381 .iter()
8382 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8383
8384 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8385 let tasks = Arc::new(tasks.to_owned());
8386 Some((buffer, *row, tasks))
8387 }
8388
8389 fn find_enclosing_node_task(
8390 &mut self,
8391 cx: &mut Context<Self>,
8392 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8393 let snapshot = self.buffer.read(cx).snapshot(cx);
8394 let offset = self.selections.newest::<usize>(cx).head();
8395 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8396 let buffer_id = excerpt.buffer().remote_id();
8397
8398 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8399 let mut cursor = layer.node().walk();
8400
8401 while cursor.goto_first_child_for_byte(offset).is_some() {
8402 if cursor.node().end_byte() == offset {
8403 cursor.goto_next_sibling();
8404 }
8405 }
8406
8407 // Ascend to the smallest ancestor that contains the range and has a task.
8408 loop {
8409 let node = cursor.node();
8410 let node_range = node.byte_range();
8411 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8412
8413 // Check if this node contains our offset
8414 if node_range.start <= offset && node_range.end >= offset {
8415 // If it contains offset, check for task
8416 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8417 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8418 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8419 }
8420 }
8421
8422 if !cursor.goto_parent() {
8423 break;
8424 }
8425 }
8426 None
8427 }
8428
8429 fn render_run_indicator(
8430 &self,
8431 _style: &EditorStyle,
8432 is_active: bool,
8433 row: DisplayRow,
8434 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8435 cx: &mut Context<Self>,
8436 ) -> IconButton {
8437 let color = Color::Muted;
8438 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8439
8440 IconButton::new(
8441 ("run_indicator", row.0 as usize),
8442 ui::IconName::PlayOutlined,
8443 )
8444 .shape(ui::IconButtonShape::Square)
8445 .icon_size(IconSize::XSmall)
8446 .icon_color(color)
8447 .toggle_state(is_active)
8448 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8449 let quick_launch = match e {
8450 ClickEvent::Keyboard(_) => true,
8451 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8452 };
8453
8454 window.focus(&editor.focus_handle(cx));
8455 editor.toggle_code_actions(
8456 &ToggleCodeActions {
8457 deployed_from: Some(CodeActionSource::RunMenu(row)),
8458 quick_launch,
8459 },
8460 window,
8461 cx,
8462 );
8463 }))
8464 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8465 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8466 }))
8467 }
8468
8469 pub fn context_menu_visible(&self) -> bool {
8470 !self.edit_prediction_preview_is_active()
8471 && self
8472 .context_menu
8473 .borrow()
8474 .as_ref()
8475 .is_some_and(|menu| menu.visible())
8476 }
8477
8478 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8479 self.context_menu
8480 .borrow()
8481 .as_ref()
8482 .map(|menu| menu.origin())
8483 }
8484
8485 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8486 self.context_menu_options = Some(options);
8487 }
8488
8489 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8490 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8491
8492 fn render_edit_prediction_popover(
8493 &mut self,
8494 text_bounds: &Bounds<Pixels>,
8495 content_origin: gpui::Point<Pixels>,
8496 right_margin: Pixels,
8497 editor_snapshot: &EditorSnapshot,
8498 visible_row_range: Range<DisplayRow>,
8499 scroll_top: f32,
8500 scroll_bottom: f32,
8501 line_layouts: &[LineWithInvisibles],
8502 line_height: Pixels,
8503 scroll_pixel_position: gpui::Point<Pixels>,
8504 newest_selection_head: Option<DisplayPoint>,
8505 editor_width: Pixels,
8506 style: &EditorStyle,
8507 window: &mut Window,
8508 cx: &mut App,
8509 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8510 if self.mode().is_minimap() {
8511 return None;
8512 }
8513 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8514
8515 if self.edit_prediction_visible_in_cursor_popover(true) {
8516 return None;
8517 }
8518
8519 match &active_edit_prediction.completion {
8520 EditPrediction::Move { target, .. } => {
8521 let target_display_point = target.to_display_point(editor_snapshot);
8522
8523 if self.edit_prediction_requires_modifier() {
8524 if !self.edit_prediction_preview_is_active() {
8525 return None;
8526 }
8527
8528 self.render_edit_prediction_modifier_jump_popover(
8529 text_bounds,
8530 content_origin,
8531 visible_row_range,
8532 line_layouts,
8533 line_height,
8534 scroll_pixel_position,
8535 newest_selection_head,
8536 target_display_point,
8537 window,
8538 cx,
8539 )
8540 } else {
8541 self.render_edit_prediction_eager_jump_popover(
8542 text_bounds,
8543 content_origin,
8544 editor_snapshot,
8545 visible_row_range,
8546 scroll_top,
8547 scroll_bottom,
8548 line_height,
8549 scroll_pixel_position,
8550 target_display_point,
8551 editor_width,
8552 window,
8553 cx,
8554 )
8555 }
8556 }
8557 EditPrediction::Edit {
8558 display_mode: EditDisplayMode::Inline,
8559 ..
8560 } => None,
8561 EditPrediction::Edit {
8562 display_mode: EditDisplayMode::TabAccept,
8563 edits,
8564 ..
8565 } => {
8566 let range = &edits.first()?.0;
8567 let target_display_point = range.end.to_display_point(editor_snapshot);
8568
8569 self.render_edit_prediction_end_of_line_popover(
8570 "Accept",
8571 editor_snapshot,
8572 visible_row_range,
8573 target_display_point,
8574 line_height,
8575 scroll_pixel_position,
8576 content_origin,
8577 editor_width,
8578 window,
8579 cx,
8580 )
8581 }
8582 EditPrediction::Edit {
8583 edits,
8584 edit_preview,
8585 display_mode: EditDisplayMode::DiffPopover,
8586 snapshot,
8587 } => self.render_edit_prediction_diff_popover(
8588 text_bounds,
8589 content_origin,
8590 right_margin,
8591 editor_snapshot,
8592 visible_row_range,
8593 line_layouts,
8594 line_height,
8595 scroll_pixel_position,
8596 newest_selection_head,
8597 editor_width,
8598 style,
8599 edits,
8600 edit_preview,
8601 snapshot,
8602 window,
8603 cx,
8604 ),
8605 }
8606 }
8607
8608 fn render_edit_prediction_modifier_jump_popover(
8609 &mut self,
8610 text_bounds: &Bounds<Pixels>,
8611 content_origin: gpui::Point<Pixels>,
8612 visible_row_range: Range<DisplayRow>,
8613 line_layouts: &[LineWithInvisibles],
8614 line_height: Pixels,
8615 scroll_pixel_position: gpui::Point<Pixels>,
8616 newest_selection_head: Option<DisplayPoint>,
8617 target_display_point: DisplayPoint,
8618 window: &mut Window,
8619 cx: &mut App,
8620 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8621 let scrolled_content_origin =
8622 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8623
8624 const SCROLL_PADDING_Y: Pixels = px(12.);
8625
8626 if target_display_point.row() < visible_row_range.start {
8627 return self.render_edit_prediction_scroll_popover(
8628 |_| SCROLL_PADDING_Y,
8629 IconName::ArrowUp,
8630 visible_row_range,
8631 line_layouts,
8632 newest_selection_head,
8633 scrolled_content_origin,
8634 window,
8635 cx,
8636 );
8637 } else if target_display_point.row() >= visible_row_range.end {
8638 return self.render_edit_prediction_scroll_popover(
8639 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8640 IconName::ArrowDown,
8641 visible_row_range,
8642 line_layouts,
8643 newest_selection_head,
8644 scrolled_content_origin,
8645 window,
8646 cx,
8647 );
8648 }
8649
8650 const POLE_WIDTH: Pixels = px(2.);
8651
8652 let line_layout =
8653 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8654 let target_column = target_display_point.column() as usize;
8655
8656 let target_x = line_layout.x_for_index(target_column);
8657 let target_y =
8658 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8659
8660 let flag_on_right = target_x < text_bounds.size.width / 2.;
8661
8662 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8663 border_color.l += 0.001;
8664
8665 let mut element = v_flex()
8666 .items_end()
8667 .when(flag_on_right, |el| el.items_start())
8668 .child(if flag_on_right {
8669 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8670 .rounded_bl(px(0.))
8671 .rounded_tl(px(0.))
8672 .border_l_2()
8673 .border_color(border_color)
8674 } else {
8675 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8676 .rounded_br(px(0.))
8677 .rounded_tr(px(0.))
8678 .border_r_2()
8679 .border_color(border_color)
8680 })
8681 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8682 .into_any();
8683
8684 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8685
8686 let mut origin = scrolled_content_origin + point(target_x, target_y)
8687 - point(
8688 if flag_on_right {
8689 POLE_WIDTH
8690 } else {
8691 size.width - POLE_WIDTH
8692 },
8693 size.height - line_height,
8694 );
8695
8696 origin.x = origin.x.max(content_origin.x);
8697
8698 element.prepaint_at(origin, window, cx);
8699
8700 Some((element, origin))
8701 }
8702
8703 fn render_edit_prediction_scroll_popover(
8704 &mut self,
8705 to_y: impl Fn(Size<Pixels>) -> Pixels,
8706 scroll_icon: IconName,
8707 visible_row_range: Range<DisplayRow>,
8708 line_layouts: &[LineWithInvisibles],
8709 newest_selection_head: Option<DisplayPoint>,
8710 scrolled_content_origin: gpui::Point<Pixels>,
8711 window: &mut Window,
8712 cx: &mut App,
8713 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8714 let mut element = self
8715 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8716 .into_any();
8717
8718 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8719
8720 let cursor = newest_selection_head?;
8721 let cursor_row_layout =
8722 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8723 let cursor_column = cursor.column() as usize;
8724
8725 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8726
8727 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8728
8729 element.prepaint_at(origin, window, cx);
8730 Some((element, origin))
8731 }
8732
8733 fn render_edit_prediction_eager_jump_popover(
8734 &mut self,
8735 text_bounds: &Bounds<Pixels>,
8736 content_origin: gpui::Point<Pixels>,
8737 editor_snapshot: &EditorSnapshot,
8738 visible_row_range: Range<DisplayRow>,
8739 scroll_top: f32,
8740 scroll_bottom: f32,
8741 line_height: Pixels,
8742 scroll_pixel_position: gpui::Point<Pixels>,
8743 target_display_point: DisplayPoint,
8744 editor_width: Pixels,
8745 window: &mut Window,
8746 cx: &mut App,
8747 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8748 if target_display_point.row().as_f32() < scroll_top {
8749 let mut element = self
8750 .render_edit_prediction_line_popover(
8751 "Jump to Edit",
8752 Some(IconName::ArrowUp),
8753 window,
8754 cx,
8755 )?
8756 .into_any();
8757
8758 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8759 let offset = point(
8760 (text_bounds.size.width - size.width) / 2.,
8761 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8762 );
8763
8764 let origin = text_bounds.origin + offset;
8765 element.prepaint_at(origin, window, cx);
8766 Some((element, origin))
8767 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8768 let mut element = self
8769 .render_edit_prediction_line_popover(
8770 "Jump to Edit",
8771 Some(IconName::ArrowDown),
8772 window,
8773 cx,
8774 )?
8775 .into_any();
8776
8777 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8778 let offset = point(
8779 (text_bounds.size.width - size.width) / 2.,
8780 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8781 );
8782
8783 let origin = text_bounds.origin + offset;
8784 element.prepaint_at(origin, window, cx);
8785 Some((element, origin))
8786 } else {
8787 self.render_edit_prediction_end_of_line_popover(
8788 "Jump to Edit",
8789 editor_snapshot,
8790 visible_row_range,
8791 target_display_point,
8792 line_height,
8793 scroll_pixel_position,
8794 content_origin,
8795 editor_width,
8796 window,
8797 cx,
8798 )
8799 }
8800 }
8801
8802 fn render_edit_prediction_end_of_line_popover(
8803 self: &mut Editor,
8804 label: &'static str,
8805 editor_snapshot: &EditorSnapshot,
8806 visible_row_range: Range<DisplayRow>,
8807 target_display_point: DisplayPoint,
8808 line_height: Pixels,
8809 scroll_pixel_position: gpui::Point<Pixels>,
8810 content_origin: gpui::Point<Pixels>,
8811 editor_width: Pixels,
8812 window: &mut Window,
8813 cx: &mut App,
8814 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8815 let target_line_end = DisplayPoint::new(
8816 target_display_point.row(),
8817 editor_snapshot.line_len(target_display_point.row()),
8818 );
8819
8820 let mut element = self
8821 .render_edit_prediction_line_popover(label, None, window, cx)?
8822 .into_any();
8823
8824 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8825
8826 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8827
8828 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8829 let mut origin = start_point
8830 + line_origin
8831 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8832 origin.x = origin.x.max(content_origin.x);
8833
8834 let max_x = content_origin.x + editor_width - size.width;
8835
8836 if origin.x > max_x {
8837 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8838
8839 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8840 origin.y += offset;
8841 IconName::ArrowUp
8842 } else {
8843 origin.y -= offset;
8844 IconName::ArrowDown
8845 };
8846
8847 element = self
8848 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8849 .into_any();
8850
8851 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8852
8853 origin.x = content_origin.x + editor_width - size.width - px(2.);
8854 }
8855
8856 element.prepaint_at(origin, window, cx);
8857 Some((element, origin))
8858 }
8859
8860 fn render_edit_prediction_diff_popover(
8861 self: &Editor,
8862 text_bounds: &Bounds<Pixels>,
8863 content_origin: gpui::Point<Pixels>,
8864 right_margin: Pixels,
8865 editor_snapshot: &EditorSnapshot,
8866 visible_row_range: Range<DisplayRow>,
8867 line_layouts: &[LineWithInvisibles],
8868 line_height: Pixels,
8869 scroll_pixel_position: gpui::Point<Pixels>,
8870 newest_selection_head: Option<DisplayPoint>,
8871 editor_width: Pixels,
8872 style: &EditorStyle,
8873 edits: &Vec<(Range<Anchor>, String)>,
8874 edit_preview: &Option<language::EditPreview>,
8875 snapshot: &language::BufferSnapshot,
8876 window: &mut Window,
8877 cx: &mut App,
8878 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8879 let edit_start = edits
8880 .first()
8881 .unwrap()
8882 .0
8883 .start
8884 .to_display_point(editor_snapshot);
8885 let edit_end = edits
8886 .last()
8887 .unwrap()
8888 .0
8889 .end
8890 .to_display_point(editor_snapshot);
8891
8892 let is_visible = visible_row_range.contains(&edit_start.row())
8893 || visible_row_range.contains(&edit_end.row());
8894 if !is_visible {
8895 return None;
8896 }
8897
8898 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8899 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8900 } else {
8901 // Fallback for providers without edit_preview
8902 crate::edit_prediction_fallback_text(edits, cx)
8903 };
8904
8905 let styled_text = highlighted_edits.to_styled_text(&style.text);
8906 let line_count = highlighted_edits.text.lines().count();
8907
8908 const BORDER_WIDTH: Pixels = px(1.);
8909
8910 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8911 let has_keybind = keybind.is_some();
8912
8913 let mut element = h_flex()
8914 .items_start()
8915 .child(
8916 h_flex()
8917 .bg(cx.theme().colors().editor_background)
8918 .border(BORDER_WIDTH)
8919 .shadow_xs()
8920 .border_color(cx.theme().colors().border)
8921 .rounded_l_lg()
8922 .when(line_count > 1, |el| el.rounded_br_lg())
8923 .pr_1()
8924 .child(styled_text),
8925 )
8926 .child(
8927 h_flex()
8928 .h(line_height + BORDER_WIDTH * 2.)
8929 .px_1p5()
8930 .gap_1()
8931 // Workaround: For some reason, there's a gap if we don't do this
8932 .ml(-BORDER_WIDTH)
8933 .shadow(vec![gpui::BoxShadow {
8934 color: gpui::black().opacity(0.05),
8935 offset: point(px(1.), px(1.)),
8936 blur_radius: px(2.),
8937 spread_radius: px(0.),
8938 }])
8939 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8940 .border(BORDER_WIDTH)
8941 .border_color(cx.theme().colors().border)
8942 .rounded_r_lg()
8943 .id("edit_prediction_diff_popover_keybind")
8944 .when(!has_keybind, |el| {
8945 let status_colors = cx.theme().status();
8946
8947 el.bg(status_colors.error_background)
8948 .border_color(status_colors.error.opacity(0.6))
8949 .child(Icon::new(IconName::Info).color(Color::Error))
8950 .cursor_default()
8951 .hoverable_tooltip(move |_window, cx| {
8952 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8953 })
8954 })
8955 .children(keybind),
8956 )
8957 .into_any();
8958
8959 let longest_row =
8960 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8961 let longest_line_width = if visible_row_range.contains(&longest_row) {
8962 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8963 } else {
8964 layout_line(
8965 longest_row,
8966 editor_snapshot,
8967 style,
8968 editor_width,
8969 |_| false,
8970 window,
8971 cx,
8972 )
8973 .width
8974 };
8975
8976 let viewport_bounds =
8977 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8978 right: -right_margin,
8979 ..Default::default()
8980 });
8981
8982 let x_after_longest =
8983 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8984 - scroll_pixel_position.x;
8985
8986 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8987
8988 // Fully visible if it can be displayed within the window (allow overlapping other
8989 // panes). However, this is only allowed if the popover starts within text_bounds.
8990 let can_position_to_the_right = x_after_longest < text_bounds.right()
8991 && x_after_longest + element_bounds.width < viewport_bounds.right();
8992
8993 let mut origin = if can_position_to_the_right {
8994 point(
8995 x_after_longest,
8996 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8997 - scroll_pixel_position.y,
8998 )
8999 } else {
9000 let cursor_row = newest_selection_head.map(|head| head.row());
9001 let above_edit = edit_start
9002 .row()
9003 .0
9004 .checked_sub(line_count as u32)
9005 .map(DisplayRow);
9006 let below_edit = Some(edit_end.row() + 1);
9007 let above_cursor =
9008 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9009 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9010
9011 // Place the edit popover adjacent to the edit if there is a location
9012 // available that is onscreen and does not obscure the cursor. Otherwise,
9013 // place it adjacent to the cursor.
9014 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9015 .into_iter()
9016 .flatten()
9017 .find(|&start_row| {
9018 let end_row = start_row + line_count as u32;
9019 visible_row_range.contains(&start_row)
9020 && visible_row_range.contains(&end_row)
9021 && cursor_row
9022 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9023 })?;
9024
9025 content_origin
9026 + point(
9027 -scroll_pixel_position.x,
9028 row_target.as_f32() * line_height - scroll_pixel_position.y,
9029 )
9030 };
9031
9032 origin.x -= BORDER_WIDTH;
9033
9034 window.defer_draw(element, origin, 1);
9035
9036 // Do not return an element, since it will already be drawn due to defer_draw.
9037 None
9038 }
9039
9040 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9041 px(30.)
9042 }
9043
9044 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9045 if self.read_only(cx) {
9046 cx.theme().players().read_only()
9047 } else {
9048 self.style.as_ref().unwrap().local_player
9049 }
9050 }
9051
9052 fn render_edit_prediction_accept_keybind(
9053 &self,
9054 window: &mut Window,
9055 cx: &App,
9056 ) -> Option<AnyElement> {
9057 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9058 let accept_keystroke = accept_binding.keystroke()?;
9059
9060 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9061
9062 let modifiers_color = if accept_keystroke.display_modifiers == window.modifiers() {
9063 Color::Accent
9064 } else {
9065 Color::Muted
9066 };
9067
9068 h_flex()
9069 .px_0p5()
9070 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9071 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9072 .text_size(TextSize::XSmall.rems(cx))
9073 .child(h_flex().children(ui::render_modifiers(
9074 &accept_keystroke.display_modifiers,
9075 PlatformStyle::platform(),
9076 Some(modifiers_color),
9077 Some(IconSize::XSmall.rems().into()),
9078 true,
9079 )))
9080 .when(is_platform_style_mac, |parent| {
9081 parent.child(accept_keystroke.display_key.clone())
9082 })
9083 .when(!is_platform_style_mac, |parent| {
9084 parent.child(
9085 Key::new(
9086 util::capitalize(&accept_keystroke.display_key),
9087 Some(Color::Default),
9088 )
9089 .size(Some(IconSize::XSmall.rems().into())),
9090 )
9091 })
9092 .into_any()
9093 .into()
9094 }
9095
9096 fn render_edit_prediction_line_popover(
9097 &self,
9098 label: impl Into<SharedString>,
9099 icon: Option<IconName>,
9100 window: &mut Window,
9101 cx: &App,
9102 ) -> Option<Stateful<Div>> {
9103 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9104
9105 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9106 let has_keybind = keybind.is_some();
9107
9108 let result = h_flex()
9109 .id("ep-line-popover")
9110 .py_0p5()
9111 .pl_1()
9112 .pr(padding_right)
9113 .gap_1()
9114 .rounded_md()
9115 .border_1()
9116 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9117 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9118 .shadow_xs()
9119 .when(!has_keybind, |el| {
9120 let status_colors = cx.theme().status();
9121
9122 el.bg(status_colors.error_background)
9123 .border_color(status_colors.error.opacity(0.6))
9124 .pl_2()
9125 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9126 .cursor_default()
9127 .hoverable_tooltip(move |_window, cx| {
9128 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9129 })
9130 })
9131 .children(keybind)
9132 .child(
9133 Label::new(label)
9134 .size(LabelSize::Small)
9135 .when(!has_keybind, |el| {
9136 el.color(cx.theme().status().error.into()).strikethrough()
9137 }),
9138 )
9139 .when(!has_keybind, |el| {
9140 el.child(
9141 h_flex().ml_1().child(
9142 Icon::new(IconName::Info)
9143 .size(IconSize::Small)
9144 .color(cx.theme().status().error.into()),
9145 ),
9146 )
9147 })
9148 .when_some(icon, |element, icon| {
9149 element.child(
9150 div()
9151 .mt(px(1.5))
9152 .child(Icon::new(icon).size(IconSize::Small)),
9153 )
9154 });
9155
9156 Some(result)
9157 }
9158
9159 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9160 let accent_color = cx.theme().colors().text_accent;
9161 let editor_bg_color = cx.theme().colors().editor_background;
9162 editor_bg_color.blend(accent_color.opacity(0.1))
9163 }
9164
9165 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9166 let accent_color = cx.theme().colors().text_accent;
9167 let editor_bg_color = cx.theme().colors().editor_background;
9168 editor_bg_color.blend(accent_color.opacity(0.6))
9169 }
9170 fn get_prediction_provider_icon_name(
9171 provider: &Option<RegisteredEditPredictionProvider>,
9172 ) -> IconName {
9173 match provider {
9174 Some(provider) => match provider.provider.name() {
9175 "copilot" => IconName::Copilot,
9176 "supermaven" => IconName::Supermaven,
9177 _ => IconName::ZedPredict,
9178 },
9179 None => IconName::ZedPredict,
9180 }
9181 }
9182
9183 fn render_edit_prediction_cursor_popover(
9184 &self,
9185 min_width: Pixels,
9186 max_width: Pixels,
9187 cursor_point: Point,
9188 style: &EditorStyle,
9189 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9190 _window: &Window,
9191 cx: &mut Context<Editor>,
9192 ) -> Option<AnyElement> {
9193 let provider = self.edit_prediction_provider.as_ref()?;
9194 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9195
9196 let is_refreshing = provider.provider.is_refreshing(cx);
9197
9198 fn pending_completion_container(icon: IconName) -> Div {
9199 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9200 }
9201
9202 let completion = match &self.active_edit_prediction {
9203 Some(prediction) => {
9204 if !self.has_visible_completions_menu() {
9205 const RADIUS: Pixels = px(6.);
9206 const BORDER_WIDTH: Pixels = px(1.);
9207
9208 return Some(
9209 h_flex()
9210 .elevation_2(cx)
9211 .border(BORDER_WIDTH)
9212 .border_color(cx.theme().colors().border)
9213 .when(accept_keystroke.is_none(), |el| {
9214 el.border_color(cx.theme().status().error)
9215 })
9216 .rounded(RADIUS)
9217 .rounded_tl(px(0.))
9218 .overflow_hidden()
9219 .child(div().px_1p5().child(match &prediction.completion {
9220 EditPrediction::Move { target, snapshot } => {
9221 use text::ToPoint as _;
9222 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9223 {
9224 Icon::new(IconName::ZedPredictDown)
9225 } else {
9226 Icon::new(IconName::ZedPredictUp)
9227 }
9228 }
9229 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9230 }))
9231 .child(
9232 h_flex()
9233 .gap_1()
9234 .py_1()
9235 .px_2()
9236 .rounded_r(RADIUS - BORDER_WIDTH)
9237 .border_l_1()
9238 .border_color(cx.theme().colors().border)
9239 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9240 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9241 el.child(
9242 Label::new("Hold")
9243 .size(LabelSize::Small)
9244 .when(accept_keystroke.is_none(), |el| {
9245 el.strikethrough()
9246 })
9247 .line_height_style(LineHeightStyle::UiLabel),
9248 )
9249 })
9250 .id("edit_prediction_cursor_popover_keybind")
9251 .when(accept_keystroke.is_none(), |el| {
9252 let status_colors = cx.theme().status();
9253
9254 el.bg(status_colors.error_background)
9255 .border_color(status_colors.error.opacity(0.6))
9256 .child(Icon::new(IconName::Info).color(Color::Error))
9257 .cursor_default()
9258 .hoverable_tooltip(move |_window, cx| {
9259 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9260 .into()
9261 })
9262 })
9263 .when_some(
9264 accept_keystroke.as_ref(),
9265 |el, accept_keystroke| {
9266 el.child(h_flex().children(ui::render_modifiers(
9267 &accept_keystroke.display_modifiers,
9268 PlatformStyle::platform(),
9269 Some(Color::Default),
9270 Some(IconSize::XSmall.rems().into()),
9271 false,
9272 )))
9273 },
9274 ),
9275 )
9276 .into_any(),
9277 );
9278 }
9279
9280 self.render_edit_prediction_cursor_popover_preview(
9281 prediction,
9282 cursor_point,
9283 style,
9284 cx,
9285 )?
9286 }
9287
9288 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9289 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9290 stale_completion,
9291 cursor_point,
9292 style,
9293 cx,
9294 )?,
9295
9296 None => pending_completion_container(provider_icon)
9297 .child(Label::new("...").size(LabelSize::Small)),
9298 },
9299
9300 None => pending_completion_container(provider_icon)
9301 .child(Label::new("...").size(LabelSize::Small)),
9302 };
9303
9304 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9305 completion
9306 .with_animation(
9307 "loading-completion",
9308 Animation::new(Duration::from_secs(2))
9309 .repeat()
9310 .with_easing(pulsating_between(0.4, 0.8)),
9311 |label, delta| label.opacity(delta),
9312 )
9313 .into_any_element()
9314 } else {
9315 completion.into_any_element()
9316 };
9317
9318 let has_completion = self.active_edit_prediction.is_some();
9319
9320 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9321 Some(
9322 h_flex()
9323 .min_w(min_width)
9324 .max_w(max_width)
9325 .flex_1()
9326 .elevation_2(cx)
9327 .border_color(cx.theme().colors().border)
9328 .child(
9329 div()
9330 .flex_1()
9331 .py_1()
9332 .px_2()
9333 .overflow_hidden()
9334 .child(completion),
9335 )
9336 .when_some(accept_keystroke, |el, accept_keystroke| {
9337 if !accept_keystroke.display_modifiers.modified() {
9338 return el;
9339 }
9340
9341 el.child(
9342 h_flex()
9343 .h_full()
9344 .border_l_1()
9345 .rounded_r_lg()
9346 .border_color(cx.theme().colors().border)
9347 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9348 .gap_1()
9349 .py_1()
9350 .px_2()
9351 .child(
9352 h_flex()
9353 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9354 .when(is_platform_style_mac, |parent| parent.gap_1())
9355 .child(h_flex().children(ui::render_modifiers(
9356 &accept_keystroke.display_modifiers,
9357 PlatformStyle::platform(),
9358 Some(if !has_completion {
9359 Color::Muted
9360 } else {
9361 Color::Default
9362 }),
9363 None,
9364 false,
9365 ))),
9366 )
9367 .child(Label::new("Preview").into_any_element())
9368 .opacity(if has_completion { 1.0 } else { 0.4 }),
9369 )
9370 })
9371 .into_any(),
9372 )
9373 }
9374
9375 fn render_edit_prediction_cursor_popover_preview(
9376 &self,
9377 completion: &EditPredictionState,
9378 cursor_point: Point,
9379 style: &EditorStyle,
9380 cx: &mut Context<Editor>,
9381 ) -> Option<Div> {
9382 use text::ToPoint as _;
9383
9384 fn render_relative_row_jump(
9385 prefix: impl Into<String>,
9386 current_row: u32,
9387 target_row: u32,
9388 ) -> Div {
9389 let (row_diff, arrow) = if target_row < current_row {
9390 (current_row - target_row, IconName::ArrowUp)
9391 } else {
9392 (target_row - current_row, IconName::ArrowDown)
9393 };
9394
9395 h_flex()
9396 .child(
9397 Label::new(format!("{}{}", prefix.into(), row_diff))
9398 .color(Color::Muted)
9399 .size(LabelSize::Small),
9400 )
9401 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9402 }
9403
9404 let supports_jump = self
9405 .edit_prediction_provider
9406 .as_ref()
9407 .map(|provider| provider.provider.supports_jump_to_edit())
9408 .unwrap_or(true);
9409
9410 match &completion.completion {
9411 EditPrediction::Move {
9412 target, snapshot, ..
9413 } => {
9414 if !supports_jump {
9415 return None;
9416 }
9417
9418 Some(
9419 h_flex()
9420 .px_2()
9421 .gap_2()
9422 .flex_1()
9423 .child(
9424 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9425 Icon::new(IconName::ZedPredictDown)
9426 } else {
9427 Icon::new(IconName::ZedPredictUp)
9428 },
9429 )
9430 .child(Label::new("Jump to Edit")),
9431 )
9432 }
9433
9434 EditPrediction::Edit {
9435 edits,
9436 edit_preview,
9437 snapshot,
9438 display_mode: _,
9439 } => {
9440 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9441
9442 let (highlighted_edits, has_more_lines) =
9443 if let Some(edit_preview) = edit_preview.as_ref() {
9444 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9445 .first_line_preview()
9446 } else {
9447 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9448 };
9449
9450 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9451 .with_default_highlights(&style.text, highlighted_edits.highlights);
9452
9453 let preview = h_flex()
9454 .gap_1()
9455 .min_w_16()
9456 .child(styled_text)
9457 .when(has_more_lines, |parent| parent.child("…"));
9458
9459 let left = if supports_jump && first_edit_row != cursor_point.row {
9460 render_relative_row_jump("", cursor_point.row, first_edit_row)
9461 .into_any_element()
9462 } else {
9463 let icon_name =
9464 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9465 Icon::new(icon_name).into_any_element()
9466 };
9467
9468 Some(
9469 h_flex()
9470 .h_full()
9471 .flex_1()
9472 .gap_2()
9473 .pr_1()
9474 .overflow_x_hidden()
9475 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9476 .child(left)
9477 .child(preview),
9478 )
9479 }
9480 }
9481 }
9482
9483 pub fn render_context_menu(
9484 &self,
9485 style: &EditorStyle,
9486 max_height_in_lines: u32,
9487 window: &mut Window,
9488 cx: &mut Context<Editor>,
9489 ) -> Option<AnyElement> {
9490 let menu = self.context_menu.borrow();
9491 let menu = menu.as_ref()?;
9492 if !menu.visible() {
9493 return None;
9494 };
9495 Some(menu.render(style, max_height_in_lines, window, cx))
9496 }
9497
9498 fn render_context_menu_aside(
9499 &mut self,
9500 max_size: Size<Pixels>,
9501 window: &mut Window,
9502 cx: &mut Context<Editor>,
9503 ) -> Option<AnyElement> {
9504 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9505 if menu.visible() {
9506 menu.render_aside(max_size, window, cx)
9507 } else {
9508 None
9509 }
9510 })
9511 }
9512
9513 fn hide_context_menu(
9514 &mut self,
9515 window: &mut Window,
9516 cx: &mut Context<Self>,
9517 ) -> Option<CodeContextMenu> {
9518 cx.notify();
9519 self.completion_tasks.clear();
9520 let context_menu = self.context_menu.borrow_mut().take();
9521 self.stale_edit_prediction_in_menu.take();
9522 self.update_visible_edit_prediction(window, cx);
9523 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9524 && let Some(completion_provider) = &self.completion_provider
9525 {
9526 completion_provider.selection_changed(None, window, cx);
9527 }
9528 context_menu
9529 }
9530
9531 fn show_snippet_choices(
9532 &mut self,
9533 choices: &Vec<String>,
9534 selection: Range<Anchor>,
9535 cx: &mut Context<Self>,
9536 ) {
9537 let Some((_, buffer, _)) = self
9538 .buffer()
9539 .read(cx)
9540 .excerpt_containing(selection.start, cx)
9541 else {
9542 return;
9543 };
9544 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9545 else {
9546 return;
9547 };
9548 if buffer != end_buffer {
9549 log::error!("expected anchor range to have matching buffer IDs");
9550 return;
9551 }
9552
9553 let id = post_inc(&mut self.next_completion_id);
9554 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9555 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9556 CompletionsMenu::new_snippet_choices(
9557 id,
9558 true,
9559 choices,
9560 selection,
9561 buffer,
9562 snippet_sort_order,
9563 ),
9564 ));
9565 }
9566
9567 pub fn insert_snippet(
9568 &mut self,
9569 insertion_ranges: &[Range<usize>],
9570 snippet: Snippet,
9571 window: &mut Window,
9572 cx: &mut Context<Self>,
9573 ) -> Result<()> {
9574 struct Tabstop<T> {
9575 is_end_tabstop: bool,
9576 ranges: Vec<Range<T>>,
9577 choices: Option<Vec<String>>,
9578 }
9579
9580 let tabstops = self.buffer.update(cx, |buffer, cx| {
9581 let snippet_text: Arc<str> = snippet.text.clone().into();
9582 let edits = insertion_ranges
9583 .iter()
9584 .cloned()
9585 .map(|range| (range, snippet_text.clone()));
9586 let autoindent_mode = AutoindentMode::Block {
9587 original_indent_columns: Vec::new(),
9588 };
9589 buffer.edit(edits, Some(autoindent_mode), cx);
9590
9591 let snapshot = &*buffer.read(cx);
9592 let snippet = &snippet;
9593 snippet
9594 .tabstops
9595 .iter()
9596 .map(|tabstop| {
9597 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9598 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9599 });
9600 let mut tabstop_ranges = tabstop
9601 .ranges
9602 .iter()
9603 .flat_map(|tabstop_range| {
9604 let mut delta = 0_isize;
9605 insertion_ranges.iter().map(move |insertion_range| {
9606 let insertion_start = insertion_range.start as isize + delta;
9607 delta +=
9608 snippet.text.len() as isize - insertion_range.len() as isize;
9609
9610 let start = ((insertion_start + tabstop_range.start) as usize)
9611 .min(snapshot.len());
9612 let end = ((insertion_start + tabstop_range.end) as usize)
9613 .min(snapshot.len());
9614 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9615 })
9616 })
9617 .collect::<Vec<_>>();
9618 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9619
9620 Tabstop {
9621 is_end_tabstop,
9622 ranges: tabstop_ranges,
9623 choices: tabstop.choices.clone(),
9624 }
9625 })
9626 .collect::<Vec<_>>()
9627 });
9628 if let Some(tabstop) = tabstops.first() {
9629 self.change_selections(Default::default(), window, cx, |s| {
9630 // Reverse order so that the first range is the newest created selection.
9631 // Completions will use it and autoscroll will prioritize it.
9632 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9633 });
9634
9635 if let Some(choices) = &tabstop.choices
9636 && let Some(selection) = tabstop.ranges.first()
9637 {
9638 self.show_snippet_choices(choices, selection.clone(), cx)
9639 }
9640
9641 // If we're already at the last tabstop and it's at the end of the snippet,
9642 // we're done, we don't need to keep the state around.
9643 if !tabstop.is_end_tabstop {
9644 let choices = tabstops
9645 .iter()
9646 .map(|tabstop| tabstop.choices.clone())
9647 .collect();
9648
9649 let ranges = tabstops
9650 .into_iter()
9651 .map(|tabstop| tabstop.ranges)
9652 .collect::<Vec<_>>();
9653
9654 self.snippet_stack.push(SnippetState {
9655 active_index: 0,
9656 ranges,
9657 choices,
9658 });
9659 }
9660
9661 // Check whether the just-entered snippet ends with an auto-closable bracket.
9662 if self.autoclose_regions.is_empty() {
9663 let snapshot = self.buffer.read(cx).snapshot(cx);
9664 let mut all_selections = self.selections.all::<Point>(cx);
9665 for selection in &mut all_selections {
9666 let selection_head = selection.head();
9667 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9668 continue;
9669 };
9670
9671 let mut bracket_pair = None;
9672 let max_lookup_length = scope
9673 .brackets()
9674 .map(|(pair, _)| {
9675 pair.start
9676 .as_str()
9677 .chars()
9678 .count()
9679 .max(pair.end.as_str().chars().count())
9680 })
9681 .max();
9682 if let Some(max_lookup_length) = max_lookup_length {
9683 let next_text = snapshot
9684 .chars_at(selection_head)
9685 .take(max_lookup_length)
9686 .collect::<String>();
9687 let prev_text = snapshot
9688 .reversed_chars_at(selection_head)
9689 .take(max_lookup_length)
9690 .collect::<String>();
9691
9692 for (pair, enabled) in scope.brackets() {
9693 if enabled
9694 && pair.close
9695 && prev_text.starts_with(pair.start.as_str())
9696 && next_text.starts_with(pair.end.as_str())
9697 {
9698 bracket_pair = Some(pair.clone());
9699 break;
9700 }
9701 }
9702 }
9703
9704 if let Some(pair) = bracket_pair {
9705 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9706 let autoclose_enabled =
9707 self.use_autoclose && snapshot_settings.use_autoclose;
9708 if autoclose_enabled {
9709 let start = snapshot.anchor_after(selection_head);
9710 let end = snapshot.anchor_after(selection_head);
9711 self.autoclose_regions.push(AutocloseRegion {
9712 selection_id: selection.id,
9713 range: start..end,
9714 pair,
9715 });
9716 }
9717 }
9718 }
9719 }
9720 }
9721 Ok(())
9722 }
9723
9724 pub fn move_to_next_snippet_tabstop(
9725 &mut self,
9726 window: &mut Window,
9727 cx: &mut Context<Self>,
9728 ) -> bool {
9729 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9730 }
9731
9732 pub fn move_to_prev_snippet_tabstop(
9733 &mut self,
9734 window: &mut Window,
9735 cx: &mut Context<Self>,
9736 ) -> bool {
9737 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9738 }
9739
9740 pub fn move_to_snippet_tabstop(
9741 &mut self,
9742 bias: Bias,
9743 window: &mut Window,
9744 cx: &mut Context<Self>,
9745 ) -> bool {
9746 if let Some(mut snippet) = self.snippet_stack.pop() {
9747 match bias {
9748 Bias::Left => {
9749 if snippet.active_index > 0 {
9750 snippet.active_index -= 1;
9751 } else {
9752 self.snippet_stack.push(snippet);
9753 return false;
9754 }
9755 }
9756 Bias::Right => {
9757 if snippet.active_index + 1 < snippet.ranges.len() {
9758 snippet.active_index += 1;
9759 } else {
9760 self.snippet_stack.push(snippet);
9761 return false;
9762 }
9763 }
9764 }
9765 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9766 self.change_selections(Default::default(), window, cx, |s| {
9767 // Reverse order so that the first range is the newest created selection.
9768 // Completions will use it and autoscroll will prioritize it.
9769 s.select_ranges(current_ranges.iter().rev().cloned())
9770 });
9771
9772 if let Some(choices) = &snippet.choices[snippet.active_index]
9773 && let Some(selection) = current_ranges.first()
9774 {
9775 self.show_snippet_choices(choices, selection.clone(), cx);
9776 }
9777
9778 // If snippet state is not at the last tabstop, push it back on the stack
9779 if snippet.active_index + 1 < snippet.ranges.len() {
9780 self.snippet_stack.push(snippet);
9781 }
9782 return true;
9783 }
9784 }
9785
9786 false
9787 }
9788
9789 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9790 self.transact(window, cx, |this, window, cx| {
9791 this.select_all(&SelectAll, window, cx);
9792 this.insert("", window, cx);
9793 });
9794 }
9795
9796 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9797 if self.read_only(cx) {
9798 return;
9799 }
9800 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9801 self.transact(window, cx, |this, window, cx| {
9802 this.select_autoclose_pair(window, cx);
9803 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9804 if !this.linked_edit_ranges.is_empty() {
9805 let selections = this.selections.all::<MultiBufferPoint>(cx);
9806 let snapshot = this.buffer.read(cx).snapshot(cx);
9807
9808 for selection in selections.iter() {
9809 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9810 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9811 if selection_start.buffer_id != selection_end.buffer_id {
9812 continue;
9813 }
9814 if let Some(ranges) =
9815 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9816 {
9817 for (buffer, entries) in ranges {
9818 linked_ranges.entry(buffer).or_default().extend(entries);
9819 }
9820 }
9821 }
9822 }
9823
9824 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9825 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9826 for selection in &mut selections {
9827 if selection.is_empty() {
9828 let old_head = selection.head();
9829 let mut new_head =
9830 movement::left(&display_map, old_head.to_display_point(&display_map))
9831 .to_point(&display_map);
9832 if let Some((buffer, line_buffer_range)) = display_map
9833 .buffer_snapshot
9834 .buffer_line_for_row(MultiBufferRow(old_head.row))
9835 {
9836 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9837 let indent_len = match indent_size.kind {
9838 IndentKind::Space => {
9839 buffer.settings_at(line_buffer_range.start, cx).tab_size
9840 }
9841 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9842 };
9843 if old_head.column <= indent_size.len && old_head.column > 0 {
9844 let indent_len = indent_len.get();
9845 new_head = cmp::min(
9846 new_head,
9847 MultiBufferPoint::new(
9848 old_head.row,
9849 ((old_head.column - 1) / indent_len) * indent_len,
9850 ),
9851 );
9852 }
9853 }
9854
9855 selection.set_head(new_head, SelectionGoal::None);
9856 }
9857 }
9858
9859 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9860 this.insert("", window, cx);
9861 let empty_str: Arc<str> = Arc::from("");
9862 for (buffer, edits) in linked_ranges {
9863 let snapshot = buffer.read(cx).snapshot();
9864 use text::ToPoint as TP;
9865
9866 let edits = edits
9867 .into_iter()
9868 .map(|range| {
9869 let end_point = TP::to_point(&range.end, &snapshot);
9870 let mut start_point = TP::to_point(&range.start, &snapshot);
9871
9872 if end_point == start_point {
9873 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9874 .saturating_sub(1);
9875 start_point =
9876 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9877 };
9878
9879 (start_point..end_point, empty_str.clone())
9880 })
9881 .sorted_by_key(|(range, _)| range.start)
9882 .collect::<Vec<_>>();
9883 buffer.update(cx, |this, cx| {
9884 this.edit(edits, None, cx);
9885 })
9886 }
9887 this.refresh_edit_prediction(true, false, window, cx);
9888 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9889 });
9890 }
9891
9892 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9893 if self.read_only(cx) {
9894 return;
9895 }
9896 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9897 self.transact(window, cx, |this, window, cx| {
9898 this.change_selections(Default::default(), window, cx, |s| {
9899 s.move_with(|map, selection| {
9900 if selection.is_empty() {
9901 let cursor = movement::right(map, selection.head());
9902 selection.end = cursor;
9903 selection.reversed = true;
9904 selection.goal = SelectionGoal::None;
9905 }
9906 })
9907 });
9908 this.insert("", window, cx);
9909 this.refresh_edit_prediction(true, false, window, cx);
9910 });
9911 }
9912
9913 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9914 if self.mode.is_single_line() {
9915 cx.propagate();
9916 return;
9917 }
9918
9919 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9920 if self.move_to_prev_snippet_tabstop(window, cx) {
9921 return;
9922 }
9923 self.outdent(&Outdent, window, cx);
9924 }
9925
9926 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9927 if self.mode.is_single_line() {
9928 cx.propagate();
9929 return;
9930 }
9931
9932 if self.move_to_next_snippet_tabstop(window, cx) {
9933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9934 return;
9935 }
9936 if self.read_only(cx) {
9937 return;
9938 }
9939 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9940 let mut selections = self.selections.all_adjusted(cx);
9941 let buffer = self.buffer.read(cx);
9942 let snapshot = buffer.snapshot(cx);
9943 let rows_iter = selections.iter().map(|s| s.head().row);
9944 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9945
9946 let has_some_cursor_in_whitespace = selections
9947 .iter()
9948 .filter(|selection| selection.is_empty())
9949 .any(|selection| {
9950 let cursor = selection.head();
9951 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9952 cursor.column < current_indent.len
9953 });
9954
9955 let mut edits = Vec::new();
9956 let mut prev_edited_row = 0;
9957 let mut row_delta = 0;
9958 for selection in &mut selections {
9959 if selection.start.row != prev_edited_row {
9960 row_delta = 0;
9961 }
9962 prev_edited_row = selection.end.row;
9963
9964 // If the selection is non-empty, then increase the indentation of the selected lines.
9965 if !selection.is_empty() {
9966 row_delta =
9967 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9968 continue;
9969 }
9970
9971 let cursor = selection.head();
9972 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9973 if let Some(suggested_indent) =
9974 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9975 {
9976 // Don't do anything if already at suggested indent
9977 // and there is any other cursor which is not
9978 if has_some_cursor_in_whitespace
9979 && cursor.column == current_indent.len
9980 && current_indent.len == suggested_indent.len
9981 {
9982 continue;
9983 }
9984
9985 // Adjust line and move cursor to suggested indent
9986 // if cursor is not at suggested indent
9987 if cursor.column < suggested_indent.len
9988 && cursor.column <= current_indent.len
9989 && current_indent.len <= suggested_indent.len
9990 {
9991 selection.start = Point::new(cursor.row, suggested_indent.len);
9992 selection.end = selection.start;
9993 if row_delta == 0 {
9994 edits.extend(Buffer::edit_for_indent_size_adjustment(
9995 cursor.row,
9996 current_indent,
9997 suggested_indent,
9998 ));
9999 row_delta = suggested_indent.len - current_indent.len;
10000 }
10001 continue;
10002 }
10003
10004 // If current indent is more than suggested indent
10005 // only move cursor to current indent and skip indent
10006 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10007 selection.start = Point::new(cursor.row, current_indent.len);
10008 selection.end = selection.start;
10009 continue;
10010 }
10011 }
10012
10013 // Otherwise, insert a hard or soft tab.
10014 let settings = buffer.language_settings_at(cursor, cx);
10015 let tab_size = if settings.hard_tabs {
10016 IndentSize::tab()
10017 } else {
10018 let tab_size = settings.tab_size.get();
10019 let indent_remainder = snapshot
10020 .text_for_range(Point::new(cursor.row, 0)..cursor)
10021 .flat_map(str::chars)
10022 .fold(row_delta % tab_size, |counter: u32, c| {
10023 if c == '\t' {
10024 0
10025 } else {
10026 (counter + 1) % tab_size
10027 }
10028 });
10029
10030 let chars_to_next_tab_stop = tab_size - indent_remainder;
10031 IndentSize::spaces(chars_to_next_tab_stop)
10032 };
10033 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10034 selection.end = selection.start;
10035 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10036 row_delta += tab_size.len;
10037 }
10038
10039 self.transact(window, cx, |this, window, cx| {
10040 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10041 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10042 this.refresh_edit_prediction(true, false, window, cx);
10043 });
10044 }
10045
10046 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10047 if self.read_only(cx) {
10048 return;
10049 }
10050 if self.mode.is_single_line() {
10051 cx.propagate();
10052 return;
10053 }
10054
10055 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10056 let mut selections = self.selections.all::<Point>(cx);
10057 let mut prev_edited_row = 0;
10058 let mut row_delta = 0;
10059 let mut edits = Vec::new();
10060 let buffer = self.buffer.read(cx);
10061 let snapshot = buffer.snapshot(cx);
10062 for selection in &mut selections {
10063 if selection.start.row != prev_edited_row {
10064 row_delta = 0;
10065 }
10066 prev_edited_row = selection.end.row;
10067
10068 row_delta =
10069 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10070 }
10071
10072 self.transact(window, cx, |this, window, cx| {
10073 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10074 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10075 });
10076 }
10077
10078 fn indent_selection(
10079 buffer: &MultiBuffer,
10080 snapshot: &MultiBufferSnapshot,
10081 selection: &mut Selection<Point>,
10082 edits: &mut Vec<(Range<Point>, String)>,
10083 delta_for_start_row: u32,
10084 cx: &App,
10085 ) -> u32 {
10086 let settings = buffer.language_settings_at(selection.start, cx);
10087 let tab_size = settings.tab_size.get();
10088 let indent_kind = if settings.hard_tabs {
10089 IndentKind::Tab
10090 } else {
10091 IndentKind::Space
10092 };
10093 let mut start_row = selection.start.row;
10094 let mut end_row = selection.end.row + 1;
10095
10096 // If a selection ends at the beginning of a line, don't indent
10097 // that last line.
10098 if selection.end.column == 0 && selection.end.row > selection.start.row {
10099 end_row -= 1;
10100 }
10101
10102 // Avoid re-indenting a row that has already been indented by a
10103 // previous selection, but still update this selection's column
10104 // to reflect that indentation.
10105 if delta_for_start_row > 0 {
10106 start_row += 1;
10107 selection.start.column += delta_for_start_row;
10108 if selection.end.row == selection.start.row {
10109 selection.end.column += delta_for_start_row;
10110 }
10111 }
10112
10113 let mut delta_for_end_row = 0;
10114 let has_multiple_rows = start_row + 1 != end_row;
10115 for row in start_row..end_row {
10116 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10117 let indent_delta = match (current_indent.kind, indent_kind) {
10118 (IndentKind::Space, IndentKind::Space) => {
10119 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10120 IndentSize::spaces(columns_to_next_tab_stop)
10121 }
10122 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10123 (_, IndentKind::Tab) => IndentSize::tab(),
10124 };
10125
10126 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10127 0
10128 } else {
10129 selection.start.column
10130 };
10131 let row_start = Point::new(row, start);
10132 edits.push((
10133 row_start..row_start,
10134 indent_delta.chars().collect::<String>(),
10135 ));
10136
10137 // Update this selection's endpoints to reflect the indentation.
10138 if row == selection.start.row {
10139 selection.start.column += indent_delta.len;
10140 }
10141 if row == selection.end.row {
10142 selection.end.column += indent_delta.len;
10143 delta_for_end_row = indent_delta.len;
10144 }
10145 }
10146
10147 if selection.start.row == selection.end.row {
10148 delta_for_start_row + delta_for_end_row
10149 } else {
10150 delta_for_end_row
10151 }
10152 }
10153
10154 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10155 if self.read_only(cx) {
10156 return;
10157 }
10158 if self.mode.is_single_line() {
10159 cx.propagate();
10160 return;
10161 }
10162
10163 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10164 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10165 let selections = self.selections.all::<Point>(cx);
10166 let mut deletion_ranges = Vec::new();
10167 let mut last_outdent = None;
10168 {
10169 let buffer = self.buffer.read(cx);
10170 let snapshot = buffer.snapshot(cx);
10171 for selection in &selections {
10172 let settings = buffer.language_settings_at(selection.start, cx);
10173 let tab_size = settings.tab_size.get();
10174 let mut rows = selection.spanned_rows(false, &display_map);
10175
10176 // Avoid re-outdenting a row that has already been outdented by a
10177 // previous selection.
10178 if let Some(last_row) = last_outdent
10179 && last_row == rows.start
10180 {
10181 rows.start = rows.start.next_row();
10182 }
10183 let has_multiple_rows = rows.len() > 1;
10184 for row in rows.iter_rows() {
10185 let indent_size = snapshot.indent_size_for_line(row);
10186 if indent_size.len > 0 {
10187 let deletion_len = match indent_size.kind {
10188 IndentKind::Space => {
10189 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10190 if columns_to_prev_tab_stop == 0 {
10191 tab_size
10192 } else {
10193 columns_to_prev_tab_stop
10194 }
10195 }
10196 IndentKind::Tab => 1,
10197 };
10198 let start = if has_multiple_rows
10199 || deletion_len > selection.start.column
10200 || indent_size.len < selection.start.column
10201 {
10202 0
10203 } else {
10204 selection.start.column - deletion_len
10205 };
10206 deletion_ranges.push(
10207 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10208 );
10209 last_outdent = Some(row);
10210 }
10211 }
10212 }
10213 }
10214
10215 self.transact(window, cx, |this, window, cx| {
10216 this.buffer.update(cx, |buffer, cx| {
10217 let empty_str: Arc<str> = Arc::default();
10218 buffer.edit(
10219 deletion_ranges
10220 .into_iter()
10221 .map(|range| (range, empty_str.clone())),
10222 None,
10223 cx,
10224 );
10225 });
10226 let selections = this.selections.all::<usize>(cx);
10227 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10228 });
10229 }
10230
10231 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10232 if self.read_only(cx) {
10233 return;
10234 }
10235 if self.mode.is_single_line() {
10236 cx.propagate();
10237 return;
10238 }
10239
10240 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10241 let selections = self
10242 .selections
10243 .all::<usize>(cx)
10244 .into_iter()
10245 .map(|s| s.range());
10246
10247 self.transact(window, cx, |this, window, cx| {
10248 this.buffer.update(cx, |buffer, cx| {
10249 buffer.autoindent_ranges(selections, cx);
10250 });
10251 let selections = this.selections.all::<usize>(cx);
10252 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10253 });
10254 }
10255
10256 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10257 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10258 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10259 let selections = self.selections.all::<Point>(cx);
10260
10261 let mut new_cursors = Vec::new();
10262 let mut edit_ranges = Vec::new();
10263 let mut selections = selections.iter().peekable();
10264 while let Some(selection) = selections.next() {
10265 let mut rows = selection.spanned_rows(false, &display_map);
10266 let goal_display_column = selection.head().to_display_point(&display_map).column();
10267
10268 // Accumulate contiguous regions of rows that we want to delete.
10269 while let Some(next_selection) = selections.peek() {
10270 let next_rows = next_selection.spanned_rows(false, &display_map);
10271 if next_rows.start <= rows.end {
10272 rows.end = next_rows.end;
10273 selections.next().unwrap();
10274 } else {
10275 break;
10276 }
10277 }
10278
10279 let buffer = &display_map.buffer_snapshot;
10280 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10281 let edit_end;
10282 let cursor_buffer_row;
10283 if buffer.max_point().row >= rows.end.0 {
10284 // If there's a line after the range, delete the \n from the end of the row range
10285 // and position the cursor on the next line.
10286 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10287 cursor_buffer_row = rows.end;
10288 } else {
10289 // If there isn't a line after the range, delete the \n from the line before the
10290 // start of the row range and position the cursor there.
10291 edit_start = edit_start.saturating_sub(1);
10292 edit_end = buffer.len();
10293 cursor_buffer_row = rows.start.previous_row();
10294 }
10295
10296 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10297 *cursor.column_mut() =
10298 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10299
10300 new_cursors.push((
10301 selection.id,
10302 buffer.anchor_after(cursor.to_point(&display_map)),
10303 ));
10304 edit_ranges.push(edit_start..edit_end);
10305 }
10306
10307 self.transact(window, cx, |this, window, cx| {
10308 let buffer = this.buffer.update(cx, |buffer, cx| {
10309 let empty_str: Arc<str> = Arc::default();
10310 buffer.edit(
10311 edit_ranges
10312 .into_iter()
10313 .map(|range| (range, empty_str.clone())),
10314 None,
10315 cx,
10316 );
10317 buffer.snapshot(cx)
10318 });
10319 let new_selections = new_cursors
10320 .into_iter()
10321 .map(|(id, cursor)| {
10322 let cursor = cursor.to_point(&buffer);
10323 Selection {
10324 id,
10325 start: cursor,
10326 end: cursor,
10327 reversed: false,
10328 goal: SelectionGoal::None,
10329 }
10330 })
10331 .collect();
10332
10333 this.change_selections(Default::default(), window, cx, |s| {
10334 s.select(new_selections);
10335 });
10336 });
10337 }
10338
10339 pub fn join_lines_impl(
10340 &mut self,
10341 insert_whitespace: bool,
10342 window: &mut Window,
10343 cx: &mut Context<Self>,
10344 ) {
10345 if self.read_only(cx) {
10346 return;
10347 }
10348 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10349 for selection in self.selections.all::<Point>(cx) {
10350 let start = MultiBufferRow(selection.start.row);
10351 // Treat single line selections as if they include the next line. Otherwise this action
10352 // would do nothing for single line selections individual cursors.
10353 let end = if selection.start.row == selection.end.row {
10354 MultiBufferRow(selection.start.row + 1)
10355 } else {
10356 MultiBufferRow(selection.end.row)
10357 };
10358
10359 if let Some(last_row_range) = row_ranges.last_mut()
10360 && start <= last_row_range.end
10361 {
10362 last_row_range.end = end;
10363 continue;
10364 }
10365 row_ranges.push(start..end);
10366 }
10367
10368 let snapshot = self.buffer.read(cx).snapshot(cx);
10369 let mut cursor_positions = Vec::new();
10370 for row_range in &row_ranges {
10371 let anchor = snapshot.anchor_before(Point::new(
10372 row_range.end.previous_row().0,
10373 snapshot.line_len(row_range.end.previous_row()),
10374 ));
10375 cursor_positions.push(anchor..anchor);
10376 }
10377
10378 self.transact(window, cx, |this, window, cx| {
10379 for row_range in row_ranges.into_iter().rev() {
10380 for row in row_range.iter_rows().rev() {
10381 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10382 let next_line_row = row.next_row();
10383 let indent = snapshot.indent_size_for_line(next_line_row);
10384 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10385
10386 let replace =
10387 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10388 " "
10389 } else {
10390 ""
10391 };
10392
10393 this.buffer.update(cx, |buffer, cx| {
10394 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10395 });
10396 }
10397 }
10398
10399 this.change_selections(Default::default(), window, cx, |s| {
10400 s.select_anchor_ranges(cursor_positions)
10401 });
10402 });
10403 }
10404
10405 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10406 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10407 self.join_lines_impl(true, window, cx);
10408 }
10409
10410 pub fn sort_lines_case_sensitive(
10411 &mut self,
10412 _: &SortLinesCaseSensitive,
10413 window: &mut Window,
10414 cx: &mut Context<Self>,
10415 ) {
10416 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10417 }
10418
10419 pub fn sort_lines_by_length(
10420 &mut self,
10421 _: &SortLinesByLength,
10422 window: &mut Window,
10423 cx: &mut Context<Self>,
10424 ) {
10425 self.manipulate_immutable_lines(window, cx, |lines| {
10426 lines.sort_by_key(|&line| line.chars().count())
10427 })
10428 }
10429
10430 pub fn sort_lines_case_insensitive(
10431 &mut self,
10432 _: &SortLinesCaseInsensitive,
10433 window: &mut Window,
10434 cx: &mut Context<Self>,
10435 ) {
10436 self.manipulate_immutable_lines(window, cx, |lines| {
10437 lines.sort_by_key(|line| line.to_lowercase())
10438 })
10439 }
10440
10441 pub fn unique_lines_case_insensitive(
10442 &mut self,
10443 _: &UniqueLinesCaseInsensitive,
10444 window: &mut Window,
10445 cx: &mut Context<Self>,
10446 ) {
10447 self.manipulate_immutable_lines(window, cx, |lines| {
10448 let mut seen = HashSet::default();
10449 lines.retain(|line| seen.insert(line.to_lowercase()));
10450 })
10451 }
10452
10453 pub fn unique_lines_case_sensitive(
10454 &mut self,
10455 _: &UniqueLinesCaseSensitive,
10456 window: &mut Window,
10457 cx: &mut Context<Self>,
10458 ) {
10459 self.manipulate_immutable_lines(window, cx, |lines| {
10460 let mut seen = HashSet::default();
10461 lines.retain(|line| seen.insert(*line));
10462 })
10463 }
10464
10465 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10466 let Some(project) = self.project.clone() else {
10467 return;
10468 };
10469 self.reload(project, window, cx)
10470 .detach_and_notify_err(window, cx);
10471 }
10472
10473 pub fn restore_file(
10474 &mut self,
10475 _: &::git::RestoreFile,
10476 window: &mut Window,
10477 cx: &mut Context<Self>,
10478 ) {
10479 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10480 let mut buffer_ids = HashSet::default();
10481 let snapshot = self.buffer().read(cx).snapshot(cx);
10482 for selection in self.selections.all::<usize>(cx) {
10483 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10484 }
10485
10486 let buffer = self.buffer().read(cx);
10487 let ranges = buffer_ids
10488 .into_iter()
10489 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10490 .collect::<Vec<_>>();
10491
10492 self.restore_hunks_in_ranges(ranges, window, cx);
10493 }
10494
10495 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10496 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10497 let selections = self
10498 .selections
10499 .all(cx)
10500 .into_iter()
10501 .map(|s| s.range())
10502 .collect();
10503 self.restore_hunks_in_ranges(selections, window, cx);
10504 }
10505
10506 pub fn restore_hunks_in_ranges(
10507 &mut self,
10508 ranges: Vec<Range<Point>>,
10509 window: &mut Window,
10510 cx: &mut Context<Editor>,
10511 ) {
10512 let mut revert_changes = HashMap::default();
10513 let chunk_by = self
10514 .snapshot(window, cx)
10515 .hunks_for_ranges(ranges)
10516 .into_iter()
10517 .chunk_by(|hunk| hunk.buffer_id);
10518 for (buffer_id, hunks) in &chunk_by {
10519 let hunks = hunks.collect::<Vec<_>>();
10520 for hunk in &hunks {
10521 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10522 }
10523 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10524 }
10525 drop(chunk_by);
10526 if !revert_changes.is_empty() {
10527 self.transact(window, cx, |editor, window, cx| {
10528 editor.restore(revert_changes, window, cx);
10529 });
10530 }
10531 }
10532
10533 pub fn open_active_item_in_terminal(
10534 &mut self,
10535 _: &OpenInTerminal,
10536 window: &mut Window,
10537 cx: &mut Context<Self>,
10538 ) {
10539 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10540 let project_path = buffer.read(cx).project_path(cx)?;
10541 let project = self.project()?.read(cx);
10542 let entry = project.entry_for_path(&project_path, cx)?;
10543 let parent = match &entry.canonical_path {
10544 Some(canonical_path) => canonical_path.to_path_buf(),
10545 None => project.absolute_path(&project_path, cx)?,
10546 }
10547 .parent()?
10548 .to_path_buf();
10549 Some(parent)
10550 }) {
10551 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10552 }
10553 }
10554
10555 fn set_breakpoint_context_menu(
10556 &mut self,
10557 display_row: DisplayRow,
10558 position: Option<Anchor>,
10559 clicked_point: gpui::Point<Pixels>,
10560 window: &mut Window,
10561 cx: &mut Context<Self>,
10562 ) {
10563 let source = self
10564 .buffer
10565 .read(cx)
10566 .snapshot(cx)
10567 .anchor_before(Point::new(display_row.0, 0u32));
10568
10569 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10570
10571 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10572 self,
10573 source,
10574 clicked_point,
10575 context_menu,
10576 window,
10577 cx,
10578 );
10579 }
10580
10581 fn add_edit_breakpoint_block(
10582 &mut self,
10583 anchor: Anchor,
10584 breakpoint: &Breakpoint,
10585 edit_action: BreakpointPromptEditAction,
10586 window: &mut Window,
10587 cx: &mut Context<Self>,
10588 ) {
10589 let weak_editor = cx.weak_entity();
10590 let bp_prompt = cx.new(|cx| {
10591 BreakpointPromptEditor::new(
10592 weak_editor,
10593 anchor,
10594 breakpoint.clone(),
10595 edit_action,
10596 window,
10597 cx,
10598 )
10599 });
10600
10601 let height = bp_prompt.update(cx, |this, cx| {
10602 this.prompt
10603 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10604 });
10605 let cloned_prompt = bp_prompt.clone();
10606 let blocks = vec![BlockProperties {
10607 style: BlockStyle::Sticky,
10608 placement: BlockPlacement::Above(anchor),
10609 height: Some(height),
10610 render: Arc::new(move |cx| {
10611 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10612 cloned_prompt.clone().into_any_element()
10613 }),
10614 priority: 0,
10615 }];
10616
10617 let focus_handle = bp_prompt.focus_handle(cx);
10618 window.focus(&focus_handle);
10619
10620 let block_ids = self.insert_blocks(blocks, None, cx);
10621 bp_prompt.update(cx, |prompt, _| {
10622 prompt.add_block_ids(block_ids);
10623 });
10624 }
10625
10626 pub(crate) fn breakpoint_at_row(
10627 &self,
10628 row: u32,
10629 window: &mut Window,
10630 cx: &mut Context<Self>,
10631 ) -> Option<(Anchor, Breakpoint)> {
10632 let snapshot = self.snapshot(window, cx);
10633 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10634
10635 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10636 }
10637
10638 pub(crate) fn breakpoint_at_anchor(
10639 &self,
10640 breakpoint_position: Anchor,
10641 snapshot: &EditorSnapshot,
10642 cx: &mut Context<Self>,
10643 ) -> Option<(Anchor, Breakpoint)> {
10644 let buffer = self
10645 .buffer
10646 .read(cx)
10647 .buffer_for_anchor(breakpoint_position, cx)?;
10648
10649 let enclosing_excerpt = breakpoint_position.excerpt_id;
10650 let buffer_snapshot = buffer.read(cx).snapshot();
10651
10652 let row = buffer_snapshot
10653 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10654 .row;
10655
10656 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10657 let anchor_end = snapshot
10658 .buffer_snapshot
10659 .anchor_after(Point::new(row, line_len));
10660
10661 self.breakpoint_store
10662 .as_ref()?
10663 .read_with(cx, |breakpoint_store, cx| {
10664 breakpoint_store
10665 .breakpoints(
10666 &buffer,
10667 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10668 &buffer_snapshot,
10669 cx,
10670 )
10671 .next()
10672 .and_then(|(bp, _)| {
10673 let breakpoint_row = buffer_snapshot
10674 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10675 .row;
10676
10677 if breakpoint_row == row {
10678 snapshot
10679 .buffer_snapshot
10680 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10681 .map(|position| (position, bp.bp.clone()))
10682 } else {
10683 None
10684 }
10685 })
10686 })
10687 }
10688
10689 pub fn edit_log_breakpoint(
10690 &mut self,
10691 _: &EditLogBreakpoint,
10692 window: &mut Window,
10693 cx: &mut Context<Self>,
10694 ) {
10695 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10696 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10697 message: None,
10698 state: BreakpointState::Enabled,
10699 condition: None,
10700 hit_condition: None,
10701 });
10702
10703 self.add_edit_breakpoint_block(
10704 anchor,
10705 &breakpoint,
10706 BreakpointPromptEditAction::Log,
10707 window,
10708 cx,
10709 );
10710 }
10711 }
10712
10713 fn breakpoints_at_cursors(
10714 &self,
10715 window: &mut Window,
10716 cx: &mut Context<Self>,
10717 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10718 let snapshot = self.snapshot(window, cx);
10719 let cursors = self
10720 .selections
10721 .disjoint_anchors()
10722 .iter()
10723 .map(|selection| {
10724 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10725
10726 let breakpoint_position = self
10727 .breakpoint_at_row(cursor_position.row, window, cx)
10728 .map(|bp| bp.0)
10729 .unwrap_or_else(|| {
10730 snapshot
10731 .display_snapshot
10732 .buffer_snapshot
10733 .anchor_after(Point::new(cursor_position.row, 0))
10734 });
10735
10736 let breakpoint = self
10737 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10738 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10739
10740 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10741 })
10742 // 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.
10743 .collect::<HashMap<Anchor, _>>();
10744
10745 cursors.into_iter().collect()
10746 }
10747
10748 pub fn enable_breakpoint(
10749 &mut self,
10750 _: &crate::actions::EnableBreakpoint,
10751 window: &mut Window,
10752 cx: &mut Context<Self>,
10753 ) {
10754 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10755 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10756 continue;
10757 };
10758 self.edit_breakpoint_at_anchor(
10759 anchor,
10760 breakpoint,
10761 BreakpointEditAction::InvertState,
10762 cx,
10763 );
10764 }
10765 }
10766
10767 pub fn disable_breakpoint(
10768 &mut self,
10769 _: &crate::actions::DisableBreakpoint,
10770 window: &mut Window,
10771 cx: &mut Context<Self>,
10772 ) {
10773 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10774 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10775 continue;
10776 };
10777 self.edit_breakpoint_at_anchor(
10778 anchor,
10779 breakpoint,
10780 BreakpointEditAction::InvertState,
10781 cx,
10782 );
10783 }
10784 }
10785
10786 pub fn toggle_breakpoint(
10787 &mut self,
10788 _: &crate::actions::ToggleBreakpoint,
10789 window: &mut Window,
10790 cx: &mut Context<Self>,
10791 ) {
10792 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10793 if let Some(breakpoint) = breakpoint {
10794 self.edit_breakpoint_at_anchor(
10795 anchor,
10796 breakpoint,
10797 BreakpointEditAction::Toggle,
10798 cx,
10799 );
10800 } else {
10801 self.edit_breakpoint_at_anchor(
10802 anchor,
10803 Breakpoint::new_standard(),
10804 BreakpointEditAction::Toggle,
10805 cx,
10806 );
10807 }
10808 }
10809 }
10810
10811 pub fn edit_breakpoint_at_anchor(
10812 &mut self,
10813 breakpoint_position: Anchor,
10814 breakpoint: Breakpoint,
10815 edit_action: BreakpointEditAction,
10816 cx: &mut Context<Self>,
10817 ) {
10818 let Some(breakpoint_store) = &self.breakpoint_store else {
10819 return;
10820 };
10821
10822 let Some(buffer) = self
10823 .buffer
10824 .read(cx)
10825 .buffer_for_anchor(breakpoint_position, cx)
10826 else {
10827 return;
10828 };
10829
10830 breakpoint_store.update(cx, |breakpoint_store, cx| {
10831 breakpoint_store.toggle_breakpoint(
10832 buffer,
10833 BreakpointWithPosition {
10834 position: breakpoint_position.text_anchor,
10835 bp: breakpoint,
10836 },
10837 edit_action,
10838 cx,
10839 );
10840 });
10841
10842 cx.notify();
10843 }
10844
10845 #[cfg(any(test, feature = "test-support"))]
10846 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10847 self.breakpoint_store.clone()
10848 }
10849
10850 pub fn prepare_restore_change(
10851 &self,
10852 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10853 hunk: &MultiBufferDiffHunk,
10854 cx: &mut App,
10855 ) -> Option<()> {
10856 if hunk.is_created_file() {
10857 return None;
10858 }
10859 let buffer = self.buffer.read(cx);
10860 let diff = buffer.diff_for(hunk.buffer_id)?;
10861 let buffer = buffer.buffer(hunk.buffer_id)?;
10862 let buffer = buffer.read(cx);
10863 let original_text = diff
10864 .read(cx)
10865 .base_text()
10866 .as_rope()
10867 .slice(hunk.diff_base_byte_range.clone());
10868 let buffer_snapshot = buffer.snapshot();
10869 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10870 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10871 probe
10872 .0
10873 .start
10874 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10875 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10876 }) {
10877 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10878 Some(())
10879 } else {
10880 None
10881 }
10882 }
10883
10884 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10885 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10886 }
10887
10888 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10889 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10890 }
10891
10892 fn manipulate_lines<M>(
10893 &mut self,
10894 window: &mut Window,
10895 cx: &mut Context<Self>,
10896 mut manipulate: M,
10897 ) where
10898 M: FnMut(&str) -> LineManipulationResult,
10899 {
10900 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10901
10902 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10903 let buffer = self.buffer.read(cx).snapshot(cx);
10904
10905 let mut edits = Vec::new();
10906
10907 let selections = self.selections.all::<Point>(cx);
10908 let mut selections = selections.iter().peekable();
10909 let mut contiguous_row_selections = Vec::new();
10910 let mut new_selections = Vec::new();
10911 let mut added_lines = 0;
10912 let mut removed_lines = 0;
10913
10914 while let Some(selection) = selections.next() {
10915 let (start_row, end_row) = consume_contiguous_rows(
10916 &mut contiguous_row_selections,
10917 selection,
10918 &display_map,
10919 &mut selections,
10920 );
10921
10922 let start_point = Point::new(start_row.0, 0);
10923 let end_point = Point::new(
10924 end_row.previous_row().0,
10925 buffer.line_len(end_row.previous_row()),
10926 );
10927 let text = buffer
10928 .text_for_range(start_point..end_point)
10929 .collect::<String>();
10930
10931 let LineManipulationResult {
10932 new_text,
10933 line_count_before,
10934 line_count_after,
10935 } = manipulate(&text);
10936
10937 edits.push((start_point..end_point, new_text));
10938
10939 // Selections must change based on added and removed line count
10940 let start_row =
10941 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10942 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10943 new_selections.push(Selection {
10944 id: selection.id,
10945 start: start_row,
10946 end: end_row,
10947 goal: SelectionGoal::None,
10948 reversed: selection.reversed,
10949 });
10950
10951 if line_count_after > line_count_before {
10952 added_lines += line_count_after - line_count_before;
10953 } else if line_count_before > line_count_after {
10954 removed_lines += line_count_before - line_count_after;
10955 }
10956 }
10957
10958 self.transact(window, cx, |this, window, cx| {
10959 let buffer = this.buffer.update(cx, |buffer, cx| {
10960 buffer.edit(edits, None, cx);
10961 buffer.snapshot(cx)
10962 });
10963
10964 // Recalculate offsets on newly edited buffer
10965 let new_selections = new_selections
10966 .iter()
10967 .map(|s| {
10968 let start_point = Point::new(s.start.0, 0);
10969 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10970 Selection {
10971 id: s.id,
10972 start: buffer.point_to_offset(start_point),
10973 end: buffer.point_to_offset(end_point),
10974 goal: s.goal,
10975 reversed: s.reversed,
10976 }
10977 })
10978 .collect();
10979
10980 this.change_selections(Default::default(), window, cx, |s| {
10981 s.select(new_selections);
10982 });
10983
10984 this.request_autoscroll(Autoscroll::fit(), cx);
10985 });
10986 }
10987
10988 fn manipulate_immutable_lines<Fn>(
10989 &mut self,
10990 window: &mut Window,
10991 cx: &mut Context<Self>,
10992 mut callback: Fn,
10993 ) where
10994 Fn: FnMut(&mut Vec<&str>),
10995 {
10996 self.manipulate_lines(window, cx, |text| {
10997 let mut lines: Vec<&str> = text.split('\n').collect();
10998 let line_count_before = lines.len();
10999
11000 callback(&mut lines);
11001
11002 LineManipulationResult {
11003 new_text: lines.join("\n"),
11004 line_count_before,
11005 line_count_after: lines.len(),
11006 }
11007 });
11008 }
11009
11010 fn manipulate_mutable_lines<Fn>(
11011 &mut self,
11012 window: &mut Window,
11013 cx: &mut Context<Self>,
11014 mut callback: Fn,
11015 ) where
11016 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11017 {
11018 self.manipulate_lines(window, cx, |text| {
11019 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11020 let line_count_before = lines.len();
11021
11022 callback(&mut lines);
11023
11024 LineManipulationResult {
11025 new_text: lines.join("\n"),
11026 line_count_before,
11027 line_count_after: lines.len(),
11028 }
11029 });
11030 }
11031
11032 pub fn convert_indentation_to_spaces(
11033 &mut self,
11034 _: &ConvertIndentationToSpaces,
11035 window: &mut Window,
11036 cx: &mut Context<Self>,
11037 ) {
11038 let settings = self.buffer.read(cx).language_settings(cx);
11039 let tab_size = settings.tab_size.get() as usize;
11040
11041 self.manipulate_mutable_lines(window, cx, |lines| {
11042 // Allocates a reasonably sized scratch buffer once for the whole loop
11043 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11044 // Avoids recomputing spaces that could be inserted many times
11045 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11046 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11047 .collect();
11048
11049 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11050 let mut chars = line.as_ref().chars();
11051 let mut col = 0;
11052 let mut changed = false;
11053
11054 for ch in chars.by_ref() {
11055 match ch {
11056 ' ' => {
11057 reindented_line.push(' ');
11058 col += 1;
11059 }
11060 '\t' => {
11061 // \t are converted to spaces depending on the current column
11062 let spaces_len = tab_size - (col % tab_size);
11063 reindented_line.extend(&space_cache[spaces_len - 1]);
11064 col += spaces_len;
11065 changed = true;
11066 }
11067 _ => {
11068 // If we dont append before break, the character is consumed
11069 reindented_line.push(ch);
11070 break;
11071 }
11072 }
11073 }
11074
11075 if !changed {
11076 reindented_line.clear();
11077 continue;
11078 }
11079 // Append the rest of the line and replace old reference with new one
11080 reindented_line.extend(chars);
11081 *line = Cow::Owned(reindented_line.clone());
11082 reindented_line.clear();
11083 }
11084 });
11085 }
11086
11087 pub fn convert_indentation_to_tabs(
11088 &mut self,
11089 _: &ConvertIndentationToTabs,
11090 window: &mut Window,
11091 cx: &mut Context<Self>,
11092 ) {
11093 let settings = self.buffer.read(cx).language_settings(cx);
11094 let tab_size = settings.tab_size.get() as usize;
11095
11096 self.manipulate_mutable_lines(window, cx, |lines| {
11097 // Allocates a reasonably sized buffer once for the whole loop
11098 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11099 // Avoids recomputing spaces that could be inserted many times
11100 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11101 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11102 .collect();
11103
11104 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11105 let mut chars = line.chars();
11106 let mut spaces_count = 0;
11107 let mut first_non_indent_char = None;
11108 let mut changed = false;
11109
11110 for ch in chars.by_ref() {
11111 match ch {
11112 ' ' => {
11113 // Keep track of spaces. Append \t when we reach tab_size
11114 spaces_count += 1;
11115 changed = true;
11116 if spaces_count == tab_size {
11117 reindented_line.push('\t');
11118 spaces_count = 0;
11119 }
11120 }
11121 '\t' => {
11122 reindented_line.push('\t');
11123 spaces_count = 0;
11124 }
11125 _ => {
11126 // Dont append it yet, we might have remaining spaces
11127 first_non_indent_char = Some(ch);
11128 break;
11129 }
11130 }
11131 }
11132
11133 if !changed {
11134 reindented_line.clear();
11135 continue;
11136 }
11137 // Remaining spaces that didn't make a full tab stop
11138 if spaces_count > 0 {
11139 reindented_line.extend(&space_cache[spaces_count - 1]);
11140 }
11141 // If we consume an extra character that was not indentation, add it back
11142 if let Some(extra_char) = first_non_indent_char {
11143 reindented_line.push(extra_char);
11144 }
11145 // Append the rest of the line and replace old reference with new one
11146 reindented_line.extend(chars);
11147 *line = Cow::Owned(reindented_line.clone());
11148 reindented_line.clear();
11149 }
11150 });
11151 }
11152
11153 pub fn convert_to_upper_case(
11154 &mut self,
11155 _: &ConvertToUpperCase,
11156 window: &mut Window,
11157 cx: &mut Context<Self>,
11158 ) {
11159 self.manipulate_text(window, cx, |text| text.to_uppercase())
11160 }
11161
11162 pub fn convert_to_lower_case(
11163 &mut self,
11164 _: &ConvertToLowerCase,
11165 window: &mut Window,
11166 cx: &mut Context<Self>,
11167 ) {
11168 self.manipulate_text(window, cx, |text| text.to_lowercase())
11169 }
11170
11171 pub fn convert_to_title_case(
11172 &mut self,
11173 _: &ConvertToTitleCase,
11174 window: &mut Window,
11175 cx: &mut Context<Self>,
11176 ) {
11177 self.manipulate_text(window, cx, |text| {
11178 text.split('\n')
11179 .map(|line| line.to_case(Case::Title))
11180 .join("\n")
11181 })
11182 }
11183
11184 pub fn convert_to_snake_case(
11185 &mut self,
11186 _: &ConvertToSnakeCase,
11187 window: &mut Window,
11188 cx: &mut Context<Self>,
11189 ) {
11190 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11191 }
11192
11193 pub fn convert_to_kebab_case(
11194 &mut self,
11195 _: &ConvertToKebabCase,
11196 window: &mut Window,
11197 cx: &mut Context<Self>,
11198 ) {
11199 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11200 }
11201
11202 pub fn convert_to_upper_camel_case(
11203 &mut self,
11204 _: &ConvertToUpperCamelCase,
11205 window: &mut Window,
11206 cx: &mut Context<Self>,
11207 ) {
11208 self.manipulate_text(window, cx, |text| {
11209 text.split('\n')
11210 .map(|line| line.to_case(Case::UpperCamel))
11211 .join("\n")
11212 })
11213 }
11214
11215 pub fn convert_to_lower_camel_case(
11216 &mut self,
11217 _: &ConvertToLowerCamelCase,
11218 window: &mut Window,
11219 cx: &mut Context<Self>,
11220 ) {
11221 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11222 }
11223
11224 pub fn convert_to_opposite_case(
11225 &mut self,
11226 _: &ConvertToOppositeCase,
11227 window: &mut Window,
11228 cx: &mut Context<Self>,
11229 ) {
11230 self.manipulate_text(window, cx, |text| {
11231 text.chars()
11232 .fold(String::with_capacity(text.len()), |mut t, c| {
11233 if c.is_uppercase() {
11234 t.extend(c.to_lowercase());
11235 } else {
11236 t.extend(c.to_uppercase());
11237 }
11238 t
11239 })
11240 })
11241 }
11242
11243 pub fn convert_to_sentence_case(
11244 &mut self,
11245 _: &ConvertToSentenceCase,
11246 window: &mut Window,
11247 cx: &mut Context<Self>,
11248 ) {
11249 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11250 }
11251
11252 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11253 self.manipulate_text(window, cx, |text| {
11254 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11255 if has_upper_case_characters {
11256 text.to_lowercase()
11257 } else {
11258 text.to_uppercase()
11259 }
11260 })
11261 }
11262
11263 pub fn convert_to_rot13(
11264 &mut self,
11265 _: &ConvertToRot13,
11266 window: &mut Window,
11267 cx: &mut Context<Self>,
11268 ) {
11269 self.manipulate_text(window, cx, |text| {
11270 text.chars()
11271 .map(|c| match c {
11272 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11273 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11274 _ => c,
11275 })
11276 .collect()
11277 })
11278 }
11279
11280 pub fn convert_to_rot47(
11281 &mut self,
11282 _: &ConvertToRot47,
11283 window: &mut Window,
11284 cx: &mut Context<Self>,
11285 ) {
11286 self.manipulate_text(window, cx, |text| {
11287 text.chars()
11288 .map(|c| {
11289 let code_point = c as u32;
11290 if code_point >= 33 && code_point <= 126 {
11291 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11292 }
11293 c
11294 })
11295 .collect()
11296 })
11297 }
11298
11299 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11300 where
11301 Fn: FnMut(&str) -> String,
11302 {
11303 let buffer = self.buffer.read(cx).snapshot(cx);
11304
11305 let mut new_selections = Vec::new();
11306 let mut edits = Vec::new();
11307 let mut selection_adjustment = 0i32;
11308
11309 for selection in self.selections.all::<usize>(cx) {
11310 let selection_is_empty = selection.is_empty();
11311
11312 let (start, end) = if selection_is_empty {
11313 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11314 (word_range.start, word_range.end)
11315 } else {
11316 (selection.start, selection.end)
11317 };
11318
11319 let text = buffer.text_for_range(start..end).collect::<String>();
11320 let old_length = text.len() as i32;
11321 let text = callback(&text);
11322
11323 new_selections.push(Selection {
11324 start: (start as i32 - selection_adjustment) as usize,
11325 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11326 goal: SelectionGoal::None,
11327 ..selection
11328 });
11329
11330 selection_adjustment += old_length - text.len() as i32;
11331
11332 edits.push((start..end, text));
11333 }
11334
11335 self.transact(window, cx, |this, window, cx| {
11336 this.buffer.update(cx, |buffer, cx| {
11337 buffer.edit(edits, None, cx);
11338 });
11339
11340 this.change_selections(Default::default(), window, cx, |s| {
11341 s.select(new_selections);
11342 });
11343
11344 this.request_autoscroll(Autoscroll::fit(), cx);
11345 });
11346 }
11347
11348 pub fn move_selection_on_drop(
11349 &mut self,
11350 selection: &Selection<Anchor>,
11351 target: DisplayPoint,
11352 is_cut: bool,
11353 window: &mut Window,
11354 cx: &mut Context<Self>,
11355 ) {
11356 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11357 let buffer = &display_map.buffer_snapshot;
11358 let mut edits = Vec::new();
11359 let insert_point = display_map
11360 .clip_point(target, Bias::Left)
11361 .to_point(&display_map);
11362 let text = buffer
11363 .text_for_range(selection.start..selection.end)
11364 .collect::<String>();
11365 if is_cut {
11366 edits.push(((selection.start..selection.end), String::new()));
11367 }
11368 let insert_anchor = buffer.anchor_before(insert_point);
11369 edits.push(((insert_anchor..insert_anchor), text));
11370 let last_edit_start = insert_anchor.bias_left(buffer);
11371 let last_edit_end = insert_anchor.bias_right(buffer);
11372 self.transact(window, cx, |this, window, cx| {
11373 this.buffer.update(cx, |buffer, cx| {
11374 buffer.edit(edits, None, cx);
11375 });
11376 this.change_selections(Default::default(), window, cx, |s| {
11377 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11378 });
11379 });
11380 }
11381
11382 pub fn clear_selection_drag_state(&mut self) {
11383 self.selection_drag_state = SelectionDragState::None;
11384 }
11385
11386 pub fn duplicate(
11387 &mut self,
11388 upwards: bool,
11389 whole_lines: bool,
11390 window: &mut Window,
11391 cx: &mut Context<Self>,
11392 ) {
11393 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11394
11395 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11396 let buffer = &display_map.buffer_snapshot;
11397 let selections = self.selections.all::<Point>(cx);
11398
11399 let mut edits = Vec::new();
11400 let mut selections_iter = selections.iter().peekable();
11401 while let Some(selection) = selections_iter.next() {
11402 let mut rows = selection.spanned_rows(false, &display_map);
11403 // duplicate line-wise
11404 if whole_lines || selection.start == selection.end {
11405 // Avoid duplicating the same lines twice.
11406 while let Some(next_selection) = selections_iter.peek() {
11407 let next_rows = next_selection.spanned_rows(false, &display_map);
11408 if next_rows.start < rows.end {
11409 rows.end = next_rows.end;
11410 selections_iter.next().unwrap();
11411 } else {
11412 break;
11413 }
11414 }
11415
11416 // Copy the text from the selected row region and splice it either at the start
11417 // or end of the region.
11418 let start = Point::new(rows.start.0, 0);
11419 let end = Point::new(
11420 rows.end.previous_row().0,
11421 buffer.line_len(rows.end.previous_row()),
11422 );
11423 let text = buffer
11424 .text_for_range(start..end)
11425 .chain(Some("\n"))
11426 .collect::<String>();
11427 let insert_location = if upwards {
11428 Point::new(rows.end.0, 0)
11429 } else {
11430 start
11431 };
11432 edits.push((insert_location..insert_location, text));
11433 } else {
11434 // duplicate character-wise
11435 let start = selection.start;
11436 let end = selection.end;
11437 let text = buffer.text_for_range(start..end).collect::<String>();
11438 edits.push((selection.end..selection.end, text));
11439 }
11440 }
11441
11442 self.transact(window, cx, |this, _, cx| {
11443 this.buffer.update(cx, |buffer, cx| {
11444 buffer.edit(edits, None, cx);
11445 });
11446
11447 this.request_autoscroll(Autoscroll::fit(), cx);
11448 });
11449 }
11450
11451 pub fn duplicate_line_up(
11452 &mut self,
11453 _: &DuplicateLineUp,
11454 window: &mut Window,
11455 cx: &mut Context<Self>,
11456 ) {
11457 self.duplicate(true, true, window, cx);
11458 }
11459
11460 pub fn duplicate_line_down(
11461 &mut self,
11462 _: &DuplicateLineDown,
11463 window: &mut Window,
11464 cx: &mut Context<Self>,
11465 ) {
11466 self.duplicate(false, true, window, cx);
11467 }
11468
11469 pub fn duplicate_selection(
11470 &mut self,
11471 _: &DuplicateSelection,
11472 window: &mut Window,
11473 cx: &mut Context<Self>,
11474 ) {
11475 self.duplicate(false, false, window, cx);
11476 }
11477
11478 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11479 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11480 if self.mode.is_single_line() {
11481 cx.propagate();
11482 return;
11483 }
11484
11485 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11486 let buffer = self.buffer.read(cx).snapshot(cx);
11487
11488 let mut edits = Vec::new();
11489 let mut unfold_ranges = Vec::new();
11490 let mut refold_creases = Vec::new();
11491
11492 let selections = self.selections.all::<Point>(cx);
11493 let mut selections = selections.iter().peekable();
11494 let mut contiguous_row_selections = Vec::new();
11495 let mut new_selections = Vec::new();
11496
11497 while let Some(selection) = selections.next() {
11498 // Find all the selections that span a contiguous row range
11499 let (start_row, end_row) = consume_contiguous_rows(
11500 &mut contiguous_row_selections,
11501 selection,
11502 &display_map,
11503 &mut selections,
11504 );
11505
11506 // Move the text spanned by the row range to be before the line preceding the row range
11507 if start_row.0 > 0 {
11508 let range_to_move = Point::new(
11509 start_row.previous_row().0,
11510 buffer.line_len(start_row.previous_row()),
11511 )
11512 ..Point::new(
11513 end_row.previous_row().0,
11514 buffer.line_len(end_row.previous_row()),
11515 );
11516 let insertion_point = display_map
11517 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11518 .0;
11519
11520 // Don't move lines across excerpts
11521 if buffer
11522 .excerpt_containing(insertion_point..range_to_move.end)
11523 .is_some()
11524 {
11525 let text = buffer
11526 .text_for_range(range_to_move.clone())
11527 .flat_map(|s| s.chars())
11528 .skip(1)
11529 .chain(['\n'])
11530 .collect::<String>();
11531
11532 edits.push((
11533 buffer.anchor_after(range_to_move.start)
11534 ..buffer.anchor_before(range_to_move.end),
11535 String::new(),
11536 ));
11537 let insertion_anchor = buffer.anchor_after(insertion_point);
11538 edits.push((insertion_anchor..insertion_anchor, text));
11539
11540 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11541
11542 // Move selections up
11543 new_selections.extend(contiguous_row_selections.drain(..).map(
11544 |mut selection| {
11545 selection.start.row -= row_delta;
11546 selection.end.row -= row_delta;
11547 selection
11548 },
11549 ));
11550
11551 // Move folds up
11552 unfold_ranges.push(range_to_move.clone());
11553 for fold in display_map.folds_in_range(
11554 buffer.anchor_before(range_to_move.start)
11555 ..buffer.anchor_after(range_to_move.end),
11556 ) {
11557 let mut start = fold.range.start.to_point(&buffer);
11558 let mut end = fold.range.end.to_point(&buffer);
11559 start.row -= row_delta;
11560 end.row -= row_delta;
11561 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11562 }
11563 }
11564 }
11565
11566 // If we didn't move line(s), preserve the existing selections
11567 new_selections.append(&mut contiguous_row_selections);
11568 }
11569
11570 self.transact(window, cx, |this, window, cx| {
11571 this.unfold_ranges(&unfold_ranges, true, true, cx);
11572 this.buffer.update(cx, |buffer, cx| {
11573 for (range, text) in edits {
11574 buffer.edit([(range, text)], None, cx);
11575 }
11576 });
11577 this.fold_creases(refold_creases, true, window, cx);
11578 this.change_selections(Default::default(), window, cx, |s| {
11579 s.select(new_selections);
11580 })
11581 });
11582 }
11583
11584 pub fn move_line_down(
11585 &mut self,
11586 _: &MoveLineDown,
11587 window: &mut Window,
11588 cx: &mut Context<Self>,
11589 ) {
11590 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11591 if self.mode.is_single_line() {
11592 cx.propagate();
11593 return;
11594 }
11595
11596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11597 let buffer = self.buffer.read(cx).snapshot(cx);
11598
11599 let mut edits = Vec::new();
11600 let mut unfold_ranges = Vec::new();
11601 let mut refold_creases = Vec::new();
11602
11603 let selections = self.selections.all::<Point>(cx);
11604 let mut selections = selections.iter().peekable();
11605 let mut contiguous_row_selections = Vec::new();
11606 let mut new_selections = Vec::new();
11607
11608 while let Some(selection) = selections.next() {
11609 // Find all the selections that span a contiguous row range
11610 let (start_row, end_row) = consume_contiguous_rows(
11611 &mut contiguous_row_selections,
11612 selection,
11613 &display_map,
11614 &mut selections,
11615 );
11616
11617 // Move the text spanned by the row range to be after the last line of the row range
11618 if end_row.0 <= buffer.max_point().row {
11619 let range_to_move =
11620 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11621 let insertion_point = display_map
11622 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11623 .0;
11624
11625 // Don't move lines across excerpt boundaries
11626 if buffer
11627 .excerpt_containing(range_to_move.start..insertion_point)
11628 .is_some()
11629 {
11630 let mut text = String::from("\n");
11631 text.extend(buffer.text_for_range(range_to_move.clone()));
11632 text.pop(); // Drop trailing newline
11633 edits.push((
11634 buffer.anchor_after(range_to_move.start)
11635 ..buffer.anchor_before(range_to_move.end),
11636 String::new(),
11637 ));
11638 let insertion_anchor = buffer.anchor_after(insertion_point);
11639 edits.push((insertion_anchor..insertion_anchor, text));
11640
11641 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11642
11643 // Move selections down
11644 new_selections.extend(contiguous_row_selections.drain(..).map(
11645 |mut selection| {
11646 selection.start.row += row_delta;
11647 selection.end.row += row_delta;
11648 selection
11649 },
11650 ));
11651
11652 // Move folds down
11653 unfold_ranges.push(range_to_move.clone());
11654 for fold in display_map.folds_in_range(
11655 buffer.anchor_before(range_to_move.start)
11656 ..buffer.anchor_after(range_to_move.end),
11657 ) {
11658 let mut start = fold.range.start.to_point(&buffer);
11659 let mut end = fold.range.end.to_point(&buffer);
11660 start.row += row_delta;
11661 end.row += row_delta;
11662 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11663 }
11664 }
11665 }
11666
11667 // If we didn't move line(s), preserve the existing selections
11668 new_selections.append(&mut contiguous_row_selections);
11669 }
11670
11671 self.transact(window, cx, |this, window, cx| {
11672 this.unfold_ranges(&unfold_ranges, true, true, cx);
11673 this.buffer.update(cx, |buffer, cx| {
11674 for (range, text) in edits {
11675 buffer.edit([(range, text)], None, cx);
11676 }
11677 });
11678 this.fold_creases(refold_creases, true, window, cx);
11679 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11680 });
11681 }
11682
11683 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11684 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11685 let text_layout_details = &self.text_layout_details(window);
11686 self.transact(window, cx, |this, window, cx| {
11687 let edits = this.change_selections(Default::default(), window, cx, |s| {
11688 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11689 s.move_with(|display_map, selection| {
11690 if !selection.is_empty() {
11691 return;
11692 }
11693
11694 let mut head = selection.head();
11695 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11696 if head.column() == display_map.line_len(head.row()) {
11697 transpose_offset = display_map
11698 .buffer_snapshot
11699 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11700 }
11701
11702 if transpose_offset == 0 {
11703 return;
11704 }
11705
11706 *head.column_mut() += 1;
11707 head = display_map.clip_point(head, Bias::Right);
11708 let goal = SelectionGoal::HorizontalPosition(
11709 display_map
11710 .x_for_display_point(head, text_layout_details)
11711 .into(),
11712 );
11713 selection.collapse_to(head, goal);
11714
11715 let transpose_start = display_map
11716 .buffer_snapshot
11717 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11718 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11719 let transpose_end = display_map
11720 .buffer_snapshot
11721 .clip_offset(transpose_offset + 1, Bias::Right);
11722 if let Some(ch) =
11723 display_map.buffer_snapshot.chars_at(transpose_start).next()
11724 {
11725 edits.push((transpose_start..transpose_offset, String::new()));
11726 edits.push((transpose_end..transpose_end, ch.to_string()));
11727 }
11728 }
11729 });
11730 edits
11731 });
11732 this.buffer
11733 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11734 let selections = this.selections.all::<usize>(cx);
11735 this.change_selections(Default::default(), window, cx, |s| {
11736 s.select(selections);
11737 });
11738 });
11739 }
11740
11741 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11742 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11743 if self.mode.is_single_line() {
11744 cx.propagate();
11745 return;
11746 }
11747
11748 self.rewrap_impl(RewrapOptions::default(), cx)
11749 }
11750
11751 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11752 let buffer = self.buffer.read(cx).snapshot(cx);
11753 let selections = self.selections.all::<Point>(cx);
11754
11755 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11756 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11757 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11758 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11759 .peekable();
11760
11761 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11762 row
11763 } else {
11764 return Vec::new();
11765 };
11766
11767 let language_settings = buffer.language_settings_at(selection.head(), cx);
11768 let language_scope = buffer.language_scope_at(selection.head());
11769
11770 let indent_and_prefix_for_row =
11771 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11772 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11773 let (comment_prefix, rewrap_prefix) =
11774 if let Some(language_scope) = &language_scope {
11775 let indent_end = Point::new(row, indent.len);
11776 let comment_prefix = language_scope
11777 .line_comment_prefixes()
11778 .iter()
11779 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11780 .map(|prefix| prefix.to_string());
11781 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11782 let line_text_after_indent = buffer
11783 .text_for_range(indent_end..line_end)
11784 .collect::<String>();
11785 let rewrap_prefix = language_scope
11786 .rewrap_prefixes()
11787 .iter()
11788 .find_map(|prefix_regex| {
11789 prefix_regex.find(&line_text_after_indent).map(|mat| {
11790 if mat.start() == 0 {
11791 Some(mat.as_str().to_string())
11792 } else {
11793 None
11794 }
11795 })
11796 })
11797 .flatten();
11798 (comment_prefix, rewrap_prefix)
11799 } else {
11800 (None, None)
11801 };
11802 (indent, comment_prefix, rewrap_prefix)
11803 };
11804
11805 let mut ranges = Vec::new();
11806 let from_empty_selection = selection.is_empty();
11807
11808 let mut current_range_start = first_row;
11809 let mut prev_row = first_row;
11810 let (
11811 mut current_range_indent,
11812 mut current_range_comment_prefix,
11813 mut current_range_rewrap_prefix,
11814 ) = indent_and_prefix_for_row(first_row);
11815
11816 for row in non_blank_rows_iter.skip(1) {
11817 let has_paragraph_break = row > prev_row + 1;
11818
11819 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11820 indent_and_prefix_for_row(row);
11821
11822 let has_indent_change = row_indent != current_range_indent;
11823 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11824
11825 let has_boundary_change = has_comment_change
11826 || row_rewrap_prefix.is_some()
11827 || (has_indent_change && current_range_comment_prefix.is_some());
11828
11829 if has_paragraph_break || has_boundary_change {
11830 ranges.push((
11831 language_settings.clone(),
11832 Point::new(current_range_start, 0)
11833 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11834 current_range_indent,
11835 current_range_comment_prefix.clone(),
11836 current_range_rewrap_prefix.clone(),
11837 from_empty_selection,
11838 ));
11839 current_range_start = row;
11840 current_range_indent = row_indent;
11841 current_range_comment_prefix = row_comment_prefix;
11842 current_range_rewrap_prefix = row_rewrap_prefix;
11843 }
11844 prev_row = row;
11845 }
11846
11847 ranges.push((
11848 language_settings.clone(),
11849 Point::new(current_range_start, 0)
11850 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11851 current_range_indent,
11852 current_range_comment_prefix,
11853 current_range_rewrap_prefix,
11854 from_empty_selection,
11855 ));
11856
11857 ranges
11858 });
11859
11860 let mut edits = Vec::new();
11861 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11862
11863 for (
11864 language_settings,
11865 wrap_range,
11866 indent_size,
11867 comment_prefix,
11868 rewrap_prefix,
11869 from_empty_selection,
11870 ) in wrap_ranges
11871 {
11872 let mut start_row = wrap_range.start.row;
11873 let mut end_row = wrap_range.end.row;
11874
11875 // Skip selections that overlap with a range that has already been rewrapped.
11876 let selection_range = start_row..end_row;
11877 if rewrapped_row_ranges
11878 .iter()
11879 .any(|range| range.overlaps(&selection_range))
11880 {
11881 continue;
11882 }
11883
11884 let tab_size = language_settings.tab_size;
11885
11886 let indent_prefix = indent_size.chars().collect::<String>();
11887 let mut line_prefix = indent_prefix.clone();
11888 let mut inside_comment = false;
11889 if let Some(prefix) = &comment_prefix {
11890 line_prefix.push_str(prefix);
11891 inside_comment = true;
11892 }
11893 if let Some(prefix) = &rewrap_prefix {
11894 line_prefix.push_str(prefix);
11895 }
11896
11897 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11898 RewrapBehavior::InComments => inside_comment,
11899 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11900 RewrapBehavior::Anywhere => true,
11901 };
11902
11903 let should_rewrap = options.override_language_settings
11904 || allow_rewrap_based_on_language
11905 || self.hard_wrap.is_some();
11906 if !should_rewrap {
11907 continue;
11908 }
11909
11910 if from_empty_selection {
11911 'expand_upwards: while start_row > 0 {
11912 let prev_row = start_row - 1;
11913 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11914 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11915 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11916 {
11917 start_row = prev_row;
11918 } else {
11919 break 'expand_upwards;
11920 }
11921 }
11922
11923 'expand_downwards: while end_row < buffer.max_point().row {
11924 let next_row = end_row + 1;
11925 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11926 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11927 && !buffer.is_line_blank(MultiBufferRow(next_row))
11928 {
11929 end_row = next_row;
11930 } else {
11931 break 'expand_downwards;
11932 }
11933 }
11934 }
11935
11936 let start = Point::new(start_row, 0);
11937 let start_offset = start.to_offset(&buffer);
11938 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11939 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11940 let Some(lines_without_prefixes) = selection_text
11941 .lines()
11942 .enumerate()
11943 .map(|(ix, line)| {
11944 let line_trimmed = line.trim_start();
11945 if rewrap_prefix.is_some() && ix > 0 {
11946 Ok(line_trimmed)
11947 } else {
11948 line_trimmed
11949 .strip_prefix(&line_prefix.trim_start())
11950 .with_context(|| {
11951 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11952 })
11953 }
11954 })
11955 .collect::<Result<Vec<_>, _>>()
11956 .log_err()
11957 else {
11958 continue;
11959 };
11960
11961 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11962 buffer
11963 .language_settings_at(Point::new(start_row, 0), cx)
11964 .preferred_line_length as usize
11965 });
11966
11967 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11968 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11969 } else {
11970 line_prefix.clone()
11971 };
11972
11973 let wrapped_text = wrap_with_prefix(
11974 line_prefix,
11975 subsequent_lines_prefix,
11976 lines_without_prefixes.join("\n"),
11977 wrap_column,
11978 tab_size,
11979 options.preserve_existing_whitespace,
11980 );
11981
11982 // TODO: should always use char-based diff while still supporting cursor behavior that
11983 // matches vim.
11984 let mut diff_options = DiffOptions::default();
11985 if options.override_language_settings {
11986 diff_options.max_word_diff_len = 0;
11987 diff_options.max_word_diff_line_count = 0;
11988 } else {
11989 diff_options.max_word_diff_len = usize::MAX;
11990 diff_options.max_word_diff_line_count = usize::MAX;
11991 }
11992
11993 for (old_range, new_text) in
11994 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11995 {
11996 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11997 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11998 edits.push((edit_start..edit_end, new_text));
11999 }
12000
12001 rewrapped_row_ranges.push(start_row..=end_row);
12002 }
12003
12004 self.buffer
12005 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12006 }
12007
12008 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
12009 let mut text = String::new();
12010 let buffer = self.buffer.read(cx).snapshot(cx);
12011 let mut selections = self.selections.all::<Point>(cx);
12012 let mut clipboard_selections = Vec::with_capacity(selections.len());
12013 {
12014 let max_point = buffer.max_point();
12015 let mut is_first = true;
12016 for selection in &mut selections {
12017 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12018 if is_entire_line {
12019 selection.start = Point::new(selection.start.row, 0);
12020 if !selection.is_empty() && selection.end.column == 0 {
12021 selection.end = cmp::min(max_point, selection.end);
12022 } else {
12023 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12024 }
12025 selection.goal = SelectionGoal::None;
12026 }
12027 if is_first {
12028 is_first = false;
12029 } else {
12030 text += "\n";
12031 }
12032 let mut len = 0;
12033 for chunk in buffer.text_for_range(selection.start..selection.end) {
12034 text.push_str(chunk);
12035 len += chunk.len();
12036 }
12037 clipboard_selections.push(ClipboardSelection {
12038 len,
12039 is_entire_line,
12040 first_line_indent: buffer
12041 .indent_size_for_line(MultiBufferRow(selection.start.row))
12042 .len,
12043 });
12044 }
12045 }
12046
12047 self.transact(window, cx, |this, window, cx| {
12048 this.change_selections(Default::default(), window, cx, |s| {
12049 s.select(selections);
12050 });
12051 this.insert("", window, cx);
12052 });
12053 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12054 }
12055
12056 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12057 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12058 let item = self.cut_common(window, cx);
12059 cx.write_to_clipboard(item);
12060 }
12061
12062 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12063 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12064 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12065 s.move_with(|snapshot, sel| {
12066 if sel.is_empty() {
12067 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12068 }
12069 });
12070 });
12071 let item = self.cut_common(window, cx);
12072 cx.set_global(KillRing(item))
12073 }
12074
12075 pub fn kill_ring_yank(
12076 &mut self,
12077 _: &KillRingYank,
12078 window: &mut Window,
12079 cx: &mut Context<Self>,
12080 ) {
12081 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12082 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12083 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12084 (kill_ring.text().to_string(), kill_ring.metadata_json())
12085 } else {
12086 return;
12087 }
12088 } else {
12089 return;
12090 };
12091 self.do_paste(&text, metadata, false, window, cx);
12092 }
12093
12094 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12095 self.do_copy(true, cx);
12096 }
12097
12098 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12099 self.do_copy(false, cx);
12100 }
12101
12102 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12103 let selections = self.selections.all::<Point>(cx);
12104 let buffer = self.buffer.read(cx).read(cx);
12105 let mut text = String::new();
12106
12107 let mut clipboard_selections = Vec::with_capacity(selections.len());
12108 {
12109 let max_point = buffer.max_point();
12110 let mut is_first = true;
12111 for selection in &selections {
12112 let mut start = selection.start;
12113 let mut end = selection.end;
12114 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12115 if is_entire_line {
12116 start = Point::new(start.row, 0);
12117 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12118 }
12119
12120 let mut trimmed_selections = Vec::new();
12121 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12122 let row = MultiBufferRow(start.row);
12123 let first_indent = buffer.indent_size_for_line(row);
12124 if first_indent.len == 0 || start.column > first_indent.len {
12125 trimmed_selections.push(start..end);
12126 } else {
12127 trimmed_selections.push(
12128 Point::new(row.0, first_indent.len)
12129 ..Point::new(row.0, buffer.line_len(row)),
12130 );
12131 for row in start.row + 1..=end.row {
12132 let mut line_len = buffer.line_len(MultiBufferRow(row));
12133 if row == end.row {
12134 line_len = end.column;
12135 }
12136 if line_len == 0 {
12137 trimmed_selections
12138 .push(Point::new(row, 0)..Point::new(row, line_len));
12139 continue;
12140 }
12141 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12142 if row_indent_size.len >= first_indent.len {
12143 trimmed_selections.push(
12144 Point::new(row, first_indent.len)..Point::new(row, line_len),
12145 );
12146 } else {
12147 trimmed_selections.clear();
12148 trimmed_selections.push(start..end);
12149 break;
12150 }
12151 }
12152 }
12153 } else {
12154 trimmed_selections.push(start..end);
12155 }
12156
12157 for trimmed_range in trimmed_selections {
12158 if is_first {
12159 is_first = false;
12160 } else {
12161 text += "\n";
12162 }
12163 let mut len = 0;
12164 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12165 text.push_str(chunk);
12166 len += chunk.len();
12167 }
12168 clipboard_selections.push(ClipboardSelection {
12169 len,
12170 is_entire_line,
12171 first_line_indent: buffer
12172 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12173 .len,
12174 });
12175 }
12176 }
12177 }
12178
12179 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12180 text,
12181 clipboard_selections,
12182 ));
12183 }
12184
12185 pub fn do_paste(
12186 &mut self,
12187 text: &String,
12188 clipboard_selections: Option<Vec<ClipboardSelection>>,
12189 handle_entire_lines: bool,
12190 window: &mut Window,
12191 cx: &mut Context<Self>,
12192 ) {
12193 if self.read_only(cx) {
12194 return;
12195 }
12196
12197 let clipboard_text = Cow::Borrowed(text);
12198
12199 self.transact(window, cx, |this, window, cx| {
12200 let had_active_edit_prediction = this.has_active_edit_prediction();
12201
12202 if let Some(mut clipboard_selections) = clipboard_selections {
12203 let old_selections = this.selections.all::<usize>(cx);
12204 let all_selections_were_entire_line =
12205 clipboard_selections.iter().all(|s| s.is_entire_line);
12206 let first_selection_indent_column =
12207 clipboard_selections.first().map(|s| s.first_line_indent);
12208 if clipboard_selections.len() != old_selections.len() {
12209 clipboard_selections.drain(..);
12210 }
12211 let cursor_offset = this.selections.last::<usize>(cx).head();
12212 let mut auto_indent_on_paste = true;
12213
12214 this.buffer.update(cx, |buffer, cx| {
12215 let snapshot = buffer.read(cx);
12216 auto_indent_on_paste = snapshot
12217 .language_settings_at(cursor_offset, cx)
12218 .auto_indent_on_paste;
12219
12220 let mut start_offset = 0;
12221 let mut edits = Vec::new();
12222 let mut original_indent_columns = Vec::new();
12223 for (ix, selection) in old_selections.iter().enumerate() {
12224 let to_insert;
12225 let entire_line;
12226 let original_indent_column;
12227 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12228 let end_offset = start_offset + clipboard_selection.len;
12229 to_insert = &clipboard_text[start_offset..end_offset];
12230 entire_line = clipboard_selection.is_entire_line;
12231 start_offset = end_offset + 1;
12232 original_indent_column = Some(clipboard_selection.first_line_indent);
12233 } else {
12234 to_insert = clipboard_text.as_str();
12235 entire_line = all_selections_were_entire_line;
12236 original_indent_column = first_selection_indent_column
12237 }
12238
12239 // If the corresponding selection was empty when this slice of the
12240 // clipboard text was written, then the entire line containing the
12241 // selection was copied. If this selection is also currently empty,
12242 // then paste the line before the current line of the buffer.
12243 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12244 let column = selection.start.to_point(&snapshot).column as usize;
12245 let line_start = selection.start - column;
12246 line_start..line_start
12247 } else {
12248 selection.range()
12249 };
12250
12251 edits.push((range, to_insert));
12252 original_indent_columns.push(original_indent_column);
12253 }
12254 drop(snapshot);
12255
12256 buffer.edit(
12257 edits,
12258 if auto_indent_on_paste {
12259 Some(AutoindentMode::Block {
12260 original_indent_columns,
12261 })
12262 } else {
12263 None
12264 },
12265 cx,
12266 );
12267 });
12268
12269 let selections = this.selections.all::<usize>(cx);
12270 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12271 } else {
12272 this.insert(&clipboard_text, window, cx);
12273 }
12274
12275 let trigger_in_words =
12276 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12277
12278 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12279 });
12280 }
12281
12282 pub fn diff_clipboard_with_selection(
12283 &mut self,
12284 _: &DiffClipboardWithSelection,
12285 window: &mut Window,
12286 cx: &mut Context<Self>,
12287 ) {
12288 let selections = self.selections.all::<usize>(cx);
12289
12290 if selections.is_empty() {
12291 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12292 return;
12293 };
12294
12295 let clipboard_text = match cx.read_from_clipboard() {
12296 Some(item) => match item.entries().first() {
12297 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12298 _ => None,
12299 },
12300 None => None,
12301 };
12302
12303 let Some(clipboard_text) = clipboard_text else {
12304 log::warn!("Clipboard doesn't contain text.");
12305 return;
12306 };
12307
12308 window.dispatch_action(
12309 Box::new(DiffClipboardWithSelectionData {
12310 clipboard_text,
12311 editor: cx.entity(),
12312 }),
12313 cx,
12314 );
12315 }
12316
12317 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12318 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12319 if let Some(item) = cx.read_from_clipboard() {
12320 let entries = item.entries();
12321
12322 match entries.first() {
12323 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12324 // of all the pasted entries.
12325 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12326 .do_paste(
12327 clipboard_string.text(),
12328 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12329 true,
12330 window,
12331 cx,
12332 ),
12333 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12334 }
12335 }
12336 }
12337
12338 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12339 if self.read_only(cx) {
12340 return;
12341 }
12342
12343 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12344
12345 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12346 if let Some((selections, _)) =
12347 self.selection_history.transaction(transaction_id).cloned()
12348 {
12349 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12350 s.select_anchors(selections.to_vec());
12351 });
12352 } else {
12353 log::error!(
12354 "No entry in selection_history found for undo. \
12355 This may correspond to a bug where undo does not update the selection. \
12356 If this is occurring, please add details to \
12357 https://github.com/zed-industries/zed/issues/22692"
12358 );
12359 }
12360 self.request_autoscroll(Autoscroll::fit(), cx);
12361 self.unmark_text(window, cx);
12362 self.refresh_edit_prediction(true, false, window, cx);
12363 cx.emit(EditorEvent::Edited { transaction_id });
12364 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12365 }
12366 }
12367
12368 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12369 if self.read_only(cx) {
12370 return;
12371 }
12372
12373 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12374
12375 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12376 if let Some((_, Some(selections))) =
12377 self.selection_history.transaction(transaction_id).cloned()
12378 {
12379 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12380 s.select_anchors(selections.to_vec());
12381 });
12382 } else {
12383 log::error!(
12384 "No entry in selection_history found for redo. \
12385 This may correspond to a bug where undo does not update the selection. \
12386 If this is occurring, please add details to \
12387 https://github.com/zed-industries/zed/issues/22692"
12388 );
12389 }
12390 self.request_autoscroll(Autoscroll::fit(), cx);
12391 self.unmark_text(window, cx);
12392 self.refresh_edit_prediction(true, false, window, cx);
12393 cx.emit(EditorEvent::Edited { transaction_id });
12394 }
12395 }
12396
12397 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12398 self.buffer
12399 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12400 }
12401
12402 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12403 self.buffer
12404 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12405 }
12406
12407 pub fn move_left(&mut self, _: &MoveLeft, 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::left(map, selection.start)
12413 } else {
12414 selection.start
12415 };
12416 selection.collapse_to(cursor, SelectionGoal::None);
12417 });
12418 })
12419 }
12420
12421 pub fn select_left(&mut self, _: &SelectLeft, 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::left(map, head), SelectionGoal::None));
12425 })
12426 }
12427
12428 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12429 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12430 self.change_selections(Default::default(), window, cx, |s| {
12431 s.move_with(|map, selection| {
12432 let cursor = if selection.is_empty() {
12433 movement::right(map, selection.end)
12434 } else {
12435 selection.end
12436 };
12437 selection.collapse_to(cursor, SelectionGoal::None)
12438 });
12439 })
12440 }
12441
12442 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12444 self.change_selections(Default::default(), window, cx, |s| {
12445 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12446 })
12447 }
12448
12449 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12450 if self.take_rename(true, window, cx).is_some() {
12451 return;
12452 }
12453
12454 if self.mode.is_single_line() {
12455 cx.propagate();
12456 return;
12457 }
12458
12459 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12460
12461 let text_layout_details = &self.text_layout_details(window);
12462 let selection_count = self.selections.count();
12463 let first_selection = self.selections.first_anchor();
12464
12465 self.change_selections(Default::default(), window, cx, |s| {
12466 s.move_with(|map, selection| {
12467 if !selection.is_empty() {
12468 selection.goal = SelectionGoal::None;
12469 }
12470 let (cursor, goal) = movement::up(
12471 map,
12472 selection.start,
12473 selection.goal,
12474 false,
12475 text_layout_details,
12476 );
12477 selection.collapse_to(cursor, goal);
12478 });
12479 });
12480
12481 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12482 {
12483 cx.propagate();
12484 }
12485 }
12486
12487 pub fn move_up_by_lines(
12488 &mut self,
12489 action: &MoveUpByLines,
12490 window: &mut Window,
12491 cx: &mut Context<Self>,
12492 ) {
12493 if self.take_rename(true, window, cx).is_some() {
12494 return;
12495 }
12496
12497 if self.mode.is_single_line() {
12498 cx.propagate();
12499 return;
12500 }
12501
12502 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12503
12504 let text_layout_details = &self.text_layout_details(window);
12505
12506 self.change_selections(Default::default(), window, cx, |s| {
12507 s.move_with(|map, selection| {
12508 if !selection.is_empty() {
12509 selection.goal = SelectionGoal::None;
12510 }
12511 let (cursor, goal) = movement::up_by_rows(
12512 map,
12513 selection.start,
12514 action.lines,
12515 selection.goal,
12516 false,
12517 text_layout_details,
12518 );
12519 selection.collapse_to(cursor, goal);
12520 });
12521 })
12522 }
12523
12524 pub fn move_down_by_lines(
12525 &mut self,
12526 action: &MoveDownByLines,
12527 window: &mut Window,
12528 cx: &mut Context<Self>,
12529 ) {
12530 if self.take_rename(true, window, cx).is_some() {
12531 return;
12532 }
12533
12534 if self.mode.is_single_line() {
12535 cx.propagate();
12536 return;
12537 }
12538
12539 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12540
12541 let text_layout_details = &self.text_layout_details(window);
12542
12543 self.change_selections(Default::default(), window, cx, |s| {
12544 s.move_with(|map, selection| {
12545 if !selection.is_empty() {
12546 selection.goal = SelectionGoal::None;
12547 }
12548 let (cursor, goal) = movement::down_by_rows(
12549 map,
12550 selection.start,
12551 action.lines,
12552 selection.goal,
12553 false,
12554 text_layout_details,
12555 );
12556 selection.collapse_to(cursor, goal);
12557 });
12558 })
12559 }
12560
12561 pub fn select_down_by_lines(
12562 &mut self,
12563 action: &SelectDownByLines,
12564 window: &mut Window,
12565 cx: &mut Context<Self>,
12566 ) {
12567 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12568 let text_layout_details = &self.text_layout_details(window);
12569 self.change_selections(Default::default(), window, cx, |s| {
12570 s.move_heads_with(|map, head, goal| {
12571 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12572 })
12573 })
12574 }
12575
12576 pub fn select_up_by_lines(
12577 &mut self,
12578 action: &SelectUpByLines,
12579 window: &mut Window,
12580 cx: &mut Context<Self>,
12581 ) {
12582 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12583 let text_layout_details = &self.text_layout_details(window);
12584 self.change_selections(Default::default(), window, cx, |s| {
12585 s.move_heads_with(|map, head, goal| {
12586 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12587 })
12588 })
12589 }
12590
12591 pub fn select_page_up(
12592 &mut self,
12593 _: &SelectPageUp,
12594 window: &mut Window,
12595 cx: &mut Context<Self>,
12596 ) {
12597 let Some(row_count) = self.visible_row_count() else {
12598 return;
12599 };
12600
12601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12602
12603 let text_layout_details = &self.text_layout_details(window);
12604
12605 self.change_selections(Default::default(), window, cx, |s| {
12606 s.move_heads_with(|map, head, goal| {
12607 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12608 })
12609 })
12610 }
12611
12612 pub fn move_page_up(
12613 &mut self,
12614 action: &MovePageUp,
12615 window: &mut Window,
12616 cx: &mut Context<Self>,
12617 ) {
12618 if self.take_rename(true, window, cx).is_some() {
12619 return;
12620 }
12621
12622 if self
12623 .context_menu
12624 .borrow_mut()
12625 .as_mut()
12626 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12627 .unwrap_or(false)
12628 {
12629 return;
12630 }
12631
12632 if matches!(self.mode, EditorMode::SingleLine) {
12633 cx.propagate();
12634 return;
12635 }
12636
12637 let Some(row_count) = self.visible_row_count() else {
12638 return;
12639 };
12640
12641 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12642
12643 let effects = if action.center_cursor {
12644 SelectionEffects::scroll(Autoscroll::center())
12645 } else {
12646 SelectionEffects::default()
12647 };
12648
12649 let text_layout_details = &self.text_layout_details(window);
12650
12651 self.change_selections(effects, window, cx, |s| {
12652 s.move_with(|map, selection| {
12653 if !selection.is_empty() {
12654 selection.goal = SelectionGoal::None;
12655 }
12656 let (cursor, goal) = movement::up_by_rows(
12657 map,
12658 selection.end,
12659 row_count,
12660 selection.goal,
12661 false,
12662 text_layout_details,
12663 );
12664 selection.collapse_to(cursor, goal);
12665 });
12666 });
12667 }
12668
12669 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12670 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12671 let text_layout_details = &self.text_layout_details(window);
12672 self.change_selections(Default::default(), window, cx, |s| {
12673 s.move_heads_with(|map, head, goal| {
12674 movement::up(map, head, goal, false, text_layout_details)
12675 })
12676 })
12677 }
12678
12679 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12680 self.take_rename(true, window, cx);
12681
12682 if self.mode.is_single_line() {
12683 cx.propagate();
12684 return;
12685 }
12686
12687 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12688
12689 let text_layout_details = &self.text_layout_details(window);
12690 let selection_count = self.selections.count();
12691 let first_selection = self.selections.first_anchor();
12692
12693 self.change_selections(Default::default(), window, cx, |s| {
12694 s.move_with(|map, selection| {
12695 if !selection.is_empty() {
12696 selection.goal = SelectionGoal::None;
12697 }
12698 let (cursor, goal) = movement::down(
12699 map,
12700 selection.end,
12701 selection.goal,
12702 false,
12703 text_layout_details,
12704 );
12705 selection.collapse_to(cursor, goal);
12706 });
12707 });
12708
12709 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12710 {
12711 cx.propagate();
12712 }
12713 }
12714
12715 pub fn select_page_down(
12716 &mut self,
12717 _: &SelectPageDown,
12718 window: &mut Window,
12719 cx: &mut Context<Self>,
12720 ) {
12721 let Some(row_count) = self.visible_row_count() else {
12722 return;
12723 };
12724
12725 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12726
12727 let text_layout_details = &self.text_layout_details(window);
12728
12729 self.change_selections(Default::default(), window, cx, |s| {
12730 s.move_heads_with(|map, head, goal| {
12731 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12732 })
12733 })
12734 }
12735
12736 pub fn move_page_down(
12737 &mut self,
12738 action: &MovePageDown,
12739 window: &mut Window,
12740 cx: &mut Context<Self>,
12741 ) {
12742 if self.take_rename(true, window, cx).is_some() {
12743 return;
12744 }
12745
12746 if self
12747 .context_menu
12748 .borrow_mut()
12749 .as_mut()
12750 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12751 .unwrap_or(false)
12752 {
12753 return;
12754 }
12755
12756 if matches!(self.mode, EditorMode::SingleLine) {
12757 cx.propagate();
12758 return;
12759 }
12760
12761 let Some(row_count) = self.visible_row_count() else {
12762 return;
12763 };
12764
12765 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12766
12767 let effects = if action.center_cursor {
12768 SelectionEffects::scroll(Autoscroll::center())
12769 } else {
12770 SelectionEffects::default()
12771 };
12772
12773 let text_layout_details = &self.text_layout_details(window);
12774 self.change_selections(effects, window, cx, |s| {
12775 s.move_with(|map, selection| {
12776 if !selection.is_empty() {
12777 selection.goal = SelectionGoal::None;
12778 }
12779 let (cursor, goal) = movement::down_by_rows(
12780 map,
12781 selection.end,
12782 row_count,
12783 selection.goal,
12784 false,
12785 text_layout_details,
12786 );
12787 selection.collapse_to(cursor, goal);
12788 });
12789 });
12790 }
12791
12792 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12793 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12794 let text_layout_details = &self.text_layout_details(window);
12795 self.change_selections(Default::default(), window, cx, |s| {
12796 s.move_heads_with(|map, head, goal| {
12797 movement::down(map, head, goal, false, text_layout_details)
12798 })
12799 });
12800 }
12801
12802 pub fn context_menu_first(
12803 &mut self,
12804 _: &ContextMenuFirst,
12805 window: &mut Window,
12806 cx: &mut Context<Self>,
12807 ) {
12808 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12809 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12810 }
12811 }
12812
12813 pub fn context_menu_prev(
12814 &mut self,
12815 _: &ContextMenuPrevious,
12816 window: &mut Window,
12817 cx: &mut Context<Self>,
12818 ) {
12819 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12820 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12821 }
12822 }
12823
12824 pub fn context_menu_next(
12825 &mut self,
12826 _: &ContextMenuNext,
12827 window: &mut Window,
12828 cx: &mut Context<Self>,
12829 ) {
12830 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12831 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12832 }
12833 }
12834
12835 pub fn context_menu_last(
12836 &mut self,
12837 _: &ContextMenuLast,
12838 window: &mut Window,
12839 cx: &mut Context<Self>,
12840 ) {
12841 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12842 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12843 }
12844 }
12845
12846 pub fn signature_help_prev(
12847 &mut self,
12848 _: &SignatureHelpPrevious,
12849 _: &mut Window,
12850 cx: &mut Context<Self>,
12851 ) {
12852 if let Some(popover) = self.signature_help_state.popover_mut() {
12853 if popover.current_signature == 0 {
12854 popover.current_signature = popover.signatures.len() - 1;
12855 } else {
12856 popover.current_signature -= 1;
12857 }
12858 cx.notify();
12859 }
12860 }
12861
12862 pub fn signature_help_next(
12863 &mut self,
12864 _: &SignatureHelpNext,
12865 _: &mut Window,
12866 cx: &mut Context<Self>,
12867 ) {
12868 if let Some(popover) = self.signature_help_state.popover_mut() {
12869 if popover.current_signature + 1 == popover.signatures.len() {
12870 popover.current_signature = 0;
12871 } else {
12872 popover.current_signature += 1;
12873 }
12874 cx.notify();
12875 }
12876 }
12877
12878 pub fn move_to_previous_word_start(
12879 &mut self,
12880 _: &MoveToPreviousWordStart,
12881 window: &mut Window,
12882 cx: &mut Context<Self>,
12883 ) {
12884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12885 self.change_selections(Default::default(), window, cx, |s| {
12886 s.move_cursors_with(|map, head, _| {
12887 (
12888 movement::previous_word_start(map, head),
12889 SelectionGoal::None,
12890 )
12891 });
12892 })
12893 }
12894
12895 pub fn move_to_previous_subword_start(
12896 &mut self,
12897 _: &MoveToPreviousSubwordStart,
12898 window: &mut Window,
12899 cx: &mut Context<Self>,
12900 ) {
12901 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12902 self.change_selections(Default::default(), window, cx, |s| {
12903 s.move_cursors_with(|map, head, _| {
12904 (
12905 movement::previous_subword_start(map, head),
12906 SelectionGoal::None,
12907 )
12908 });
12909 })
12910 }
12911
12912 pub fn select_to_previous_word_start(
12913 &mut self,
12914 _: &SelectToPreviousWordStart,
12915 window: &mut Window,
12916 cx: &mut Context<Self>,
12917 ) {
12918 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12919 self.change_selections(Default::default(), window, cx, |s| {
12920 s.move_heads_with(|map, head, _| {
12921 (
12922 movement::previous_word_start(map, head),
12923 SelectionGoal::None,
12924 )
12925 });
12926 })
12927 }
12928
12929 pub fn select_to_previous_subword_start(
12930 &mut self,
12931 _: &SelectToPreviousSubwordStart,
12932 window: &mut Window,
12933 cx: &mut Context<Self>,
12934 ) {
12935 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12936 self.change_selections(Default::default(), window, cx, |s| {
12937 s.move_heads_with(|map, head, _| {
12938 (
12939 movement::previous_subword_start(map, head),
12940 SelectionGoal::None,
12941 )
12942 });
12943 })
12944 }
12945
12946 pub fn delete_to_previous_word_start(
12947 &mut self,
12948 action: &DeleteToPreviousWordStart,
12949 window: &mut Window,
12950 cx: &mut Context<Self>,
12951 ) {
12952 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12953 self.transact(window, cx, |this, window, cx| {
12954 this.select_autoclose_pair(window, cx);
12955 this.change_selections(Default::default(), window, cx, |s| {
12956 s.move_with(|map, selection| {
12957 if selection.is_empty() {
12958 let cursor = if action.ignore_newlines {
12959 movement::previous_word_start(map, selection.head())
12960 } else {
12961 movement::previous_word_start_or_newline(map, selection.head())
12962 };
12963 selection.set_head(cursor, SelectionGoal::None);
12964 }
12965 });
12966 });
12967 this.insert("", window, cx);
12968 });
12969 }
12970
12971 pub fn delete_to_previous_subword_start(
12972 &mut self,
12973 _: &DeleteToPreviousSubwordStart,
12974 window: &mut Window,
12975 cx: &mut Context<Self>,
12976 ) {
12977 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12978 self.transact(window, cx, |this, window, cx| {
12979 this.select_autoclose_pair(window, cx);
12980 this.change_selections(Default::default(), window, cx, |s| {
12981 s.move_with(|map, selection| {
12982 if selection.is_empty() {
12983 let cursor = movement::previous_subword_start(map, selection.head());
12984 selection.set_head(cursor, SelectionGoal::None);
12985 }
12986 });
12987 });
12988 this.insert("", window, cx);
12989 });
12990 }
12991
12992 pub fn move_to_next_word_end(
12993 &mut self,
12994 _: &MoveToNextWordEnd,
12995 window: &mut Window,
12996 cx: &mut Context<Self>,
12997 ) {
12998 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12999 self.change_selections(Default::default(), window, cx, |s| {
13000 s.move_cursors_with(|map, head, _| {
13001 (movement::next_word_end(map, head), SelectionGoal::None)
13002 });
13003 })
13004 }
13005
13006 pub fn move_to_next_subword_end(
13007 &mut self,
13008 _: &MoveToNextSubwordEnd,
13009 window: &mut Window,
13010 cx: &mut Context<Self>,
13011 ) {
13012 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13013 self.change_selections(Default::default(), window, cx, |s| {
13014 s.move_cursors_with(|map, head, _| {
13015 (movement::next_subword_end(map, head), SelectionGoal::None)
13016 });
13017 })
13018 }
13019
13020 pub fn select_to_next_word_end(
13021 &mut self,
13022 _: &SelectToNextWordEnd,
13023 window: &mut Window,
13024 cx: &mut Context<Self>,
13025 ) {
13026 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13027 self.change_selections(Default::default(), window, cx, |s| {
13028 s.move_heads_with(|map, head, _| {
13029 (movement::next_word_end(map, head), SelectionGoal::None)
13030 });
13031 })
13032 }
13033
13034 pub fn select_to_next_subword_end(
13035 &mut self,
13036 _: &SelectToNextSubwordEnd,
13037 window: &mut Window,
13038 cx: &mut Context<Self>,
13039 ) {
13040 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13041 self.change_selections(Default::default(), window, cx, |s| {
13042 s.move_heads_with(|map, head, _| {
13043 (movement::next_subword_end(map, head), SelectionGoal::None)
13044 });
13045 })
13046 }
13047
13048 pub fn delete_to_next_word_end(
13049 &mut self,
13050 action: &DeleteToNextWordEnd,
13051 window: &mut Window,
13052 cx: &mut Context<Self>,
13053 ) {
13054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13055 self.transact(window, cx, |this, window, cx| {
13056 this.change_selections(Default::default(), window, cx, |s| {
13057 s.move_with(|map, selection| {
13058 if selection.is_empty() {
13059 let cursor = if action.ignore_newlines {
13060 movement::next_word_end(map, selection.head())
13061 } else {
13062 movement::next_word_end_or_newline(map, selection.head())
13063 };
13064 selection.set_head(cursor, SelectionGoal::None);
13065 }
13066 });
13067 });
13068 this.insert("", window, cx);
13069 });
13070 }
13071
13072 pub fn delete_to_next_subword_end(
13073 &mut self,
13074 _: &DeleteToNextSubwordEnd,
13075 window: &mut Window,
13076 cx: &mut Context<Self>,
13077 ) {
13078 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13079 self.transact(window, cx, |this, window, cx| {
13080 this.change_selections(Default::default(), window, cx, |s| {
13081 s.move_with(|map, selection| {
13082 if selection.is_empty() {
13083 let cursor = movement::next_subword_end(map, selection.head());
13084 selection.set_head(cursor, SelectionGoal::None);
13085 }
13086 });
13087 });
13088 this.insert("", window, cx);
13089 });
13090 }
13091
13092 pub fn move_to_beginning_of_line(
13093 &mut self,
13094 action: &MoveToBeginningOfLine,
13095 window: &mut Window,
13096 cx: &mut Context<Self>,
13097 ) {
13098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13099 self.change_selections(Default::default(), window, cx, |s| {
13100 s.move_cursors_with(|map, head, _| {
13101 (
13102 movement::indented_line_beginning(
13103 map,
13104 head,
13105 action.stop_at_soft_wraps,
13106 action.stop_at_indent,
13107 ),
13108 SelectionGoal::None,
13109 )
13110 });
13111 })
13112 }
13113
13114 pub fn select_to_beginning_of_line(
13115 &mut self,
13116 action: &SelectToBeginningOfLine,
13117 window: &mut Window,
13118 cx: &mut Context<Self>,
13119 ) {
13120 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13121 self.change_selections(Default::default(), window, cx, |s| {
13122 s.move_heads_with(|map, head, _| {
13123 (
13124 movement::indented_line_beginning(
13125 map,
13126 head,
13127 action.stop_at_soft_wraps,
13128 action.stop_at_indent,
13129 ),
13130 SelectionGoal::None,
13131 )
13132 });
13133 });
13134 }
13135
13136 pub fn delete_to_beginning_of_line(
13137 &mut self,
13138 action: &DeleteToBeginningOfLine,
13139 window: &mut Window,
13140 cx: &mut Context<Self>,
13141 ) {
13142 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13143 self.transact(window, cx, |this, window, cx| {
13144 this.change_selections(Default::default(), window, cx, |s| {
13145 s.move_with(|_, selection| {
13146 selection.reversed = true;
13147 });
13148 });
13149
13150 this.select_to_beginning_of_line(
13151 &SelectToBeginningOfLine {
13152 stop_at_soft_wraps: false,
13153 stop_at_indent: action.stop_at_indent,
13154 },
13155 window,
13156 cx,
13157 );
13158 this.backspace(&Backspace, window, cx);
13159 });
13160 }
13161
13162 pub fn move_to_end_of_line(
13163 &mut self,
13164 action: &MoveToEndOfLine,
13165 window: &mut Window,
13166 cx: &mut Context<Self>,
13167 ) {
13168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13169 self.change_selections(Default::default(), window, cx, |s| {
13170 s.move_cursors_with(|map, head, _| {
13171 (
13172 movement::line_end(map, head, action.stop_at_soft_wraps),
13173 SelectionGoal::None,
13174 )
13175 });
13176 })
13177 }
13178
13179 pub fn select_to_end_of_line(
13180 &mut self,
13181 action: &SelectToEndOfLine,
13182 window: &mut Window,
13183 cx: &mut Context<Self>,
13184 ) {
13185 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13186 self.change_selections(Default::default(), window, cx, |s| {
13187 s.move_heads_with(|map, head, _| {
13188 (
13189 movement::line_end(map, head, action.stop_at_soft_wraps),
13190 SelectionGoal::None,
13191 )
13192 });
13193 })
13194 }
13195
13196 pub fn delete_to_end_of_line(
13197 &mut self,
13198 _: &DeleteToEndOfLine,
13199 window: &mut Window,
13200 cx: &mut Context<Self>,
13201 ) {
13202 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13203 self.transact(window, cx, |this, window, cx| {
13204 this.select_to_end_of_line(
13205 &SelectToEndOfLine {
13206 stop_at_soft_wraps: false,
13207 },
13208 window,
13209 cx,
13210 );
13211 this.delete(&Delete, window, cx);
13212 });
13213 }
13214
13215 pub fn cut_to_end_of_line(
13216 &mut self,
13217 _: &CutToEndOfLine,
13218 window: &mut Window,
13219 cx: &mut Context<Self>,
13220 ) {
13221 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13222 self.transact(window, cx, |this, window, cx| {
13223 this.select_to_end_of_line(
13224 &SelectToEndOfLine {
13225 stop_at_soft_wraps: false,
13226 },
13227 window,
13228 cx,
13229 );
13230 this.cut(&Cut, window, cx);
13231 });
13232 }
13233
13234 pub fn move_to_start_of_paragraph(
13235 &mut self,
13236 _: &MoveToStartOfParagraph,
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::start_of_paragraph(map, selection.head(), 1),
13249 SelectionGoal::None,
13250 )
13251 });
13252 })
13253 }
13254
13255 pub fn move_to_end_of_paragraph(
13256 &mut self,
13257 _: &MoveToEndOfParagraph,
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_with(|map, selection| {
13268 selection.collapse_to(
13269 movement::end_of_paragraph(map, selection.head(), 1),
13270 SelectionGoal::None,
13271 )
13272 });
13273 })
13274 }
13275
13276 pub fn select_to_start_of_paragraph(
13277 &mut self,
13278 _: &SelectToStartOfParagraph,
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::start_of_paragraph(map, head, 1),
13291 SelectionGoal::None,
13292 )
13293 });
13294 })
13295 }
13296
13297 pub fn select_to_end_of_paragraph(
13298 &mut self,
13299 _: &SelectToEndOfParagraph,
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_heads_with(|map, head, _| {
13310 (
13311 movement::end_of_paragraph(map, head, 1),
13312 SelectionGoal::None,
13313 )
13314 });
13315 })
13316 }
13317
13318 pub fn move_to_start_of_excerpt(
13319 &mut self,
13320 _: &MoveToStartOfExcerpt,
13321 window: &mut Window,
13322 cx: &mut Context<Self>,
13323 ) {
13324 if matches!(self.mode, EditorMode::SingleLine) {
13325 cx.propagate();
13326 return;
13327 }
13328 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13329 self.change_selections(Default::default(), window, cx, |s| {
13330 s.move_with(|map, selection| {
13331 selection.collapse_to(
13332 movement::start_of_excerpt(
13333 map,
13334 selection.head(),
13335 workspace::searchable::Direction::Prev,
13336 ),
13337 SelectionGoal::None,
13338 )
13339 });
13340 })
13341 }
13342
13343 pub fn move_to_start_of_next_excerpt(
13344 &mut self,
13345 _: &MoveToStartOfNextExcerpt,
13346 window: &mut Window,
13347 cx: &mut Context<Self>,
13348 ) {
13349 if matches!(self.mode, EditorMode::SingleLine) {
13350 cx.propagate();
13351 return;
13352 }
13353
13354 self.change_selections(Default::default(), window, cx, |s| {
13355 s.move_with(|map, selection| {
13356 selection.collapse_to(
13357 movement::start_of_excerpt(
13358 map,
13359 selection.head(),
13360 workspace::searchable::Direction::Next,
13361 ),
13362 SelectionGoal::None,
13363 )
13364 });
13365 })
13366 }
13367
13368 pub fn move_to_end_of_excerpt(
13369 &mut self,
13370 _: &MoveToEndOfExcerpt,
13371 window: &mut Window,
13372 cx: &mut Context<Self>,
13373 ) {
13374 if matches!(self.mode, EditorMode::SingleLine) {
13375 cx.propagate();
13376 return;
13377 }
13378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13379 self.change_selections(Default::default(), window, cx, |s| {
13380 s.move_with(|map, selection| {
13381 selection.collapse_to(
13382 movement::end_of_excerpt(
13383 map,
13384 selection.head(),
13385 workspace::searchable::Direction::Next,
13386 ),
13387 SelectionGoal::None,
13388 )
13389 });
13390 })
13391 }
13392
13393 pub fn move_to_end_of_previous_excerpt(
13394 &mut self,
13395 _: &MoveToEndOfPreviousExcerpt,
13396 window: &mut Window,
13397 cx: &mut Context<Self>,
13398 ) {
13399 if matches!(self.mode, EditorMode::SingleLine) {
13400 cx.propagate();
13401 return;
13402 }
13403 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13404 self.change_selections(Default::default(), window, cx, |s| {
13405 s.move_with(|map, selection| {
13406 selection.collapse_to(
13407 movement::end_of_excerpt(
13408 map,
13409 selection.head(),
13410 workspace::searchable::Direction::Prev,
13411 ),
13412 SelectionGoal::None,
13413 )
13414 });
13415 })
13416 }
13417
13418 pub fn select_to_start_of_excerpt(
13419 &mut self,
13420 _: &SelectToStartOfExcerpt,
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::Prev),
13433 SelectionGoal::None,
13434 )
13435 });
13436 })
13437 }
13438
13439 pub fn select_to_start_of_next_excerpt(
13440 &mut self,
13441 _: &SelectToStartOfNextExcerpt,
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::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13454 SelectionGoal::None,
13455 )
13456 });
13457 })
13458 }
13459
13460 pub fn select_to_end_of_excerpt(
13461 &mut self,
13462 _: &SelectToEndOfExcerpt,
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::Next),
13475 SelectionGoal::None,
13476 )
13477 });
13478 })
13479 }
13480
13481 pub fn select_to_end_of_previous_excerpt(
13482 &mut self,
13483 _: &SelectToEndOfPreviousExcerpt,
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.move_heads_with(|map, head, _| {
13494 (
13495 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13496 SelectionGoal::None,
13497 )
13498 });
13499 })
13500 }
13501
13502 pub fn move_to_beginning(
13503 &mut self,
13504 _: &MoveToBeginning,
13505 window: &mut Window,
13506 cx: &mut Context<Self>,
13507 ) {
13508 if matches!(self.mode, EditorMode::SingleLine) {
13509 cx.propagate();
13510 return;
13511 }
13512 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13513 self.change_selections(Default::default(), window, cx, |s| {
13514 s.select_ranges(vec![0..0]);
13515 });
13516 }
13517
13518 pub fn select_to_beginning(
13519 &mut self,
13520 _: &SelectToBeginning,
13521 window: &mut Window,
13522 cx: &mut Context<Self>,
13523 ) {
13524 let mut selection = self.selections.last::<Point>(cx);
13525 selection.set_head(Point::zero(), SelectionGoal::None);
13526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13527 self.change_selections(Default::default(), window, cx, |s| {
13528 s.select(vec![selection]);
13529 });
13530 }
13531
13532 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13533 if matches!(self.mode, EditorMode::SingleLine) {
13534 cx.propagate();
13535 return;
13536 }
13537 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13538 let cursor = self.buffer.read(cx).read(cx).len();
13539 self.change_selections(Default::default(), window, cx, |s| {
13540 s.select_ranges(vec![cursor..cursor])
13541 });
13542 }
13543
13544 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13545 self.nav_history = nav_history;
13546 }
13547
13548 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13549 self.nav_history.as_ref()
13550 }
13551
13552 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13553 self.push_to_nav_history(
13554 self.selections.newest_anchor().head(),
13555 None,
13556 false,
13557 true,
13558 cx,
13559 );
13560 }
13561
13562 fn push_to_nav_history(
13563 &mut self,
13564 cursor_anchor: Anchor,
13565 new_position: Option<Point>,
13566 is_deactivate: bool,
13567 always: bool,
13568 cx: &mut Context<Self>,
13569 ) {
13570 if let Some(nav_history) = self.nav_history.as_mut() {
13571 let buffer = self.buffer.read(cx).read(cx);
13572 let cursor_position = cursor_anchor.to_point(&buffer);
13573 let scroll_state = self.scroll_manager.anchor();
13574 let scroll_top_row = scroll_state.top_row(&buffer);
13575 drop(buffer);
13576
13577 if let Some(new_position) = new_position {
13578 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13579 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13580 return;
13581 }
13582 }
13583
13584 nav_history.push(
13585 Some(NavigationData {
13586 cursor_anchor,
13587 cursor_position,
13588 scroll_anchor: scroll_state,
13589 scroll_top_row,
13590 }),
13591 cx,
13592 );
13593 cx.emit(EditorEvent::PushedToNavHistory {
13594 anchor: cursor_anchor,
13595 is_deactivate,
13596 })
13597 }
13598 }
13599
13600 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13602 let buffer = self.buffer.read(cx).snapshot(cx);
13603 let mut selection = self.selections.first::<usize>(cx);
13604 selection.set_head(buffer.len(), SelectionGoal::None);
13605 self.change_selections(Default::default(), window, cx, |s| {
13606 s.select(vec![selection]);
13607 });
13608 }
13609
13610 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13611 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13612 let end = self.buffer.read(cx).read(cx).len();
13613 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13614 s.select_ranges(vec![0..end]);
13615 });
13616 }
13617
13618 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13619 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13620 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13621 let mut selections = self.selections.all::<Point>(cx);
13622 let max_point = display_map.buffer_snapshot.max_point();
13623 for selection in &mut selections {
13624 let rows = selection.spanned_rows(true, &display_map);
13625 selection.start = Point::new(rows.start.0, 0);
13626 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13627 selection.reversed = false;
13628 }
13629 self.change_selections(Default::default(), window, cx, |s| {
13630 s.select(selections);
13631 });
13632 }
13633
13634 pub fn split_selection_into_lines(
13635 &mut self,
13636 action: &SplitSelectionIntoLines,
13637 window: &mut Window,
13638 cx: &mut Context<Self>,
13639 ) {
13640 let selections = self
13641 .selections
13642 .all::<Point>(cx)
13643 .into_iter()
13644 .map(|selection| selection.start..selection.end)
13645 .collect::<Vec<_>>();
13646 self.unfold_ranges(&selections, true, true, cx);
13647
13648 let mut new_selection_ranges = Vec::new();
13649 {
13650 let buffer = self.buffer.read(cx).read(cx);
13651 for selection in selections {
13652 for row in selection.start.row..selection.end.row {
13653 let line_start = Point::new(row, 0);
13654 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13655
13656 if action.keep_selections {
13657 // Keep the selection range for each line
13658 let selection_start = if row == selection.start.row {
13659 selection.start
13660 } else {
13661 line_start
13662 };
13663 new_selection_ranges.push(selection_start..line_end);
13664 } else {
13665 // Collapse to cursor at end of line
13666 new_selection_ranges.push(line_end..line_end);
13667 }
13668 }
13669
13670 let is_multiline_selection = selection.start.row != selection.end.row;
13671 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13672 // so this action feels more ergonomic when paired with other selection operations
13673 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13674 if !should_skip_last {
13675 if action.keep_selections {
13676 if is_multiline_selection {
13677 let line_start = Point::new(selection.end.row, 0);
13678 new_selection_ranges.push(line_start..selection.end);
13679 } else {
13680 new_selection_ranges.push(selection.start..selection.end);
13681 }
13682 } else {
13683 new_selection_ranges.push(selection.end..selection.end);
13684 }
13685 }
13686 }
13687 }
13688 self.change_selections(Default::default(), window, cx, |s| {
13689 s.select_ranges(new_selection_ranges);
13690 });
13691 }
13692
13693 pub fn add_selection_above(
13694 &mut self,
13695 _: &AddSelectionAbove,
13696 window: &mut Window,
13697 cx: &mut Context<Self>,
13698 ) {
13699 self.add_selection(true, window, cx);
13700 }
13701
13702 pub fn add_selection_below(
13703 &mut self,
13704 _: &AddSelectionBelow,
13705 window: &mut Window,
13706 cx: &mut Context<Self>,
13707 ) {
13708 self.add_selection(false, window, cx);
13709 }
13710
13711 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13712 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13713
13714 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13715 let all_selections = self.selections.all::<Point>(cx);
13716 let text_layout_details = self.text_layout_details(window);
13717
13718 let (mut columnar_selections, new_selections_to_columnarize) = {
13719 if let Some(state) = self.add_selections_state.as_ref() {
13720 let columnar_selection_ids: HashSet<_> = state
13721 .groups
13722 .iter()
13723 .flat_map(|group| group.stack.iter())
13724 .copied()
13725 .collect();
13726
13727 all_selections
13728 .into_iter()
13729 .partition(|s| columnar_selection_ids.contains(&s.id))
13730 } else {
13731 (Vec::new(), all_selections)
13732 }
13733 };
13734
13735 let mut state = self
13736 .add_selections_state
13737 .take()
13738 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13739
13740 for selection in new_selections_to_columnarize {
13741 let range = selection.display_range(&display_map).sorted();
13742 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13743 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13744 let positions = start_x.min(end_x)..start_x.max(end_x);
13745 let mut stack = Vec::new();
13746 for row in range.start.row().0..=range.end.row().0 {
13747 if let Some(selection) = self.selections.build_columnar_selection(
13748 &display_map,
13749 DisplayRow(row),
13750 &positions,
13751 selection.reversed,
13752 &text_layout_details,
13753 ) {
13754 stack.push(selection.id);
13755 columnar_selections.push(selection);
13756 }
13757 }
13758 if !stack.is_empty() {
13759 if above {
13760 stack.reverse();
13761 }
13762 state.groups.push(AddSelectionsGroup { above, stack });
13763 }
13764 }
13765
13766 let mut final_selections = Vec::new();
13767 let end_row = if above {
13768 DisplayRow(0)
13769 } else {
13770 display_map.max_point().row()
13771 };
13772
13773 let mut last_added_item_per_group = HashMap::default();
13774 for group in state.groups.iter_mut() {
13775 if let Some(last_id) = group.stack.last() {
13776 last_added_item_per_group.insert(*last_id, group);
13777 }
13778 }
13779
13780 for selection in columnar_selections {
13781 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13782 if above == group.above {
13783 let range = selection.display_range(&display_map).sorted();
13784 debug_assert_eq!(range.start.row(), range.end.row());
13785 let mut row = range.start.row();
13786 let positions =
13787 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13788 px(start)..px(end)
13789 } else {
13790 let start_x =
13791 display_map.x_for_display_point(range.start, &text_layout_details);
13792 let end_x =
13793 display_map.x_for_display_point(range.end, &text_layout_details);
13794 start_x.min(end_x)..start_x.max(end_x)
13795 };
13796
13797 let mut maybe_new_selection = None;
13798 while row != end_row {
13799 if above {
13800 row.0 -= 1;
13801 } else {
13802 row.0 += 1;
13803 }
13804 if let Some(new_selection) = self.selections.build_columnar_selection(
13805 &display_map,
13806 row,
13807 &positions,
13808 selection.reversed,
13809 &text_layout_details,
13810 ) {
13811 maybe_new_selection = Some(new_selection);
13812 break;
13813 }
13814 }
13815
13816 if let Some(new_selection) = maybe_new_selection {
13817 group.stack.push(new_selection.id);
13818 if above {
13819 final_selections.push(new_selection);
13820 final_selections.push(selection);
13821 } else {
13822 final_selections.push(selection);
13823 final_selections.push(new_selection);
13824 }
13825 } else {
13826 final_selections.push(selection);
13827 }
13828 } else {
13829 group.stack.pop();
13830 }
13831 } else {
13832 final_selections.push(selection);
13833 }
13834 }
13835
13836 self.change_selections(Default::default(), window, cx, |s| {
13837 s.select(final_selections);
13838 });
13839
13840 let final_selection_ids: HashSet<_> = self
13841 .selections
13842 .all::<Point>(cx)
13843 .iter()
13844 .map(|s| s.id)
13845 .collect();
13846 state.groups.retain_mut(|group| {
13847 // selections might get merged above so we remove invalid items from stacks
13848 group.stack.retain(|id| final_selection_ids.contains(id));
13849
13850 // single selection in stack can be treated as initial state
13851 group.stack.len() > 1
13852 });
13853
13854 if !state.groups.is_empty() {
13855 self.add_selections_state = Some(state);
13856 }
13857 }
13858
13859 fn select_match_ranges(
13860 &mut self,
13861 range: Range<usize>,
13862 reversed: bool,
13863 replace_newest: bool,
13864 auto_scroll: Option<Autoscroll>,
13865 window: &mut Window,
13866 cx: &mut Context<Editor>,
13867 ) {
13868 self.unfold_ranges(
13869 std::slice::from_ref(&range),
13870 false,
13871 auto_scroll.is_some(),
13872 cx,
13873 );
13874 let effects = if let Some(scroll) = auto_scroll {
13875 SelectionEffects::scroll(scroll)
13876 } else {
13877 SelectionEffects::no_scroll()
13878 };
13879 self.change_selections(effects, window, cx, |s| {
13880 if replace_newest {
13881 s.delete(s.newest_anchor().id);
13882 }
13883 if reversed {
13884 s.insert_range(range.end..range.start);
13885 } else {
13886 s.insert_range(range);
13887 }
13888 });
13889 }
13890
13891 pub fn select_next_match_internal(
13892 &mut self,
13893 display_map: &DisplaySnapshot,
13894 replace_newest: bool,
13895 autoscroll: Option<Autoscroll>,
13896 window: &mut Window,
13897 cx: &mut Context<Self>,
13898 ) -> Result<()> {
13899 let buffer = &display_map.buffer_snapshot;
13900 let mut selections = self.selections.all::<usize>(cx);
13901 if let Some(mut select_next_state) = self.select_next_state.take() {
13902 let query = &select_next_state.query;
13903 if !select_next_state.done {
13904 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13905 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13906 let mut next_selected_range = None;
13907
13908 let bytes_after_last_selection =
13909 buffer.bytes_in_range(last_selection.end..buffer.len());
13910 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13911 let query_matches = query
13912 .stream_find_iter(bytes_after_last_selection)
13913 .map(|result| (last_selection.end, result))
13914 .chain(
13915 query
13916 .stream_find_iter(bytes_before_first_selection)
13917 .map(|result| (0, result)),
13918 );
13919
13920 for (start_offset, query_match) in query_matches {
13921 let query_match = query_match.unwrap(); // can only fail due to I/O
13922 let offset_range =
13923 start_offset + query_match.start()..start_offset + query_match.end();
13924
13925 if !select_next_state.wordwise
13926 || (!buffer.is_inside_word(offset_range.start, false)
13927 && !buffer.is_inside_word(offset_range.end, false))
13928 {
13929 // TODO: This is n^2, because we might check all the selections
13930 if !selections
13931 .iter()
13932 .any(|selection| selection.range().overlaps(&offset_range))
13933 {
13934 next_selected_range = Some(offset_range);
13935 break;
13936 }
13937 }
13938 }
13939
13940 if let Some(next_selected_range) = next_selected_range {
13941 self.select_match_ranges(
13942 next_selected_range,
13943 last_selection.reversed,
13944 replace_newest,
13945 autoscroll,
13946 window,
13947 cx,
13948 );
13949 } else {
13950 select_next_state.done = true;
13951 }
13952 }
13953
13954 self.select_next_state = Some(select_next_state);
13955 } else {
13956 let mut only_carets = true;
13957 let mut same_text_selected = true;
13958 let mut selected_text = None;
13959
13960 let mut selections_iter = selections.iter().peekable();
13961 while let Some(selection) = selections_iter.next() {
13962 if selection.start != selection.end {
13963 only_carets = false;
13964 }
13965
13966 if same_text_selected {
13967 if selected_text.is_none() {
13968 selected_text =
13969 Some(buffer.text_for_range(selection.range()).collect::<String>());
13970 }
13971
13972 if let Some(next_selection) = selections_iter.peek() {
13973 if next_selection.range().len() == selection.range().len() {
13974 let next_selected_text = buffer
13975 .text_for_range(next_selection.range())
13976 .collect::<String>();
13977 if Some(next_selected_text) != selected_text {
13978 same_text_selected = false;
13979 selected_text = None;
13980 }
13981 } else {
13982 same_text_selected = false;
13983 selected_text = None;
13984 }
13985 }
13986 }
13987 }
13988
13989 if only_carets {
13990 for selection in &mut selections {
13991 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13992 selection.start = word_range.start;
13993 selection.end = word_range.end;
13994 selection.goal = SelectionGoal::None;
13995 selection.reversed = false;
13996 self.select_match_ranges(
13997 selection.start..selection.end,
13998 selection.reversed,
13999 replace_newest,
14000 autoscroll,
14001 window,
14002 cx,
14003 );
14004 }
14005
14006 if selections.len() == 1 {
14007 let selection = selections
14008 .last()
14009 .expect("ensured that there's only one selection");
14010 let query = buffer
14011 .text_for_range(selection.start..selection.end)
14012 .collect::<String>();
14013 let is_empty = query.is_empty();
14014 let select_state = SelectNextState {
14015 query: AhoCorasick::new(&[query])?,
14016 wordwise: true,
14017 done: is_empty,
14018 };
14019 self.select_next_state = Some(select_state);
14020 } else {
14021 self.select_next_state = None;
14022 }
14023 } else if let Some(selected_text) = selected_text {
14024 self.select_next_state = Some(SelectNextState {
14025 query: AhoCorasick::new(&[selected_text])?,
14026 wordwise: false,
14027 done: false,
14028 });
14029 self.select_next_match_internal(
14030 display_map,
14031 replace_newest,
14032 autoscroll,
14033 window,
14034 cx,
14035 )?;
14036 }
14037 }
14038 Ok(())
14039 }
14040
14041 pub fn select_all_matches(
14042 &mut self,
14043 _action: &SelectAllMatches,
14044 window: &mut Window,
14045 cx: &mut Context<Self>,
14046 ) -> Result<()> {
14047 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14048
14049 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14050
14051 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14052 let Some(select_next_state) = self.select_next_state.as_mut() else {
14053 return Ok(());
14054 };
14055 if select_next_state.done {
14056 return Ok(());
14057 }
14058
14059 let mut new_selections = Vec::new();
14060
14061 let reversed = self.selections.oldest::<usize>(cx).reversed;
14062 let buffer = &display_map.buffer_snapshot;
14063 let query_matches = select_next_state
14064 .query
14065 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14066
14067 for query_match in query_matches.into_iter() {
14068 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14069 let offset_range = if reversed {
14070 query_match.end()..query_match.start()
14071 } else {
14072 query_match.start()..query_match.end()
14073 };
14074
14075 if !select_next_state.wordwise
14076 || (!buffer.is_inside_word(offset_range.start, false)
14077 && !buffer.is_inside_word(offset_range.end, false))
14078 {
14079 new_selections.push(offset_range.start..offset_range.end);
14080 }
14081 }
14082
14083 select_next_state.done = true;
14084
14085 if new_selections.is_empty() {
14086 log::error!("bug: new_selections is empty in select_all_matches");
14087 return Ok(());
14088 }
14089
14090 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14091 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14092 selections.select_ranges(new_selections)
14093 });
14094
14095 Ok(())
14096 }
14097
14098 pub fn select_next(
14099 &mut self,
14100 action: &SelectNext,
14101 window: &mut Window,
14102 cx: &mut Context<Self>,
14103 ) -> Result<()> {
14104 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14105 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14106 self.select_next_match_internal(
14107 &display_map,
14108 action.replace_newest,
14109 Some(Autoscroll::newest()),
14110 window,
14111 cx,
14112 )?;
14113 Ok(())
14114 }
14115
14116 pub fn select_previous(
14117 &mut self,
14118 action: &SelectPrevious,
14119 window: &mut Window,
14120 cx: &mut Context<Self>,
14121 ) -> Result<()> {
14122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14123 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14124 let buffer = &display_map.buffer_snapshot;
14125 let mut selections = self.selections.all::<usize>(cx);
14126 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14127 let query = &select_prev_state.query;
14128 if !select_prev_state.done {
14129 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14130 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14131 let mut next_selected_range = None;
14132 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14133 let bytes_before_last_selection =
14134 buffer.reversed_bytes_in_range(0..last_selection.start);
14135 let bytes_after_first_selection =
14136 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14137 let query_matches = query
14138 .stream_find_iter(bytes_before_last_selection)
14139 .map(|result| (last_selection.start, result))
14140 .chain(
14141 query
14142 .stream_find_iter(bytes_after_first_selection)
14143 .map(|result| (buffer.len(), result)),
14144 );
14145 for (end_offset, query_match) in query_matches {
14146 let query_match = query_match.unwrap(); // can only fail due to I/O
14147 let offset_range =
14148 end_offset - query_match.end()..end_offset - query_match.start();
14149
14150 if !select_prev_state.wordwise
14151 || (!buffer.is_inside_word(offset_range.start, false)
14152 && !buffer.is_inside_word(offset_range.end, false))
14153 {
14154 next_selected_range = Some(offset_range);
14155 break;
14156 }
14157 }
14158
14159 if let Some(next_selected_range) = next_selected_range {
14160 self.select_match_ranges(
14161 next_selected_range,
14162 last_selection.reversed,
14163 action.replace_newest,
14164 Some(Autoscroll::newest()),
14165 window,
14166 cx,
14167 );
14168 } else {
14169 select_prev_state.done = true;
14170 }
14171 }
14172
14173 self.select_prev_state = Some(select_prev_state);
14174 } else {
14175 let mut only_carets = true;
14176 let mut same_text_selected = true;
14177 let mut selected_text = None;
14178
14179 let mut selections_iter = selections.iter().peekable();
14180 while let Some(selection) = selections_iter.next() {
14181 if selection.start != selection.end {
14182 only_carets = false;
14183 }
14184
14185 if same_text_selected {
14186 if selected_text.is_none() {
14187 selected_text =
14188 Some(buffer.text_for_range(selection.range()).collect::<String>());
14189 }
14190
14191 if let Some(next_selection) = selections_iter.peek() {
14192 if next_selection.range().len() == selection.range().len() {
14193 let next_selected_text = buffer
14194 .text_for_range(next_selection.range())
14195 .collect::<String>();
14196 if Some(next_selected_text) != selected_text {
14197 same_text_selected = false;
14198 selected_text = None;
14199 }
14200 } else {
14201 same_text_selected = false;
14202 selected_text = None;
14203 }
14204 }
14205 }
14206 }
14207
14208 if only_carets {
14209 for selection in &mut selections {
14210 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14211 selection.start = word_range.start;
14212 selection.end = word_range.end;
14213 selection.goal = SelectionGoal::None;
14214 selection.reversed = false;
14215 self.select_match_ranges(
14216 selection.start..selection.end,
14217 selection.reversed,
14218 action.replace_newest,
14219 Some(Autoscroll::newest()),
14220 window,
14221 cx,
14222 );
14223 }
14224 if selections.len() == 1 {
14225 let selection = selections
14226 .last()
14227 .expect("ensured that there's only one selection");
14228 let query = buffer
14229 .text_for_range(selection.start..selection.end)
14230 .collect::<String>();
14231 let is_empty = query.is_empty();
14232 let select_state = SelectNextState {
14233 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14234 wordwise: true,
14235 done: is_empty,
14236 };
14237 self.select_prev_state = Some(select_state);
14238 } else {
14239 self.select_prev_state = None;
14240 }
14241 } else if let Some(selected_text) = selected_text {
14242 self.select_prev_state = Some(SelectNextState {
14243 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14244 wordwise: false,
14245 done: false,
14246 });
14247 self.select_previous(action, window, cx)?;
14248 }
14249 }
14250 Ok(())
14251 }
14252
14253 pub fn find_next_match(
14254 &mut self,
14255 _: &FindNextMatch,
14256 window: &mut Window,
14257 cx: &mut Context<Self>,
14258 ) -> Result<()> {
14259 let selections = self.selections.disjoint_anchors();
14260 match selections.first() {
14261 Some(first) if selections.len() >= 2 => {
14262 self.change_selections(Default::default(), window, cx, |s| {
14263 s.select_ranges([first.range()]);
14264 });
14265 }
14266 _ => self.select_next(
14267 &SelectNext {
14268 replace_newest: true,
14269 },
14270 window,
14271 cx,
14272 )?,
14273 }
14274 Ok(())
14275 }
14276
14277 pub fn find_previous_match(
14278 &mut self,
14279 _: &FindPreviousMatch,
14280 window: &mut Window,
14281 cx: &mut Context<Self>,
14282 ) -> Result<()> {
14283 let selections = self.selections.disjoint_anchors();
14284 match selections.last() {
14285 Some(last) if selections.len() >= 2 => {
14286 self.change_selections(Default::default(), window, cx, |s| {
14287 s.select_ranges([last.range()]);
14288 });
14289 }
14290 _ => self.select_previous(
14291 &SelectPrevious {
14292 replace_newest: true,
14293 },
14294 window,
14295 cx,
14296 )?,
14297 }
14298 Ok(())
14299 }
14300
14301 pub fn toggle_comments(
14302 &mut self,
14303 action: &ToggleComments,
14304 window: &mut Window,
14305 cx: &mut Context<Self>,
14306 ) {
14307 if self.read_only(cx) {
14308 return;
14309 }
14310 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14311 let text_layout_details = &self.text_layout_details(window);
14312 self.transact(window, cx, |this, window, cx| {
14313 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14314 let mut edits = Vec::new();
14315 let mut selection_edit_ranges = Vec::new();
14316 let mut last_toggled_row = None;
14317 let snapshot = this.buffer.read(cx).read(cx);
14318 let empty_str: Arc<str> = Arc::default();
14319 let mut suffixes_inserted = Vec::new();
14320 let ignore_indent = action.ignore_indent;
14321
14322 fn comment_prefix_range(
14323 snapshot: &MultiBufferSnapshot,
14324 row: MultiBufferRow,
14325 comment_prefix: &str,
14326 comment_prefix_whitespace: &str,
14327 ignore_indent: bool,
14328 ) -> Range<Point> {
14329 let indent_size = if ignore_indent {
14330 0
14331 } else {
14332 snapshot.indent_size_for_line(row).len
14333 };
14334
14335 let start = Point::new(row.0, indent_size);
14336
14337 let mut line_bytes = snapshot
14338 .bytes_in_range(start..snapshot.max_point())
14339 .flatten()
14340 .copied();
14341
14342 // If this line currently begins with the line comment prefix, then record
14343 // the range containing the prefix.
14344 if line_bytes
14345 .by_ref()
14346 .take(comment_prefix.len())
14347 .eq(comment_prefix.bytes())
14348 {
14349 // Include any whitespace that matches the comment prefix.
14350 let matching_whitespace_len = line_bytes
14351 .zip(comment_prefix_whitespace.bytes())
14352 .take_while(|(a, b)| a == b)
14353 .count() as u32;
14354 let end = Point::new(
14355 start.row,
14356 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14357 );
14358 start..end
14359 } else {
14360 start..start
14361 }
14362 }
14363
14364 fn comment_suffix_range(
14365 snapshot: &MultiBufferSnapshot,
14366 row: MultiBufferRow,
14367 comment_suffix: &str,
14368 comment_suffix_has_leading_space: bool,
14369 ) -> Range<Point> {
14370 let end = Point::new(row.0, snapshot.line_len(row));
14371 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14372
14373 let mut line_end_bytes = snapshot
14374 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14375 .flatten()
14376 .copied();
14377
14378 let leading_space_len = if suffix_start_column > 0
14379 && line_end_bytes.next() == Some(b' ')
14380 && comment_suffix_has_leading_space
14381 {
14382 1
14383 } else {
14384 0
14385 };
14386
14387 // If this line currently begins with the line comment prefix, then record
14388 // the range containing the prefix.
14389 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14390 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14391 start..end
14392 } else {
14393 end..end
14394 }
14395 }
14396
14397 // TODO: Handle selections that cross excerpts
14398 for selection in &mut selections {
14399 let start_column = snapshot
14400 .indent_size_for_line(MultiBufferRow(selection.start.row))
14401 .len;
14402 let language = if let Some(language) =
14403 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14404 {
14405 language
14406 } else {
14407 continue;
14408 };
14409
14410 selection_edit_ranges.clear();
14411
14412 // If multiple selections contain a given row, avoid processing that
14413 // row more than once.
14414 let mut start_row = MultiBufferRow(selection.start.row);
14415 if last_toggled_row == Some(start_row) {
14416 start_row = start_row.next_row();
14417 }
14418 let end_row =
14419 if selection.end.row > selection.start.row && selection.end.column == 0 {
14420 MultiBufferRow(selection.end.row - 1)
14421 } else {
14422 MultiBufferRow(selection.end.row)
14423 };
14424 last_toggled_row = Some(end_row);
14425
14426 if start_row > end_row {
14427 continue;
14428 }
14429
14430 // If the language has line comments, toggle those.
14431 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14432
14433 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14434 if ignore_indent {
14435 full_comment_prefixes = full_comment_prefixes
14436 .into_iter()
14437 .map(|s| Arc::from(s.trim_end()))
14438 .collect();
14439 }
14440
14441 if !full_comment_prefixes.is_empty() {
14442 let first_prefix = full_comment_prefixes
14443 .first()
14444 .expect("prefixes is non-empty");
14445 let prefix_trimmed_lengths = full_comment_prefixes
14446 .iter()
14447 .map(|p| p.trim_end_matches(' ').len())
14448 .collect::<SmallVec<[usize; 4]>>();
14449
14450 let mut all_selection_lines_are_comments = true;
14451
14452 for row in start_row.0..=end_row.0 {
14453 let row = MultiBufferRow(row);
14454 if start_row < end_row && snapshot.is_line_blank(row) {
14455 continue;
14456 }
14457
14458 let prefix_range = full_comment_prefixes
14459 .iter()
14460 .zip(prefix_trimmed_lengths.iter().copied())
14461 .map(|(prefix, trimmed_prefix_len)| {
14462 comment_prefix_range(
14463 snapshot.deref(),
14464 row,
14465 &prefix[..trimmed_prefix_len],
14466 &prefix[trimmed_prefix_len..],
14467 ignore_indent,
14468 )
14469 })
14470 .max_by_key(|range| range.end.column - range.start.column)
14471 .expect("prefixes is non-empty");
14472
14473 if prefix_range.is_empty() {
14474 all_selection_lines_are_comments = false;
14475 }
14476
14477 selection_edit_ranges.push(prefix_range);
14478 }
14479
14480 if all_selection_lines_are_comments {
14481 edits.extend(
14482 selection_edit_ranges
14483 .iter()
14484 .cloned()
14485 .map(|range| (range, empty_str.clone())),
14486 );
14487 } else {
14488 let min_column = selection_edit_ranges
14489 .iter()
14490 .map(|range| range.start.column)
14491 .min()
14492 .unwrap_or(0);
14493 edits.extend(selection_edit_ranges.iter().map(|range| {
14494 let position = Point::new(range.start.row, min_column);
14495 (position..position, first_prefix.clone())
14496 }));
14497 }
14498 } else if let Some(BlockCommentConfig {
14499 start: full_comment_prefix,
14500 end: comment_suffix,
14501 ..
14502 }) = language.block_comment()
14503 {
14504 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14505 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14506 let prefix_range = comment_prefix_range(
14507 snapshot.deref(),
14508 start_row,
14509 comment_prefix,
14510 comment_prefix_whitespace,
14511 ignore_indent,
14512 );
14513 let suffix_range = comment_suffix_range(
14514 snapshot.deref(),
14515 end_row,
14516 comment_suffix.trim_start_matches(' '),
14517 comment_suffix.starts_with(' '),
14518 );
14519
14520 if prefix_range.is_empty() || suffix_range.is_empty() {
14521 edits.push((
14522 prefix_range.start..prefix_range.start,
14523 full_comment_prefix.clone(),
14524 ));
14525 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14526 suffixes_inserted.push((end_row, comment_suffix.len()));
14527 } else {
14528 edits.push((prefix_range, empty_str.clone()));
14529 edits.push((suffix_range, empty_str.clone()));
14530 }
14531 } else {
14532 continue;
14533 }
14534 }
14535
14536 drop(snapshot);
14537 this.buffer.update(cx, |buffer, cx| {
14538 buffer.edit(edits, None, cx);
14539 });
14540
14541 // Adjust selections so that they end before any comment suffixes that
14542 // were inserted.
14543 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14544 let mut selections = this.selections.all::<Point>(cx);
14545 let snapshot = this.buffer.read(cx).read(cx);
14546 for selection in &mut selections {
14547 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14548 match row.cmp(&MultiBufferRow(selection.end.row)) {
14549 Ordering::Less => {
14550 suffixes_inserted.next();
14551 continue;
14552 }
14553 Ordering::Greater => break,
14554 Ordering::Equal => {
14555 if selection.end.column == snapshot.line_len(row) {
14556 if selection.is_empty() {
14557 selection.start.column -= suffix_len as u32;
14558 }
14559 selection.end.column -= suffix_len as u32;
14560 }
14561 break;
14562 }
14563 }
14564 }
14565 }
14566
14567 drop(snapshot);
14568 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14569
14570 let selections = this.selections.all::<Point>(cx);
14571 let selections_on_single_row = selections.windows(2).all(|selections| {
14572 selections[0].start.row == selections[1].start.row
14573 && selections[0].end.row == selections[1].end.row
14574 && selections[0].start.row == selections[0].end.row
14575 });
14576 let selections_selecting = selections
14577 .iter()
14578 .any(|selection| selection.start != selection.end);
14579 let advance_downwards = action.advance_downwards
14580 && selections_on_single_row
14581 && !selections_selecting
14582 && !matches!(this.mode, EditorMode::SingleLine);
14583
14584 if advance_downwards {
14585 let snapshot = this.buffer.read(cx).snapshot(cx);
14586
14587 this.change_selections(Default::default(), window, cx, |s| {
14588 s.move_cursors_with(|display_snapshot, display_point, _| {
14589 let mut point = display_point.to_point(display_snapshot);
14590 point.row += 1;
14591 point = snapshot.clip_point(point, Bias::Left);
14592 let display_point = point.to_display_point(display_snapshot);
14593 let goal = SelectionGoal::HorizontalPosition(
14594 display_snapshot
14595 .x_for_display_point(display_point, text_layout_details)
14596 .into(),
14597 );
14598 (display_point, goal)
14599 })
14600 });
14601 }
14602 });
14603 }
14604
14605 pub fn select_enclosing_symbol(
14606 &mut self,
14607 _: &SelectEnclosingSymbol,
14608 window: &mut Window,
14609 cx: &mut Context<Self>,
14610 ) {
14611 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14612
14613 let buffer = self.buffer.read(cx).snapshot(cx);
14614 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14615
14616 fn update_selection(
14617 selection: &Selection<usize>,
14618 buffer_snap: &MultiBufferSnapshot,
14619 ) -> Option<Selection<usize>> {
14620 let cursor = selection.head();
14621 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14622 for symbol in symbols.iter().rev() {
14623 let start = symbol.range.start.to_offset(buffer_snap);
14624 let end = symbol.range.end.to_offset(buffer_snap);
14625 let new_range = start..end;
14626 if start < selection.start || end > selection.end {
14627 return Some(Selection {
14628 id: selection.id,
14629 start: new_range.start,
14630 end: new_range.end,
14631 goal: SelectionGoal::None,
14632 reversed: selection.reversed,
14633 });
14634 }
14635 }
14636 None
14637 }
14638
14639 let mut selected_larger_symbol = false;
14640 let new_selections = old_selections
14641 .iter()
14642 .map(|selection| match update_selection(selection, &buffer) {
14643 Some(new_selection) => {
14644 if new_selection.range() != selection.range() {
14645 selected_larger_symbol = true;
14646 }
14647 new_selection
14648 }
14649 None => selection.clone(),
14650 })
14651 .collect::<Vec<_>>();
14652
14653 if selected_larger_symbol {
14654 self.change_selections(Default::default(), window, cx, |s| {
14655 s.select(new_selections);
14656 });
14657 }
14658 }
14659
14660 pub fn select_larger_syntax_node(
14661 &mut self,
14662 _: &SelectLargerSyntaxNode,
14663 window: &mut Window,
14664 cx: &mut Context<Self>,
14665 ) {
14666 let Some(visible_row_count) = self.visible_row_count() else {
14667 return;
14668 };
14669 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14670 if old_selections.is_empty() {
14671 return;
14672 }
14673
14674 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14675
14676 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14677 let buffer = self.buffer.read(cx).snapshot(cx);
14678
14679 let mut selected_larger_node = false;
14680 let mut new_selections = old_selections
14681 .iter()
14682 .map(|selection| {
14683 let old_range = selection.start..selection.end;
14684
14685 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14686 // manually select word at selection
14687 if ["string_content", "inline"].contains(&node.kind()) {
14688 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14689 // ignore if word is already selected
14690 if !word_range.is_empty() && old_range != word_range {
14691 let (last_word_range, _) =
14692 buffer.surrounding_word(old_range.end, false);
14693 // only select word if start and end point belongs to same word
14694 if word_range == last_word_range {
14695 selected_larger_node = true;
14696 return Selection {
14697 id: selection.id,
14698 start: word_range.start,
14699 end: word_range.end,
14700 goal: SelectionGoal::None,
14701 reversed: selection.reversed,
14702 };
14703 }
14704 }
14705 }
14706 }
14707
14708 let mut new_range = old_range.clone();
14709 while let Some((_node, containing_range)) =
14710 buffer.syntax_ancestor(new_range.clone())
14711 {
14712 new_range = match containing_range {
14713 MultiOrSingleBufferOffsetRange::Single(_) => break,
14714 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14715 };
14716 if !display_map.intersects_fold(new_range.start)
14717 && !display_map.intersects_fold(new_range.end)
14718 {
14719 break;
14720 }
14721 }
14722
14723 selected_larger_node |= new_range != old_range;
14724 Selection {
14725 id: selection.id,
14726 start: new_range.start,
14727 end: new_range.end,
14728 goal: SelectionGoal::None,
14729 reversed: selection.reversed,
14730 }
14731 })
14732 .collect::<Vec<_>>();
14733
14734 if !selected_larger_node {
14735 return; // don't put this call in the history
14736 }
14737
14738 // scroll based on transformation done to the last selection created by the user
14739 let (last_old, last_new) = old_selections
14740 .last()
14741 .zip(new_selections.last().cloned())
14742 .expect("old_selections isn't empty");
14743
14744 // revert selection
14745 let is_selection_reversed = {
14746 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14747 new_selections.last_mut().expect("checked above").reversed =
14748 should_newest_selection_be_reversed;
14749 should_newest_selection_be_reversed
14750 };
14751
14752 if selected_larger_node {
14753 self.select_syntax_node_history.disable_clearing = true;
14754 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14755 s.select(new_selections.clone());
14756 });
14757 self.select_syntax_node_history.disable_clearing = false;
14758 }
14759
14760 let start_row = last_new.start.to_display_point(&display_map).row().0;
14761 let end_row = last_new.end.to_display_point(&display_map).row().0;
14762 let selection_height = end_row - start_row + 1;
14763 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14764
14765 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14766 let scroll_behavior = if fits_on_the_screen {
14767 self.request_autoscroll(Autoscroll::fit(), cx);
14768 SelectSyntaxNodeScrollBehavior::FitSelection
14769 } else if is_selection_reversed {
14770 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14771 SelectSyntaxNodeScrollBehavior::CursorTop
14772 } else {
14773 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14774 SelectSyntaxNodeScrollBehavior::CursorBottom
14775 };
14776
14777 self.select_syntax_node_history.push((
14778 old_selections,
14779 scroll_behavior,
14780 is_selection_reversed,
14781 ));
14782 }
14783
14784 pub fn select_smaller_syntax_node(
14785 &mut self,
14786 _: &SelectSmallerSyntaxNode,
14787 window: &mut Window,
14788 cx: &mut Context<Self>,
14789 ) {
14790 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14791
14792 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14793 self.select_syntax_node_history.pop()
14794 {
14795 if let Some(selection) = selections.last_mut() {
14796 selection.reversed = is_selection_reversed;
14797 }
14798
14799 self.select_syntax_node_history.disable_clearing = true;
14800 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14801 s.select(selections.to_vec());
14802 });
14803 self.select_syntax_node_history.disable_clearing = false;
14804
14805 match scroll_behavior {
14806 SelectSyntaxNodeScrollBehavior::CursorTop => {
14807 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14808 }
14809 SelectSyntaxNodeScrollBehavior::FitSelection => {
14810 self.request_autoscroll(Autoscroll::fit(), cx);
14811 }
14812 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14813 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14814 }
14815 }
14816 }
14817 }
14818
14819 pub fn unwrap_syntax_node(
14820 &mut self,
14821 _: &UnwrapSyntaxNode,
14822 window: &mut Window,
14823 cx: &mut Context<Self>,
14824 ) {
14825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14826
14827 let buffer = self.buffer.read(cx).snapshot(cx);
14828 let selections = self
14829 .selections
14830 .all::<usize>(cx)
14831 .into_iter()
14832 // subtracting the offset requires sorting
14833 .sorted_by_key(|i| i.start);
14834
14835 let full_edits = selections
14836 .into_iter()
14837 .filter_map(|selection| {
14838 // Only requires two branches once if-let-chains stabilize (#53667)
14839 let child = if !selection.is_empty() {
14840 selection.range()
14841 } else if let Some((_, ancestor_range)) =
14842 buffer.syntax_ancestor(selection.start..selection.end)
14843 {
14844 match ancestor_range {
14845 MultiOrSingleBufferOffsetRange::Single(range) => range,
14846 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14847 }
14848 } else {
14849 selection.range()
14850 };
14851
14852 let mut parent = child.clone();
14853 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
14854 parent = match ancestor_range {
14855 MultiOrSingleBufferOffsetRange::Single(range) => range,
14856 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14857 };
14858 if parent.start < child.start || parent.end > child.end {
14859 break;
14860 }
14861 }
14862
14863 if parent == child {
14864 return None;
14865 }
14866 let text = buffer.text_for_range(child).collect::<String>();
14867 Some((selection.id, parent, text))
14868 })
14869 .collect::<Vec<_>>();
14870
14871 self.transact(window, cx, |this, window, cx| {
14872 this.buffer.update(cx, |buffer, cx| {
14873 buffer.edit(
14874 full_edits
14875 .iter()
14876 .map(|(_, p, t)| (p.clone(), t.clone()))
14877 .collect::<Vec<_>>(),
14878 None,
14879 cx,
14880 );
14881 });
14882 this.change_selections(Default::default(), window, cx, |s| {
14883 let mut offset = 0;
14884 let mut selections = vec![];
14885 for (id, parent, text) in full_edits {
14886 let start = parent.start - offset;
14887 offset += parent.len() - text.len();
14888 selections.push(Selection {
14889 id,
14890 start,
14891 end: start + text.len(),
14892 reversed: false,
14893 goal: Default::default(),
14894 });
14895 }
14896 s.select(selections);
14897 });
14898 });
14899 }
14900
14901 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14902 if !EditorSettings::get_global(cx).gutter.runnables {
14903 self.clear_tasks();
14904 return Task::ready(());
14905 }
14906 let project = self.project().map(Entity::downgrade);
14907 let task_sources = self.lsp_task_sources(cx);
14908 let multi_buffer = self.buffer.downgrade();
14909 cx.spawn_in(window, async move |editor, cx| {
14910 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14911 let Some(project) = project.and_then(|p| p.upgrade()) else {
14912 return;
14913 };
14914 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14915 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14916 }) else {
14917 return;
14918 };
14919
14920 let hide_runnables = project
14921 .update(cx, |project, _| project.is_via_collab())
14922 .unwrap_or(true);
14923 if hide_runnables {
14924 return;
14925 }
14926 let new_rows =
14927 cx.background_spawn({
14928 let snapshot = display_snapshot.clone();
14929 async move {
14930 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14931 }
14932 })
14933 .await;
14934 let Ok(lsp_tasks) =
14935 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14936 else {
14937 return;
14938 };
14939 let lsp_tasks = lsp_tasks.await;
14940
14941 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14942 lsp_tasks
14943 .into_iter()
14944 .flat_map(|(kind, tasks)| {
14945 tasks.into_iter().filter_map(move |(location, task)| {
14946 Some((kind.clone(), location?, task))
14947 })
14948 })
14949 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14950 let buffer = location.target.buffer;
14951 let buffer_snapshot = buffer.read(cx).snapshot();
14952 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14953 |(excerpt_id, snapshot, _)| {
14954 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14955 display_snapshot
14956 .buffer_snapshot
14957 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14958 } else {
14959 None
14960 }
14961 },
14962 );
14963 if let Some(offset) = offset {
14964 let task_buffer_range =
14965 location.target.range.to_point(&buffer_snapshot);
14966 let context_buffer_range =
14967 task_buffer_range.to_offset(&buffer_snapshot);
14968 let context_range = BufferOffset(context_buffer_range.start)
14969 ..BufferOffset(context_buffer_range.end);
14970
14971 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14972 .or_insert_with(|| RunnableTasks {
14973 templates: Vec::new(),
14974 offset,
14975 column: task_buffer_range.start.column,
14976 extra_variables: HashMap::default(),
14977 context_range,
14978 })
14979 .templates
14980 .push((kind, task.original_task().clone()));
14981 }
14982
14983 acc
14984 })
14985 }) else {
14986 return;
14987 };
14988
14989 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14990 buffer.language_settings(cx).tasks.prefer_lsp
14991 }) else {
14992 return;
14993 };
14994
14995 let rows = Self::runnable_rows(
14996 project,
14997 display_snapshot,
14998 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14999 new_rows,
15000 cx.clone(),
15001 )
15002 .await;
15003 editor
15004 .update(cx, |editor, _| {
15005 editor.clear_tasks();
15006 for (key, mut value) in rows {
15007 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15008 value.templates.extend(lsp_tasks.templates);
15009 }
15010
15011 editor.insert_tasks(key, value);
15012 }
15013 for (key, value) in lsp_tasks_by_rows {
15014 editor.insert_tasks(key, value);
15015 }
15016 })
15017 .ok();
15018 })
15019 }
15020 fn fetch_runnable_ranges(
15021 snapshot: &DisplaySnapshot,
15022 range: Range<Anchor>,
15023 ) -> Vec<language::RunnableRange> {
15024 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15025 }
15026
15027 fn runnable_rows(
15028 project: Entity<Project>,
15029 snapshot: DisplaySnapshot,
15030 prefer_lsp: bool,
15031 runnable_ranges: Vec<RunnableRange>,
15032 cx: AsyncWindowContext,
15033 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15034 cx.spawn(async move |cx| {
15035 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15036 for mut runnable in runnable_ranges {
15037 let Some(tasks) = cx
15038 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15039 .ok()
15040 else {
15041 continue;
15042 };
15043 let mut tasks = tasks.await;
15044
15045 if prefer_lsp {
15046 tasks.retain(|(task_kind, _)| {
15047 !matches!(task_kind, TaskSourceKind::Language { .. })
15048 });
15049 }
15050 if tasks.is_empty() {
15051 continue;
15052 }
15053
15054 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15055 let Some(row) = snapshot
15056 .buffer_snapshot
15057 .buffer_line_for_row(MultiBufferRow(point.row))
15058 .map(|(_, range)| range.start.row)
15059 else {
15060 continue;
15061 };
15062
15063 let context_range =
15064 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15065 runnable_rows.push((
15066 (runnable.buffer_id, row),
15067 RunnableTasks {
15068 templates: tasks,
15069 offset: snapshot
15070 .buffer_snapshot
15071 .anchor_before(runnable.run_range.start),
15072 context_range,
15073 column: point.column,
15074 extra_variables: runnable.extra_captures,
15075 },
15076 ));
15077 }
15078 runnable_rows
15079 })
15080 }
15081
15082 fn templates_with_tags(
15083 project: &Entity<Project>,
15084 runnable: &mut Runnable,
15085 cx: &mut App,
15086 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15087 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15088 let (worktree_id, file) = project
15089 .buffer_for_id(runnable.buffer, cx)
15090 .and_then(|buffer| buffer.read(cx).file())
15091 .map(|file| (file.worktree_id(cx), file.clone()))
15092 .unzip();
15093
15094 (
15095 project.task_store().read(cx).task_inventory().cloned(),
15096 worktree_id,
15097 file,
15098 )
15099 });
15100
15101 let tags = mem::take(&mut runnable.tags);
15102 let language = runnable.language.clone();
15103 cx.spawn(async move |cx| {
15104 let mut templates_with_tags = Vec::new();
15105 if let Some(inventory) = inventory {
15106 for RunnableTag(tag) in tags {
15107 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15108 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15109 }) else {
15110 return templates_with_tags;
15111 };
15112 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15113 move |(_, template)| {
15114 template.tags.iter().any(|source_tag| source_tag == &tag)
15115 },
15116 ));
15117 }
15118 }
15119 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15120
15121 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15122 // Strongest source wins; if we have worktree tag binding, prefer that to
15123 // global and language bindings;
15124 // if we have a global binding, prefer that to language binding.
15125 let first_mismatch = templates_with_tags
15126 .iter()
15127 .position(|(tag_source, _)| tag_source != leading_tag_source);
15128 if let Some(index) = first_mismatch {
15129 templates_with_tags.truncate(index);
15130 }
15131 }
15132
15133 templates_with_tags
15134 })
15135 }
15136
15137 pub fn move_to_enclosing_bracket(
15138 &mut self,
15139 _: &MoveToEnclosingBracket,
15140 window: &mut Window,
15141 cx: &mut Context<Self>,
15142 ) {
15143 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15144 self.change_selections(Default::default(), window, cx, |s| {
15145 s.move_offsets_with(|snapshot, selection| {
15146 let Some(enclosing_bracket_ranges) =
15147 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15148 else {
15149 return;
15150 };
15151
15152 let mut best_length = usize::MAX;
15153 let mut best_inside = false;
15154 let mut best_in_bracket_range = false;
15155 let mut best_destination = None;
15156 for (open, close) in enclosing_bracket_ranges {
15157 let close = close.to_inclusive();
15158 let length = close.end() - open.start;
15159 let inside = selection.start >= open.end && selection.end <= *close.start();
15160 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15161 || close.contains(&selection.head());
15162
15163 // If best is next to a bracket and current isn't, skip
15164 if !in_bracket_range && best_in_bracket_range {
15165 continue;
15166 }
15167
15168 // Prefer smaller lengths unless best is inside and current isn't
15169 if length > best_length && (best_inside || !inside) {
15170 continue;
15171 }
15172
15173 best_length = length;
15174 best_inside = inside;
15175 best_in_bracket_range = in_bracket_range;
15176 best_destination = Some(
15177 if close.contains(&selection.start) && close.contains(&selection.end) {
15178 if inside { open.end } else { open.start }
15179 } else if inside {
15180 *close.start()
15181 } else {
15182 *close.end()
15183 },
15184 );
15185 }
15186
15187 if let Some(destination) = best_destination {
15188 selection.collapse_to(destination, SelectionGoal::None);
15189 }
15190 })
15191 });
15192 }
15193
15194 pub fn undo_selection(
15195 &mut self,
15196 _: &UndoSelection,
15197 window: &mut Window,
15198 cx: &mut Context<Self>,
15199 ) {
15200 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15201 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15202 self.selection_history.mode = SelectionHistoryMode::Undoing;
15203 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15204 this.end_selection(window, cx);
15205 this.change_selections(
15206 SelectionEffects::scroll(Autoscroll::newest()),
15207 window,
15208 cx,
15209 |s| s.select_anchors(entry.selections.to_vec()),
15210 );
15211 });
15212 self.selection_history.mode = SelectionHistoryMode::Normal;
15213
15214 self.select_next_state = entry.select_next_state;
15215 self.select_prev_state = entry.select_prev_state;
15216 self.add_selections_state = entry.add_selections_state;
15217 }
15218 }
15219
15220 pub fn redo_selection(
15221 &mut self,
15222 _: &RedoSelection,
15223 window: &mut Window,
15224 cx: &mut Context<Self>,
15225 ) {
15226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15227 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15228 self.selection_history.mode = SelectionHistoryMode::Redoing;
15229 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15230 this.end_selection(window, cx);
15231 this.change_selections(
15232 SelectionEffects::scroll(Autoscroll::newest()),
15233 window,
15234 cx,
15235 |s| s.select_anchors(entry.selections.to_vec()),
15236 );
15237 });
15238 self.selection_history.mode = SelectionHistoryMode::Normal;
15239
15240 self.select_next_state = entry.select_next_state;
15241 self.select_prev_state = entry.select_prev_state;
15242 self.add_selections_state = entry.add_selections_state;
15243 }
15244 }
15245
15246 pub fn expand_excerpts(
15247 &mut self,
15248 action: &ExpandExcerpts,
15249 _: &mut Window,
15250 cx: &mut Context<Self>,
15251 ) {
15252 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15253 }
15254
15255 pub fn expand_excerpts_down(
15256 &mut self,
15257 action: &ExpandExcerptsDown,
15258 _: &mut Window,
15259 cx: &mut Context<Self>,
15260 ) {
15261 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15262 }
15263
15264 pub fn expand_excerpts_up(
15265 &mut self,
15266 action: &ExpandExcerptsUp,
15267 _: &mut Window,
15268 cx: &mut Context<Self>,
15269 ) {
15270 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15271 }
15272
15273 pub fn expand_excerpts_for_direction(
15274 &mut self,
15275 lines: u32,
15276 direction: ExpandExcerptDirection,
15277
15278 cx: &mut Context<Self>,
15279 ) {
15280 let selections = self.selections.disjoint_anchors();
15281
15282 let lines = if lines == 0 {
15283 EditorSettings::get_global(cx).expand_excerpt_lines
15284 } else {
15285 lines
15286 };
15287
15288 self.buffer.update(cx, |buffer, cx| {
15289 let snapshot = buffer.snapshot(cx);
15290 let mut excerpt_ids = selections
15291 .iter()
15292 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15293 .collect::<Vec<_>>();
15294 excerpt_ids.sort();
15295 excerpt_ids.dedup();
15296 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15297 })
15298 }
15299
15300 pub fn expand_excerpt(
15301 &mut self,
15302 excerpt: ExcerptId,
15303 direction: ExpandExcerptDirection,
15304 window: &mut Window,
15305 cx: &mut Context<Self>,
15306 ) {
15307 let current_scroll_position = self.scroll_position(cx);
15308 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15309 let mut should_scroll_up = false;
15310
15311 if direction == ExpandExcerptDirection::Down {
15312 let multi_buffer = self.buffer.read(cx);
15313 let snapshot = multi_buffer.snapshot(cx);
15314 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15315 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15316 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15317 {
15318 let buffer_snapshot = buffer.read(cx).snapshot();
15319 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15320 let last_row = buffer_snapshot.max_point().row;
15321 let lines_below = last_row.saturating_sub(excerpt_end_row);
15322 should_scroll_up = lines_below >= lines_to_expand;
15323 }
15324 }
15325
15326 self.buffer.update(cx, |buffer, cx| {
15327 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15328 });
15329
15330 if should_scroll_up {
15331 let new_scroll_position =
15332 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15333 self.set_scroll_position(new_scroll_position, window, cx);
15334 }
15335 }
15336
15337 pub fn go_to_singleton_buffer_point(
15338 &mut self,
15339 point: Point,
15340 window: &mut Window,
15341 cx: &mut Context<Self>,
15342 ) {
15343 self.go_to_singleton_buffer_range(point..point, window, cx);
15344 }
15345
15346 pub fn go_to_singleton_buffer_range(
15347 &mut self,
15348 range: Range<Point>,
15349 window: &mut Window,
15350 cx: &mut Context<Self>,
15351 ) {
15352 let multibuffer = self.buffer().read(cx);
15353 let Some(buffer) = multibuffer.as_singleton() else {
15354 return;
15355 };
15356 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15357 return;
15358 };
15359 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15360 return;
15361 };
15362 self.change_selections(
15363 SelectionEffects::default().nav_history(true),
15364 window,
15365 cx,
15366 |s| s.select_anchor_ranges([start..end]),
15367 );
15368 }
15369
15370 pub fn go_to_diagnostic(
15371 &mut self,
15372 action: &GoToDiagnostic,
15373 window: &mut Window,
15374 cx: &mut Context<Self>,
15375 ) {
15376 if !self.diagnostics_enabled() {
15377 return;
15378 }
15379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15380 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15381 }
15382
15383 pub fn go_to_prev_diagnostic(
15384 &mut self,
15385 action: &GoToPreviousDiagnostic,
15386 window: &mut Window,
15387 cx: &mut Context<Self>,
15388 ) {
15389 if !self.diagnostics_enabled() {
15390 return;
15391 }
15392 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15393 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15394 }
15395
15396 pub fn go_to_diagnostic_impl(
15397 &mut self,
15398 direction: Direction,
15399 severity: GoToDiagnosticSeverityFilter,
15400 window: &mut Window,
15401 cx: &mut Context<Self>,
15402 ) {
15403 let buffer = self.buffer.read(cx).snapshot(cx);
15404 let selection = self.selections.newest::<usize>(cx);
15405
15406 let mut active_group_id = None;
15407 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15408 && active_group.active_range.start.to_offset(&buffer) == selection.start
15409 {
15410 active_group_id = Some(active_group.group_id);
15411 }
15412
15413 fn filtered(
15414 snapshot: EditorSnapshot,
15415 severity: GoToDiagnosticSeverityFilter,
15416 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15417 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15418 diagnostics
15419 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15420 .filter(|entry| entry.range.start != entry.range.end)
15421 .filter(|entry| !entry.diagnostic.is_unnecessary)
15422 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15423 }
15424
15425 let snapshot = self.snapshot(window, cx);
15426 let before = filtered(
15427 snapshot.clone(),
15428 severity,
15429 buffer
15430 .diagnostics_in_range(0..selection.start)
15431 .filter(|entry| entry.range.start <= selection.start),
15432 );
15433 let after = filtered(
15434 snapshot,
15435 severity,
15436 buffer
15437 .diagnostics_in_range(selection.start..buffer.len())
15438 .filter(|entry| entry.range.start >= selection.start),
15439 );
15440
15441 let mut found: Option<DiagnosticEntry<usize>> = None;
15442 if direction == Direction::Prev {
15443 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15444 {
15445 for diagnostic in prev_diagnostics.into_iter().rev() {
15446 if diagnostic.range.start != selection.start
15447 || active_group_id
15448 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15449 {
15450 found = Some(diagnostic);
15451 break 'outer;
15452 }
15453 }
15454 }
15455 } else {
15456 for diagnostic in after.chain(before) {
15457 if diagnostic.range.start != selection.start
15458 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15459 {
15460 found = Some(diagnostic);
15461 break;
15462 }
15463 }
15464 }
15465 let Some(next_diagnostic) = found else {
15466 return;
15467 };
15468
15469 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15470 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15471 return;
15472 };
15473 self.change_selections(Default::default(), window, cx, |s| {
15474 s.select_ranges(vec![
15475 next_diagnostic.range.start..next_diagnostic.range.start,
15476 ])
15477 });
15478 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15479 self.refresh_edit_prediction(false, true, window, cx);
15480 }
15481
15482 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15483 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15484 let snapshot = self.snapshot(window, cx);
15485 let selection = self.selections.newest::<Point>(cx);
15486 self.go_to_hunk_before_or_after_position(
15487 &snapshot,
15488 selection.head(),
15489 Direction::Next,
15490 window,
15491 cx,
15492 );
15493 }
15494
15495 pub fn go_to_hunk_before_or_after_position(
15496 &mut self,
15497 snapshot: &EditorSnapshot,
15498 position: Point,
15499 direction: Direction,
15500 window: &mut Window,
15501 cx: &mut Context<Editor>,
15502 ) {
15503 let row = if direction == Direction::Next {
15504 self.hunk_after_position(snapshot, position)
15505 .map(|hunk| hunk.row_range.start)
15506 } else {
15507 self.hunk_before_position(snapshot, position)
15508 };
15509
15510 if let Some(row) = row {
15511 let destination = Point::new(row.0, 0);
15512 let autoscroll = Autoscroll::center();
15513
15514 self.unfold_ranges(&[destination..destination], false, false, cx);
15515 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15516 s.select_ranges([destination..destination]);
15517 });
15518 }
15519 }
15520
15521 fn hunk_after_position(
15522 &mut self,
15523 snapshot: &EditorSnapshot,
15524 position: Point,
15525 ) -> Option<MultiBufferDiffHunk> {
15526 snapshot
15527 .buffer_snapshot
15528 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15529 .find(|hunk| hunk.row_range.start.0 > position.row)
15530 .or_else(|| {
15531 snapshot
15532 .buffer_snapshot
15533 .diff_hunks_in_range(Point::zero()..position)
15534 .find(|hunk| hunk.row_range.end.0 < position.row)
15535 })
15536 }
15537
15538 fn go_to_prev_hunk(
15539 &mut self,
15540 _: &GoToPreviousHunk,
15541 window: &mut Window,
15542 cx: &mut Context<Self>,
15543 ) {
15544 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15545 let snapshot = self.snapshot(window, cx);
15546 let selection = self.selections.newest::<Point>(cx);
15547 self.go_to_hunk_before_or_after_position(
15548 &snapshot,
15549 selection.head(),
15550 Direction::Prev,
15551 window,
15552 cx,
15553 );
15554 }
15555
15556 fn hunk_before_position(
15557 &mut self,
15558 snapshot: &EditorSnapshot,
15559 position: Point,
15560 ) -> Option<MultiBufferRow> {
15561 snapshot
15562 .buffer_snapshot
15563 .diff_hunk_before(position)
15564 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15565 }
15566
15567 fn go_to_next_change(
15568 &mut self,
15569 _: &GoToNextChange,
15570 window: &mut Window,
15571 cx: &mut Context<Self>,
15572 ) {
15573 if let Some(selections) = self
15574 .change_list
15575 .next_change(1, Direction::Next)
15576 .map(|s| s.to_vec())
15577 {
15578 self.change_selections(Default::default(), window, cx, |s| {
15579 let map = s.display_map();
15580 s.select_display_ranges(selections.iter().map(|a| {
15581 let point = a.to_display_point(&map);
15582 point..point
15583 }))
15584 })
15585 }
15586 }
15587
15588 fn go_to_previous_change(
15589 &mut self,
15590 _: &GoToPreviousChange,
15591 window: &mut Window,
15592 cx: &mut Context<Self>,
15593 ) {
15594 if let Some(selections) = self
15595 .change_list
15596 .next_change(1, Direction::Prev)
15597 .map(|s| s.to_vec())
15598 {
15599 self.change_selections(Default::default(), window, cx, |s| {
15600 let map = s.display_map();
15601 s.select_display_ranges(selections.iter().map(|a| {
15602 let point = a.to_display_point(&map);
15603 point..point
15604 }))
15605 })
15606 }
15607 }
15608
15609 fn go_to_line<T: 'static>(
15610 &mut self,
15611 position: Anchor,
15612 highlight_color: Option<Hsla>,
15613 window: &mut Window,
15614 cx: &mut Context<Self>,
15615 ) {
15616 let snapshot = self.snapshot(window, cx).display_snapshot;
15617 let position = position.to_point(&snapshot.buffer_snapshot);
15618 let start = snapshot
15619 .buffer_snapshot
15620 .clip_point(Point::new(position.row, 0), Bias::Left);
15621 let end = start + Point::new(1, 0);
15622 let start = snapshot.buffer_snapshot.anchor_before(start);
15623 let end = snapshot.buffer_snapshot.anchor_before(end);
15624
15625 self.highlight_rows::<T>(
15626 start..end,
15627 highlight_color
15628 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15629 Default::default(),
15630 cx,
15631 );
15632
15633 if self.buffer.read(cx).is_singleton() {
15634 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15635 }
15636 }
15637
15638 pub fn go_to_definition(
15639 &mut self,
15640 _: &GoToDefinition,
15641 window: &mut Window,
15642 cx: &mut Context<Self>,
15643 ) -> Task<Result<Navigated>> {
15644 let definition =
15645 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15646 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15647 cx.spawn_in(window, async move |editor, cx| {
15648 if definition.await? == Navigated::Yes {
15649 return Ok(Navigated::Yes);
15650 }
15651 match fallback_strategy {
15652 GoToDefinitionFallback::None => Ok(Navigated::No),
15653 GoToDefinitionFallback::FindAllReferences => {
15654 match editor.update_in(cx, |editor, window, cx| {
15655 editor.find_all_references(&FindAllReferences, window, cx)
15656 })? {
15657 Some(references) => references.await,
15658 None => Ok(Navigated::No),
15659 }
15660 }
15661 }
15662 })
15663 }
15664
15665 pub fn go_to_declaration(
15666 &mut self,
15667 _: &GoToDeclaration,
15668 window: &mut Window,
15669 cx: &mut Context<Self>,
15670 ) -> Task<Result<Navigated>> {
15671 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15672 }
15673
15674 pub fn go_to_declaration_split(
15675 &mut self,
15676 _: &GoToDeclaration,
15677 window: &mut Window,
15678 cx: &mut Context<Self>,
15679 ) -> Task<Result<Navigated>> {
15680 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15681 }
15682
15683 pub fn go_to_implementation(
15684 &mut self,
15685 _: &GoToImplementation,
15686 window: &mut Window,
15687 cx: &mut Context<Self>,
15688 ) -> Task<Result<Navigated>> {
15689 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15690 }
15691
15692 pub fn go_to_implementation_split(
15693 &mut self,
15694 _: &GoToImplementationSplit,
15695 window: &mut Window,
15696 cx: &mut Context<Self>,
15697 ) -> Task<Result<Navigated>> {
15698 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15699 }
15700
15701 pub fn go_to_type_definition(
15702 &mut self,
15703 _: &GoToTypeDefinition,
15704 window: &mut Window,
15705 cx: &mut Context<Self>,
15706 ) -> Task<Result<Navigated>> {
15707 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15708 }
15709
15710 pub fn go_to_definition_split(
15711 &mut self,
15712 _: &GoToDefinitionSplit,
15713 window: &mut Window,
15714 cx: &mut Context<Self>,
15715 ) -> Task<Result<Navigated>> {
15716 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15717 }
15718
15719 pub fn go_to_type_definition_split(
15720 &mut self,
15721 _: &GoToTypeDefinitionSplit,
15722 window: &mut Window,
15723 cx: &mut Context<Self>,
15724 ) -> Task<Result<Navigated>> {
15725 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15726 }
15727
15728 fn go_to_definition_of_kind(
15729 &mut self,
15730 kind: GotoDefinitionKind,
15731 split: bool,
15732 window: &mut Window,
15733 cx: &mut Context<Self>,
15734 ) -> Task<Result<Navigated>> {
15735 let Some(provider) = self.semantics_provider.clone() else {
15736 return Task::ready(Ok(Navigated::No));
15737 };
15738 let head = self.selections.newest::<usize>(cx).head();
15739 let buffer = self.buffer.read(cx);
15740 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15741 return Task::ready(Ok(Navigated::No));
15742 };
15743 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15744 return Task::ready(Ok(Navigated::No));
15745 };
15746
15747 cx.spawn_in(window, async move |editor, cx| {
15748 let Some(definitions) = definitions.await? else {
15749 return Ok(Navigated::No);
15750 };
15751 let navigated = editor
15752 .update_in(cx, |editor, window, cx| {
15753 editor.navigate_to_hover_links(
15754 Some(kind),
15755 definitions
15756 .into_iter()
15757 .filter(|location| {
15758 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15759 })
15760 .map(HoverLink::Text)
15761 .collect::<Vec<_>>(),
15762 split,
15763 window,
15764 cx,
15765 )
15766 })?
15767 .await?;
15768 anyhow::Ok(navigated)
15769 })
15770 }
15771
15772 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15773 let selection = self.selections.newest_anchor();
15774 let head = selection.head();
15775 let tail = selection.tail();
15776
15777 let Some((buffer, start_position)) =
15778 self.buffer.read(cx).text_anchor_for_position(head, cx)
15779 else {
15780 return;
15781 };
15782
15783 let end_position = if head != tail {
15784 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15785 return;
15786 };
15787 Some(pos)
15788 } else {
15789 None
15790 };
15791
15792 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15793 let url = if let Some(end_pos) = end_position {
15794 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15795 } else {
15796 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15797 };
15798
15799 if let Some(url) = url {
15800 editor.update(cx, |_, cx| {
15801 cx.open_url(&url);
15802 })
15803 } else {
15804 Ok(())
15805 }
15806 });
15807
15808 url_finder.detach();
15809 }
15810
15811 pub fn open_selected_filename(
15812 &mut self,
15813 _: &OpenSelectedFilename,
15814 window: &mut Window,
15815 cx: &mut Context<Self>,
15816 ) {
15817 let Some(workspace) = self.workspace() else {
15818 return;
15819 };
15820
15821 let position = self.selections.newest_anchor().head();
15822
15823 let Some((buffer, buffer_position)) =
15824 self.buffer.read(cx).text_anchor_for_position(position, cx)
15825 else {
15826 return;
15827 };
15828
15829 let project = self.project.clone();
15830
15831 cx.spawn_in(window, async move |_, cx| {
15832 let result = find_file(&buffer, project, buffer_position, cx).await;
15833
15834 if let Some((_, path)) = result {
15835 workspace
15836 .update_in(cx, |workspace, window, cx| {
15837 workspace.open_resolved_path(path, window, cx)
15838 })?
15839 .await?;
15840 }
15841 anyhow::Ok(())
15842 })
15843 .detach();
15844 }
15845
15846 pub(crate) fn navigate_to_hover_links(
15847 &mut self,
15848 kind: Option<GotoDefinitionKind>,
15849 definitions: Vec<HoverLink>,
15850 split: bool,
15851 window: &mut Window,
15852 cx: &mut Context<Editor>,
15853 ) -> Task<Result<Navigated>> {
15854 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15855 let mut first_url_or_file = None;
15856 let definitions: Vec<_> = definitions
15857 .into_iter()
15858 .filter_map(|def| match def {
15859 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15860 HoverLink::InlayHint(lsp_location, server_id) => {
15861 let computation =
15862 self.compute_target_location(lsp_location, server_id, window, cx);
15863 Some(cx.background_spawn(computation))
15864 }
15865 HoverLink::Url(url) => {
15866 first_url_or_file = Some(Either::Left(url));
15867 None
15868 }
15869 HoverLink::File(path) => {
15870 first_url_or_file = Some(Either::Right(path));
15871 None
15872 }
15873 })
15874 .collect();
15875
15876 let workspace = self.workspace();
15877
15878 cx.spawn_in(window, async move |editor, acx| {
15879 let mut locations: Vec<Location> = future::join_all(definitions)
15880 .await
15881 .into_iter()
15882 .filter_map(|location| location.transpose())
15883 .collect::<Result<_>>()
15884 .context("location tasks")?;
15885
15886 if locations.len() > 1 {
15887 let Some(workspace) = workspace else {
15888 return Ok(Navigated::No);
15889 };
15890
15891 let tab_kind = match kind {
15892 Some(GotoDefinitionKind::Implementation) => "Implementations",
15893 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15894 Some(GotoDefinitionKind::Declaration) => "Declarations",
15895 Some(GotoDefinitionKind::Type) => "Types",
15896 };
15897 let title = editor
15898 .update_in(acx, |_, _, cx| {
15899 let target = locations
15900 .iter()
15901 .map(|location| {
15902 location
15903 .buffer
15904 .read(cx)
15905 .text_for_range(location.range.clone())
15906 .collect::<String>()
15907 })
15908 .filter(|text| !text.contains('\n'))
15909 .unique()
15910 .take(3)
15911 .join(", ");
15912 if target.is_empty() {
15913 tab_kind.to_owned()
15914 } else {
15915 format!("{tab_kind} for {target}")
15916 }
15917 })
15918 .context("buffer title")?;
15919
15920 let opened = workspace
15921 .update_in(acx, |workspace, window, cx| {
15922 Self::open_locations_in_multibuffer(
15923 workspace,
15924 locations,
15925 title,
15926 split,
15927 MultibufferSelectionMode::First,
15928 window,
15929 cx,
15930 )
15931 })
15932 .is_ok();
15933
15934 anyhow::Ok(Navigated::from_bool(opened))
15935 } else if locations.is_empty() {
15936 // If there is one definition, just open it directly
15937 match first_url_or_file {
15938 Some(Either::Left(url)) => {
15939 acx.update(|_, cx| cx.open_url(&url))?;
15940 Ok(Navigated::Yes)
15941 }
15942 Some(Either::Right(path)) => {
15943 let Some(workspace) = workspace else {
15944 return Ok(Navigated::No);
15945 };
15946
15947 workspace
15948 .update_in(acx, |workspace, window, cx| {
15949 workspace.open_resolved_path(path, window, cx)
15950 })?
15951 .await?;
15952 Ok(Navigated::Yes)
15953 }
15954 None => Ok(Navigated::No),
15955 }
15956 } else {
15957 let Some(workspace) = workspace else {
15958 return Ok(Navigated::No);
15959 };
15960
15961 let target = locations.pop().unwrap();
15962 editor.update_in(acx, |editor, window, cx| {
15963 let pane = workspace.read(cx).active_pane().clone();
15964
15965 let range = target.range.to_point(target.buffer.read(cx));
15966 let range = editor.range_for_match(&range);
15967 let range = collapse_multiline_range(range);
15968
15969 if !split
15970 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15971 {
15972 editor.go_to_singleton_buffer_range(range, window, cx);
15973 } else {
15974 window.defer(cx, move |window, cx| {
15975 let target_editor: Entity<Self> =
15976 workspace.update(cx, |workspace, cx| {
15977 let pane = if split {
15978 workspace.adjacent_pane(window, cx)
15979 } else {
15980 workspace.active_pane().clone()
15981 };
15982
15983 workspace.open_project_item(
15984 pane,
15985 target.buffer.clone(),
15986 true,
15987 true,
15988 window,
15989 cx,
15990 )
15991 });
15992 target_editor.update(cx, |target_editor, cx| {
15993 // When selecting a definition in a different buffer, disable the nav history
15994 // to avoid creating a history entry at the previous cursor location.
15995 pane.update(cx, |pane, _| pane.disable_history());
15996 target_editor.go_to_singleton_buffer_range(range, window, cx);
15997 pane.update(cx, |pane, _| pane.enable_history());
15998 });
15999 });
16000 }
16001 Navigated::Yes
16002 })
16003 }
16004 })
16005 }
16006
16007 fn compute_target_location(
16008 &self,
16009 lsp_location: lsp::Location,
16010 server_id: LanguageServerId,
16011 window: &mut Window,
16012 cx: &mut Context<Self>,
16013 ) -> Task<anyhow::Result<Option<Location>>> {
16014 let Some(project) = self.project.clone() else {
16015 return Task::ready(Ok(None));
16016 };
16017
16018 cx.spawn_in(window, async move |editor, cx| {
16019 let location_task = editor.update(cx, |_, cx| {
16020 project.update(cx, |project, cx| {
16021 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16022 })
16023 })?;
16024 let location = Some({
16025 let target_buffer_handle = location_task.await.context("open local buffer")?;
16026 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16027 let target_start = target_buffer
16028 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16029 let target_end = target_buffer
16030 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16031 target_buffer.anchor_after(target_start)
16032 ..target_buffer.anchor_before(target_end)
16033 })?;
16034 Location {
16035 buffer: target_buffer_handle,
16036 range,
16037 }
16038 });
16039 Ok(location)
16040 })
16041 }
16042
16043 pub fn find_all_references(
16044 &mut self,
16045 _: &FindAllReferences,
16046 window: &mut Window,
16047 cx: &mut Context<Self>,
16048 ) -> Option<Task<Result<Navigated>>> {
16049 let selection = self.selections.newest::<usize>(cx);
16050 let multi_buffer = self.buffer.read(cx);
16051 let head = selection.head();
16052
16053 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16054 let head_anchor = multi_buffer_snapshot.anchor_at(
16055 head,
16056 if head < selection.tail() {
16057 Bias::Right
16058 } else {
16059 Bias::Left
16060 },
16061 );
16062
16063 match self
16064 .find_all_references_task_sources
16065 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16066 {
16067 Ok(_) => {
16068 log::info!(
16069 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16070 );
16071 return None;
16072 }
16073 Err(i) => {
16074 self.find_all_references_task_sources.insert(i, head_anchor);
16075 }
16076 }
16077
16078 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16079 let workspace = self.workspace()?;
16080 let project = workspace.read(cx).project().clone();
16081 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16082 Some(cx.spawn_in(window, async move |editor, cx| {
16083 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16084 if let Ok(i) = editor
16085 .find_all_references_task_sources
16086 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16087 {
16088 editor.find_all_references_task_sources.remove(i);
16089 }
16090 });
16091
16092 let Some(locations) = references.await? else {
16093 return anyhow::Ok(Navigated::No);
16094 };
16095 if locations.is_empty() {
16096 return anyhow::Ok(Navigated::No);
16097 }
16098
16099 workspace.update_in(cx, |workspace, window, cx| {
16100 let target = locations
16101 .iter()
16102 .map(|location| {
16103 location
16104 .buffer
16105 .read(cx)
16106 .text_for_range(location.range.clone())
16107 .collect::<String>()
16108 })
16109 .filter(|text| !text.contains('\n'))
16110 .unique()
16111 .take(3)
16112 .join(", ");
16113 let title = if target.is_empty() {
16114 "References".to_owned()
16115 } else {
16116 format!("References to {target}")
16117 };
16118 Self::open_locations_in_multibuffer(
16119 workspace,
16120 locations,
16121 title,
16122 false,
16123 MultibufferSelectionMode::First,
16124 window,
16125 cx,
16126 );
16127 Navigated::Yes
16128 })
16129 }))
16130 }
16131
16132 /// Opens a multibuffer with the given project locations in it
16133 pub fn open_locations_in_multibuffer(
16134 workspace: &mut Workspace,
16135 mut locations: Vec<Location>,
16136 title: String,
16137 split: bool,
16138 multibuffer_selection_mode: MultibufferSelectionMode,
16139 window: &mut Window,
16140 cx: &mut Context<Workspace>,
16141 ) {
16142 if locations.is_empty() {
16143 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16144 return;
16145 }
16146
16147 // If there are multiple definitions, open them in a multibuffer
16148 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16149 let mut locations = locations.into_iter().peekable();
16150 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16151 let capability = workspace.project().read(cx).capability();
16152
16153 let excerpt_buffer = cx.new(|cx| {
16154 let mut multibuffer = MultiBuffer::new(capability);
16155 while let Some(location) = locations.next() {
16156 let buffer = location.buffer.read(cx);
16157 let mut ranges_for_buffer = Vec::new();
16158 let range = location.range.to_point(buffer);
16159 ranges_for_buffer.push(range.clone());
16160
16161 while let Some(next_location) = locations.peek() {
16162 if next_location.buffer == location.buffer {
16163 ranges_for_buffer.push(next_location.range.to_point(buffer));
16164 locations.next();
16165 } else {
16166 break;
16167 }
16168 }
16169
16170 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16171 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16172 PathKey::for_buffer(&location.buffer, cx),
16173 location.buffer.clone(),
16174 ranges_for_buffer,
16175 DEFAULT_MULTIBUFFER_CONTEXT,
16176 cx,
16177 );
16178 ranges.extend(new_ranges)
16179 }
16180
16181 multibuffer.with_title(title)
16182 });
16183
16184 let editor = cx.new(|cx| {
16185 Editor::for_multibuffer(
16186 excerpt_buffer,
16187 Some(workspace.project().clone()),
16188 window,
16189 cx,
16190 )
16191 });
16192 editor.update(cx, |editor, cx| {
16193 match multibuffer_selection_mode {
16194 MultibufferSelectionMode::First => {
16195 if let Some(first_range) = ranges.first() {
16196 editor.change_selections(
16197 SelectionEffects::no_scroll(),
16198 window,
16199 cx,
16200 |selections| {
16201 selections.clear_disjoint();
16202 selections
16203 .select_anchor_ranges(std::iter::once(first_range.clone()));
16204 },
16205 );
16206 }
16207 editor.highlight_background::<Self>(
16208 &ranges,
16209 |theme| theme.colors().editor_highlighted_line_background,
16210 cx,
16211 );
16212 }
16213 MultibufferSelectionMode::All => {
16214 editor.change_selections(
16215 SelectionEffects::no_scroll(),
16216 window,
16217 cx,
16218 |selections| {
16219 selections.clear_disjoint();
16220 selections.select_anchor_ranges(ranges);
16221 },
16222 );
16223 }
16224 }
16225 editor.register_buffers_with_language_servers(cx);
16226 });
16227
16228 let item = Box::new(editor);
16229 let item_id = item.item_id();
16230
16231 if split {
16232 workspace.split_item(SplitDirection::Right, item, window, cx);
16233 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16234 let (preview_item_id, preview_item_idx) =
16235 workspace.active_pane().read_with(cx, |pane, _| {
16236 (pane.preview_item_id(), pane.preview_item_idx())
16237 });
16238
16239 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16240
16241 if let Some(preview_item_id) = preview_item_id {
16242 workspace.active_pane().update(cx, |pane, cx| {
16243 pane.remove_item(preview_item_id, false, false, window, cx);
16244 });
16245 }
16246 } else {
16247 workspace.add_item_to_active_pane(item, None, true, window, cx);
16248 }
16249 workspace.active_pane().update(cx, |pane, cx| {
16250 pane.set_preview_item_id(Some(item_id), cx);
16251 });
16252 }
16253
16254 pub fn rename(
16255 &mut self,
16256 _: &Rename,
16257 window: &mut Window,
16258 cx: &mut Context<Self>,
16259 ) -> Option<Task<Result<()>>> {
16260 use language::ToOffset as _;
16261
16262 let provider = self.semantics_provider.clone()?;
16263 let selection = self.selections.newest_anchor().clone();
16264 let (cursor_buffer, cursor_buffer_position) = self
16265 .buffer
16266 .read(cx)
16267 .text_anchor_for_position(selection.head(), cx)?;
16268 let (tail_buffer, cursor_buffer_position_end) = self
16269 .buffer
16270 .read(cx)
16271 .text_anchor_for_position(selection.tail(), cx)?;
16272 if tail_buffer != cursor_buffer {
16273 return None;
16274 }
16275
16276 let snapshot = cursor_buffer.read(cx).snapshot();
16277 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16278 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16279 let prepare_rename = provider
16280 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16281 .unwrap_or_else(|| Task::ready(Ok(None)));
16282 drop(snapshot);
16283
16284 Some(cx.spawn_in(window, async move |this, cx| {
16285 let rename_range = if let Some(range) = prepare_rename.await? {
16286 Some(range)
16287 } else {
16288 this.update(cx, |this, cx| {
16289 let buffer = this.buffer.read(cx).snapshot(cx);
16290 let mut buffer_highlights = this
16291 .document_highlights_for_position(selection.head(), &buffer)
16292 .filter(|highlight| {
16293 highlight.start.excerpt_id == selection.head().excerpt_id
16294 && highlight.end.excerpt_id == selection.head().excerpt_id
16295 });
16296 buffer_highlights
16297 .next()
16298 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16299 })?
16300 };
16301 if let Some(rename_range) = rename_range {
16302 this.update_in(cx, |this, window, cx| {
16303 let snapshot = cursor_buffer.read(cx).snapshot();
16304 let rename_buffer_range = rename_range.to_offset(&snapshot);
16305 let cursor_offset_in_rename_range =
16306 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16307 let cursor_offset_in_rename_range_end =
16308 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16309
16310 this.take_rename(false, window, cx);
16311 let buffer = this.buffer.read(cx).read(cx);
16312 let cursor_offset = selection.head().to_offset(&buffer);
16313 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16314 let rename_end = rename_start + rename_buffer_range.len();
16315 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16316 let mut old_highlight_id = None;
16317 let old_name: Arc<str> = buffer
16318 .chunks(rename_start..rename_end, true)
16319 .map(|chunk| {
16320 if old_highlight_id.is_none() {
16321 old_highlight_id = chunk.syntax_highlight_id;
16322 }
16323 chunk.text
16324 })
16325 .collect::<String>()
16326 .into();
16327
16328 drop(buffer);
16329
16330 // Position the selection in the rename editor so that it matches the current selection.
16331 this.show_local_selections = false;
16332 let rename_editor = cx.new(|cx| {
16333 let mut editor = Editor::single_line(window, cx);
16334 editor.buffer.update(cx, |buffer, cx| {
16335 buffer.edit([(0..0, old_name.clone())], None, cx)
16336 });
16337 let rename_selection_range = match cursor_offset_in_rename_range
16338 .cmp(&cursor_offset_in_rename_range_end)
16339 {
16340 Ordering::Equal => {
16341 editor.select_all(&SelectAll, window, cx);
16342 return editor;
16343 }
16344 Ordering::Less => {
16345 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16346 }
16347 Ordering::Greater => {
16348 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16349 }
16350 };
16351 if rename_selection_range.end > old_name.len() {
16352 editor.select_all(&SelectAll, window, cx);
16353 } else {
16354 editor.change_selections(Default::default(), window, cx, |s| {
16355 s.select_ranges([rename_selection_range]);
16356 });
16357 }
16358 editor
16359 });
16360 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16361 if e == &EditorEvent::Focused {
16362 cx.emit(EditorEvent::FocusedIn)
16363 }
16364 })
16365 .detach();
16366
16367 let write_highlights =
16368 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16369 let read_highlights =
16370 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16371 let ranges = write_highlights
16372 .iter()
16373 .flat_map(|(_, ranges)| ranges.iter())
16374 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16375 .cloned()
16376 .collect();
16377
16378 this.highlight_text::<Rename>(
16379 ranges,
16380 HighlightStyle {
16381 fade_out: Some(0.6),
16382 ..Default::default()
16383 },
16384 cx,
16385 );
16386 let rename_focus_handle = rename_editor.focus_handle(cx);
16387 window.focus(&rename_focus_handle);
16388 let block_id = this.insert_blocks(
16389 [BlockProperties {
16390 style: BlockStyle::Flex,
16391 placement: BlockPlacement::Below(range.start),
16392 height: Some(1),
16393 render: Arc::new({
16394 let rename_editor = rename_editor.clone();
16395 move |cx: &mut BlockContext| {
16396 let mut text_style = cx.editor_style.text.clone();
16397 if let Some(highlight_style) = old_highlight_id
16398 .and_then(|h| h.style(&cx.editor_style.syntax))
16399 {
16400 text_style = text_style.highlight(highlight_style);
16401 }
16402 div()
16403 .block_mouse_except_scroll()
16404 .pl(cx.anchor_x)
16405 .child(EditorElement::new(
16406 &rename_editor,
16407 EditorStyle {
16408 background: cx.theme().system().transparent,
16409 local_player: cx.editor_style.local_player,
16410 text: text_style,
16411 scrollbar_width: cx.editor_style.scrollbar_width,
16412 syntax: cx.editor_style.syntax.clone(),
16413 status: cx.editor_style.status.clone(),
16414 inlay_hints_style: HighlightStyle {
16415 font_weight: Some(FontWeight::BOLD),
16416 ..make_inlay_hints_style(cx.app)
16417 },
16418 edit_prediction_styles: make_suggestion_styles(
16419 cx.app,
16420 ),
16421 ..EditorStyle::default()
16422 },
16423 ))
16424 .into_any_element()
16425 }
16426 }),
16427 priority: 0,
16428 }],
16429 Some(Autoscroll::fit()),
16430 cx,
16431 )[0];
16432 this.pending_rename = Some(RenameState {
16433 range,
16434 old_name,
16435 editor: rename_editor,
16436 block_id,
16437 });
16438 })?;
16439 }
16440
16441 Ok(())
16442 }))
16443 }
16444
16445 pub fn confirm_rename(
16446 &mut self,
16447 _: &ConfirmRename,
16448 window: &mut Window,
16449 cx: &mut Context<Self>,
16450 ) -> Option<Task<Result<()>>> {
16451 let rename = self.take_rename(false, window, cx)?;
16452 let workspace = self.workspace()?.downgrade();
16453 let (buffer, start) = self
16454 .buffer
16455 .read(cx)
16456 .text_anchor_for_position(rename.range.start, cx)?;
16457 let (end_buffer, _) = self
16458 .buffer
16459 .read(cx)
16460 .text_anchor_for_position(rename.range.end, cx)?;
16461 if buffer != end_buffer {
16462 return None;
16463 }
16464
16465 let old_name = rename.old_name;
16466 let new_name = rename.editor.read(cx).text(cx);
16467
16468 let rename = self.semantics_provider.as_ref()?.perform_rename(
16469 &buffer,
16470 start,
16471 new_name.clone(),
16472 cx,
16473 )?;
16474
16475 Some(cx.spawn_in(window, async move |editor, cx| {
16476 let project_transaction = rename.await?;
16477 Self::open_project_transaction(
16478 &editor,
16479 workspace,
16480 project_transaction,
16481 format!("Rename: {} → {}", old_name, new_name),
16482 cx,
16483 )
16484 .await?;
16485
16486 editor.update(cx, |editor, cx| {
16487 editor.refresh_document_highlights(cx);
16488 })?;
16489 Ok(())
16490 }))
16491 }
16492
16493 fn take_rename(
16494 &mut self,
16495 moving_cursor: bool,
16496 window: &mut Window,
16497 cx: &mut Context<Self>,
16498 ) -> Option<RenameState> {
16499 let rename = self.pending_rename.take()?;
16500 if rename.editor.focus_handle(cx).is_focused(window) {
16501 window.focus(&self.focus_handle);
16502 }
16503
16504 self.remove_blocks(
16505 [rename.block_id].into_iter().collect(),
16506 Some(Autoscroll::fit()),
16507 cx,
16508 );
16509 self.clear_highlights::<Rename>(cx);
16510 self.show_local_selections = true;
16511
16512 if moving_cursor {
16513 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16514 editor.selections.newest::<usize>(cx).head()
16515 });
16516
16517 // Update the selection to match the position of the selection inside
16518 // the rename editor.
16519 let snapshot = self.buffer.read(cx).read(cx);
16520 let rename_range = rename.range.to_offset(&snapshot);
16521 let cursor_in_editor = snapshot
16522 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16523 .min(rename_range.end);
16524 drop(snapshot);
16525
16526 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16527 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16528 });
16529 } else {
16530 self.refresh_document_highlights(cx);
16531 }
16532
16533 Some(rename)
16534 }
16535
16536 pub fn pending_rename(&self) -> Option<&RenameState> {
16537 self.pending_rename.as_ref()
16538 }
16539
16540 fn format(
16541 &mut self,
16542 _: &Format,
16543 window: &mut Window,
16544 cx: &mut Context<Self>,
16545 ) -> Option<Task<Result<()>>> {
16546 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16547
16548 let project = match &self.project {
16549 Some(project) => project.clone(),
16550 None => return None,
16551 };
16552
16553 Some(self.perform_format(
16554 project,
16555 FormatTrigger::Manual,
16556 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16557 window,
16558 cx,
16559 ))
16560 }
16561
16562 fn format_selections(
16563 &mut self,
16564 _: &FormatSelections,
16565 window: &mut Window,
16566 cx: &mut Context<Self>,
16567 ) -> Option<Task<Result<()>>> {
16568 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16569
16570 let project = match &self.project {
16571 Some(project) => project.clone(),
16572 None => return None,
16573 };
16574
16575 let ranges = self
16576 .selections
16577 .all_adjusted(cx)
16578 .into_iter()
16579 .map(|selection| selection.range())
16580 .collect_vec();
16581
16582 Some(self.perform_format(
16583 project,
16584 FormatTrigger::Manual,
16585 FormatTarget::Ranges(ranges),
16586 window,
16587 cx,
16588 ))
16589 }
16590
16591 fn perform_format(
16592 &mut self,
16593 project: Entity<Project>,
16594 trigger: FormatTrigger,
16595 target: FormatTarget,
16596 window: &mut Window,
16597 cx: &mut Context<Self>,
16598 ) -> Task<Result<()>> {
16599 let buffer = self.buffer.clone();
16600 let (buffers, target) = match target {
16601 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16602 FormatTarget::Ranges(selection_ranges) => {
16603 let multi_buffer = buffer.read(cx);
16604 let snapshot = multi_buffer.read(cx);
16605 let mut buffers = HashSet::default();
16606 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16607 BTreeMap::new();
16608 for selection_range in selection_ranges {
16609 for (buffer, buffer_range, _) in
16610 snapshot.range_to_buffer_ranges(selection_range)
16611 {
16612 let buffer_id = buffer.remote_id();
16613 let start = buffer.anchor_before(buffer_range.start);
16614 let end = buffer.anchor_after(buffer_range.end);
16615 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16616 buffer_id_to_ranges
16617 .entry(buffer_id)
16618 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16619 .or_insert_with(|| vec![start..end]);
16620 }
16621 }
16622 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16623 }
16624 };
16625
16626 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16627 let selections_prev = transaction_id_prev
16628 .and_then(|transaction_id_prev| {
16629 // default to selections as they were after the last edit, if we have them,
16630 // instead of how they are now.
16631 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16632 // will take you back to where you made the last edit, instead of staying where you scrolled
16633 self.selection_history
16634 .transaction(transaction_id_prev)
16635 .map(|t| t.0.clone())
16636 })
16637 .unwrap_or_else(|| self.selections.disjoint_anchors());
16638
16639 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16640 let format = project.update(cx, |project, cx| {
16641 project.format(buffers, target, true, trigger, cx)
16642 });
16643
16644 cx.spawn_in(window, async move |editor, cx| {
16645 let transaction = futures::select_biased! {
16646 transaction = format.log_err().fuse() => transaction,
16647 () = timeout => {
16648 log::warn!("timed out waiting for formatting");
16649 None
16650 }
16651 };
16652
16653 buffer
16654 .update(cx, |buffer, cx| {
16655 if let Some(transaction) = transaction
16656 && !buffer.is_singleton()
16657 {
16658 buffer.push_transaction(&transaction.0, cx);
16659 }
16660 cx.notify();
16661 })
16662 .ok();
16663
16664 if let Some(transaction_id_now) =
16665 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16666 {
16667 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16668 if has_new_transaction {
16669 _ = editor.update(cx, |editor, _| {
16670 editor
16671 .selection_history
16672 .insert_transaction(transaction_id_now, selections_prev);
16673 });
16674 }
16675 }
16676
16677 Ok(())
16678 })
16679 }
16680
16681 fn organize_imports(
16682 &mut self,
16683 _: &OrganizeImports,
16684 window: &mut Window,
16685 cx: &mut Context<Self>,
16686 ) -> Option<Task<Result<()>>> {
16687 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16688 let project = match &self.project {
16689 Some(project) => project.clone(),
16690 None => return None,
16691 };
16692 Some(self.perform_code_action_kind(
16693 project,
16694 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16695 window,
16696 cx,
16697 ))
16698 }
16699
16700 fn perform_code_action_kind(
16701 &mut self,
16702 project: Entity<Project>,
16703 kind: CodeActionKind,
16704 window: &mut Window,
16705 cx: &mut Context<Self>,
16706 ) -> Task<Result<()>> {
16707 let buffer = self.buffer.clone();
16708 let buffers = buffer.read(cx).all_buffers();
16709 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16710 let apply_action = project.update(cx, |project, cx| {
16711 project.apply_code_action_kind(buffers, kind, true, cx)
16712 });
16713 cx.spawn_in(window, async move |_, cx| {
16714 let transaction = futures::select_biased! {
16715 () = timeout => {
16716 log::warn!("timed out waiting for executing code action");
16717 None
16718 }
16719 transaction = apply_action.log_err().fuse() => transaction,
16720 };
16721 buffer
16722 .update(cx, |buffer, cx| {
16723 // check if we need this
16724 if let Some(transaction) = transaction
16725 && !buffer.is_singleton()
16726 {
16727 buffer.push_transaction(&transaction.0, cx);
16728 }
16729 cx.notify();
16730 })
16731 .ok();
16732 Ok(())
16733 })
16734 }
16735
16736 pub fn restart_language_server(
16737 &mut self,
16738 _: &RestartLanguageServer,
16739 _: &mut Window,
16740 cx: &mut Context<Self>,
16741 ) {
16742 if let Some(project) = self.project.clone() {
16743 self.buffer.update(cx, |multi_buffer, cx| {
16744 project.update(cx, |project, cx| {
16745 project.restart_language_servers_for_buffers(
16746 multi_buffer.all_buffers().into_iter().collect(),
16747 HashSet::default(),
16748 cx,
16749 );
16750 });
16751 })
16752 }
16753 }
16754
16755 pub fn stop_language_server(
16756 &mut self,
16757 _: &StopLanguageServer,
16758 _: &mut Window,
16759 cx: &mut Context<Self>,
16760 ) {
16761 if let Some(project) = self.project.clone() {
16762 self.buffer.update(cx, |multi_buffer, cx| {
16763 project.update(cx, |project, cx| {
16764 project.stop_language_servers_for_buffers(
16765 multi_buffer.all_buffers().into_iter().collect(),
16766 HashSet::default(),
16767 cx,
16768 );
16769 cx.emit(project::Event::RefreshInlayHints);
16770 });
16771 });
16772 }
16773 }
16774
16775 fn cancel_language_server_work(
16776 workspace: &mut Workspace,
16777 _: &actions::CancelLanguageServerWork,
16778 _: &mut Window,
16779 cx: &mut Context<Workspace>,
16780 ) {
16781 let project = workspace.project();
16782 let buffers = workspace
16783 .active_item(cx)
16784 .and_then(|item| item.act_as::<Editor>(cx))
16785 .map_or(HashSet::default(), |editor| {
16786 editor.read(cx).buffer.read(cx).all_buffers()
16787 });
16788 project.update(cx, |project, cx| {
16789 project.cancel_language_server_work_for_buffers(buffers, cx);
16790 });
16791 }
16792
16793 fn show_character_palette(
16794 &mut self,
16795 _: &ShowCharacterPalette,
16796 window: &mut Window,
16797 _: &mut Context<Self>,
16798 ) {
16799 window.show_character_palette();
16800 }
16801
16802 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16803 if !self.diagnostics_enabled() {
16804 return;
16805 }
16806
16807 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16808 let buffer = self.buffer.read(cx).snapshot(cx);
16809 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16810 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16811 let is_valid = buffer
16812 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16813 .any(|entry| {
16814 entry.diagnostic.is_primary
16815 && !entry.range.is_empty()
16816 && entry.range.start == primary_range_start
16817 && entry.diagnostic.message == active_diagnostics.active_message
16818 });
16819
16820 if !is_valid {
16821 self.dismiss_diagnostics(cx);
16822 }
16823 }
16824 }
16825
16826 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16827 match &self.active_diagnostics {
16828 ActiveDiagnostic::Group(group) => Some(group),
16829 _ => None,
16830 }
16831 }
16832
16833 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16834 if !self.diagnostics_enabled() {
16835 return;
16836 }
16837 self.dismiss_diagnostics(cx);
16838 self.active_diagnostics = ActiveDiagnostic::All;
16839 }
16840
16841 fn activate_diagnostics(
16842 &mut self,
16843 buffer_id: BufferId,
16844 diagnostic: DiagnosticEntry<usize>,
16845 window: &mut Window,
16846 cx: &mut Context<Self>,
16847 ) {
16848 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16849 return;
16850 }
16851 self.dismiss_diagnostics(cx);
16852 let snapshot = self.snapshot(window, cx);
16853 let buffer = self.buffer.read(cx).snapshot(cx);
16854 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16855 return;
16856 };
16857
16858 let diagnostic_group = buffer
16859 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16860 .collect::<Vec<_>>();
16861
16862 let blocks =
16863 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16864
16865 let blocks = self.display_map.update(cx, |display_map, cx| {
16866 display_map.insert_blocks(blocks, cx).into_iter().collect()
16867 });
16868 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16869 active_range: buffer.anchor_before(diagnostic.range.start)
16870 ..buffer.anchor_after(diagnostic.range.end),
16871 active_message: diagnostic.diagnostic.message.clone(),
16872 group_id: diagnostic.diagnostic.group_id,
16873 blocks,
16874 });
16875 cx.notify();
16876 }
16877
16878 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16879 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16880 return;
16881 };
16882
16883 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16884 if let ActiveDiagnostic::Group(group) = prev {
16885 self.display_map.update(cx, |display_map, cx| {
16886 display_map.remove_blocks(group.blocks, cx);
16887 });
16888 cx.notify();
16889 }
16890 }
16891
16892 /// Disable inline diagnostics rendering for this editor.
16893 pub fn disable_inline_diagnostics(&mut self) {
16894 self.inline_diagnostics_enabled = false;
16895 self.inline_diagnostics_update = Task::ready(());
16896 self.inline_diagnostics.clear();
16897 }
16898
16899 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16900 self.diagnostics_enabled = false;
16901 self.dismiss_diagnostics(cx);
16902 self.inline_diagnostics_update = Task::ready(());
16903 self.inline_diagnostics.clear();
16904 }
16905
16906 pub fn diagnostics_enabled(&self) -> bool {
16907 self.diagnostics_enabled && self.mode.is_full()
16908 }
16909
16910 pub fn inline_diagnostics_enabled(&self) -> bool {
16911 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16912 }
16913
16914 pub fn show_inline_diagnostics(&self) -> bool {
16915 self.show_inline_diagnostics
16916 }
16917
16918 pub fn toggle_inline_diagnostics(
16919 &mut self,
16920 _: &ToggleInlineDiagnostics,
16921 window: &mut Window,
16922 cx: &mut Context<Editor>,
16923 ) {
16924 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16925 self.refresh_inline_diagnostics(false, window, cx);
16926 }
16927
16928 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16929 self.diagnostics_max_severity = severity;
16930 self.display_map.update(cx, |display_map, _| {
16931 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16932 });
16933 }
16934
16935 pub fn toggle_diagnostics(
16936 &mut self,
16937 _: &ToggleDiagnostics,
16938 window: &mut Window,
16939 cx: &mut Context<Editor>,
16940 ) {
16941 if !self.diagnostics_enabled() {
16942 return;
16943 }
16944
16945 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16946 EditorSettings::get_global(cx)
16947 .diagnostics_max_severity
16948 .filter(|severity| severity != &DiagnosticSeverity::Off)
16949 .unwrap_or(DiagnosticSeverity::Hint)
16950 } else {
16951 DiagnosticSeverity::Off
16952 };
16953 self.set_max_diagnostics_severity(new_severity, cx);
16954 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16955 self.active_diagnostics = ActiveDiagnostic::None;
16956 self.inline_diagnostics_update = Task::ready(());
16957 self.inline_diagnostics.clear();
16958 } else {
16959 self.refresh_inline_diagnostics(false, window, cx);
16960 }
16961
16962 cx.notify();
16963 }
16964
16965 pub fn toggle_minimap(
16966 &mut self,
16967 _: &ToggleMinimap,
16968 window: &mut Window,
16969 cx: &mut Context<Editor>,
16970 ) {
16971 if self.supports_minimap(cx) {
16972 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16973 }
16974 }
16975
16976 fn refresh_inline_diagnostics(
16977 &mut self,
16978 debounce: bool,
16979 window: &mut Window,
16980 cx: &mut Context<Self>,
16981 ) {
16982 let max_severity = ProjectSettings::get_global(cx)
16983 .diagnostics
16984 .inline
16985 .max_severity
16986 .unwrap_or(self.diagnostics_max_severity);
16987
16988 if !self.inline_diagnostics_enabled()
16989 || !self.show_inline_diagnostics
16990 || max_severity == DiagnosticSeverity::Off
16991 {
16992 self.inline_diagnostics_update = Task::ready(());
16993 self.inline_diagnostics.clear();
16994 return;
16995 }
16996
16997 let debounce_ms = ProjectSettings::get_global(cx)
16998 .diagnostics
16999 .inline
17000 .update_debounce_ms;
17001 let debounce = if debounce && debounce_ms > 0 {
17002 Some(Duration::from_millis(debounce_ms))
17003 } else {
17004 None
17005 };
17006 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17007 if let Some(debounce) = debounce {
17008 cx.background_executor().timer(debounce).await;
17009 }
17010 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17011 editor
17012 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17013 .ok()
17014 }) else {
17015 return;
17016 };
17017
17018 let new_inline_diagnostics = cx
17019 .background_spawn(async move {
17020 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17021 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17022 let message = diagnostic_entry
17023 .diagnostic
17024 .message
17025 .split_once('\n')
17026 .map(|(line, _)| line)
17027 .map(SharedString::new)
17028 .unwrap_or_else(|| {
17029 SharedString::from(diagnostic_entry.diagnostic.message)
17030 });
17031 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17032 let (Ok(i) | Err(i)) = inline_diagnostics
17033 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17034 inline_diagnostics.insert(
17035 i,
17036 (
17037 start_anchor,
17038 InlineDiagnostic {
17039 message,
17040 group_id: diagnostic_entry.diagnostic.group_id,
17041 start: diagnostic_entry.range.start.to_point(&snapshot),
17042 is_primary: diagnostic_entry.diagnostic.is_primary,
17043 severity: diagnostic_entry.diagnostic.severity,
17044 },
17045 ),
17046 );
17047 }
17048 inline_diagnostics
17049 })
17050 .await;
17051
17052 editor
17053 .update(cx, |editor, cx| {
17054 editor.inline_diagnostics = new_inline_diagnostics;
17055 cx.notify();
17056 })
17057 .ok();
17058 });
17059 }
17060
17061 fn pull_diagnostics(
17062 &mut self,
17063 buffer_id: Option<BufferId>,
17064 window: &Window,
17065 cx: &mut Context<Self>,
17066 ) -> Option<()> {
17067 if !self.mode().is_full() {
17068 return None;
17069 }
17070 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17071 .diagnostics
17072 .lsp_pull_diagnostics;
17073 if !pull_diagnostics_settings.enabled {
17074 return None;
17075 }
17076 let project = self.project()?.downgrade();
17077 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17078 let mut buffers = self.buffer.read(cx).all_buffers();
17079 if let Some(buffer_id) = buffer_id {
17080 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17081 }
17082
17083 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17084 cx.background_executor().timer(debounce).await;
17085
17086 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17087 buffers
17088 .into_iter()
17089 .filter_map(|buffer| {
17090 project
17091 .update(cx, |project, cx| {
17092 project.lsp_store().update(cx, |lsp_store, cx| {
17093 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17094 })
17095 })
17096 .ok()
17097 })
17098 .collect::<FuturesUnordered<_>>()
17099 }) else {
17100 return;
17101 };
17102
17103 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17104 match pull_task {
17105 Ok(()) => {
17106 if editor
17107 .update_in(cx, |editor, window, cx| {
17108 editor.update_diagnostics_state(window, cx);
17109 })
17110 .is_err()
17111 {
17112 return;
17113 }
17114 }
17115 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17116 }
17117 }
17118 });
17119
17120 Some(())
17121 }
17122
17123 pub fn set_selections_from_remote(
17124 &mut self,
17125 selections: Vec<Selection<Anchor>>,
17126 pending_selection: Option<Selection<Anchor>>,
17127 window: &mut Window,
17128 cx: &mut Context<Self>,
17129 ) {
17130 let old_cursor_position = self.selections.newest_anchor().head();
17131 self.selections.change_with(cx, |s| {
17132 s.select_anchors(selections);
17133 if let Some(pending_selection) = pending_selection {
17134 s.set_pending(pending_selection, SelectMode::Character);
17135 } else {
17136 s.clear_pending();
17137 }
17138 });
17139 self.selections_did_change(
17140 false,
17141 &old_cursor_position,
17142 SelectionEffects::default(),
17143 window,
17144 cx,
17145 );
17146 }
17147
17148 pub fn transact(
17149 &mut self,
17150 window: &mut Window,
17151 cx: &mut Context<Self>,
17152 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17153 ) -> Option<TransactionId> {
17154 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17155 this.start_transaction_at(Instant::now(), window, cx);
17156 update(this, window, cx);
17157 this.end_transaction_at(Instant::now(), cx)
17158 })
17159 }
17160
17161 pub fn start_transaction_at(
17162 &mut self,
17163 now: Instant,
17164 window: &mut Window,
17165 cx: &mut Context<Self>,
17166 ) -> Option<TransactionId> {
17167 self.end_selection(window, cx);
17168 if let Some(tx_id) = self
17169 .buffer
17170 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17171 {
17172 self.selection_history
17173 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17174 cx.emit(EditorEvent::TransactionBegun {
17175 transaction_id: tx_id,
17176 });
17177 Some(tx_id)
17178 } else {
17179 None
17180 }
17181 }
17182
17183 pub fn end_transaction_at(
17184 &mut self,
17185 now: Instant,
17186 cx: &mut Context<Self>,
17187 ) -> Option<TransactionId> {
17188 if let Some(transaction_id) = self
17189 .buffer
17190 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17191 {
17192 if let Some((_, end_selections)) =
17193 self.selection_history.transaction_mut(transaction_id)
17194 {
17195 *end_selections = Some(self.selections.disjoint_anchors());
17196 } else {
17197 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17198 }
17199
17200 cx.emit(EditorEvent::Edited { transaction_id });
17201 Some(transaction_id)
17202 } else {
17203 None
17204 }
17205 }
17206
17207 pub fn modify_transaction_selection_history(
17208 &mut self,
17209 transaction_id: TransactionId,
17210 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17211 ) -> bool {
17212 self.selection_history
17213 .transaction_mut(transaction_id)
17214 .map(modify)
17215 .is_some()
17216 }
17217
17218 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17219 if self.selection_mark_mode {
17220 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17221 s.move_with(|_, sel| {
17222 sel.collapse_to(sel.head(), SelectionGoal::None);
17223 });
17224 })
17225 }
17226 self.selection_mark_mode = true;
17227 cx.notify();
17228 }
17229
17230 pub fn swap_selection_ends(
17231 &mut self,
17232 _: &actions::SwapSelectionEnds,
17233 window: &mut Window,
17234 cx: &mut Context<Self>,
17235 ) {
17236 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17237 s.move_with(|_, sel| {
17238 if sel.start != sel.end {
17239 sel.reversed = !sel.reversed
17240 }
17241 });
17242 });
17243 self.request_autoscroll(Autoscroll::newest(), cx);
17244 cx.notify();
17245 }
17246
17247 pub fn toggle_focus(
17248 workspace: &mut Workspace,
17249 _: &actions::ToggleFocus,
17250 window: &mut Window,
17251 cx: &mut Context<Workspace>,
17252 ) {
17253 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17254 return;
17255 };
17256 workspace.activate_item(&item, true, true, window, cx);
17257 }
17258
17259 pub fn toggle_fold(
17260 &mut self,
17261 _: &actions::ToggleFold,
17262 window: &mut Window,
17263 cx: &mut Context<Self>,
17264 ) {
17265 if self.is_singleton(cx) {
17266 let selection = self.selections.newest::<Point>(cx);
17267
17268 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17269 let range = if selection.is_empty() {
17270 let point = selection.head().to_display_point(&display_map);
17271 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17272 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17273 .to_point(&display_map);
17274 start..end
17275 } else {
17276 selection.range()
17277 };
17278 if display_map.folds_in_range(range).next().is_some() {
17279 self.unfold_lines(&Default::default(), window, cx)
17280 } else {
17281 self.fold(&Default::default(), window, cx)
17282 }
17283 } else {
17284 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17285 let buffer_ids: HashSet<_> = self
17286 .selections
17287 .disjoint_anchor_ranges()
17288 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17289 .collect();
17290
17291 let should_unfold = buffer_ids
17292 .iter()
17293 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17294
17295 for buffer_id in buffer_ids {
17296 if should_unfold {
17297 self.unfold_buffer(buffer_id, cx);
17298 } else {
17299 self.fold_buffer(buffer_id, cx);
17300 }
17301 }
17302 }
17303 }
17304
17305 pub fn toggle_fold_recursive(
17306 &mut self,
17307 _: &actions::ToggleFoldRecursive,
17308 window: &mut Window,
17309 cx: &mut Context<Self>,
17310 ) {
17311 let selection = self.selections.newest::<Point>(cx);
17312
17313 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17314 let range = if selection.is_empty() {
17315 let point = selection.head().to_display_point(&display_map);
17316 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17317 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17318 .to_point(&display_map);
17319 start..end
17320 } else {
17321 selection.range()
17322 };
17323 if display_map.folds_in_range(range).next().is_some() {
17324 self.unfold_recursive(&Default::default(), window, cx)
17325 } else {
17326 self.fold_recursive(&Default::default(), window, cx)
17327 }
17328 }
17329
17330 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17331 if self.is_singleton(cx) {
17332 let mut to_fold = Vec::new();
17333 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17334 let selections = self.selections.all_adjusted(cx);
17335
17336 for selection in selections {
17337 let range = selection.range().sorted();
17338 let buffer_start_row = range.start.row;
17339
17340 if range.start.row != range.end.row {
17341 let mut found = false;
17342 let mut row = range.start.row;
17343 while row <= range.end.row {
17344 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17345 {
17346 found = true;
17347 row = crease.range().end.row + 1;
17348 to_fold.push(crease);
17349 } else {
17350 row += 1
17351 }
17352 }
17353 if found {
17354 continue;
17355 }
17356 }
17357
17358 for row in (0..=range.start.row).rev() {
17359 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17360 && crease.range().end.row >= buffer_start_row
17361 {
17362 to_fold.push(crease);
17363 if row <= range.start.row {
17364 break;
17365 }
17366 }
17367 }
17368 }
17369
17370 self.fold_creases(to_fold, true, window, cx);
17371 } else {
17372 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17373 let buffer_ids = self
17374 .selections
17375 .disjoint_anchor_ranges()
17376 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17377 .collect::<HashSet<_>>();
17378 for buffer_id in buffer_ids {
17379 self.fold_buffer(buffer_id, cx);
17380 }
17381 }
17382 }
17383
17384 pub fn toggle_fold_all(
17385 &mut self,
17386 _: &actions::ToggleFoldAll,
17387 window: &mut Window,
17388 cx: &mut Context<Self>,
17389 ) {
17390 if self.buffer.read(cx).is_singleton() {
17391 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17392 let has_folds = display_map
17393 .folds_in_range(0..display_map.buffer_snapshot.len())
17394 .next()
17395 .is_some();
17396
17397 if has_folds {
17398 self.unfold_all(&actions::UnfoldAll, window, cx);
17399 } else {
17400 self.fold_all(&actions::FoldAll, window, cx);
17401 }
17402 } else {
17403 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17404 let should_unfold = buffer_ids
17405 .iter()
17406 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17407
17408 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17409 editor
17410 .update_in(cx, |editor, _, cx| {
17411 for buffer_id in buffer_ids {
17412 if should_unfold {
17413 editor.unfold_buffer(buffer_id, cx);
17414 } else {
17415 editor.fold_buffer(buffer_id, cx);
17416 }
17417 }
17418 })
17419 .ok();
17420 });
17421 }
17422 }
17423
17424 fn fold_at_level(
17425 &mut self,
17426 fold_at: &FoldAtLevel,
17427 window: &mut Window,
17428 cx: &mut Context<Self>,
17429 ) {
17430 if !self.buffer.read(cx).is_singleton() {
17431 return;
17432 }
17433
17434 let fold_at_level = fold_at.0;
17435 let snapshot = self.buffer.read(cx).snapshot(cx);
17436 let mut to_fold = Vec::new();
17437 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17438
17439 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17440 while start_row < end_row {
17441 match self
17442 .snapshot(window, cx)
17443 .crease_for_buffer_row(MultiBufferRow(start_row))
17444 {
17445 Some(crease) => {
17446 let nested_start_row = crease.range().start.row + 1;
17447 let nested_end_row = crease.range().end.row;
17448
17449 if current_level < fold_at_level {
17450 stack.push((nested_start_row, nested_end_row, current_level + 1));
17451 } else if current_level == fold_at_level {
17452 to_fold.push(crease);
17453 }
17454
17455 start_row = nested_end_row + 1;
17456 }
17457 None => start_row += 1,
17458 }
17459 }
17460 }
17461
17462 self.fold_creases(to_fold, true, window, cx);
17463 }
17464
17465 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17466 if self.buffer.read(cx).is_singleton() {
17467 let mut fold_ranges = Vec::new();
17468 let snapshot = self.buffer.read(cx).snapshot(cx);
17469
17470 for row in 0..snapshot.max_row().0 {
17471 if let Some(foldable_range) = self
17472 .snapshot(window, cx)
17473 .crease_for_buffer_row(MultiBufferRow(row))
17474 {
17475 fold_ranges.push(foldable_range);
17476 }
17477 }
17478
17479 self.fold_creases(fold_ranges, true, window, cx);
17480 } else {
17481 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17482 editor
17483 .update_in(cx, |editor, _, cx| {
17484 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17485 editor.fold_buffer(buffer_id, cx);
17486 }
17487 })
17488 .ok();
17489 });
17490 }
17491 }
17492
17493 pub fn fold_function_bodies(
17494 &mut self,
17495 _: &actions::FoldFunctionBodies,
17496 window: &mut Window,
17497 cx: &mut Context<Self>,
17498 ) {
17499 let snapshot = self.buffer.read(cx).snapshot(cx);
17500
17501 let ranges = snapshot
17502 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17503 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17504 .collect::<Vec<_>>();
17505
17506 let creases = ranges
17507 .into_iter()
17508 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17509 .collect();
17510
17511 self.fold_creases(creases, true, window, cx);
17512 }
17513
17514 pub fn fold_recursive(
17515 &mut self,
17516 _: &actions::FoldRecursive,
17517 window: &mut Window,
17518 cx: &mut Context<Self>,
17519 ) {
17520 let mut to_fold = Vec::new();
17521 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17522 let selections = self.selections.all_adjusted(cx);
17523
17524 for selection in selections {
17525 let range = selection.range().sorted();
17526 let buffer_start_row = range.start.row;
17527
17528 if range.start.row != range.end.row {
17529 let mut found = false;
17530 for row in range.start.row..=range.end.row {
17531 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17532 found = true;
17533 to_fold.push(crease);
17534 }
17535 }
17536 if found {
17537 continue;
17538 }
17539 }
17540
17541 for row in (0..=range.start.row).rev() {
17542 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17543 if crease.range().end.row >= buffer_start_row {
17544 to_fold.push(crease);
17545 } else {
17546 break;
17547 }
17548 }
17549 }
17550 }
17551
17552 self.fold_creases(to_fold, true, window, cx);
17553 }
17554
17555 pub fn fold_at(
17556 &mut self,
17557 buffer_row: MultiBufferRow,
17558 window: &mut Window,
17559 cx: &mut Context<Self>,
17560 ) {
17561 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17562
17563 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17564 let autoscroll = self
17565 .selections
17566 .all::<Point>(cx)
17567 .iter()
17568 .any(|selection| crease.range().overlaps(&selection.range()));
17569
17570 self.fold_creases(vec![crease], autoscroll, window, cx);
17571 }
17572 }
17573
17574 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17575 if self.is_singleton(cx) {
17576 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17577 let buffer = &display_map.buffer_snapshot;
17578 let selections = self.selections.all::<Point>(cx);
17579 let ranges = selections
17580 .iter()
17581 .map(|s| {
17582 let range = s.display_range(&display_map).sorted();
17583 let mut start = range.start.to_point(&display_map);
17584 let mut end = range.end.to_point(&display_map);
17585 start.column = 0;
17586 end.column = buffer.line_len(MultiBufferRow(end.row));
17587 start..end
17588 })
17589 .collect::<Vec<_>>();
17590
17591 self.unfold_ranges(&ranges, true, true, cx);
17592 } else {
17593 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17594 let buffer_ids = self
17595 .selections
17596 .disjoint_anchor_ranges()
17597 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17598 .collect::<HashSet<_>>();
17599 for buffer_id in buffer_ids {
17600 self.unfold_buffer(buffer_id, cx);
17601 }
17602 }
17603 }
17604
17605 pub fn unfold_recursive(
17606 &mut self,
17607 _: &UnfoldRecursive,
17608 _window: &mut Window,
17609 cx: &mut Context<Self>,
17610 ) {
17611 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17612 let selections = self.selections.all::<Point>(cx);
17613 let ranges = selections
17614 .iter()
17615 .map(|s| {
17616 let mut range = s.display_range(&display_map).sorted();
17617 *range.start.column_mut() = 0;
17618 *range.end.column_mut() = display_map.line_len(range.end.row());
17619 let start = range.start.to_point(&display_map);
17620 let end = range.end.to_point(&display_map);
17621 start..end
17622 })
17623 .collect::<Vec<_>>();
17624
17625 self.unfold_ranges(&ranges, true, true, cx);
17626 }
17627
17628 pub fn unfold_at(
17629 &mut self,
17630 buffer_row: MultiBufferRow,
17631 _window: &mut Window,
17632 cx: &mut Context<Self>,
17633 ) {
17634 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17635
17636 let intersection_range = Point::new(buffer_row.0, 0)
17637 ..Point::new(
17638 buffer_row.0,
17639 display_map.buffer_snapshot.line_len(buffer_row),
17640 );
17641
17642 let autoscroll = self
17643 .selections
17644 .all::<Point>(cx)
17645 .iter()
17646 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17647
17648 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17649 }
17650
17651 pub fn unfold_all(
17652 &mut self,
17653 _: &actions::UnfoldAll,
17654 _window: &mut Window,
17655 cx: &mut Context<Self>,
17656 ) {
17657 if self.buffer.read(cx).is_singleton() {
17658 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17659 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17660 } else {
17661 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17662 editor
17663 .update(cx, |editor, cx| {
17664 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17665 editor.unfold_buffer(buffer_id, cx);
17666 }
17667 })
17668 .ok();
17669 });
17670 }
17671 }
17672
17673 pub fn fold_selected_ranges(
17674 &mut self,
17675 _: &FoldSelectedRanges,
17676 window: &mut Window,
17677 cx: &mut Context<Self>,
17678 ) {
17679 let selections = self.selections.all_adjusted(cx);
17680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17681 let ranges = selections
17682 .into_iter()
17683 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17684 .collect::<Vec<_>>();
17685 self.fold_creases(ranges, true, window, cx);
17686 }
17687
17688 pub fn fold_ranges<T: ToOffset + Clone>(
17689 &mut self,
17690 ranges: Vec<Range<T>>,
17691 auto_scroll: bool,
17692 window: &mut Window,
17693 cx: &mut Context<Self>,
17694 ) {
17695 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17696 let ranges = ranges
17697 .into_iter()
17698 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17699 .collect::<Vec<_>>();
17700 self.fold_creases(ranges, auto_scroll, window, cx);
17701 }
17702
17703 pub fn fold_creases<T: ToOffset + Clone>(
17704 &mut self,
17705 creases: Vec<Crease<T>>,
17706 auto_scroll: bool,
17707 _window: &mut Window,
17708 cx: &mut Context<Self>,
17709 ) {
17710 if creases.is_empty() {
17711 return;
17712 }
17713
17714 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17715
17716 if auto_scroll {
17717 self.request_autoscroll(Autoscroll::fit(), cx);
17718 }
17719
17720 cx.notify();
17721
17722 self.scrollbar_marker_state.dirty = true;
17723 self.folds_did_change(cx);
17724 }
17725
17726 /// Removes any folds whose ranges intersect any of the given ranges.
17727 pub fn unfold_ranges<T: ToOffset + Clone>(
17728 &mut self,
17729 ranges: &[Range<T>],
17730 inclusive: bool,
17731 auto_scroll: bool,
17732 cx: &mut Context<Self>,
17733 ) {
17734 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17735 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17736 });
17737 self.folds_did_change(cx);
17738 }
17739
17740 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17741 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17742 return;
17743 }
17744 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17745 self.display_map.update(cx, |display_map, cx| {
17746 display_map.fold_buffers([buffer_id], cx)
17747 });
17748 cx.emit(EditorEvent::BufferFoldToggled {
17749 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17750 folded: true,
17751 });
17752 cx.notify();
17753 }
17754
17755 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17756 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17757 return;
17758 }
17759 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17760 self.display_map.update(cx, |display_map, cx| {
17761 display_map.unfold_buffers([buffer_id], cx);
17762 });
17763 cx.emit(EditorEvent::BufferFoldToggled {
17764 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17765 folded: false,
17766 });
17767 cx.notify();
17768 }
17769
17770 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17771 self.display_map.read(cx).is_buffer_folded(buffer)
17772 }
17773
17774 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17775 self.display_map.read(cx).folded_buffers()
17776 }
17777
17778 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17779 self.display_map.update(cx, |display_map, cx| {
17780 display_map.disable_header_for_buffer(buffer_id, cx);
17781 });
17782 cx.notify();
17783 }
17784
17785 /// Removes any folds with the given ranges.
17786 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17787 &mut self,
17788 ranges: &[Range<T>],
17789 type_id: TypeId,
17790 auto_scroll: bool,
17791 cx: &mut Context<Self>,
17792 ) {
17793 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17794 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17795 });
17796 self.folds_did_change(cx);
17797 }
17798
17799 fn remove_folds_with<T: ToOffset + Clone>(
17800 &mut self,
17801 ranges: &[Range<T>],
17802 auto_scroll: bool,
17803 cx: &mut Context<Self>,
17804 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17805 ) {
17806 if ranges.is_empty() {
17807 return;
17808 }
17809
17810 let mut buffers_affected = HashSet::default();
17811 let multi_buffer = self.buffer().read(cx);
17812 for range in ranges {
17813 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17814 buffers_affected.insert(buffer.read(cx).remote_id());
17815 };
17816 }
17817
17818 self.display_map.update(cx, update);
17819
17820 if auto_scroll {
17821 self.request_autoscroll(Autoscroll::fit(), cx);
17822 }
17823
17824 cx.notify();
17825 self.scrollbar_marker_state.dirty = true;
17826 self.active_indent_guides_state.dirty = true;
17827 }
17828
17829 pub fn update_renderer_widths(
17830 &mut self,
17831 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17832 cx: &mut Context<Self>,
17833 ) -> bool {
17834 self.display_map
17835 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17836 }
17837
17838 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17839 self.display_map.read(cx).fold_placeholder.clone()
17840 }
17841
17842 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17843 self.buffer.update(cx, |buffer, cx| {
17844 buffer.set_all_diff_hunks_expanded(cx);
17845 });
17846 }
17847
17848 pub fn expand_all_diff_hunks(
17849 &mut self,
17850 _: &ExpandAllDiffHunks,
17851 _window: &mut Window,
17852 cx: &mut Context<Self>,
17853 ) {
17854 self.buffer.update(cx, |buffer, cx| {
17855 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17856 });
17857 }
17858
17859 pub fn toggle_selected_diff_hunks(
17860 &mut self,
17861 _: &ToggleSelectedDiffHunks,
17862 _window: &mut Window,
17863 cx: &mut Context<Self>,
17864 ) {
17865 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17866 self.toggle_diff_hunks_in_ranges(ranges, cx);
17867 }
17868
17869 pub fn diff_hunks_in_ranges<'a>(
17870 &'a self,
17871 ranges: &'a [Range<Anchor>],
17872 buffer: &'a MultiBufferSnapshot,
17873 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17874 ranges.iter().flat_map(move |range| {
17875 let end_excerpt_id = range.end.excerpt_id;
17876 let range = range.to_point(buffer);
17877 let mut peek_end = range.end;
17878 if range.end.row < buffer.max_row().0 {
17879 peek_end = Point::new(range.end.row + 1, 0);
17880 }
17881 buffer
17882 .diff_hunks_in_range(range.start..peek_end)
17883 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17884 })
17885 }
17886
17887 pub fn has_stageable_diff_hunks_in_ranges(
17888 &self,
17889 ranges: &[Range<Anchor>],
17890 snapshot: &MultiBufferSnapshot,
17891 ) -> bool {
17892 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
17893 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17894 }
17895
17896 pub fn toggle_staged_selected_diff_hunks(
17897 &mut self,
17898 _: &::git::ToggleStaged,
17899 _: &mut Window,
17900 cx: &mut Context<Self>,
17901 ) {
17902 let snapshot = self.buffer.read(cx).snapshot(cx);
17903 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17904 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17905 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17906 }
17907
17908 pub fn set_render_diff_hunk_controls(
17909 &mut self,
17910 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17911 cx: &mut Context<Self>,
17912 ) {
17913 self.render_diff_hunk_controls = render_diff_hunk_controls;
17914 cx.notify();
17915 }
17916
17917 pub fn stage_and_next(
17918 &mut self,
17919 _: &::git::StageAndNext,
17920 window: &mut Window,
17921 cx: &mut Context<Self>,
17922 ) {
17923 self.do_stage_or_unstage_and_next(true, window, cx);
17924 }
17925
17926 pub fn unstage_and_next(
17927 &mut self,
17928 _: &::git::UnstageAndNext,
17929 window: &mut Window,
17930 cx: &mut Context<Self>,
17931 ) {
17932 self.do_stage_or_unstage_and_next(false, window, cx);
17933 }
17934
17935 pub fn stage_or_unstage_diff_hunks(
17936 &mut self,
17937 stage: bool,
17938 ranges: Vec<Range<Anchor>>,
17939 cx: &mut Context<Self>,
17940 ) {
17941 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17942 cx.spawn(async move |this, cx| {
17943 task.await?;
17944 this.update(cx, |this, cx| {
17945 let snapshot = this.buffer.read(cx).snapshot(cx);
17946 let chunk_by = this
17947 .diff_hunks_in_ranges(&ranges, &snapshot)
17948 .chunk_by(|hunk| hunk.buffer_id);
17949 for (buffer_id, hunks) in &chunk_by {
17950 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17951 }
17952 })
17953 })
17954 .detach_and_log_err(cx);
17955 }
17956
17957 fn save_buffers_for_ranges_if_needed(
17958 &mut self,
17959 ranges: &[Range<Anchor>],
17960 cx: &mut Context<Editor>,
17961 ) -> Task<Result<()>> {
17962 let multibuffer = self.buffer.read(cx);
17963 let snapshot = multibuffer.read(cx);
17964 let buffer_ids: HashSet<_> = ranges
17965 .iter()
17966 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17967 .collect();
17968 drop(snapshot);
17969
17970 let mut buffers = HashSet::default();
17971 for buffer_id in buffer_ids {
17972 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17973 let buffer = buffer_entity.read(cx);
17974 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17975 {
17976 buffers.insert(buffer_entity);
17977 }
17978 }
17979 }
17980
17981 if let Some(project) = &self.project {
17982 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17983 } else {
17984 Task::ready(Ok(()))
17985 }
17986 }
17987
17988 fn do_stage_or_unstage_and_next(
17989 &mut self,
17990 stage: bool,
17991 window: &mut Window,
17992 cx: &mut Context<Self>,
17993 ) {
17994 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17995
17996 if ranges.iter().any(|range| range.start != range.end) {
17997 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17998 return;
17999 }
18000
18001 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18002 let snapshot = self.snapshot(window, cx);
18003 let position = self.selections.newest::<Point>(cx).head();
18004 let mut row = snapshot
18005 .buffer_snapshot
18006 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18007 .find(|hunk| hunk.row_range.start.0 > position.row)
18008 .map(|hunk| hunk.row_range.start);
18009
18010 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18011 // Outside of the project diff editor, wrap around to the beginning.
18012 if !all_diff_hunks_expanded {
18013 row = row.or_else(|| {
18014 snapshot
18015 .buffer_snapshot
18016 .diff_hunks_in_range(Point::zero()..position)
18017 .find(|hunk| hunk.row_range.end.0 < position.row)
18018 .map(|hunk| hunk.row_range.start)
18019 });
18020 }
18021
18022 if let Some(row) = row {
18023 let destination = Point::new(row.0, 0);
18024 let autoscroll = Autoscroll::center();
18025
18026 self.unfold_ranges(&[destination..destination], false, false, cx);
18027 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18028 s.select_ranges([destination..destination]);
18029 });
18030 }
18031 }
18032
18033 fn do_stage_or_unstage(
18034 &self,
18035 stage: bool,
18036 buffer_id: BufferId,
18037 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18038 cx: &mut App,
18039 ) -> Option<()> {
18040 let project = self.project()?;
18041 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18042 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18043 let buffer_snapshot = buffer.read(cx).snapshot();
18044 let file_exists = buffer_snapshot
18045 .file()
18046 .is_some_and(|file| file.disk_state().exists());
18047 diff.update(cx, |diff, cx| {
18048 diff.stage_or_unstage_hunks(
18049 stage,
18050 &hunks
18051 .map(|hunk| buffer_diff::DiffHunk {
18052 buffer_range: hunk.buffer_range,
18053 diff_base_byte_range: hunk.diff_base_byte_range,
18054 secondary_status: hunk.secondary_status,
18055 range: Point::zero()..Point::zero(), // unused
18056 })
18057 .collect::<Vec<_>>(),
18058 &buffer_snapshot,
18059 file_exists,
18060 cx,
18061 )
18062 });
18063 None
18064 }
18065
18066 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18067 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18068 self.buffer
18069 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18070 }
18071
18072 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18073 self.buffer.update(cx, |buffer, cx| {
18074 let ranges = vec![Anchor::min()..Anchor::max()];
18075 if !buffer.all_diff_hunks_expanded()
18076 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18077 {
18078 buffer.collapse_diff_hunks(ranges, cx);
18079 true
18080 } else {
18081 false
18082 }
18083 })
18084 }
18085
18086 fn toggle_diff_hunks_in_ranges(
18087 &mut self,
18088 ranges: Vec<Range<Anchor>>,
18089 cx: &mut Context<Editor>,
18090 ) {
18091 self.buffer.update(cx, |buffer, cx| {
18092 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18093 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18094 })
18095 }
18096
18097 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18098 self.buffer.update(cx, |buffer, cx| {
18099 let snapshot = buffer.snapshot(cx);
18100 let excerpt_id = range.end.excerpt_id;
18101 let point_range = range.to_point(&snapshot);
18102 let expand = !buffer.single_hunk_is_expanded(range, cx);
18103 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18104 })
18105 }
18106
18107 pub(crate) fn apply_all_diff_hunks(
18108 &mut self,
18109 _: &ApplyAllDiffHunks,
18110 window: &mut Window,
18111 cx: &mut Context<Self>,
18112 ) {
18113 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18114
18115 let buffers = self.buffer.read(cx).all_buffers();
18116 for branch_buffer in buffers {
18117 branch_buffer.update(cx, |branch_buffer, cx| {
18118 branch_buffer.merge_into_base(Vec::new(), cx);
18119 });
18120 }
18121
18122 if let Some(project) = self.project.clone() {
18123 self.save(
18124 SaveOptions {
18125 format: true,
18126 autosave: false,
18127 },
18128 project,
18129 window,
18130 cx,
18131 )
18132 .detach_and_log_err(cx);
18133 }
18134 }
18135
18136 pub(crate) fn apply_selected_diff_hunks(
18137 &mut self,
18138 _: &ApplyDiffHunk,
18139 window: &mut Window,
18140 cx: &mut Context<Self>,
18141 ) {
18142 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18143 let snapshot = self.snapshot(window, cx);
18144 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18145 let mut ranges_by_buffer = HashMap::default();
18146 self.transact(window, cx, |editor, _window, cx| {
18147 for hunk in hunks {
18148 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18149 ranges_by_buffer
18150 .entry(buffer.clone())
18151 .or_insert_with(Vec::new)
18152 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18153 }
18154 }
18155
18156 for (buffer, ranges) in ranges_by_buffer {
18157 buffer.update(cx, |buffer, cx| {
18158 buffer.merge_into_base(ranges, cx);
18159 });
18160 }
18161 });
18162
18163 if let Some(project) = self.project.clone() {
18164 self.save(
18165 SaveOptions {
18166 format: true,
18167 autosave: false,
18168 },
18169 project,
18170 window,
18171 cx,
18172 )
18173 .detach_and_log_err(cx);
18174 }
18175 }
18176
18177 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18178 if hovered != self.gutter_hovered {
18179 self.gutter_hovered = hovered;
18180 cx.notify();
18181 }
18182 }
18183
18184 pub fn insert_blocks(
18185 &mut self,
18186 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18187 autoscroll: Option<Autoscroll>,
18188 cx: &mut Context<Self>,
18189 ) -> Vec<CustomBlockId> {
18190 let blocks = self
18191 .display_map
18192 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18193 if let Some(autoscroll) = autoscroll {
18194 self.request_autoscroll(autoscroll, cx);
18195 }
18196 cx.notify();
18197 blocks
18198 }
18199
18200 pub fn resize_blocks(
18201 &mut self,
18202 heights: HashMap<CustomBlockId, u32>,
18203 autoscroll: Option<Autoscroll>,
18204 cx: &mut Context<Self>,
18205 ) {
18206 self.display_map
18207 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18208 if let Some(autoscroll) = autoscroll {
18209 self.request_autoscroll(autoscroll, cx);
18210 }
18211 cx.notify();
18212 }
18213
18214 pub fn replace_blocks(
18215 &mut self,
18216 renderers: HashMap<CustomBlockId, RenderBlock>,
18217 autoscroll: Option<Autoscroll>,
18218 cx: &mut Context<Self>,
18219 ) {
18220 self.display_map
18221 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18222 if let Some(autoscroll) = autoscroll {
18223 self.request_autoscroll(autoscroll, cx);
18224 }
18225 cx.notify();
18226 }
18227
18228 pub fn remove_blocks(
18229 &mut self,
18230 block_ids: HashSet<CustomBlockId>,
18231 autoscroll: Option<Autoscroll>,
18232 cx: &mut Context<Self>,
18233 ) {
18234 self.display_map.update(cx, |display_map, cx| {
18235 display_map.remove_blocks(block_ids, cx)
18236 });
18237 if let Some(autoscroll) = autoscroll {
18238 self.request_autoscroll(autoscroll, cx);
18239 }
18240 cx.notify();
18241 }
18242
18243 pub fn row_for_block(
18244 &self,
18245 block_id: CustomBlockId,
18246 cx: &mut Context<Self>,
18247 ) -> Option<DisplayRow> {
18248 self.display_map
18249 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18250 }
18251
18252 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18253 self.focused_block = Some(focused_block);
18254 }
18255
18256 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18257 self.focused_block.take()
18258 }
18259
18260 pub fn insert_creases(
18261 &mut self,
18262 creases: impl IntoIterator<Item = Crease<Anchor>>,
18263 cx: &mut Context<Self>,
18264 ) -> Vec<CreaseId> {
18265 self.display_map
18266 .update(cx, |map, cx| map.insert_creases(creases, cx))
18267 }
18268
18269 pub fn remove_creases(
18270 &mut self,
18271 ids: impl IntoIterator<Item = CreaseId>,
18272 cx: &mut Context<Self>,
18273 ) -> Vec<(CreaseId, Range<Anchor>)> {
18274 self.display_map
18275 .update(cx, |map, cx| map.remove_creases(ids, cx))
18276 }
18277
18278 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18279 self.display_map
18280 .update(cx, |map, cx| map.snapshot(cx))
18281 .longest_row()
18282 }
18283
18284 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18285 self.display_map
18286 .update(cx, |map, cx| map.snapshot(cx))
18287 .max_point()
18288 }
18289
18290 pub fn text(&self, cx: &App) -> String {
18291 self.buffer.read(cx).read(cx).text()
18292 }
18293
18294 pub fn is_empty(&self, cx: &App) -> bool {
18295 self.buffer.read(cx).read(cx).is_empty()
18296 }
18297
18298 pub fn text_option(&self, cx: &App) -> Option<String> {
18299 let text = self.text(cx);
18300 let text = text.trim();
18301
18302 if text.is_empty() {
18303 return None;
18304 }
18305
18306 Some(text.to_string())
18307 }
18308
18309 pub fn set_text(
18310 &mut self,
18311 text: impl Into<Arc<str>>,
18312 window: &mut Window,
18313 cx: &mut Context<Self>,
18314 ) {
18315 self.transact(window, cx, |this, _, cx| {
18316 this.buffer
18317 .read(cx)
18318 .as_singleton()
18319 .expect("you can only call set_text on editors for singleton buffers")
18320 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18321 });
18322 }
18323
18324 pub fn display_text(&self, cx: &mut App) -> String {
18325 self.display_map
18326 .update(cx, |map, cx| map.snapshot(cx))
18327 .text()
18328 }
18329
18330 fn create_minimap(
18331 &self,
18332 minimap_settings: MinimapSettings,
18333 window: &mut Window,
18334 cx: &mut Context<Self>,
18335 ) -> Option<Entity<Self>> {
18336 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18337 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18338 }
18339
18340 fn initialize_new_minimap(
18341 &self,
18342 minimap_settings: MinimapSettings,
18343 window: &mut Window,
18344 cx: &mut Context<Self>,
18345 ) -> Entity<Self> {
18346 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18347
18348 let mut minimap = Editor::new_internal(
18349 EditorMode::Minimap {
18350 parent: cx.weak_entity(),
18351 },
18352 self.buffer.clone(),
18353 None,
18354 Some(self.display_map.clone()),
18355 window,
18356 cx,
18357 );
18358 minimap.scroll_manager.clone_state(&self.scroll_manager);
18359 minimap.set_text_style_refinement(TextStyleRefinement {
18360 font_size: Some(MINIMAP_FONT_SIZE),
18361 font_weight: Some(MINIMAP_FONT_WEIGHT),
18362 ..Default::default()
18363 });
18364 minimap.update_minimap_configuration(minimap_settings, cx);
18365 cx.new(|_| minimap)
18366 }
18367
18368 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18369 let current_line_highlight = minimap_settings
18370 .current_line_highlight
18371 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18372 self.set_current_line_highlight(Some(current_line_highlight));
18373 }
18374
18375 pub fn minimap(&self) -> Option<&Entity<Self>> {
18376 self.minimap
18377 .as_ref()
18378 .filter(|_| self.minimap_visibility.visible())
18379 }
18380
18381 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18382 let mut wrap_guides = smallvec![];
18383
18384 if self.show_wrap_guides == Some(false) {
18385 return wrap_guides;
18386 }
18387
18388 let settings = self.buffer.read(cx).language_settings(cx);
18389 if settings.show_wrap_guides {
18390 match self.soft_wrap_mode(cx) {
18391 SoftWrap::Column(soft_wrap) => {
18392 wrap_guides.push((soft_wrap as usize, true));
18393 }
18394 SoftWrap::Bounded(soft_wrap) => {
18395 wrap_guides.push((soft_wrap as usize, true));
18396 }
18397 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18398 }
18399 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18400 }
18401
18402 wrap_guides
18403 }
18404
18405 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18406 let settings = self.buffer.read(cx).language_settings(cx);
18407 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18408 match mode {
18409 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18410 SoftWrap::None
18411 }
18412 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18413 language_settings::SoftWrap::PreferredLineLength => {
18414 SoftWrap::Column(settings.preferred_line_length)
18415 }
18416 language_settings::SoftWrap::Bounded => {
18417 SoftWrap::Bounded(settings.preferred_line_length)
18418 }
18419 }
18420 }
18421
18422 pub fn set_soft_wrap_mode(
18423 &mut self,
18424 mode: language_settings::SoftWrap,
18425
18426 cx: &mut Context<Self>,
18427 ) {
18428 self.soft_wrap_mode_override = Some(mode);
18429 cx.notify();
18430 }
18431
18432 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18433 self.hard_wrap = hard_wrap;
18434 cx.notify();
18435 }
18436
18437 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18438 self.text_style_refinement = Some(style);
18439 }
18440
18441 /// called by the Element so we know what style we were most recently rendered with.
18442 pub(crate) fn set_style(
18443 &mut self,
18444 style: EditorStyle,
18445 window: &mut Window,
18446 cx: &mut Context<Self>,
18447 ) {
18448 // We intentionally do not inform the display map about the minimap style
18449 // so that wrapping is not recalculated and stays consistent for the editor
18450 // and its linked minimap.
18451 if !self.mode.is_minimap() {
18452 let rem_size = window.rem_size();
18453 self.display_map.update(cx, |map, cx| {
18454 map.set_font(
18455 style.text.font(),
18456 style.text.font_size.to_pixels(rem_size),
18457 cx,
18458 )
18459 });
18460 }
18461 self.style = Some(style);
18462 }
18463
18464 pub fn style(&self) -> Option<&EditorStyle> {
18465 self.style.as_ref()
18466 }
18467
18468 // Called by the element. This method is not designed to be called outside of the editor
18469 // element's layout code because it does not notify when rewrapping is computed synchronously.
18470 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18471 self.display_map
18472 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18473 }
18474
18475 pub fn set_soft_wrap(&mut self) {
18476 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18477 }
18478
18479 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18480 if self.soft_wrap_mode_override.is_some() {
18481 self.soft_wrap_mode_override.take();
18482 } else {
18483 let soft_wrap = match self.soft_wrap_mode(cx) {
18484 SoftWrap::GitDiff => return,
18485 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18486 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18487 language_settings::SoftWrap::None
18488 }
18489 };
18490 self.soft_wrap_mode_override = Some(soft_wrap);
18491 }
18492 cx.notify();
18493 }
18494
18495 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18496 let Some(workspace) = self.workspace() else {
18497 return;
18498 };
18499 let fs = workspace.read(cx).app_state().fs.clone();
18500 let current_show = TabBarSettings::get_global(cx).show;
18501 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18502 setting.show = Some(!current_show);
18503 });
18504 }
18505
18506 pub fn toggle_indent_guides(
18507 &mut self,
18508 _: &ToggleIndentGuides,
18509 _: &mut Window,
18510 cx: &mut Context<Self>,
18511 ) {
18512 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18513 self.buffer
18514 .read(cx)
18515 .language_settings(cx)
18516 .indent_guides
18517 .enabled
18518 });
18519 self.show_indent_guides = Some(!currently_enabled);
18520 cx.notify();
18521 }
18522
18523 fn should_show_indent_guides(&self) -> Option<bool> {
18524 self.show_indent_guides
18525 }
18526
18527 pub fn toggle_line_numbers(
18528 &mut self,
18529 _: &ToggleLineNumbers,
18530 _: &mut Window,
18531 cx: &mut Context<Self>,
18532 ) {
18533 let mut editor_settings = EditorSettings::get_global(cx).clone();
18534 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18535 EditorSettings::override_global(editor_settings, cx);
18536 }
18537
18538 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18539 if let Some(show_line_numbers) = self.show_line_numbers {
18540 return show_line_numbers;
18541 }
18542 EditorSettings::get_global(cx).gutter.line_numbers
18543 }
18544
18545 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18546 self.use_relative_line_numbers
18547 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18548 }
18549
18550 pub fn toggle_relative_line_numbers(
18551 &mut self,
18552 _: &ToggleRelativeLineNumbers,
18553 _: &mut Window,
18554 cx: &mut Context<Self>,
18555 ) {
18556 let is_relative = self.should_use_relative_line_numbers(cx);
18557 self.set_relative_line_number(Some(!is_relative), cx)
18558 }
18559
18560 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18561 self.use_relative_line_numbers = is_relative;
18562 cx.notify();
18563 }
18564
18565 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18566 self.show_gutter = show_gutter;
18567 cx.notify();
18568 }
18569
18570 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18571 self.show_scrollbars = ScrollbarAxes {
18572 horizontal: show,
18573 vertical: show,
18574 };
18575 cx.notify();
18576 }
18577
18578 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18579 self.show_scrollbars.vertical = show;
18580 cx.notify();
18581 }
18582
18583 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18584 self.show_scrollbars.horizontal = show;
18585 cx.notify();
18586 }
18587
18588 pub fn set_minimap_visibility(
18589 &mut self,
18590 minimap_visibility: MinimapVisibility,
18591 window: &mut Window,
18592 cx: &mut Context<Self>,
18593 ) {
18594 if self.minimap_visibility != minimap_visibility {
18595 if minimap_visibility.visible() && self.minimap.is_none() {
18596 let minimap_settings = EditorSettings::get_global(cx).minimap;
18597 self.minimap =
18598 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18599 }
18600 self.minimap_visibility = minimap_visibility;
18601 cx.notify();
18602 }
18603 }
18604
18605 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18606 self.set_show_scrollbars(false, cx);
18607 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18608 }
18609
18610 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18611 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18612 }
18613
18614 /// Normally the text in full mode and auto height editors is padded on the
18615 /// left side by roughly half a character width for improved hit testing.
18616 ///
18617 /// Use this method to disable this for cases where this is not wanted (e.g.
18618 /// if you want to align the editor text with some other text above or below)
18619 /// or if you want to add this padding to single-line editors.
18620 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18621 self.offset_content = offset_content;
18622 cx.notify();
18623 }
18624
18625 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18626 self.show_line_numbers = Some(show_line_numbers);
18627 cx.notify();
18628 }
18629
18630 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18631 self.disable_expand_excerpt_buttons = true;
18632 cx.notify();
18633 }
18634
18635 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18636 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18637 cx.notify();
18638 }
18639
18640 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18641 self.show_code_actions = Some(show_code_actions);
18642 cx.notify();
18643 }
18644
18645 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18646 self.show_runnables = Some(show_runnables);
18647 cx.notify();
18648 }
18649
18650 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18651 self.show_breakpoints = Some(show_breakpoints);
18652 cx.notify();
18653 }
18654
18655 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18656 if self.display_map.read(cx).masked != masked {
18657 self.display_map.update(cx, |map, _| map.masked = masked);
18658 }
18659 cx.notify()
18660 }
18661
18662 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18663 self.show_wrap_guides = Some(show_wrap_guides);
18664 cx.notify();
18665 }
18666
18667 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18668 self.show_indent_guides = Some(show_indent_guides);
18669 cx.notify();
18670 }
18671
18672 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18673 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18674 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18675 && let Some(dir) = file.abs_path(cx).parent()
18676 {
18677 return Some(dir.to_owned());
18678 }
18679
18680 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18681 return Some(project_path.path.to_path_buf());
18682 }
18683 }
18684
18685 None
18686 }
18687
18688 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18689 self.active_excerpt(cx)?
18690 .1
18691 .read(cx)
18692 .file()
18693 .and_then(|f| f.as_local())
18694 }
18695
18696 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18697 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18698 let buffer = buffer.read(cx);
18699 if let Some(project_path) = buffer.project_path(cx) {
18700 let project = self.project()?.read(cx);
18701 project.absolute_path(&project_path, cx)
18702 } else {
18703 buffer
18704 .file()
18705 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18706 }
18707 })
18708 }
18709
18710 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18711 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18712 let project_path = buffer.read(cx).project_path(cx)?;
18713 let project = self.project()?.read(cx);
18714 let entry = project.entry_for_path(&project_path, cx)?;
18715 let path = entry.path.to_path_buf();
18716 Some(path)
18717 })
18718 }
18719
18720 pub fn reveal_in_finder(
18721 &mut self,
18722 _: &RevealInFileManager,
18723 _window: &mut Window,
18724 cx: &mut Context<Self>,
18725 ) {
18726 if let Some(target) = self.target_file(cx) {
18727 cx.reveal_path(&target.abs_path(cx));
18728 }
18729 }
18730
18731 pub fn copy_path(
18732 &mut self,
18733 _: &zed_actions::workspace::CopyPath,
18734 _window: &mut Window,
18735 cx: &mut Context<Self>,
18736 ) {
18737 if let Some(path) = self.target_file_abs_path(cx)
18738 && let Some(path) = path.to_str()
18739 {
18740 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18741 }
18742 }
18743
18744 pub fn copy_relative_path(
18745 &mut self,
18746 _: &zed_actions::workspace::CopyRelativePath,
18747 _window: &mut Window,
18748 cx: &mut Context<Self>,
18749 ) {
18750 if let Some(path) = self.target_file_path(cx)
18751 && let Some(path) = path.to_str()
18752 {
18753 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18754 }
18755 }
18756
18757 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18758 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18759 buffer.read(cx).project_path(cx)
18760 } else {
18761 None
18762 }
18763 }
18764
18765 // Returns true if the editor handled a go-to-line request
18766 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18767 maybe!({
18768 let breakpoint_store = self.breakpoint_store.as_ref()?;
18769
18770 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18771 else {
18772 self.clear_row_highlights::<ActiveDebugLine>();
18773 return None;
18774 };
18775
18776 let position = active_stack_frame.position;
18777 let buffer_id = position.buffer_id?;
18778 let snapshot = self
18779 .project
18780 .as_ref()?
18781 .read(cx)
18782 .buffer_for_id(buffer_id, cx)?
18783 .read(cx)
18784 .snapshot();
18785
18786 let mut handled = false;
18787 for (id, ExcerptRange { context, .. }) in
18788 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18789 {
18790 if context.start.cmp(&position, &snapshot).is_ge()
18791 || context.end.cmp(&position, &snapshot).is_lt()
18792 {
18793 continue;
18794 }
18795 let snapshot = self.buffer.read(cx).snapshot(cx);
18796 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18797
18798 handled = true;
18799 self.clear_row_highlights::<ActiveDebugLine>();
18800
18801 self.go_to_line::<ActiveDebugLine>(
18802 multibuffer_anchor,
18803 Some(cx.theme().colors().editor_debugger_active_line_background),
18804 window,
18805 cx,
18806 );
18807
18808 cx.notify();
18809 }
18810
18811 handled.then_some(())
18812 })
18813 .is_some()
18814 }
18815
18816 pub fn copy_file_name_without_extension(
18817 &mut self,
18818 _: &CopyFileNameWithoutExtension,
18819 _: &mut Window,
18820 cx: &mut Context<Self>,
18821 ) {
18822 if let Some(file) = self.target_file(cx)
18823 && let Some(file_stem) = file.path().file_stem()
18824 && let Some(name) = file_stem.to_str()
18825 {
18826 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18827 }
18828 }
18829
18830 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18831 if let Some(file) = self.target_file(cx)
18832 && let Some(file_name) = file.path().file_name()
18833 && let Some(name) = file_name.to_str()
18834 {
18835 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18836 }
18837 }
18838
18839 pub fn toggle_git_blame(
18840 &mut self,
18841 _: &::git::Blame,
18842 window: &mut Window,
18843 cx: &mut Context<Self>,
18844 ) {
18845 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18846
18847 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18848 self.start_git_blame(true, window, cx);
18849 }
18850
18851 cx.notify();
18852 }
18853
18854 pub fn toggle_git_blame_inline(
18855 &mut self,
18856 _: &ToggleGitBlameInline,
18857 window: &mut Window,
18858 cx: &mut Context<Self>,
18859 ) {
18860 self.toggle_git_blame_inline_internal(true, window, cx);
18861 cx.notify();
18862 }
18863
18864 pub fn open_git_blame_commit(
18865 &mut self,
18866 _: &OpenGitBlameCommit,
18867 window: &mut Window,
18868 cx: &mut Context<Self>,
18869 ) {
18870 self.open_git_blame_commit_internal(window, cx);
18871 }
18872
18873 fn open_git_blame_commit_internal(
18874 &mut self,
18875 window: &mut Window,
18876 cx: &mut Context<Self>,
18877 ) -> Option<()> {
18878 let blame = self.blame.as_ref()?;
18879 let snapshot = self.snapshot(window, cx);
18880 let cursor = self.selections.newest::<Point>(cx).head();
18881 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18882 let blame_entry = blame
18883 .update(cx, |blame, cx| {
18884 blame
18885 .blame_for_rows(
18886 &[RowInfo {
18887 buffer_id: Some(buffer.remote_id()),
18888 buffer_row: Some(point.row),
18889 ..Default::default()
18890 }],
18891 cx,
18892 )
18893 .next()
18894 })
18895 .flatten()?;
18896 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18897 let repo = blame.read(cx).repository(cx)?;
18898 let workspace = self.workspace()?.downgrade();
18899 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18900 None
18901 }
18902
18903 pub fn git_blame_inline_enabled(&self) -> bool {
18904 self.git_blame_inline_enabled
18905 }
18906
18907 pub fn toggle_selection_menu(
18908 &mut self,
18909 _: &ToggleSelectionMenu,
18910 _: &mut Window,
18911 cx: &mut Context<Self>,
18912 ) {
18913 self.show_selection_menu = self
18914 .show_selection_menu
18915 .map(|show_selections_menu| !show_selections_menu)
18916 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18917
18918 cx.notify();
18919 }
18920
18921 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18922 self.show_selection_menu
18923 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18924 }
18925
18926 fn start_git_blame(
18927 &mut self,
18928 user_triggered: bool,
18929 window: &mut Window,
18930 cx: &mut Context<Self>,
18931 ) {
18932 if let Some(project) = self.project() {
18933 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18934 return;
18935 };
18936
18937 if buffer.read(cx).file().is_none() {
18938 return;
18939 }
18940
18941 let focused = self.focus_handle(cx).contains_focused(window, cx);
18942
18943 let project = project.clone();
18944 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18945 self.blame_subscription =
18946 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18947 self.blame = Some(blame);
18948 }
18949 }
18950
18951 fn toggle_git_blame_inline_internal(
18952 &mut self,
18953 user_triggered: bool,
18954 window: &mut Window,
18955 cx: &mut Context<Self>,
18956 ) {
18957 if self.git_blame_inline_enabled {
18958 self.git_blame_inline_enabled = false;
18959 self.show_git_blame_inline = false;
18960 self.show_git_blame_inline_delay_task.take();
18961 } else {
18962 self.git_blame_inline_enabled = true;
18963 self.start_git_blame_inline(user_triggered, window, cx);
18964 }
18965
18966 cx.notify();
18967 }
18968
18969 fn start_git_blame_inline(
18970 &mut self,
18971 user_triggered: bool,
18972 window: &mut Window,
18973 cx: &mut Context<Self>,
18974 ) {
18975 self.start_git_blame(user_triggered, window, cx);
18976
18977 if ProjectSettings::get_global(cx)
18978 .git
18979 .inline_blame_delay()
18980 .is_some()
18981 {
18982 self.start_inline_blame_timer(window, cx);
18983 } else {
18984 self.show_git_blame_inline = true
18985 }
18986 }
18987
18988 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18989 self.blame.as_ref()
18990 }
18991
18992 pub fn show_git_blame_gutter(&self) -> bool {
18993 self.show_git_blame_gutter
18994 }
18995
18996 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18997 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18998 }
18999
19000 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19001 self.show_git_blame_inline
19002 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19003 && !self.newest_selection_head_on_empty_line(cx)
19004 && self.has_blame_entries(cx)
19005 }
19006
19007 fn has_blame_entries(&self, cx: &App) -> bool {
19008 self.blame()
19009 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19010 }
19011
19012 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19013 let cursor_anchor = self.selections.newest_anchor().head();
19014
19015 let snapshot = self.buffer.read(cx).snapshot(cx);
19016 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19017
19018 snapshot.line_len(buffer_row) == 0
19019 }
19020
19021 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19022 let buffer_and_selection = maybe!({
19023 let selection = self.selections.newest::<Point>(cx);
19024 let selection_range = selection.range();
19025
19026 let multi_buffer = self.buffer().read(cx);
19027 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19028 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19029
19030 let (buffer, range, _) = if selection.reversed {
19031 buffer_ranges.first()
19032 } else {
19033 buffer_ranges.last()
19034 }?;
19035
19036 let selection = text::ToPoint::to_point(&range.start, buffer).row
19037 ..text::ToPoint::to_point(&range.end, buffer).row;
19038 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19039 });
19040
19041 let Some((buffer, selection)) = buffer_and_selection else {
19042 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19043 };
19044
19045 let Some(project) = self.project() else {
19046 return Task::ready(Err(anyhow!("editor does not have project")));
19047 };
19048
19049 project.update(cx, |project, cx| {
19050 project.get_permalink_to_line(&buffer, selection, cx)
19051 })
19052 }
19053
19054 pub fn copy_permalink_to_line(
19055 &mut self,
19056 _: &CopyPermalinkToLine,
19057 window: &mut Window,
19058 cx: &mut Context<Self>,
19059 ) {
19060 let permalink_task = self.get_permalink_to_line(cx);
19061 let workspace = self.workspace();
19062
19063 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19064 Ok(permalink) => {
19065 cx.update(|_, cx| {
19066 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19067 })
19068 .ok();
19069 }
19070 Err(err) => {
19071 let message = format!("Failed to copy permalink: {err}");
19072
19073 anyhow::Result::<()>::Err(err).log_err();
19074
19075 if let Some(workspace) = workspace {
19076 workspace
19077 .update_in(cx, |workspace, _, cx| {
19078 struct CopyPermalinkToLine;
19079
19080 workspace.show_toast(
19081 Toast::new(
19082 NotificationId::unique::<CopyPermalinkToLine>(),
19083 message,
19084 ),
19085 cx,
19086 )
19087 })
19088 .ok();
19089 }
19090 }
19091 })
19092 .detach();
19093 }
19094
19095 pub fn copy_file_location(
19096 &mut self,
19097 _: &CopyFileLocation,
19098 _: &mut Window,
19099 cx: &mut Context<Self>,
19100 ) {
19101 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19102 if let Some(file) = self.target_file(cx)
19103 && let Some(path) = file.path().to_str()
19104 {
19105 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19106 }
19107 }
19108
19109 pub fn open_permalink_to_line(
19110 &mut self,
19111 _: &OpenPermalinkToLine,
19112 window: &mut Window,
19113 cx: &mut Context<Self>,
19114 ) {
19115 let permalink_task = self.get_permalink_to_line(cx);
19116 let workspace = self.workspace();
19117
19118 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19119 Ok(permalink) => {
19120 cx.update(|_, cx| {
19121 cx.open_url(permalink.as_ref());
19122 })
19123 .ok();
19124 }
19125 Err(err) => {
19126 let message = format!("Failed to open permalink: {err}");
19127
19128 anyhow::Result::<()>::Err(err).log_err();
19129
19130 if let Some(workspace) = workspace {
19131 workspace
19132 .update(cx, |workspace, cx| {
19133 struct OpenPermalinkToLine;
19134
19135 workspace.show_toast(
19136 Toast::new(
19137 NotificationId::unique::<OpenPermalinkToLine>(),
19138 message,
19139 ),
19140 cx,
19141 )
19142 })
19143 .ok();
19144 }
19145 }
19146 })
19147 .detach();
19148 }
19149
19150 pub fn insert_uuid_v4(
19151 &mut self,
19152 _: &InsertUuidV4,
19153 window: &mut Window,
19154 cx: &mut Context<Self>,
19155 ) {
19156 self.insert_uuid(UuidVersion::V4, window, cx);
19157 }
19158
19159 pub fn insert_uuid_v7(
19160 &mut self,
19161 _: &InsertUuidV7,
19162 window: &mut Window,
19163 cx: &mut Context<Self>,
19164 ) {
19165 self.insert_uuid(UuidVersion::V7, window, cx);
19166 }
19167
19168 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19169 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19170 self.transact(window, cx, |this, window, cx| {
19171 let edits = this
19172 .selections
19173 .all::<Point>(cx)
19174 .into_iter()
19175 .map(|selection| {
19176 let uuid = match version {
19177 UuidVersion::V4 => uuid::Uuid::new_v4(),
19178 UuidVersion::V7 => uuid::Uuid::now_v7(),
19179 };
19180
19181 (selection.range(), uuid.to_string())
19182 });
19183 this.edit(edits, cx);
19184 this.refresh_edit_prediction(true, false, window, cx);
19185 });
19186 }
19187
19188 pub fn open_selections_in_multibuffer(
19189 &mut self,
19190 _: &OpenSelectionsInMultibuffer,
19191 window: &mut Window,
19192 cx: &mut Context<Self>,
19193 ) {
19194 let multibuffer = self.buffer.read(cx);
19195
19196 let Some(buffer) = multibuffer.as_singleton() else {
19197 return;
19198 };
19199
19200 let Some(workspace) = self.workspace() else {
19201 return;
19202 };
19203
19204 let title = multibuffer.title(cx).to_string();
19205
19206 let locations = self
19207 .selections
19208 .all_anchors(cx)
19209 .iter()
19210 .map(|selection| Location {
19211 buffer: buffer.clone(),
19212 range: selection.start.text_anchor..selection.end.text_anchor,
19213 })
19214 .collect::<Vec<_>>();
19215
19216 cx.spawn_in(window, async move |_, cx| {
19217 workspace.update_in(cx, |workspace, window, cx| {
19218 Self::open_locations_in_multibuffer(
19219 workspace,
19220 locations,
19221 format!("Selections for '{title}'"),
19222 false,
19223 MultibufferSelectionMode::All,
19224 window,
19225 cx,
19226 );
19227 })
19228 })
19229 .detach();
19230 }
19231
19232 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19233 /// last highlight added will be used.
19234 ///
19235 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19236 pub fn highlight_rows<T: 'static>(
19237 &mut self,
19238 range: Range<Anchor>,
19239 color: Hsla,
19240 options: RowHighlightOptions,
19241 cx: &mut Context<Self>,
19242 ) {
19243 let snapshot = self.buffer().read(cx).snapshot(cx);
19244 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19245 let ix = row_highlights.binary_search_by(|highlight| {
19246 Ordering::Equal
19247 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19248 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19249 });
19250
19251 if let Err(mut ix) = ix {
19252 let index = post_inc(&mut self.highlight_order);
19253
19254 // If this range intersects with the preceding highlight, then merge it with
19255 // the preceding highlight. Otherwise insert a new highlight.
19256 let mut merged = false;
19257 if ix > 0 {
19258 let prev_highlight = &mut row_highlights[ix - 1];
19259 if prev_highlight
19260 .range
19261 .end
19262 .cmp(&range.start, &snapshot)
19263 .is_ge()
19264 {
19265 ix -= 1;
19266 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19267 prev_highlight.range.end = range.end;
19268 }
19269 merged = true;
19270 prev_highlight.index = index;
19271 prev_highlight.color = color;
19272 prev_highlight.options = options;
19273 }
19274 }
19275
19276 if !merged {
19277 row_highlights.insert(
19278 ix,
19279 RowHighlight {
19280 range,
19281 index,
19282 color,
19283 options,
19284 type_id: TypeId::of::<T>(),
19285 },
19286 );
19287 }
19288
19289 // If any of the following highlights intersect with this one, merge them.
19290 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19291 let highlight = &row_highlights[ix];
19292 if next_highlight
19293 .range
19294 .start
19295 .cmp(&highlight.range.end, &snapshot)
19296 .is_le()
19297 {
19298 if next_highlight
19299 .range
19300 .end
19301 .cmp(&highlight.range.end, &snapshot)
19302 .is_gt()
19303 {
19304 row_highlights[ix].range.end = next_highlight.range.end;
19305 }
19306 row_highlights.remove(ix + 1);
19307 } else {
19308 break;
19309 }
19310 }
19311 }
19312 }
19313
19314 /// Remove any highlighted row ranges of the given type that intersect the
19315 /// given ranges.
19316 pub fn remove_highlighted_rows<T: 'static>(
19317 &mut self,
19318 ranges_to_remove: Vec<Range<Anchor>>,
19319 cx: &mut Context<Self>,
19320 ) {
19321 let snapshot = self.buffer().read(cx).snapshot(cx);
19322 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19323 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19324 row_highlights.retain(|highlight| {
19325 while let Some(range_to_remove) = ranges_to_remove.peek() {
19326 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19327 Ordering::Less | Ordering::Equal => {
19328 ranges_to_remove.next();
19329 }
19330 Ordering::Greater => {
19331 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19332 Ordering::Less | Ordering::Equal => {
19333 return false;
19334 }
19335 Ordering::Greater => break,
19336 }
19337 }
19338 }
19339 }
19340
19341 true
19342 })
19343 }
19344
19345 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19346 pub fn clear_row_highlights<T: 'static>(&mut self) {
19347 self.highlighted_rows.remove(&TypeId::of::<T>());
19348 }
19349
19350 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19351 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19352 self.highlighted_rows
19353 .get(&TypeId::of::<T>())
19354 .map_or(&[] as &[_], |vec| vec.as_slice())
19355 .iter()
19356 .map(|highlight| (highlight.range.clone(), highlight.color))
19357 }
19358
19359 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19360 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19361 /// Allows to ignore certain kinds of highlights.
19362 pub fn highlighted_display_rows(
19363 &self,
19364 window: &mut Window,
19365 cx: &mut App,
19366 ) -> BTreeMap<DisplayRow, LineHighlight> {
19367 let snapshot = self.snapshot(window, cx);
19368 let mut used_highlight_orders = HashMap::default();
19369 self.highlighted_rows
19370 .iter()
19371 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19372 .fold(
19373 BTreeMap::<DisplayRow, LineHighlight>::new(),
19374 |mut unique_rows, highlight| {
19375 let start = highlight.range.start.to_display_point(&snapshot);
19376 let end = highlight.range.end.to_display_point(&snapshot);
19377 let start_row = start.row().0;
19378 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19379 && end.column() == 0
19380 {
19381 end.row().0.saturating_sub(1)
19382 } else {
19383 end.row().0
19384 };
19385 for row in start_row..=end_row {
19386 let used_index =
19387 used_highlight_orders.entry(row).or_insert(highlight.index);
19388 if highlight.index >= *used_index {
19389 *used_index = highlight.index;
19390 unique_rows.insert(
19391 DisplayRow(row),
19392 LineHighlight {
19393 include_gutter: highlight.options.include_gutter,
19394 border: None,
19395 background: highlight.color.into(),
19396 type_id: Some(highlight.type_id),
19397 },
19398 );
19399 }
19400 }
19401 unique_rows
19402 },
19403 )
19404 }
19405
19406 pub fn highlighted_display_row_for_autoscroll(
19407 &self,
19408 snapshot: &DisplaySnapshot,
19409 ) -> Option<DisplayRow> {
19410 self.highlighted_rows
19411 .values()
19412 .flat_map(|highlighted_rows| highlighted_rows.iter())
19413 .filter_map(|highlight| {
19414 if highlight.options.autoscroll {
19415 Some(highlight.range.start.to_display_point(snapshot).row())
19416 } else {
19417 None
19418 }
19419 })
19420 .min()
19421 }
19422
19423 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19424 self.highlight_background::<SearchWithinRange>(
19425 ranges,
19426 |colors| colors.colors().editor_document_highlight_read_background,
19427 cx,
19428 )
19429 }
19430
19431 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19432 self.breadcrumb_header = Some(new_header);
19433 }
19434
19435 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19436 self.clear_background_highlights::<SearchWithinRange>(cx);
19437 }
19438
19439 pub fn highlight_background<T: 'static>(
19440 &mut self,
19441 ranges: &[Range<Anchor>],
19442 color_fetcher: fn(&Theme) -> Hsla,
19443 cx: &mut Context<Self>,
19444 ) {
19445 self.background_highlights.insert(
19446 HighlightKey::Type(TypeId::of::<T>()),
19447 (color_fetcher, Arc::from(ranges)),
19448 );
19449 self.scrollbar_marker_state.dirty = true;
19450 cx.notify();
19451 }
19452
19453 pub fn highlight_background_key<T: 'static>(
19454 &mut self,
19455 key: usize,
19456 ranges: &[Range<Anchor>],
19457 color_fetcher: fn(&Theme) -> Hsla,
19458 cx: &mut Context<Self>,
19459 ) {
19460 self.background_highlights.insert(
19461 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19462 (color_fetcher, Arc::from(ranges)),
19463 );
19464 self.scrollbar_marker_state.dirty = true;
19465 cx.notify();
19466 }
19467
19468 pub fn clear_background_highlights<T: 'static>(
19469 &mut self,
19470 cx: &mut Context<Self>,
19471 ) -> Option<BackgroundHighlight> {
19472 let text_highlights = self
19473 .background_highlights
19474 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19475 if !text_highlights.1.is_empty() {
19476 self.scrollbar_marker_state.dirty = true;
19477 cx.notify();
19478 }
19479 Some(text_highlights)
19480 }
19481
19482 pub fn highlight_gutter<T: 'static>(
19483 &mut self,
19484 ranges: impl Into<Vec<Range<Anchor>>>,
19485 color_fetcher: fn(&App) -> Hsla,
19486 cx: &mut Context<Self>,
19487 ) {
19488 self.gutter_highlights
19489 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19490 cx.notify();
19491 }
19492
19493 pub fn clear_gutter_highlights<T: 'static>(
19494 &mut self,
19495 cx: &mut Context<Self>,
19496 ) -> Option<GutterHighlight> {
19497 cx.notify();
19498 self.gutter_highlights.remove(&TypeId::of::<T>())
19499 }
19500
19501 pub fn insert_gutter_highlight<T: 'static>(
19502 &mut self,
19503 range: Range<Anchor>,
19504 color_fetcher: fn(&App) -> Hsla,
19505 cx: &mut Context<Self>,
19506 ) {
19507 let snapshot = self.buffer().read(cx).snapshot(cx);
19508 let mut highlights = self
19509 .gutter_highlights
19510 .remove(&TypeId::of::<T>())
19511 .map(|(_, highlights)| highlights)
19512 .unwrap_or_default();
19513 let ix = highlights.binary_search_by(|highlight| {
19514 Ordering::Equal
19515 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19516 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19517 });
19518 if let Err(ix) = ix {
19519 highlights.insert(ix, range);
19520 }
19521 self.gutter_highlights
19522 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19523 }
19524
19525 pub fn remove_gutter_highlights<T: 'static>(
19526 &mut self,
19527 ranges_to_remove: Vec<Range<Anchor>>,
19528 cx: &mut Context<Self>,
19529 ) {
19530 let snapshot = self.buffer().read(cx).snapshot(cx);
19531 let Some((color_fetcher, mut gutter_highlights)) =
19532 self.gutter_highlights.remove(&TypeId::of::<T>())
19533 else {
19534 return;
19535 };
19536 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19537 gutter_highlights.retain(|highlight| {
19538 while let Some(range_to_remove) = ranges_to_remove.peek() {
19539 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19540 Ordering::Less | Ordering::Equal => {
19541 ranges_to_remove.next();
19542 }
19543 Ordering::Greater => {
19544 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19545 Ordering::Less | Ordering::Equal => {
19546 return false;
19547 }
19548 Ordering::Greater => break,
19549 }
19550 }
19551 }
19552 }
19553
19554 true
19555 });
19556 self.gutter_highlights
19557 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19558 }
19559
19560 #[cfg(feature = "test-support")]
19561 pub fn all_text_highlights(
19562 &self,
19563 window: &mut Window,
19564 cx: &mut Context<Self>,
19565 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19566 let snapshot = self.snapshot(window, cx);
19567 self.display_map.update(cx, |display_map, _| {
19568 display_map
19569 .all_text_highlights()
19570 .map(|highlight| {
19571 let (style, ranges) = highlight.as_ref();
19572 (
19573 *style,
19574 ranges
19575 .iter()
19576 .map(|range| range.clone().to_display_points(&snapshot))
19577 .collect(),
19578 )
19579 })
19580 .collect()
19581 })
19582 }
19583
19584 #[cfg(feature = "test-support")]
19585 pub fn all_text_background_highlights(
19586 &self,
19587 window: &mut Window,
19588 cx: &mut Context<Self>,
19589 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19590 let snapshot = self.snapshot(window, cx);
19591 let buffer = &snapshot.buffer_snapshot;
19592 let start = buffer.anchor_before(0);
19593 let end = buffer.anchor_after(buffer.len());
19594 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19595 }
19596
19597 #[cfg(feature = "test-support")]
19598 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19599 let snapshot = self.buffer().read(cx).snapshot(cx);
19600
19601 let highlights = self
19602 .background_highlights
19603 .get(&HighlightKey::Type(TypeId::of::<
19604 items::BufferSearchHighlights,
19605 >()));
19606
19607 if let Some((_color, ranges)) = highlights {
19608 ranges
19609 .iter()
19610 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19611 .collect_vec()
19612 } else {
19613 vec![]
19614 }
19615 }
19616
19617 fn document_highlights_for_position<'a>(
19618 &'a self,
19619 position: Anchor,
19620 buffer: &'a MultiBufferSnapshot,
19621 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19622 let read_highlights = self
19623 .background_highlights
19624 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19625 .map(|h| &h.1);
19626 let write_highlights = self
19627 .background_highlights
19628 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19629 .map(|h| &h.1);
19630 let left_position = position.bias_left(buffer);
19631 let right_position = position.bias_right(buffer);
19632 read_highlights
19633 .into_iter()
19634 .chain(write_highlights)
19635 .flat_map(move |ranges| {
19636 let start_ix = match ranges.binary_search_by(|probe| {
19637 let cmp = probe.end.cmp(&left_position, buffer);
19638 if cmp.is_ge() {
19639 Ordering::Greater
19640 } else {
19641 Ordering::Less
19642 }
19643 }) {
19644 Ok(i) | Err(i) => i,
19645 };
19646
19647 ranges[start_ix..]
19648 .iter()
19649 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19650 })
19651 }
19652
19653 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19654 self.background_highlights
19655 .get(&HighlightKey::Type(TypeId::of::<T>()))
19656 .is_some_and(|(_, highlights)| !highlights.is_empty())
19657 }
19658
19659 pub fn background_highlights_in_range(
19660 &self,
19661 search_range: Range<Anchor>,
19662 display_snapshot: &DisplaySnapshot,
19663 theme: &Theme,
19664 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19665 let mut results = Vec::new();
19666 for (color_fetcher, ranges) in self.background_highlights.values() {
19667 let color = color_fetcher(theme);
19668 let start_ix = match ranges.binary_search_by(|probe| {
19669 let cmp = probe
19670 .end
19671 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19672 if cmp.is_gt() {
19673 Ordering::Greater
19674 } else {
19675 Ordering::Less
19676 }
19677 }) {
19678 Ok(i) | Err(i) => i,
19679 };
19680 for range in &ranges[start_ix..] {
19681 if range
19682 .start
19683 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19684 .is_ge()
19685 {
19686 break;
19687 }
19688
19689 let start = range.start.to_display_point(display_snapshot);
19690 let end = range.end.to_display_point(display_snapshot);
19691 results.push((start..end, color))
19692 }
19693 }
19694 results
19695 }
19696
19697 pub fn background_highlight_row_ranges<T: 'static>(
19698 &self,
19699 search_range: Range<Anchor>,
19700 display_snapshot: &DisplaySnapshot,
19701 count: usize,
19702 ) -> Vec<RangeInclusive<DisplayPoint>> {
19703 let mut results = Vec::new();
19704 let Some((_, ranges)) = self
19705 .background_highlights
19706 .get(&HighlightKey::Type(TypeId::of::<T>()))
19707 else {
19708 return vec![];
19709 };
19710
19711 let start_ix = match ranges.binary_search_by(|probe| {
19712 let cmp = probe
19713 .end
19714 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19715 if cmp.is_gt() {
19716 Ordering::Greater
19717 } else {
19718 Ordering::Less
19719 }
19720 }) {
19721 Ok(i) | Err(i) => i,
19722 };
19723 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19724 if let (Some(start_display), Some(end_display)) = (start, end) {
19725 results.push(
19726 start_display.to_display_point(display_snapshot)
19727 ..=end_display.to_display_point(display_snapshot),
19728 );
19729 }
19730 };
19731 let mut start_row: Option<Point> = None;
19732 let mut end_row: Option<Point> = None;
19733 if ranges.len() > count {
19734 return Vec::new();
19735 }
19736 for range in &ranges[start_ix..] {
19737 if range
19738 .start
19739 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19740 .is_ge()
19741 {
19742 break;
19743 }
19744 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19745 if let Some(current_row) = &end_row
19746 && end.row == current_row.row
19747 {
19748 continue;
19749 }
19750 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19751 if start_row.is_none() {
19752 assert_eq!(end_row, None);
19753 start_row = Some(start);
19754 end_row = Some(end);
19755 continue;
19756 }
19757 if let Some(current_end) = end_row.as_mut() {
19758 if start.row > current_end.row + 1 {
19759 push_region(start_row, end_row);
19760 start_row = Some(start);
19761 end_row = Some(end);
19762 } else {
19763 // Merge two hunks.
19764 *current_end = end;
19765 }
19766 } else {
19767 unreachable!();
19768 }
19769 }
19770 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19771 push_region(start_row, end_row);
19772 results
19773 }
19774
19775 pub fn gutter_highlights_in_range(
19776 &self,
19777 search_range: Range<Anchor>,
19778 display_snapshot: &DisplaySnapshot,
19779 cx: &App,
19780 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19781 let mut results = Vec::new();
19782 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19783 let color = color_fetcher(cx);
19784 let start_ix = match ranges.binary_search_by(|probe| {
19785 let cmp = probe
19786 .end
19787 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19788 if cmp.is_gt() {
19789 Ordering::Greater
19790 } else {
19791 Ordering::Less
19792 }
19793 }) {
19794 Ok(i) | Err(i) => i,
19795 };
19796 for range in &ranges[start_ix..] {
19797 if range
19798 .start
19799 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19800 .is_ge()
19801 {
19802 break;
19803 }
19804
19805 let start = range.start.to_display_point(display_snapshot);
19806 let end = range.end.to_display_point(display_snapshot);
19807 results.push((start..end, color))
19808 }
19809 }
19810 results
19811 }
19812
19813 /// Get the text ranges corresponding to the redaction query
19814 pub fn redacted_ranges(
19815 &self,
19816 search_range: Range<Anchor>,
19817 display_snapshot: &DisplaySnapshot,
19818 cx: &App,
19819 ) -> Vec<Range<DisplayPoint>> {
19820 display_snapshot
19821 .buffer_snapshot
19822 .redacted_ranges(search_range, |file| {
19823 if let Some(file) = file {
19824 file.is_private()
19825 && EditorSettings::get(
19826 Some(SettingsLocation {
19827 worktree_id: file.worktree_id(cx),
19828 path: file.path().as_ref(),
19829 }),
19830 cx,
19831 )
19832 .redact_private_values
19833 } else {
19834 false
19835 }
19836 })
19837 .map(|range| {
19838 range.start.to_display_point(display_snapshot)
19839 ..range.end.to_display_point(display_snapshot)
19840 })
19841 .collect()
19842 }
19843
19844 pub fn highlight_text_key<T: 'static>(
19845 &mut self,
19846 key: usize,
19847 ranges: Vec<Range<Anchor>>,
19848 style: HighlightStyle,
19849 cx: &mut Context<Self>,
19850 ) {
19851 self.display_map.update(cx, |map, _| {
19852 map.highlight_text(
19853 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19854 ranges,
19855 style,
19856 );
19857 });
19858 cx.notify();
19859 }
19860
19861 pub fn highlight_text<T: 'static>(
19862 &mut self,
19863 ranges: Vec<Range<Anchor>>,
19864 style: HighlightStyle,
19865 cx: &mut Context<Self>,
19866 ) {
19867 self.display_map.update(cx, |map, _| {
19868 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19869 });
19870 cx.notify();
19871 }
19872
19873 pub(crate) fn highlight_inlays<T: 'static>(
19874 &mut self,
19875 highlights: Vec<InlayHighlight>,
19876 style: HighlightStyle,
19877 cx: &mut Context<Self>,
19878 ) {
19879 self.display_map.update(cx, |map, _| {
19880 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19881 });
19882 cx.notify();
19883 }
19884
19885 pub fn text_highlights<'a, T: 'static>(
19886 &'a self,
19887 cx: &'a App,
19888 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19889 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19890 }
19891
19892 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19893 let cleared = self
19894 .display_map
19895 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19896 if cleared {
19897 cx.notify();
19898 }
19899 }
19900
19901 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19902 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19903 && self.focus_handle.is_focused(window)
19904 }
19905
19906 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19907 self.show_cursor_when_unfocused = is_enabled;
19908 cx.notify();
19909 }
19910
19911 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19912 cx.notify();
19913 }
19914
19915 fn on_debug_session_event(
19916 &mut self,
19917 _session: Entity<Session>,
19918 event: &SessionEvent,
19919 cx: &mut Context<Self>,
19920 ) {
19921 if let SessionEvent::InvalidateInlineValue = event {
19922 self.refresh_inline_values(cx);
19923 }
19924 }
19925
19926 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19927 let Some(project) = self.project.clone() else {
19928 return;
19929 };
19930
19931 if !self.inline_value_cache.enabled {
19932 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19933 self.splice_inlays(&inlays, Vec::new(), cx);
19934 return;
19935 }
19936
19937 let current_execution_position = self
19938 .highlighted_rows
19939 .get(&TypeId::of::<ActiveDebugLine>())
19940 .and_then(|lines| lines.last().map(|line| line.range.end));
19941
19942 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19943 let inline_values = editor
19944 .update(cx, |editor, cx| {
19945 let Some(current_execution_position) = current_execution_position else {
19946 return Some(Task::ready(Ok(Vec::new())));
19947 };
19948
19949 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19950 let snapshot = buffer.snapshot(cx);
19951
19952 let excerpt = snapshot.excerpt_containing(
19953 current_execution_position..current_execution_position,
19954 )?;
19955
19956 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19957 })?;
19958
19959 let range =
19960 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19961
19962 project.inline_values(buffer, range, cx)
19963 })
19964 .ok()
19965 .flatten()?
19966 .await
19967 .context("refreshing debugger inlays")
19968 .log_err()?;
19969
19970 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19971
19972 for (buffer_id, inline_value) in inline_values
19973 .into_iter()
19974 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19975 {
19976 buffer_inline_values
19977 .entry(buffer_id)
19978 .or_default()
19979 .push(inline_value);
19980 }
19981
19982 editor
19983 .update(cx, |editor, cx| {
19984 let snapshot = editor.buffer.read(cx).snapshot(cx);
19985 let mut new_inlays = Vec::default();
19986
19987 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19988 let buffer_id = buffer_snapshot.remote_id();
19989 buffer_inline_values
19990 .get(&buffer_id)
19991 .into_iter()
19992 .flatten()
19993 .for_each(|hint| {
19994 let inlay = Inlay::debugger(
19995 post_inc(&mut editor.next_inlay_id),
19996 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19997 hint.text(),
19998 );
19999 if !inlay.text.chars().contains(&'\n') {
20000 new_inlays.push(inlay);
20001 }
20002 });
20003 }
20004
20005 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20006 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20007
20008 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20009 })
20010 .ok()?;
20011 Some(())
20012 });
20013 }
20014
20015 fn on_buffer_event(
20016 &mut self,
20017 multibuffer: &Entity<MultiBuffer>,
20018 event: &multi_buffer::Event,
20019 window: &mut Window,
20020 cx: &mut Context<Self>,
20021 ) {
20022 match event {
20023 multi_buffer::Event::Edited {
20024 singleton_buffer_edited,
20025 edited_buffer,
20026 } => {
20027 self.scrollbar_marker_state.dirty = true;
20028 self.active_indent_guides_state.dirty = true;
20029 self.refresh_active_diagnostics(cx);
20030 self.refresh_code_actions(window, cx);
20031 self.refresh_selected_text_highlights(true, window, cx);
20032 self.refresh_single_line_folds(window, cx);
20033 refresh_matching_bracket_highlights(self, window, cx);
20034 if self.has_active_edit_prediction() {
20035 self.update_visible_edit_prediction(window, cx);
20036 }
20037 if let Some(project) = self.project.as_ref()
20038 && let Some(edited_buffer) = edited_buffer
20039 {
20040 project.update(cx, |project, cx| {
20041 self.registered_buffers
20042 .entry(edited_buffer.read(cx).remote_id())
20043 .or_insert_with(|| {
20044 project.register_buffer_with_language_servers(edited_buffer, cx)
20045 });
20046 });
20047 }
20048 cx.emit(EditorEvent::BufferEdited);
20049 cx.emit(SearchEvent::MatchesInvalidated);
20050
20051 if let Some(buffer) = edited_buffer {
20052 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20053 }
20054
20055 if *singleton_buffer_edited {
20056 if let Some(buffer) = edited_buffer
20057 && buffer.read(cx).file().is_none()
20058 {
20059 cx.emit(EditorEvent::TitleChanged);
20060 }
20061 if let Some(project) = &self.project {
20062 #[allow(clippy::mutable_key_type)]
20063 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20064 multibuffer
20065 .all_buffers()
20066 .into_iter()
20067 .filter_map(|buffer| {
20068 buffer.update(cx, |buffer, cx| {
20069 let language = buffer.language()?;
20070 let should_discard = project.update(cx, |project, cx| {
20071 project.is_local()
20072 && !project.has_language_servers_for(buffer, cx)
20073 });
20074 should_discard.not().then_some(language.clone())
20075 })
20076 })
20077 .collect::<HashSet<_>>()
20078 });
20079 if !languages_affected.is_empty() {
20080 self.refresh_inlay_hints(
20081 InlayHintRefreshReason::BufferEdited(languages_affected),
20082 cx,
20083 );
20084 }
20085 }
20086 }
20087
20088 let Some(project) = &self.project else { return };
20089 let (telemetry, is_via_ssh) = {
20090 let project = project.read(cx);
20091 let telemetry = project.client().telemetry().clone();
20092 let is_via_ssh = project.is_via_remote_server();
20093 (telemetry, is_via_ssh)
20094 };
20095 refresh_linked_ranges(self, window, cx);
20096 telemetry.log_edit_event("editor", is_via_ssh);
20097 }
20098 multi_buffer::Event::ExcerptsAdded {
20099 buffer,
20100 predecessor,
20101 excerpts,
20102 } => {
20103 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20104 let buffer_id = buffer.read(cx).remote_id();
20105 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20106 && let Some(project) = &self.project
20107 {
20108 update_uncommitted_diff_for_buffer(
20109 cx.entity(),
20110 project,
20111 [buffer.clone()],
20112 self.buffer.clone(),
20113 cx,
20114 )
20115 .detach();
20116 }
20117 self.update_lsp_data(false, Some(buffer_id), window, cx);
20118 cx.emit(EditorEvent::ExcerptsAdded {
20119 buffer: buffer.clone(),
20120 predecessor: *predecessor,
20121 excerpts: excerpts.clone(),
20122 });
20123 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20124 }
20125 multi_buffer::Event::ExcerptsRemoved {
20126 ids,
20127 removed_buffer_ids,
20128 } => {
20129 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20130 let buffer = self.buffer.read(cx);
20131 self.registered_buffers
20132 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20133 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20134 cx.emit(EditorEvent::ExcerptsRemoved {
20135 ids: ids.clone(),
20136 removed_buffer_ids: removed_buffer_ids.clone(),
20137 });
20138 }
20139 multi_buffer::Event::ExcerptsEdited {
20140 excerpt_ids,
20141 buffer_ids,
20142 } => {
20143 self.display_map.update(cx, |map, cx| {
20144 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20145 });
20146 cx.emit(EditorEvent::ExcerptsEdited {
20147 ids: excerpt_ids.clone(),
20148 });
20149 }
20150 multi_buffer::Event::ExcerptsExpanded { ids } => {
20151 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20152 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20153 }
20154 multi_buffer::Event::Reparsed(buffer_id) => {
20155 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20156 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20157
20158 cx.emit(EditorEvent::Reparsed(*buffer_id));
20159 }
20160 multi_buffer::Event::DiffHunksToggled => {
20161 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20162 }
20163 multi_buffer::Event::LanguageChanged(buffer_id) => {
20164 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20165 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20166 cx.emit(EditorEvent::Reparsed(*buffer_id));
20167 cx.notify();
20168 }
20169 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20170 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20171 multi_buffer::Event::FileHandleChanged
20172 | multi_buffer::Event::Reloaded
20173 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20174 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20175 multi_buffer::Event::DiagnosticsUpdated => {
20176 self.update_diagnostics_state(window, cx);
20177 }
20178 _ => {}
20179 };
20180 }
20181
20182 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20183 if !self.diagnostics_enabled() {
20184 return;
20185 }
20186 self.refresh_active_diagnostics(cx);
20187 self.refresh_inline_diagnostics(true, window, cx);
20188 self.scrollbar_marker_state.dirty = true;
20189 cx.notify();
20190 }
20191
20192 pub fn start_temporary_diff_override(&mut self) {
20193 self.load_diff_task.take();
20194 self.temporary_diff_override = true;
20195 }
20196
20197 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20198 self.temporary_diff_override = false;
20199 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20200 self.buffer.update(cx, |buffer, cx| {
20201 buffer.set_all_diff_hunks_collapsed(cx);
20202 });
20203
20204 if let Some(project) = self.project.clone() {
20205 self.load_diff_task = Some(
20206 update_uncommitted_diff_for_buffer(
20207 cx.entity(),
20208 &project,
20209 self.buffer.read(cx).all_buffers(),
20210 self.buffer.clone(),
20211 cx,
20212 )
20213 .shared(),
20214 );
20215 }
20216 }
20217
20218 fn on_display_map_changed(
20219 &mut self,
20220 _: Entity<DisplayMap>,
20221 _: &mut Window,
20222 cx: &mut Context<Self>,
20223 ) {
20224 cx.notify();
20225 }
20226
20227 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20228 if self.diagnostics_enabled() {
20229 let new_severity = EditorSettings::get_global(cx)
20230 .diagnostics_max_severity
20231 .unwrap_or(DiagnosticSeverity::Hint);
20232 self.set_max_diagnostics_severity(new_severity, cx);
20233 }
20234 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20235 self.update_edit_prediction_settings(cx);
20236 self.refresh_edit_prediction(true, false, window, cx);
20237 self.refresh_inline_values(cx);
20238 self.refresh_inlay_hints(
20239 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20240 self.selections.newest_anchor().head(),
20241 &self.buffer.read(cx).snapshot(cx),
20242 cx,
20243 )),
20244 cx,
20245 );
20246
20247 let old_cursor_shape = self.cursor_shape;
20248 let old_show_breadcrumbs = self.show_breadcrumbs;
20249
20250 {
20251 let editor_settings = EditorSettings::get_global(cx);
20252 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20253 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20254 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20255 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20256 }
20257
20258 if old_cursor_shape != self.cursor_shape {
20259 cx.emit(EditorEvent::CursorShapeChanged);
20260 }
20261
20262 if old_show_breadcrumbs != self.show_breadcrumbs {
20263 cx.emit(EditorEvent::BreadcrumbsChanged);
20264 }
20265
20266 let project_settings = ProjectSettings::get_global(cx);
20267 self.serialize_dirty_buffers =
20268 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20269
20270 if self.mode.is_full() {
20271 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20272 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20273 if self.show_inline_diagnostics != show_inline_diagnostics {
20274 self.show_inline_diagnostics = show_inline_diagnostics;
20275 self.refresh_inline_diagnostics(false, window, cx);
20276 }
20277
20278 if self.git_blame_inline_enabled != inline_blame_enabled {
20279 self.toggle_git_blame_inline_internal(false, window, cx);
20280 }
20281
20282 let minimap_settings = EditorSettings::get_global(cx).minimap;
20283 if self.minimap_visibility != MinimapVisibility::Disabled {
20284 if self.minimap_visibility.settings_visibility()
20285 != minimap_settings.minimap_enabled()
20286 {
20287 self.set_minimap_visibility(
20288 MinimapVisibility::for_mode(self.mode(), cx),
20289 window,
20290 cx,
20291 );
20292 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20293 minimap_entity.update(cx, |minimap_editor, cx| {
20294 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20295 })
20296 }
20297 }
20298 }
20299
20300 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20301 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20302 }) {
20303 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20304 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20305 }
20306 self.refresh_colors(false, None, window, cx);
20307 }
20308
20309 cx.notify();
20310 }
20311
20312 pub fn set_searchable(&mut self, searchable: bool) {
20313 self.searchable = searchable;
20314 }
20315
20316 pub fn searchable(&self) -> bool {
20317 self.searchable
20318 }
20319
20320 fn open_proposed_changes_editor(
20321 &mut self,
20322 _: &OpenProposedChangesEditor,
20323 window: &mut Window,
20324 cx: &mut Context<Self>,
20325 ) {
20326 let Some(workspace) = self.workspace() else {
20327 cx.propagate();
20328 return;
20329 };
20330
20331 let selections = self.selections.all::<usize>(cx);
20332 let multi_buffer = self.buffer.read(cx);
20333 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20334 let mut new_selections_by_buffer = HashMap::default();
20335 for selection in selections {
20336 for (buffer, range, _) in
20337 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20338 {
20339 let mut range = range.to_point(buffer);
20340 range.start.column = 0;
20341 range.end.column = buffer.line_len(range.end.row);
20342 new_selections_by_buffer
20343 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20344 .or_insert(Vec::new())
20345 .push(range)
20346 }
20347 }
20348
20349 let proposed_changes_buffers = new_selections_by_buffer
20350 .into_iter()
20351 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20352 .collect::<Vec<_>>();
20353 let proposed_changes_editor = cx.new(|cx| {
20354 ProposedChangesEditor::new(
20355 "Proposed changes",
20356 proposed_changes_buffers,
20357 self.project.clone(),
20358 window,
20359 cx,
20360 )
20361 });
20362
20363 window.defer(cx, move |window, cx| {
20364 workspace.update(cx, |workspace, cx| {
20365 workspace.active_pane().update(cx, |pane, cx| {
20366 pane.add_item(
20367 Box::new(proposed_changes_editor),
20368 true,
20369 true,
20370 None,
20371 window,
20372 cx,
20373 );
20374 });
20375 });
20376 });
20377 }
20378
20379 pub fn open_excerpts_in_split(
20380 &mut self,
20381 _: &OpenExcerptsSplit,
20382 window: &mut Window,
20383 cx: &mut Context<Self>,
20384 ) {
20385 self.open_excerpts_common(None, true, window, cx)
20386 }
20387
20388 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20389 self.open_excerpts_common(None, false, window, cx)
20390 }
20391
20392 fn open_excerpts_common(
20393 &mut self,
20394 jump_data: Option<JumpData>,
20395 split: bool,
20396 window: &mut Window,
20397 cx: &mut Context<Self>,
20398 ) {
20399 let Some(workspace) = self.workspace() else {
20400 cx.propagate();
20401 return;
20402 };
20403
20404 if self.buffer.read(cx).is_singleton() {
20405 cx.propagate();
20406 return;
20407 }
20408
20409 let mut new_selections_by_buffer = HashMap::default();
20410 match &jump_data {
20411 Some(JumpData::MultiBufferPoint {
20412 excerpt_id,
20413 position,
20414 anchor,
20415 line_offset_from_top,
20416 }) => {
20417 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20418 if let Some(buffer) = multi_buffer_snapshot
20419 .buffer_id_for_excerpt(*excerpt_id)
20420 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20421 {
20422 let buffer_snapshot = buffer.read(cx).snapshot();
20423 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20424 language::ToPoint::to_point(anchor, &buffer_snapshot)
20425 } else {
20426 buffer_snapshot.clip_point(*position, Bias::Left)
20427 };
20428 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20429 new_selections_by_buffer.insert(
20430 buffer,
20431 (
20432 vec![jump_to_offset..jump_to_offset],
20433 Some(*line_offset_from_top),
20434 ),
20435 );
20436 }
20437 }
20438 Some(JumpData::MultiBufferRow {
20439 row,
20440 line_offset_from_top,
20441 }) => {
20442 let point = MultiBufferPoint::new(row.0, 0);
20443 if let Some((buffer, buffer_point, _)) =
20444 self.buffer.read(cx).point_to_buffer_point(point, cx)
20445 {
20446 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20447 new_selections_by_buffer
20448 .entry(buffer)
20449 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20450 .0
20451 .push(buffer_offset..buffer_offset)
20452 }
20453 }
20454 None => {
20455 let selections = self.selections.all::<usize>(cx);
20456 let multi_buffer = self.buffer.read(cx);
20457 for selection in selections {
20458 for (snapshot, range, _, anchor) in multi_buffer
20459 .snapshot(cx)
20460 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20461 {
20462 if let Some(anchor) = anchor {
20463 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20464 else {
20465 continue;
20466 };
20467 let offset = text::ToOffset::to_offset(
20468 &anchor.text_anchor,
20469 &buffer_handle.read(cx).snapshot(),
20470 );
20471 let range = offset..offset;
20472 new_selections_by_buffer
20473 .entry(buffer_handle)
20474 .or_insert((Vec::new(), None))
20475 .0
20476 .push(range)
20477 } else {
20478 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20479 else {
20480 continue;
20481 };
20482 new_selections_by_buffer
20483 .entry(buffer_handle)
20484 .or_insert((Vec::new(), None))
20485 .0
20486 .push(range)
20487 }
20488 }
20489 }
20490 }
20491 }
20492
20493 new_selections_by_buffer
20494 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20495
20496 if new_selections_by_buffer.is_empty() {
20497 return;
20498 }
20499
20500 // We defer the pane interaction because we ourselves are a workspace item
20501 // and activating a new item causes the pane to call a method on us reentrantly,
20502 // which panics if we're on the stack.
20503 window.defer(cx, move |window, cx| {
20504 workspace.update(cx, |workspace, cx| {
20505 let pane = if split {
20506 workspace.adjacent_pane(window, cx)
20507 } else {
20508 workspace.active_pane().clone()
20509 };
20510
20511 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20512 let editor = buffer
20513 .read(cx)
20514 .file()
20515 .is_none()
20516 .then(|| {
20517 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20518 // so `workspace.open_project_item` will never find them, always opening a new editor.
20519 // Instead, we try to activate the existing editor in the pane first.
20520 let (editor, pane_item_index) =
20521 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20522 let editor = item.downcast::<Editor>()?;
20523 let singleton_buffer =
20524 editor.read(cx).buffer().read(cx).as_singleton()?;
20525 if singleton_buffer == buffer {
20526 Some((editor, i))
20527 } else {
20528 None
20529 }
20530 })?;
20531 pane.update(cx, |pane, cx| {
20532 pane.activate_item(pane_item_index, true, true, window, cx)
20533 });
20534 Some(editor)
20535 })
20536 .flatten()
20537 .unwrap_or_else(|| {
20538 workspace.open_project_item::<Self>(
20539 pane.clone(),
20540 buffer,
20541 true,
20542 true,
20543 window,
20544 cx,
20545 )
20546 });
20547
20548 editor.update(cx, |editor, cx| {
20549 let autoscroll = match scroll_offset {
20550 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20551 None => Autoscroll::newest(),
20552 };
20553 let nav_history = editor.nav_history.take();
20554 editor.change_selections(
20555 SelectionEffects::scroll(autoscroll),
20556 window,
20557 cx,
20558 |s| {
20559 s.select_ranges(ranges);
20560 },
20561 );
20562 editor.nav_history = nav_history;
20563 });
20564 }
20565 })
20566 });
20567 }
20568
20569 // For now, don't allow opening excerpts in buffers that aren't backed by
20570 // regular project files.
20571 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20572 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20573 }
20574
20575 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20576 let snapshot = self.buffer.read(cx).read(cx);
20577 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20578 Some(
20579 ranges
20580 .iter()
20581 .map(move |range| {
20582 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20583 })
20584 .collect(),
20585 )
20586 }
20587
20588 fn selection_replacement_ranges(
20589 &self,
20590 range: Range<OffsetUtf16>,
20591 cx: &mut App,
20592 ) -> Vec<Range<OffsetUtf16>> {
20593 let selections = self.selections.all::<OffsetUtf16>(cx);
20594 let newest_selection = selections
20595 .iter()
20596 .max_by_key(|selection| selection.id)
20597 .unwrap();
20598 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20599 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20600 let snapshot = self.buffer.read(cx).read(cx);
20601 selections
20602 .into_iter()
20603 .map(|mut selection| {
20604 selection.start.0 =
20605 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20606 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20607 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20608 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20609 })
20610 .collect()
20611 }
20612
20613 fn report_editor_event(
20614 &self,
20615 reported_event: ReportEditorEvent,
20616 file_extension: Option<String>,
20617 cx: &App,
20618 ) {
20619 if cfg!(any(test, feature = "test-support")) {
20620 return;
20621 }
20622
20623 let Some(project) = &self.project else { return };
20624
20625 // If None, we are in a file without an extension
20626 let file = self
20627 .buffer
20628 .read(cx)
20629 .as_singleton()
20630 .and_then(|b| b.read(cx).file());
20631 let file_extension = file_extension.or(file
20632 .as_ref()
20633 .and_then(|file| Path::new(file.file_name(cx)).extension())
20634 .and_then(|e| e.to_str())
20635 .map(|a| a.to_string()));
20636
20637 let vim_mode = vim_enabled(cx);
20638
20639 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20640 let copilot_enabled = edit_predictions_provider
20641 == language::language_settings::EditPredictionProvider::Copilot;
20642 let copilot_enabled_for_language = self
20643 .buffer
20644 .read(cx)
20645 .language_settings(cx)
20646 .show_edit_predictions;
20647
20648 let project = project.read(cx);
20649 let event_type = reported_event.event_type();
20650
20651 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20652 telemetry::event!(
20653 event_type,
20654 type = if auto_saved {"autosave"} else {"manual"},
20655 file_extension,
20656 vim_mode,
20657 copilot_enabled,
20658 copilot_enabled_for_language,
20659 edit_predictions_provider,
20660 is_via_ssh = project.is_via_remote_server(),
20661 );
20662 } else {
20663 telemetry::event!(
20664 event_type,
20665 file_extension,
20666 vim_mode,
20667 copilot_enabled,
20668 copilot_enabled_for_language,
20669 edit_predictions_provider,
20670 is_via_ssh = project.is_via_remote_server(),
20671 );
20672 };
20673 }
20674
20675 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20676 /// with each line being an array of {text, highlight} objects.
20677 fn copy_highlight_json(
20678 &mut self,
20679 _: &CopyHighlightJson,
20680 window: &mut Window,
20681 cx: &mut Context<Self>,
20682 ) {
20683 #[derive(Serialize)]
20684 struct Chunk<'a> {
20685 text: String,
20686 highlight: Option<&'a str>,
20687 }
20688
20689 let snapshot = self.buffer.read(cx).snapshot(cx);
20690 let range = self
20691 .selected_text_range(false, window, cx)
20692 .and_then(|selection| {
20693 if selection.range.is_empty() {
20694 None
20695 } else {
20696 Some(selection.range)
20697 }
20698 })
20699 .unwrap_or_else(|| 0..snapshot.len());
20700
20701 let chunks = snapshot.chunks(range, true);
20702 let mut lines = Vec::new();
20703 let mut line: VecDeque<Chunk> = VecDeque::new();
20704
20705 let Some(style) = self.style.as_ref() else {
20706 return;
20707 };
20708
20709 for chunk in chunks {
20710 let highlight = chunk
20711 .syntax_highlight_id
20712 .and_then(|id| id.name(&style.syntax));
20713 let mut chunk_lines = chunk.text.split('\n').peekable();
20714 while let Some(text) = chunk_lines.next() {
20715 let mut merged_with_last_token = false;
20716 if let Some(last_token) = line.back_mut()
20717 && last_token.highlight == highlight
20718 {
20719 last_token.text.push_str(text);
20720 merged_with_last_token = true;
20721 }
20722
20723 if !merged_with_last_token {
20724 line.push_back(Chunk {
20725 text: text.into(),
20726 highlight,
20727 });
20728 }
20729
20730 if chunk_lines.peek().is_some() {
20731 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20732 line.pop_front();
20733 }
20734 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20735 line.pop_back();
20736 }
20737
20738 lines.push(mem::take(&mut line));
20739 }
20740 }
20741 }
20742
20743 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20744 return;
20745 };
20746 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20747 }
20748
20749 pub fn open_context_menu(
20750 &mut self,
20751 _: &OpenContextMenu,
20752 window: &mut Window,
20753 cx: &mut Context<Self>,
20754 ) {
20755 self.request_autoscroll(Autoscroll::newest(), cx);
20756 let position = self.selections.newest_display(cx).start;
20757 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20758 }
20759
20760 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20761 &self.inlay_hint_cache
20762 }
20763
20764 pub fn replay_insert_event(
20765 &mut self,
20766 text: &str,
20767 relative_utf16_range: Option<Range<isize>>,
20768 window: &mut Window,
20769 cx: &mut Context<Self>,
20770 ) {
20771 if !self.input_enabled {
20772 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20773 return;
20774 }
20775 if let Some(relative_utf16_range) = relative_utf16_range {
20776 let selections = self.selections.all::<OffsetUtf16>(cx);
20777 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20778 let new_ranges = selections.into_iter().map(|range| {
20779 let start = OffsetUtf16(
20780 range
20781 .head()
20782 .0
20783 .saturating_add_signed(relative_utf16_range.start),
20784 );
20785 let end = OffsetUtf16(
20786 range
20787 .head()
20788 .0
20789 .saturating_add_signed(relative_utf16_range.end),
20790 );
20791 start..end
20792 });
20793 s.select_ranges(new_ranges);
20794 });
20795 }
20796
20797 self.handle_input(text, window, cx);
20798 }
20799
20800 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20801 let Some(provider) = self.semantics_provider.as_ref() else {
20802 return false;
20803 };
20804
20805 let mut supports = false;
20806 self.buffer().update(cx, |this, cx| {
20807 this.for_each_buffer(|buffer| {
20808 supports |= provider.supports_inlay_hints(buffer, cx);
20809 });
20810 });
20811
20812 supports
20813 }
20814
20815 pub fn is_focused(&self, window: &Window) -> bool {
20816 self.focus_handle.is_focused(window)
20817 }
20818
20819 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20820 cx.emit(EditorEvent::Focused);
20821
20822 if let Some(descendant) = self
20823 .last_focused_descendant
20824 .take()
20825 .and_then(|descendant| descendant.upgrade())
20826 {
20827 window.focus(&descendant);
20828 } else {
20829 if let Some(blame) = self.blame.as_ref() {
20830 blame.update(cx, GitBlame::focus)
20831 }
20832
20833 self.blink_manager.update(cx, BlinkManager::enable);
20834 self.show_cursor_names(window, cx);
20835 self.buffer.update(cx, |buffer, cx| {
20836 buffer.finalize_last_transaction(cx);
20837 if self.leader_id.is_none() {
20838 buffer.set_active_selections(
20839 &self.selections.disjoint_anchors(),
20840 self.selections.line_mode,
20841 self.cursor_shape,
20842 cx,
20843 );
20844 }
20845 });
20846 }
20847 }
20848
20849 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20850 cx.emit(EditorEvent::FocusedIn)
20851 }
20852
20853 fn handle_focus_out(
20854 &mut self,
20855 event: FocusOutEvent,
20856 _window: &mut Window,
20857 cx: &mut Context<Self>,
20858 ) {
20859 if event.blurred != self.focus_handle {
20860 self.last_focused_descendant = Some(event.blurred);
20861 }
20862 self.selection_drag_state = SelectionDragState::None;
20863 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20864 }
20865
20866 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20867 self.blink_manager.update(cx, BlinkManager::disable);
20868 self.buffer
20869 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20870
20871 if let Some(blame) = self.blame.as_ref() {
20872 blame.update(cx, GitBlame::blur)
20873 }
20874 if !self.hover_state.focused(window, cx) {
20875 hide_hover(self, cx);
20876 }
20877 if !self
20878 .context_menu
20879 .borrow()
20880 .as_ref()
20881 .is_some_and(|context_menu| context_menu.focused(window, cx))
20882 {
20883 self.hide_context_menu(window, cx);
20884 }
20885 self.discard_edit_prediction(false, cx);
20886 cx.emit(EditorEvent::Blurred);
20887 cx.notify();
20888 }
20889
20890 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20891 let mut pending: String = window
20892 .pending_input_keystrokes()
20893 .into_iter()
20894 .flatten()
20895 .filter_map(|keystroke| {
20896 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20897 keystroke.key_char.clone()
20898 } else {
20899 None
20900 }
20901 })
20902 .collect();
20903
20904 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20905 pending = "".to_string();
20906 }
20907
20908 let existing_pending = self
20909 .text_highlights::<PendingInput>(cx)
20910 .map(|(_, ranges)| ranges.to_vec());
20911 if existing_pending.is_none() && pending.is_empty() {
20912 return;
20913 }
20914 let transaction =
20915 self.transact(window, cx, |this, window, cx| {
20916 let selections = this.selections.all::<usize>(cx);
20917 let edits = selections
20918 .iter()
20919 .map(|selection| (selection.end..selection.end, pending.clone()));
20920 this.edit(edits, cx);
20921 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20922 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20923 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20924 }));
20925 });
20926 if let Some(existing_ranges) = existing_pending {
20927 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20928 this.edit(edits, cx);
20929 }
20930 });
20931
20932 let snapshot = self.snapshot(window, cx);
20933 let ranges = self
20934 .selections
20935 .all::<usize>(cx)
20936 .into_iter()
20937 .map(|selection| {
20938 snapshot.buffer_snapshot.anchor_after(selection.end)
20939 ..snapshot
20940 .buffer_snapshot
20941 .anchor_before(selection.end + pending.len())
20942 })
20943 .collect();
20944
20945 if pending.is_empty() {
20946 self.clear_highlights::<PendingInput>(cx);
20947 } else {
20948 self.highlight_text::<PendingInput>(
20949 ranges,
20950 HighlightStyle {
20951 underline: Some(UnderlineStyle {
20952 thickness: px(1.),
20953 color: None,
20954 wavy: false,
20955 }),
20956 ..Default::default()
20957 },
20958 cx,
20959 );
20960 }
20961
20962 self.ime_transaction = self.ime_transaction.or(transaction);
20963 if let Some(transaction) = self.ime_transaction {
20964 self.buffer.update(cx, |buffer, cx| {
20965 buffer.group_until_transaction(transaction, cx);
20966 });
20967 }
20968
20969 if self.text_highlights::<PendingInput>(cx).is_none() {
20970 self.ime_transaction.take();
20971 }
20972 }
20973
20974 pub fn register_action_renderer(
20975 &mut self,
20976 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20977 ) -> Subscription {
20978 let id = self.next_editor_action_id.post_inc();
20979 self.editor_actions
20980 .borrow_mut()
20981 .insert(id, Box::new(listener));
20982
20983 let editor_actions = self.editor_actions.clone();
20984 Subscription::new(move || {
20985 editor_actions.borrow_mut().remove(&id);
20986 })
20987 }
20988
20989 pub fn register_action<A: Action>(
20990 &mut self,
20991 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20992 ) -> Subscription {
20993 let id = self.next_editor_action_id.post_inc();
20994 let listener = Arc::new(listener);
20995 self.editor_actions.borrow_mut().insert(
20996 id,
20997 Box::new(move |_, window, _| {
20998 let listener = listener.clone();
20999 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21000 let action = action.downcast_ref().unwrap();
21001 if phase == DispatchPhase::Bubble {
21002 listener(action, window, cx)
21003 }
21004 })
21005 }),
21006 );
21007
21008 let editor_actions = self.editor_actions.clone();
21009 Subscription::new(move || {
21010 editor_actions.borrow_mut().remove(&id);
21011 })
21012 }
21013
21014 pub fn file_header_size(&self) -> u32 {
21015 FILE_HEADER_HEIGHT
21016 }
21017
21018 pub fn restore(
21019 &mut self,
21020 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21021 window: &mut Window,
21022 cx: &mut Context<Self>,
21023 ) {
21024 let workspace = self.workspace();
21025 let project = self.project();
21026 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21027 let mut tasks = Vec::new();
21028 for (buffer_id, changes) in revert_changes {
21029 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21030 buffer.update(cx, |buffer, cx| {
21031 buffer.edit(
21032 changes
21033 .into_iter()
21034 .map(|(range, text)| (range, text.to_string())),
21035 None,
21036 cx,
21037 );
21038 });
21039
21040 if let Some(project) =
21041 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21042 {
21043 project.update(cx, |project, cx| {
21044 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21045 })
21046 }
21047 }
21048 }
21049 tasks
21050 });
21051 cx.spawn_in(window, async move |_, cx| {
21052 for (buffer, task) in save_tasks {
21053 let result = task.await;
21054 if result.is_err() {
21055 let Some(path) = buffer
21056 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21057 .ok()
21058 else {
21059 continue;
21060 };
21061 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21062 let Some(task) = cx
21063 .update_window_entity(workspace, |workspace, window, cx| {
21064 workspace
21065 .open_path_preview(path, None, false, false, false, window, cx)
21066 })
21067 .ok()
21068 else {
21069 continue;
21070 };
21071 task.await.log_err();
21072 }
21073 }
21074 }
21075 })
21076 .detach();
21077 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21078 selections.refresh()
21079 });
21080 }
21081
21082 pub fn to_pixel_point(
21083 &self,
21084 source: multi_buffer::Anchor,
21085 editor_snapshot: &EditorSnapshot,
21086 window: &mut Window,
21087 ) -> Option<gpui::Point<Pixels>> {
21088 let source_point = source.to_display_point(editor_snapshot);
21089 self.display_to_pixel_point(source_point, editor_snapshot, window)
21090 }
21091
21092 pub fn display_to_pixel_point(
21093 &self,
21094 source: DisplayPoint,
21095 editor_snapshot: &EditorSnapshot,
21096 window: &mut Window,
21097 ) -> Option<gpui::Point<Pixels>> {
21098 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21099 let text_layout_details = self.text_layout_details(window);
21100 let scroll_top = text_layout_details
21101 .scroll_anchor
21102 .scroll_position(editor_snapshot)
21103 .y;
21104
21105 if source.row().as_f32() < scroll_top.floor() {
21106 return None;
21107 }
21108 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21109 let source_y = line_height * (source.row().as_f32() - scroll_top);
21110 Some(gpui::Point::new(source_x, source_y))
21111 }
21112
21113 pub fn has_visible_completions_menu(&self) -> bool {
21114 !self.edit_prediction_preview_is_active()
21115 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21116 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21117 })
21118 }
21119
21120 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21121 if self.mode.is_minimap() {
21122 return;
21123 }
21124 self.addons
21125 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21126 }
21127
21128 pub fn unregister_addon<T: Addon>(&mut self) {
21129 self.addons.remove(&std::any::TypeId::of::<T>());
21130 }
21131
21132 pub fn addon<T: Addon>(&self) -> Option<&T> {
21133 let type_id = std::any::TypeId::of::<T>();
21134 self.addons
21135 .get(&type_id)
21136 .and_then(|item| item.to_any().downcast_ref::<T>())
21137 }
21138
21139 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21140 let type_id = std::any::TypeId::of::<T>();
21141 self.addons
21142 .get_mut(&type_id)
21143 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21144 }
21145
21146 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21147 let text_layout_details = self.text_layout_details(window);
21148 let style = &text_layout_details.editor_style;
21149 let font_id = window.text_system().resolve_font(&style.text.font());
21150 let font_size = style.text.font_size.to_pixels(window.rem_size());
21151 let line_height = style.text.line_height_in_pixels(window.rem_size());
21152 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21153 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21154
21155 CharacterDimensions {
21156 em_width,
21157 em_advance,
21158 line_height,
21159 }
21160 }
21161
21162 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21163 self.load_diff_task.clone()
21164 }
21165
21166 fn read_metadata_from_db(
21167 &mut self,
21168 item_id: u64,
21169 workspace_id: WorkspaceId,
21170 window: &mut Window,
21171 cx: &mut Context<Editor>,
21172 ) {
21173 if self.is_singleton(cx)
21174 && !self.mode.is_minimap()
21175 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21176 {
21177 let buffer_snapshot = OnceCell::new();
21178
21179 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21180 && !folds.is_empty()
21181 {
21182 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21183 self.fold_ranges(
21184 folds
21185 .into_iter()
21186 .map(|(start, end)| {
21187 snapshot.clip_offset(start, Bias::Left)
21188 ..snapshot.clip_offset(end, Bias::Right)
21189 })
21190 .collect(),
21191 false,
21192 window,
21193 cx,
21194 );
21195 }
21196
21197 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21198 && !selections.is_empty()
21199 {
21200 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21201 // skip adding the initial selection to selection history
21202 self.selection_history.mode = SelectionHistoryMode::Skipping;
21203 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21204 s.select_ranges(selections.into_iter().map(|(start, end)| {
21205 snapshot.clip_offset(start, Bias::Left)
21206 ..snapshot.clip_offset(end, Bias::Right)
21207 }));
21208 });
21209 self.selection_history.mode = SelectionHistoryMode::Normal;
21210 };
21211 }
21212
21213 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21214 }
21215
21216 fn update_lsp_data(
21217 &mut self,
21218 ignore_cache: bool,
21219 for_buffer: Option<BufferId>,
21220 window: &mut Window,
21221 cx: &mut Context<'_, Self>,
21222 ) {
21223 self.pull_diagnostics(for_buffer, window, cx);
21224 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21225 }
21226}
21227
21228fn vim_enabled(cx: &App) -> bool {
21229 cx.global::<SettingsStore>()
21230 .raw_user_settings()
21231 .get("vim_mode")
21232 == Some(&serde_json::Value::Bool(true))
21233}
21234
21235fn process_completion_for_edit(
21236 completion: &Completion,
21237 intent: CompletionIntent,
21238 buffer: &Entity<Buffer>,
21239 cursor_position: &text::Anchor,
21240 cx: &mut Context<Editor>,
21241) -> CompletionEdit {
21242 let buffer = buffer.read(cx);
21243 let buffer_snapshot = buffer.snapshot();
21244 let (snippet, new_text) = if completion.is_snippet() {
21245 // Workaround for typescript language server issues so that methods don't expand within
21246 // strings and functions with type expressions. The previous point is used because the query
21247 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21248 let mut snippet_source = completion.new_text.clone();
21249 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21250 previous_point.column = previous_point.column.saturating_sub(1);
21251 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21252 && scope.prefers_label_for_snippet_in_completion()
21253 && let Some(label) = completion.label()
21254 && matches!(
21255 completion.kind(),
21256 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21257 )
21258 {
21259 snippet_source = label;
21260 }
21261 match Snippet::parse(&snippet_source).log_err() {
21262 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21263 None => (None, completion.new_text.clone()),
21264 }
21265 } else {
21266 (None, completion.new_text.clone())
21267 };
21268
21269 let mut range_to_replace = {
21270 let replace_range = &completion.replace_range;
21271 if let CompletionSource::Lsp {
21272 insert_range: Some(insert_range),
21273 ..
21274 } = &completion.source
21275 {
21276 debug_assert_eq!(
21277 insert_range.start, replace_range.start,
21278 "insert_range and replace_range should start at the same position"
21279 );
21280 debug_assert!(
21281 insert_range
21282 .start
21283 .cmp(cursor_position, &buffer_snapshot)
21284 .is_le(),
21285 "insert_range should start before or at cursor position"
21286 );
21287 debug_assert!(
21288 replace_range
21289 .start
21290 .cmp(cursor_position, &buffer_snapshot)
21291 .is_le(),
21292 "replace_range should start before or at cursor position"
21293 );
21294
21295 let should_replace = match intent {
21296 CompletionIntent::CompleteWithInsert => false,
21297 CompletionIntent::CompleteWithReplace => true,
21298 CompletionIntent::Complete | CompletionIntent::Compose => {
21299 let insert_mode =
21300 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21301 .completions
21302 .lsp_insert_mode;
21303 match insert_mode {
21304 LspInsertMode::Insert => false,
21305 LspInsertMode::Replace => true,
21306 LspInsertMode::ReplaceSubsequence => {
21307 let mut text_to_replace = buffer.chars_for_range(
21308 buffer.anchor_before(replace_range.start)
21309 ..buffer.anchor_after(replace_range.end),
21310 );
21311 let mut current_needle = text_to_replace.next();
21312 for haystack_ch in completion.label.text.chars() {
21313 if let Some(needle_ch) = current_needle
21314 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21315 {
21316 current_needle = text_to_replace.next();
21317 }
21318 }
21319 current_needle.is_none()
21320 }
21321 LspInsertMode::ReplaceSuffix => {
21322 if replace_range
21323 .end
21324 .cmp(cursor_position, &buffer_snapshot)
21325 .is_gt()
21326 {
21327 let range_after_cursor = *cursor_position..replace_range.end;
21328 let text_after_cursor = buffer
21329 .text_for_range(
21330 buffer.anchor_before(range_after_cursor.start)
21331 ..buffer.anchor_after(range_after_cursor.end),
21332 )
21333 .collect::<String>()
21334 .to_ascii_lowercase();
21335 completion
21336 .label
21337 .text
21338 .to_ascii_lowercase()
21339 .ends_with(&text_after_cursor)
21340 } else {
21341 true
21342 }
21343 }
21344 }
21345 }
21346 };
21347
21348 if should_replace {
21349 replace_range.clone()
21350 } else {
21351 insert_range.clone()
21352 }
21353 } else {
21354 replace_range.clone()
21355 }
21356 };
21357
21358 if range_to_replace
21359 .end
21360 .cmp(cursor_position, &buffer_snapshot)
21361 .is_lt()
21362 {
21363 range_to_replace.end = *cursor_position;
21364 }
21365
21366 CompletionEdit {
21367 new_text,
21368 replace_range: range_to_replace.to_offset(buffer),
21369 snippet,
21370 }
21371}
21372
21373struct CompletionEdit {
21374 new_text: String,
21375 replace_range: Range<usize>,
21376 snippet: Option<Snippet>,
21377}
21378
21379fn insert_extra_newline_brackets(
21380 buffer: &MultiBufferSnapshot,
21381 range: Range<usize>,
21382 language: &language::LanguageScope,
21383) -> bool {
21384 let leading_whitespace_len = buffer
21385 .reversed_chars_at(range.start)
21386 .take_while(|c| c.is_whitespace() && *c != '\n')
21387 .map(|c| c.len_utf8())
21388 .sum::<usize>();
21389 let trailing_whitespace_len = buffer
21390 .chars_at(range.end)
21391 .take_while(|c| c.is_whitespace() && *c != '\n')
21392 .map(|c| c.len_utf8())
21393 .sum::<usize>();
21394 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21395
21396 language.brackets().any(|(pair, enabled)| {
21397 let pair_start = pair.start.trim_end();
21398 let pair_end = pair.end.trim_start();
21399
21400 enabled
21401 && pair.newline
21402 && buffer.contains_str_at(range.end, pair_end)
21403 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21404 })
21405}
21406
21407fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21408 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21409 [(buffer, range, _)] => (*buffer, range.clone()),
21410 _ => return false,
21411 };
21412 let pair = {
21413 let mut result: Option<BracketMatch> = None;
21414
21415 for pair in buffer
21416 .all_bracket_ranges(range.clone())
21417 .filter(move |pair| {
21418 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21419 })
21420 {
21421 let len = pair.close_range.end - pair.open_range.start;
21422
21423 if let Some(existing) = &result {
21424 let existing_len = existing.close_range.end - existing.open_range.start;
21425 if len > existing_len {
21426 continue;
21427 }
21428 }
21429
21430 result = Some(pair);
21431 }
21432
21433 result
21434 };
21435 let Some(pair) = pair else {
21436 return false;
21437 };
21438 pair.newline_only
21439 && buffer
21440 .chars_for_range(pair.open_range.end..range.start)
21441 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21442 .all(|c| c.is_whitespace() && c != '\n')
21443}
21444
21445fn update_uncommitted_diff_for_buffer(
21446 editor: Entity<Editor>,
21447 project: &Entity<Project>,
21448 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21449 buffer: Entity<MultiBuffer>,
21450 cx: &mut App,
21451) -> Task<()> {
21452 let mut tasks = Vec::new();
21453 project.update(cx, |project, cx| {
21454 for buffer in buffers {
21455 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21456 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21457 }
21458 }
21459 });
21460 cx.spawn(async move |cx| {
21461 let diffs = future::join_all(tasks).await;
21462 if editor
21463 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21464 .unwrap_or(false)
21465 {
21466 return;
21467 }
21468
21469 buffer
21470 .update(cx, |buffer, cx| {
21471 for diff in diffs.into_iter().flatten() {
21472 buffer.add_diff(diff, cx);
21473 }
21474 })
21475 .ok();
21476 })
21477}
21478
21479fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21480 let tab_size = tab_size.get() as usize;
21481 let mut width = offset;
21482
21483 for ch in text.chars() {
21484 width += if ch == '\t' {
21485 tab_size - (width % tab_size)
21486 } else {
21487 1
21488 };
21489 }
21490
21491 width - offset
21492}
21493
21494#[cfg(test)]
21495mod tests {
21496 use super::*;
21497
21498 #[test]
21499 fn test_string_size_with_expanded_tabs() {
21500 let nz = |val| NonZeroU32::new(val).unwrap();
21501 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21502 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21503 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21504 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21505 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21506 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21507 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21508 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21509 }
21510}
21511
21512/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21513struct WordBreakingTokenizer<'a> {
21514 input: &'a str,
21515}
21516
21517impl<'a> WordBreakingTokenizer<'a> {
21518 fn new(input: &'a str) -> Self {
21519 Self { input }
21520 }
21521}
21522
21523fn is_char_ideographic(ch: char) -> bool {
21524 use unicode_script::Script::*;
21525 use unicode_script::UnicodeScript;
21526 matches!(ch.script(), Han | Tangut | Yi)
21527}
21528
21529fn is_grapheme_ideographic(text: &str) -> bool {
21530 text.chars().any(is_char_ideographic)
21531}
21532
21533fn is_grapheme_whitespace(text: &str) -> bool {
21534 text.chars().any(|x| x.is_whitespace())
21535}
21536
21537fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21538 text.chars()
21539 .next()
21540 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21541}
21542
21543#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21544enum WordBreakToken<'a> {
21545 Word { token: &'a str, grapheme_len: usize },
21546 InlineWhitespace { token: &'a str, grapheme_len: usize },
21547 Newline,
21548}
21549
21550impl<'a> Iterator for WordBreakingTokenizer<'a> {
21551 /// Yields a span, the count of graphemes in the token, and whether it was
21552 /// whitespace. Note that it also breaks at word boundaries.
21553 type Item = WordBreakToken<'a>;
21554
21555 fn next(&mut self) -> Option<Self::Item> {
21556 use unicode_segmentation::UnicodeSegmentation;
21557 if self.input.is_empty() {
21558 return None;
21559 }
21560
21561 let mut iter = self.input.graphemes(true).peekable();
21562 let mut offset = 0;
21563 let mut grapheme_len = 0;
21564 if let Some(first_grapheme) = iter.next() {
21565 let is_newline = first_grapheme == "\n";
21566 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21567 offset += first_grapheme.len();
21568 grapheme_len += 1;
21569 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21570 if let Some(grapheme) = iter.peek().copied()
21571 && should_stay_with_preceding_ideograph(grapheme)
21572 {
21573 offset += grapheme.len();
21574 grapheme_len += 1;
21575 }
21576 } else {
21577 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21578 let mut next_word_bound = words.peek().copied();
21579 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21580 next_word_bound = words.next();
21581 }
21582 while let Some(grapheme) = iter.peek().copied() {
21583 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21584 break;
21585 };
21586 if is_grapheme_whitespace(grapheme) != is_whitespace
21587 || (grapheme == "\n") != is_newline
21588 {
21589 break;
21590 };
21591 offset += grapheme.len();
21592 grapheme_len += 1;
21593 iter.next();
21594 }
21595 }
21596 let token = &self.input[..offset];
21597 self.input = &self.input[offset..];
21598 if token == "\n" {
21599 Some(WordBreakToken::Newline)
21600 } else if is_whitespace {
21601 Some(WordBreakToken::InlineWhitespace {
21602 token,
21603 grapheme_len,
21604 })
21605 } else {
21606 Some(WordBreakToken::Word {
21607 token,
21608 grapheme_len,
21609 })
21610 }
21611 } else {
21612 None
21613 }
21614 }
21615}
21616
21617#[test]
21618fn test_word_breaking_tokenizer() {
21619 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21620 ("", &[]),
21621 (" ", &[whitespace(" ", 2)]),
21622 ("Ʒ", &[word("Ʒ", 1)]),
21623 ("Ǽ", &[word("Ǽ", 1)]),
21624 ("⋑", &[word("⋑", 1)]),
21625 ("⋑⋑", &[word("⋑⋑", 2)]),
21626 (
21627 "原理,进而",
21628 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21629 ),
21630 (
21631 "hello world",
21632 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21633 ),
21634 (
21635 "hello, world",
21636 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21637 ),
21638 (
21639 " hello world",
21640 &[
21641 whitespace(" ", 2),
21642 word("hello", 5),
21643 whitespace(" ", 1),
21644 word("world", 5),
21645 ],
21646 ),
21647 (
21648 "这是什么 \n 钢笔",
21649 &[
21650 word("这", 1),
21651 word("是", 1),
21652 word("什", 1),
21653 word("么", 1),
21654 whitespace(" ", 1),
21655 newline(),
21656 whitespace(" ", 1),
21657 word("钢", 1),
21658 word("笔", 1),
21659 ],
21660 ),
21661 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21662 ];
21663
21664 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21665 WordBreakToken::Word {
21666 token,
21667 grapheme_len,
21668 }
21669 }
21670
21671 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21672 WordBreakToken::InlineWhitespace {
21673 token,
21674 grapheme_len,
21675 }
21676 }
21677
21678 fn newline() -> WordBreakToken<'static> {
21679 WordBreakToken::Newline
21680 }
21681
21682 for (input, result) in tests {
21683 assert_eq!(
21684 WordBreakingTokenizer::new(input)
21685 .collect::<Vec<_>>()
21686 .as_slice(),
21687 *result,
21688 );
21689 }
21690}
21691
21692fn wrap_with_prefix(
21693 first_line_prefix: String,
21694 subsequent_lines_prefix: String,
21695 unwrapped_text: String,
21696 wrap_column: usize,
21697 tab_size: NonZeroU32,
21698 preserve_existing_whitespace: bool,
21699) -> String {
21700 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21701 let subsequent_lines_prefix_len =
21702 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21703 let mut wrapped_text = String::new();
21704 let mut current_line = first_line_prefix;
21705 let mut is_first_line = true;
21706
21707 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21708 let mut current_line_len = first_line_prefix_len;
21709 let mut in_whitespace = false;
21710 for token in tokenizer {
21711 let have_preceding_whitespace = in_whitespace;
21712 match token {
21713 WordBreakToken::Word {
21714 token,
21715 grapheme_len,
21716 } => {
21717 in_whitespace = false;
21718 let current_prefix_len = if is_first_line {
21719 first_line_prefix_len
21720 } else {
21721 subsequent_lines_prefix_len
21722 };
21723 if current_line_len + grapheme_len > wrap_column
21724 && current_line_len != current_prefix_len
21725 {
21726 wrapped_text.push_str(current_line.trim_end());
21727 wrapped_text.push('\n');
21728 is_first_line = false;
21729 current_line = subsequent_lines_prefix.clone();
21730 current_line_len = subsequent_lines_prefix_len;
21731 }
21732 current_line.push_str(token);
21733 current_line_len += grapheme_len;
21734 }
21735 WordBreakToken::InlineWhitespace {
21736 mut token,
21737 mut grapheme_len,
21738 } => {
21739 in_whitespace = true;
21740 if have_preceding_whitespace && !preserve_existing_whitespace {
21741 continue;
21742 }
21743 if !preserve_existing_whitespace {
21744 token = " ";
21745 grapheme_len = 1;
21746 }
21747 let current_prefix_len = if is_first_line {
21748 first_line_prefix_len
21749 } else {
21750 subsequent_lines_prefix_len
21751 };
21752 if current_line_len + grapheme_len > wrap_column {
21753 wrapped_text.push_str(current_line.trim_end());
21754 wrapped_text.push('\n');
21755 is_first_line = false;
21756 current_line = subsequent_lines_prefix.clone();
21757 current_line_len = subsequent_lines_prefix_len;
21758 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21759 current_line.push_str(token);
21760 current_line_len += grapheme_len;
21761 }
21762 }
21763 WordBreakToken::Newline => {
21764 in_whitespace = true;
21765 let current_prefix_len = if is_first_line {
21766 first_line_prefix_len
21767 } else {
21768 subsequent_lines_prefix_len
21769 };
21770 if preserve_existing_whitespace {
21771 wrapped_text.push_str(current_line.trim_end());
21772 wrapped_text.push('\n');
21773 is_first_line = false;
21774 current_line = subsequent_lines_prefix.clone();
21775 current_line_len = subsequent_lines_prefix_len;
21776 } else if have_preceding_whitespace {
21777 continue;
21778 } else if current_line_len + 1 > wrap_column
21779 && current_line_len != current_prefix_len
21780 {
21781 wrapped_text.push_str(current_line.trim_end());
21782 wrapped_text.push('\n');
21783 is_first_line = false;
21784 current_line = subsequent_lines_prefix.clone();
21785 current_line_len = subsequent_lines_prefix_len;
21786 } else if current_line_len != current_prefix_len {
21787 current_line.push(' ');
21788 current_line_len += 1;
21789 }
21790 }
21791 }
21792 }
21793
21794 if !current_line.is_empty() {
21795 wrapped_text.push_str(¤t_line);
21796 }
21797 wrapped_text
21798}
21799
21800#[test]
21801fn test_wrap_with_prefix() {
21802 assert_eq!(
21803 wrap_with_prefix(
21804 "# ".to_string(),
21805 "# ".to_string(),
21806 "abcdefg".to_string(),
21807 4,
21808 NonZeroU32::new(4).unwrap(),
21809 false,
21810 ),
21811 "# abcdefg"
21812 );
21813 assert_eq!(
21814 wrap_with_prefix(
21815 "".to_string(),
21816 "".to_string(),
21817 "\thello world".to_string(),
21818 8,
21819 NonZeroU32::new(4).unwrap(),
21820 false,
21821 ),
21822 "hello\nworld"
21823 );
21824 assert_eq!(
21825 wrap_with_prefix(
21826 "// ".to_string(),
21827 "// ".to_string(),
21828 "xx \nyy zz aa bb cc".to_string(),
21829 12,
21830 NonZeroU32::new(4).unwrap(),
21831 false,
21832 ),
21833 "// xx yy zz\n// aa bb cc"
21834 );
21835 assert_eq!(
21836 wrap_with_prefix(
21837 String::new(),
21838 String::new(),
21839 "这是什么 \n 钢笔".to_string(),
21840 3,
21841 NonZeroU32::new(4).unwrap(),
21842 false,
21843 ),
21844 "这是什\n么 钢\n笔"
21845 );
21846}
21847
21848pub trait CollaborationHub {
21849 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21850 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21851 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21852}
21853
21854impl CollaborationHub for Entity<Project> {
21855 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21856 self.read(cx).collaborators()
21857 }
21858
21859 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21860 self.read(cx).user_store().read(cx).participant_indices()
21861 }
21862
21863 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21864 let this = self.read(cx);
21865 let user_ids = this.collaborators().values().map(|c| c.user_id);
21866 this.user_store().read(cx).participant_names(user_ids, cx)
21867 }
21868}
21869
21870pub trait SemanticsProvider {
21871 fn hover(
21872 &self,
21873 buffer: &Entity<Buffer>,
21874 position: text::Anchor,
21875 cx: &mut App,
21876 ) -> Option<Task<Option<Vec<project::Hover>>>>;
21877
21878 fn inline_values(
21879 &self,
21880 buffer_handle: Entity<Buffer>,
21881 range: Range<text::Anchor>,
21882 cx: &mut App,
21883 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21884
21885 fn inlay_hints(
21886 &self,
21887 buffer_handle: Entity<Buffer>,
21888 range: Range<text::Anchor>,
21889 cx: &mut App,
21890 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21891
21892 fn resolve_inlay_hint(
21893 &self,
21894 hint: InlayHint,
21895 buffer_handle: Entity<Buffer>,
21896 server_id: LanguageServerId,
21897 cx: &mut App,
21898 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21899
21900 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21901
21902 fn document_highlights(
21903 &self,
21904 buffer: &Entity<Buffer>,
21905 position: text::Anchor,
21906 cx: &mut App,
21907 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21908
21909 fn definitions(
21910 &self,
21911 buffer: &Entity<Buffer>,
21912 position: text::Anchor,
21913 kind: GotoDefinitionKind,
21914 cx: &mut App,
21915 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
21916
21917 fn range_for_rename(
21918 &self,
21919 buffer: &Entity<Buffer>,
21920 position: text::Anchor,
21921 cx: &mut App,
21922 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21923
21924 fn perform_rename(
21925 &self,
21926 buffer: &Entity<Buffer>,
21927 position: text::Anchor,
21928 new_name: String,
21929 cx: &mut App,
21930 ) -> Option<Task<Result<ProjectTransaction>>>;
21931}
21932
21933pub trait CompletionProvider {
21934 fn completions(
21935 &self,
21936 excerpt_id: ExcerptId,
21937 buffer: &Entity<Buffer>,
21938 buffer_position: text::Anchor,
21939 trigger: CompletionContext,
21940 window: &mut Window,
21941 cx: &mut Context<Editor>,
21942 ) -> Task<Result<Vec<CompletionResponse>>>;
21943
21944 fn resolve_completions(
21945 &self,
21946 _buffer: Entity<Buffer>,
21947 _completion_indices: Vec<usize>,
21948 _completions: Rc<RefCell<Box<[Completion]>>>,
21949 _cx: &mut Context<Editor>,
21950 ) -> Task<Result<bool>> {
21951 Task::ready(Ok(false))
21952 }
21953
21954 fn apply_additional_edits_for_completion(
21955 &self,
21956 _buffer: Entity<Buffer>,
21957 _completions: Rc<RefCell<Box<[Completion]>>>,
21958 _completion_index: usize,
21959 _push_to_history: bool,
21960 _cx: &mut Context<Editor>,
21961 ) -> Task<Result<Option<language::Transaction>>> {
21962 Task::ready(Ok(None))
21963 }
21964
21965 fn is_completion_trigger(
21966 &self,
21967 buffer: &Entity<Buffer>,
21968 position: language::Anchor,
21969 text: &str,
21970 trigger_in_words: bool,
21971 menu_is_open: bool,
21972 cx: &mut Context<Editor>,
21973 ) -> bool;
21974
21975 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21976
21977 fn sort_completions(&self) -> bool {
21978 true
21979 }
21980
21981 fn filter_completions(&self) -> bool {
21982 true
21983 }
21984}
21985
21986pub trait CodeActionProvider {
21987 fn id(&self) -> Arc<str>;
21988
21989 fn code_actions(
21990 &self,
21991 buffer: &Entity<Buffer>,
21992 range: Range<text::Anchor>,
21993 window: &mut Window,
21994 cx: &mut App,
21995 ) -> Task<Result<Vec<CodeAction>>>;
21996
21997 fn apply_code_action(
21998 &self,
21999 buffer_handle: Entity<Buffer>,
22000 action: CodeAction,
22001 excerpt_id: ExcerptId,
22002 push_to_history: bool,
22003 window: &mut Window,
22004 cx: &mut App,
22005 ) -> Task<Result<ProjectTransaction>>;
22006}
22007
22008impl CodeActionProvider for Entity<Project> {
22009 fn id(&self) -> Arc<str> {
22010 "project".into()
22011 }
22012
22013 fn code_actions(
22014 &self,
22015 buffer: &Entity<Buffer>,
22016 range: Range<text::Anchor>,
22017 _window: &mut Window,
22018 cx: &mut App,
22019 ) -> Task<Result<Vec<CodeAction>>> {
22020 self.update(cx, |project, cx| {
22021 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22022 let code_actions = project.code_actions(buffer, range, None, cx);
22023 cx.background_spawn(async move {
22024 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22025 Ok(code_lens_actions
22026 .context("code lens fetch")?
22027 .into_iter()
22028 .flatten()
22029 .chain(
22030 code_actions
22031 .context("code action fetch")?
22032 .into_iter()
22033 .flatten(),
22034 )
22035 .collect())
22036 })
22037 })
22038 }
22039
22040 fn apply_code_action(
22041 &self,
22042 buffer_handle: Entity<Buffer>,
22043 action: CodeAction,
22044 _excerpt_id: ExcerptId,
22045 push_to_history: bool,
22046 _window: &mut Window,
22047 cx: &mut App,
22048 ) -> Task<Result<ProjectTransaction>> {
22049 self.update(cx, |project, cx| {
22050 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22051 })
22052 }
22053}
22054
22055fn snippet_completions(
22056 project: &Project,
22057 buffer: &Entity<Buffer>,
22058 buffer_position: text::Anchor,
22059 cx: &mut App,
22060) -> Task<Result<CompletionResponse>> {
22061 let languages = buffer.read(cx).languages_at(buffer_position);
22062 let snippet_store = project.snippets().read(cx);
22063
22064 let scopes: Vec<_> = languages
22065 .iter()
22066 .filter_map(|language| {
22067 let language_name = language.lsp_id();
22068 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22069
22070 if snippets.is_empty() {
22071 None
22072 } else {
22073 Some((language.default_scope(), snippets))
22074 }
22075 })
22076 .collect();
22077
22078 if scopes.is_empty() {
22079 return Task::ready(Ok(CompletionResponse {
22080 completions: vec![],
22081 display_options: CompletionDisplayOptions::default(),
22082 is_incomplete: false,
22083 }));
22084 }
22085
22086 let snapshot = buffer.read(cx).text_snapshot();
22087 let chars: String = snapshot
22088 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22089 .collect();
22090 let executor = cx.background_executor().clone();
22091
22092 cx.background_spawn(async move {
22093 let mut is_incomplete = false;
22094 let mut completions: Vec<Completion> = Vec::new();
22095 for (scope, snippets) in scopes.into_iter() {
22096 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22097 let mut last_word = chars
22098 .chars()
22099 .take_while(|c| classifier.is_word(*c))
22100 .collect::<String>();
22101 last_word = last_word.chars().rev().collect();
22102
22103 if last_word.is_empty() {
22104 return Ok(CompletionResponse {
22105 completions: vec![],
22106 display_options: CompletionDisplayOptions::default(),
22107 is_incomplete: true,
22108 });
22109 }
22110
22111 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22112 let to_lsp = |point: &text::Anchor| {
22113 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22114 point_to_lsp(end)
22115 };
22116 let lsp_end = to_lsp(&buffer_position);
22117
22118 let candidates = snippets
22119 .iter()
22120 .enumerate()
22121 .flat_map(|(ix, snippet)| {
22122 snippet
22123 .prefix
22124 .iter()
22125 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22126 })
22127 .collect::<Vec<StringMatchCandidate>>();
22128
22129 const MAX_RESULTS: usize = 100;
22130 let mut matches = fuzzy::match_strings(
22131 &candidates,
22132 &last_word,
22133 last_word.chars().any(|c| c.is_uppercase()),
22134 true,
22135 MAX_RESULTS,
22136 &Default::default(),
22137 executor.clone(),
22138 )
22139 .await;
22140
22141 if matches.len() >= MAX_RESULTS {
22142 is_incomplete = true;
22143 }
22144
22145 // Remove all candidates where the query's start does not match the start of any word in the candidate
22146 if let Some(query_start) = last_word.chars().next() {
22147 matches.retain(|string_match| {
22148 split_words(&string_match.string).any(|word| {
22149 // Check that the first codepoint of the word as lowercase matches the first
22150 // codepoint of the query as lowercase
22151 word.chars()
22152 .flat_map(|codepoint| codepoint.to_lowercase())
22153 .zip(query_start.to_lowercase())
22154 .all(|(word_cp, query_cp)| word_cp == query_cp)
22155 })
22156 });
22157 }
22158
22159 let matched_strings = matches
22160 .into_iter()
22161 .map(|m| m.string)
22162 .collect::<HashSet<_>>();
22163
22164 completions.extend(snippets.iter().filter_map(|snippet| {
22165 let matching_prefix = snippet
22166 .prefix
22167 .iter()
22168 .find(|prefix| matched_strings.contains(*prefix))?;
22169 let start = as_offset - last_word.len();
22170 let start = snapshot.anchor_before(start);
22171 let range = start..buffer_position;
22172 let lsp_start = to_lsp(&start);
22173 let lsp_range = lsp::Range {
22174 start: lsp_start,
22175 end: lsp_end,
22176 };
22177 Some(Completion {
22178 replace_range: range,
22179 new_text: snippet.body.clone(),
22180 source: CompletionSource::Lsp {
22181 insert_range: None,
22182 server_id: LanguageServerId(usize::MAX),
22183 resolved: true,
22184 lsp_completion: Box::new(lsp::CompletionItem {
22185 label: snippet.prefix.first().unwrap().clone(),
22186 kind: Some(CompletionItemKind::SNIPPET),
22187 label_details: snippet.description.as_ref().map(|description| {
22188 lsp::CompletionItemLabelDetails {
22189 detail: Some(description.clone()),
22190 description: None,
22191 }
22192 }),
22193 insert_text_format: Some(InsertTextFormat::SNIPPET),
22194 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22195 lsp::InsertReplaceEdit {
22196 new_text: snippet.body.clone(),
22197 insert: lsp_range,
22198 replace: lsp_range,
22199 },
22200 )),
22201 filter_text: Some(snippet.body.clone()),
22202 sort_text: Some(char::MAX.to_string()),
22203 ..lsp::CompletionItem::default()
22204 }),
22205 lsp_defaults: None,
22206 },
22207 label: CodeLabel {
22208 text: matching_prefix.clone(),
22209 runs: Vec::new(),
22210 filter_range: 0..matching_prefix.len(),
22211 },
22212 icon_path: None,
22213 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22214 single_line: snippet.name.clone().into(),
22215 plain_text: snippet
22216 .description
22217 .clone()
22218 .map(|description| description.into()),
22219 }),
22220 insert_text_mode: None,
22221 confirm: None,
22222 })
22223 }))
22224 }
22225
22226 Ok(CompletionResponse {
22227 completions,
22228 display_options: CompletionDisplayOptions::default(),
22229 is_incomplete,
22230 })
22231 })
22232}
22233
22234impl CompletionProvider for Entity<Project> {
22235 fn completions(
22236 &self,
22237 _excerpt_id: ExcerptId,
22238 buffer: &Entity<Buffer>,
22239 buffer_position: text::Anchor,
22240 options: CompletionContext,
22241 _window: &mut Window,
22242 cx: &mut Context<Editor>,
22243 ) -> Task<Result<Vec<CompletionResponse>>> {
22244 self.update(cx, |project, cx| {
22245 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22246 let project_completions = project.completions(buffer, buffer_position, options, cx);
22247 cx.background_spawn(async move {
22248 let mut responses = project_completions.await?;
22249 let snippets = snippets.await?;
22250 if !snippets.completions.is_empty() {
22251 responses.push(snippets);
22252 }
22253 Ok(responses)
22254 })
22255 })
22256 }
22257
22258 fn resolve_completions(
22259 &self,
22260 buffer: Entity<Buffer>,
22261 completion_indices: Vec<usize>,
22262 completions: Rc<RefCell<Box<[Completion]>>>,
22263 cx: &mut Context<Editor>,
22264 ) -> Task<Result<bool>> {
22265 self.update(cx, |project, cx| {
22266 project.lsp_store().update(cx, |lsp_store, cx| {
22267 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22268 })
22269 })
22270 }
22271
22272 fn apply_additional_edits_for_completion(
22273 &self,
22274 buffer: Entity<Buffer>,
22275 completions: Rc<RefCell<Box<[Completion]>>>,
22276 completion_index: usize,
22277 push_to_history: bool,
22278 cx: &mut Context<Editor>,
22279 ) -> Task<Result<Option<language::Transaction>>> {
22280 self.update(cx, |project, cx| {
22281 project.lsp_store().update(cx, |lsp_store, cx| {
22282 lsp_store.apply_additional_edits_for_completion(
22283 buffer,
22284 completions,
22285 completion_index,
22286 push_to_history,
22287 cx,
22288 )
22289 })
22290 })
22291 }
22292
22293 fn is_completion_trigger(
22294 &self,
22295 buffer: &Entity<Buffer>,
22296 position: language::Anchor,
22297 text: &str,
22298 trigger_in_words: bool,
22299 menu_is_open: bool,
22300 cx: &mut Context<Editor>,
22301 ) -> bool {
22302 let mut chars = text.chars();
22303 let char = if let Some(char) = chars.next() {
22304 char
22305 } else {
22306 return false;
22307 };
22308 if chars.next().is_some() {
22309 return false;
22310 }
22311
22312 let buffer = buffer.read(cx);
22313 let snapshot = buffer.snapshot();
22314 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22315 return false;
22316 }
22317 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22318 if trigger_in_words && classifier.is_word(char) {
22319 return true;
22320 }
22321
22322 buffer.completion_triggers().contains(text)
22323 }
22324}
22325
22326impl SemanticsProvider for Entity<Project> {
22327 fn hover(
22328 &self,
22329 buffer: &Entity<Buffer>,
22330 position: text::Anchor,
22331 cx: &mut App,
22332 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22333 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22334 }
22335
22336 fn document_highlights(
22337 &self,
22338 buffer: &Entity<Buffer>,
22339 position: text::Anchor,
22340 cx: &mut App,
22341 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22342 Some(self.update(cx, |project, cx| {
22343 project.document_highlights(buffer, position, cx)
22344 }))
22345 }
22346
22347 fn definitions(
22348 &self,
22349 buffer: &Entity<Buffer>,
22350 position: text::Anchor,
22351 kind: GotoDefinitionKind,
22352 cx: &mut App,
22353 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22354 Some(self.update(cx, |project, cx| match kind {
22355 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22356 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22357 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22358 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22359 }))
22360 }
22361
22362 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22363 self.update(cx, |project, cx| {
22364 if project
22365 .active_debug_session(cx)
22366 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22367 {
22368 return true;
22369 }
22370
22371 buffer.update(cx, |buffer, cx| {
22372 project.any_language_server_supports_inlay_hints(buffer, cx)
22373 })
22374 })
22375 }
22376
22377 fn inline_values(
22378 &self,
22379 buffer_handle: Entity<Buffer>,
22380 range: Range<text::Anchor>,
22381 cx: &mut App,
22382 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22383 self.update(cx, |project, cx| {
22384 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22385
22386 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22387 })
22388 }
22389
22390 fn inlay_hints(
22391 &self,
22392 buffer_handle: Entity<Buffer>,
22393 range: Range<text::Anchor>,
22394 cx: &mut App,
22395 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22396 Some(self.update(cx, |project, cx| {
22397 project.inlay_hints(buffer_handle, range, cx)
22398 }))
22399 }
22400
22401 fn resolve_inlay_hint(
22402 &self,
22403 hint: InlayHint,
22404 buffer_handle: Entity<Buffer>,
22405 server_id: LanguageServerId,
22406 cx: &mut App,
22407 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22408 Some(self.update(cx, |project, cx| {
22409 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22410 }))
22411 }
22412
22413 fn range_for_rename(
22414 &self,
22415 buffer: &Entity<Buffer>,
22416 position: text::Anchor,
22417 cx: &mut App,
22418 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22419 Some(self.update(cx, |project, cx| {
22420 let buffer = buffer.clone();
22421 let task = project.prepare_rename(buffer.clone(), position, cx);
22422 cx.spawn(async move |_, cx| {
22423 Ok(match task.await? {
22424 PrepareRenameResponse::Success(range) => Some(range),
22425 PrepareRenameResponse::InvalidPosition => None,
22426 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22427 // Fallback on using TreeSitter info to determine identifier range
22428 buffer.read_with(cx, |buffer, _| {
22429 let snapshot = buffer.snapshot();
22430 let (range, kind) = snapshot.surrounding_word(position, false);
22431 if kind != Some(CharKind::Word) {
22432 return None;
22433 }
22434 Some(
22435 snapshot.anchor_before(range.start)
22436 ..snapshot.anchor_after(range.end),
22437 )
22438 })?
22439 }
22440 })
22441 })
22442 }))
22443 }
22444
22445 fn perform_rename(
22446 &self,
22447 buffer: &Entity<Buffer>,
22448 position: text::Anchor,
22449 new_name: String,
22450 cx: &mut App,
22451 ) -> Option<Task<Result<ProjectTransaction>>> {
22452 Some(self.update(cx, |project, cx| {
22453 project.perform_rename(buffer.clone(), position, new_name, cx)
22454 }))
22455 }
22456}
22457
22458fn inlay_hint_settings(
22459 location: Anchor,
22460 snapshot: &MultiBufferSnapshot,
22461 cx: &mut Context<Editor>,
22462) -> InlayHintSettings {
22463 let file = snapshot.file_at(location);
22464 let language = snapshot.language_at(location).map(|l| l.name());
22465 language_settings(language, file, cx).inlay_hints
22466}
22467
22468fn consume_contiguous_rows(
22469 contiguous_row_selections: &mut Vec<Selection<Point>>,
22470 selection: &Selection<Point>,
22471 display_map: &DisplaySnapshot,
22472 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22473) -> (MultiBufferRow, MultiBufferRow) {
22474 contiguous_row_selections.push(selection.clone());
22475 let start_row = starting_row(selection, display_map);
22476 let mut end_row = ending_row(selection, display_map);
22477
22478 while let Some(next_selection) = selections.peek() {
22479 if next_selection.start.row <= end_row.0 {
22480 end_row = ending_row(next_selection, display_map);
22481 contiguous_row_selections.push(selections.next().unwrap().clone());
22482 } else {
22483 break;
22484 }
22485 }
22486 (start_row, end_row)
22487}
22488
22489fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22490 if selection.start.column > 0 {
22491 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22492 } else {
22493 MultiBufferRow(selection.start.row)
22494 }
22495}
22496
22497fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22498 if next_selection.end.column > 0 || next_selection.is_empty() {
22499 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22500 } else {
22501 MultiBufferRow(next_selection.end.row)
22502 }
22503}
22504
22505impl EditorSnapshot {
22506 pub fn remote_selections_in_range<'a>(
22507 &'a self,
22508 range: &'a Range<Anchor>,
22509 collaboration_hub: &dyn CollaborationHub,
22510 cx: &'a App,
22511 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22512 let participant_names = collaboration_hub.user_names(cx);
22513 let participant_indices = collaboration_hub.user_participant_indices(cx);
22514 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22515 let collaborators_by_replica_id = collaborators_by_peer_id
22516 .values()
22517 .map(|collaborator| (collaborator.replica_id, collaborator))
22518 .collect::<HashMap<_, _>>();
22519 self.buffer_snapshot
22520 .selections_in_range(range, false)
22521 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22522 if replica_id == AGENT_REPLICA_ID {
22523 Some(RemoteSelection {
22524 replica_id,
22525 selection,
22526 cursor_shape,
22527 line_mode,
22528 collaborator_id: CollaboratorId::Agent,
22529 user_name: Some("Agent".into()),
22530 color: cx.theme().players().agent(),
22531 })
22532 } else {
22533 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22534 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22535 let user_name = participant_names.get(&collaborator.user_id).cloned();
22536 Some(RemoteSelection {
22537 replica_id,
22538 selection,
22539 cursor_shape,
22540 line_mode,
22541 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22542 user_name,
22543 color: if let Some(index) = participant_index {
22544 cx.theme().players().color_for_participant(index.0)
22545 } else {
22546 cx.theme().players().absent()
22547 },
22548 })
22549 }
22550 })
22551 }
22552
22553 pub fn hunks_for_ranges(
22554 &self,
22555 ranges: impl IntoIterator<Item = Range<Point>>,
22556 ) -> Vec<MultiBufferDiffHunk> {
22557 let mut hunks = Vec::new();
22558 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22559 HashMap::default();
22560 for query_range in ranges {
22561 let query_rows =
22562 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22563 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22564 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22565 ) {
22566 // Include deleted hunks that are adjacent to the query range, because
22567 // otherwise they would be missed.
22568 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22569 if hunk.status().is_deleted() {
22570 intersects_range |= hunk.row_range.start == query_rows.end;
22571 intersects_range |= hunk.row_range.end == query_rows.start;
22572 }
22573 if intersects_range {
22574 if !processed_buffer_rows
22575 .entry(hunk.buffer_id)
22576 .or_default()
22577 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22578 {
22579 continue;
22580 }
22581 hunks.push(hunk);
22582 }
22583 }
22584 }
22585
22586 hunks
22587 }
22588
22589 fn display_diff_hunks_for_rows<'a>(
22590 &'a self,
22591 display_rows: Range<DisplayRow>,
22592 folded_buffers: &'a HashSet<BufferId>,
22593 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22594 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22595 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22596
22597 self.buffer_snapshot
22598 .diff_hunks_in_range(buffer_start..buffer_end)
22599 .filter_map(|hunk| {
22600 if folded_buffers.contains(&hunk.buffer_id) {
22601 return None;
22602 }
22603
22604 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22605 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22606
22607 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22608 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22609
22610 let display_hunk = if hunk_display_start.column() != 0 {
22611 DisplayDiffHunk::Folded {
22612 display_row: hunk_display_start.row(),
22613 }
22614 } else {
22615 let mut end_row = hunk_display_end.row();
22616 if hunk_display_end.column() > 0 {
22617 end_row.0 += 1;
22618 }
22619 let is_created_file = hunk.is_created_file();
22620 DisplayDiffHunk::Unfolded {
22621 status: hunk.status(),
22622 diff_base_byte_range: hunk.diff_base_byte_range,
22623 display_row_range: hunk_display_start.row()..end_row,
22624 multi_buffer_range: Anchor::range_in_buffer(
22625 hunk.excerpt_id,
22626 hunk.buffer_id,
22627 hunk.buffer_range,
22628 ),
22629 is_created_file,
22630 }
22631 };
22632
22633 Some(display_hunk)
22634 })
22635 }
22636
22637 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22638 self.display_snapshot.buffer_snapshot.language_at(position)
22639 }
22640
22641 pub fn is_focused(&self) -> bool {
22642 self.is_focused
22643 }
22644
22645 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22646 self.placeholder_text.as_ref()
22647 }
22648
22649 pub fn scroll_position(&self) -> gpui::Point<f32> {
22650 self.scroll_anchor.scroll_position(&self.display_snapshot)
22651 }
22652
22653 fn gutter_dimensions(
22654 &self,
22655 font_id: FontId,
22656 font_size: Pixels,
22657 max_line_number_width: Pixels,
22658 cx: &App,
22659 ) -> Option<GutterDimensions> {
22660 if !self.show_gutter {
22661 return None;
22662 }
22663
22664 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22665 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22666
22667 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22668 matches!(
22669 ProjectSettings::get_global(cx).git.git_gutter,
22670 Some(GitGutterSetting::TrackedFiles)
22671 )
22672 });
22673 let gutter_settings = EditorSettings::get_global(cx).gutter;
22674 let show_line_numbers = self
22675 .show_line_numbers
22676 .unwrap_or(gutter_settings.line_numbers);
22677 let line_gutter_width = if show_line_numbers {
22678 // Avoid flicker-like gutter resizes when the line number gains another digit by
22679 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22680 let min_width_for_number_on_gutter =
22681 ch_advance * gutter_settings.min_line_number_digits as f32;
22682 max_line_number_width.max(min_width_for_number_on_gutter)
22683 } else {
22684 0.0.into()
22685 };
22686
22687 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22688 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22689
22690 let git_blame_entries_width =
22691 self.git_blame_gutter_max_author_length
22692 .map(|max_author_length| {
22693 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22694 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22695
22696 /// The number of characters to dedicate to gaps and margins.
22697 const SPACING_WIDTH: usize = 4;
22698
22699 let max_char_count = max_author_length.min(renderer.max_author_length())
22700 + ::git::SHORT_SHA_LENGTH
22701 + MAX_RELATIVE_TIMESTAMP.len()
22702 + SPACING_WIDTH;
22703
22704 ch_advance * max_char_count
22705 });
22706
22707 let is_singleton = self.buffer_snapshot.is_singleton();
22708
22709 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22710 left_padding += if !is_singleton {
22711 ch_width * 4.0
22712 } else if show_runnables || show_breakpoints {
22713 ch_width * 3.0
22714 } else if show_git_gutter && show_line_numbers {
22715 ch_width * 2.0
22716 } else if show_git_gutter || show_line_numbers {
22717 ch_width
22718 } else {
22719 px(0.)
22720 };
22721
22722 let shows_folds = is_singleton && gutter_settings.folds;
22723
22724 let right_padding = if shows_folds && show_line_numbers {
22725 ch_width * 4.0
22726 } else if shows_folds || (!is_singleton && show_line_numbers) {
22727 ch_width * 3.0
22728 } else if show_line_numbers {
22729 ch_width
22730 } else {
22731 px(0.)
22732 };
22733
22734 Some(GutterDimensions {
22735 left_padding,
22736 right_padding,
22737 width: line_gutter_width + left_padding + right_padding,
22738 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22739 git_blame_entries_width,
22740 })
22741 }
22742
22743 pub fn render_crease_toggle(
22744 &self,
22745 buffer_row: MultiBufferRow,
22746 row_contains_cursor: bool,
22747 editor: Entity<Editor>,
22748 window: &mut Window,
22749 cx: &mut App,
22750 ) -> Option<AnyElement> {
22751 let folded = self.is_line_folded(buffer_row);
22752 let mut is_foldable = false;
22753
22754 if let Some(crease) = self
22755 .crease_snapshot
22756 .query_row(buffer_row, &self.buffer_snapshot)
22757 {
22758 is_foldable = true;
22759 match crease {
22760 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22761 if let Some(render_toggle) = render_toggle {
22762 let toggle_callback =
22763 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22764 if folded {
22765 editor.update(cx, |editor, cx| {
22766 editor.fold_at(buffer_row, window, cx)
22767 });
22768 } else {
22769 editor.update(cx, |editor, cx| {
22770 editor.unfold_at(buffer_row, window, cx)
22771 });
22772 }
22773 });
22774 return Some((render_toggle)(
22775 buffer_row,
22776 folded,
22777 toggle_callback,
22778 window,
22779 cx,
22780 ));
22781 }
22782 }
22783 }
22784 }
22785
22786 is_foldable |= self.starts_indent(buffer_row);
22787
22788 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22789 Some(
22790 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22791 .toggle_state(folded)
22792 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22793 if folded {
22794 this.unfold_at(buffer_row, window, cx);
22795 } else {
22796 this.fold_at(buffer_row, window, cx);
22797 }
22798 }))
22799 .into_any_element(),
22800 )
22801 } else {
22802 None
22803 }
22804 }
22805
22806 pub fn render_crease_trailer(
22807 &self,
22808 buffer_row: MultiBufferRow,
22809 window: &mut Window,
22810 cx: &mut App,
22811 ) -> Option<AnyElement> {
22812 let folded = self.is_line_folded(buffer_row);
22813 if let Crease::Inline { render_trailer, .. } = self
22814 .crease_snapshot
22815 .query_row(buffer_row, &self.buffer_snapshot)?
22816 {
22817 let render_trailer = render_trailer.as_ref()?;
22818 Some(render_trailer(buffer_row, folded, window, cx))
22819 } else {
22820 None
22821 }
22822 }
22823}
22824
22825impl Deref for EditorSnapshot {
22826 type Target = DisplaySnapshot;
22827
22828 fn deref(&self) -> &Self::Target {
22829 &self.display_snapshot
22830 }
22831}
22832
22833#[derive(Clone, Debug, PartialEq, Eq)]
22834pub enum EditorEvent {
22835 InputIgnored {
22836 text: Arc<str>,
22837 },
22838 InputHandled {
22839 utf16_range_to_replace: Option<Range<isize>>,
22840 text: Arc<str>,
22841 },
22842 ExcerptsAdded {
22843 buffer: Entity<Buffer>,
22844 predecessor: ExcerptId,
22845 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22846 },
22847 ExcerptsRemoved {
22848 ids: Vec<ExcerptId>,
22849 removed_buffer_ids: Vec<BufferId>,
22850 },
22851 BufferFoldToggled {
22852 ids: Vec<ExcerptId>,
22853 folded: bool,
22854 },
22855 ExcerptsEdited {
22856 ids: Vec<ExcerptId>,
22857 },
22858 ExcerptsExpanded {
22859 ids: Vec<ExcerptId>,
22860 },
22861 BufferEdited,
22862 Edited {
22863 transaction_id: clock::Lamport,
22864 },
22865 Reparsed(BufferId),
22866 Focused,
22867 FocusedIn,
22868 Blurred,
22869 DirtyChanged,
22870 Saved,
22871 TitleChanged,
22872 DiffBaseChanged,
22873 SelectionsChanged {
22874 local: bool,
22875 },
22876 ScrollPositionChanged {
22877 local: bool,
22878 autoscroll: bool,
22879 },
22880 Closed,
22881 TransactionUndone {
22882 transaction_id: clock::Lamport,
22883 },
22884 TransactionBegun {
22885 transaction_id: clock::Lamport,
22886 },
22887 Reloaded,
22888 CursorShapeChanged,
22889 BreadcrumbsChanged,
22890 PushedToNavHistory {
22891 anchor: Anchor,
22892 is_deactivate: bool,
22893 },
22894}
22895
22896impl EventEmitter<EditorEvent> for Editor {}
22897
22898impl Focusable for Editor {
22899 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22900 self.focus_handle.clone()
22901 }
22902}
22903
22904impl Render for Editor {
22905 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22906 let settings = ThemeSettings::get_global(cx);
22907
22908 let mut text_style = match self.mode {
22909 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
22910 color: cx.theme().colors().editor_foreground,
22911 font_family: settings.ui_font.family.clone(),
22912 font_features: settings.ui_font.features.clone(),
22913 font_fallbacks: settings.ui_font.fallbacks.clone(),
22914 font_size: rems(0.875).into(),
22915 font_weight: settings.ui_font.weight,
22916 line_height: relative(settings.buffer_line_height.value()),
22917 ..Default::default()
22918 },
22919 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22920 color: cx.theme().colors().editor_foreground,
22921 font_family: settings.buffer_font.family.clone(),
22922 font_features: settings.buffer_font.features.clone(),
22923 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22924 font_size: settings.buffer_font_size(cx).into(),
22925 font_weight: settings.buffer_font.weight,
22926 line_height: relative(settings.buffer_line_height.value()),
22927 ..Default::default()
22928 },
22929 };
22930 if let Some(text_style_refinement) = &self.text_style_refinement {
22931 text_style.refine(text_style_refinement)
22932 }
22933
22934 let background = match self.mode {
22935 EditorMode::SingleLine => cx.theme().system().transparent,
22936 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22937 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22938 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22939 };
22940
22941 EditorElement::new(
22942 &cx.entity(),
22943 EditorStyle {
22944 background,
22945 border: cx.theme().colors().border,
22946 local_player: cx.theme().players().local(),
22947 text: text_style,
22948 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22949 syntax: cx.theme().syntax().clone(),
22950 status: cx.theme().status().clone(),
22951 inlay_hints_style: make_inlay_hints_style(cx),
22952 edit_prediction_styles: make_suggestion_styles(cx),
22953 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22954 show_underlines: self.diagnostics_enabled(),
22955 },
22956 )
22957 }
22958}
22959
22960impl EntityInputHandler for Editor {
22961 fn text_for_range(
22962 &mut self,
22963 range_utf16: Range<usize>,
22964 adjusted_range: &mut Option<Range<usize>>,
22965 _: &mut Window,
22966 cx: &mut Context<Self>,
22967 ) -> Option<String> {
22968 let snapshot = self.buffer.read(cx).read(cx);
22969 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22970 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22971 if (start.0..end.0) != range_utf16 {
22972 adjusted_range.replace(start.0..end.0);
22973 }
22974 Some(snapshot.text_for_range(start..end).collect())
22975 }
22976
22977 fn selected_text_range(
22978 &mut self,
22979 ignore_disabled_input: bool,
22980 _: &mut Window,
22981 cx: &mut Context<Self>,
22982 ) -> Option<UTF16Selection> {
22983 // Prevent the IME menu from appearing when holding down an alphabetic key
22984 // while input is disabled.
22985 if !ignore_disabled_input && !self.input_enabled {
22986 return None;
22987 }
22988
22989 let selection = self.selections.newest::<OffsetUtf16>(cx);
22990 let range = selection.range();
22991
22992 Some(UTF16Selection {
22993 range: range.start.0..range.end.0,
22994 reversed: selection.reversed,
22995 })
22996 }
22997
22998 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22999 let snapshot = self.buffer.read(cx).read(cx);
23000 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23001 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23002 }
23003
23004 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23005 self.clear_highlights::<InputComposition>(cx);
23006 self.ime_transaction.take();
23007 }
23008
23009 fn replace_text_in_range(
23010 &mut self,
23011 range_utf16: Option<Range<usize>>,
23012 text: &str,
23013 window: &mut Window,
23014 cx: &mut Context<Self>,
23015 ) {
23016 if !self.input_enabled {
23017 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23018 return;
23019 }
23020
23021 self.transact(window, cx, |this, window, cx| {
23022 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23023 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23024 Some(this.selection_replacement_ranges(range_utf16, cx))
23025 } else {
23026 this.marked_text_ranges(cx)
23027 };
23028
23029 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23030 let newest_selection_id = this.selections.newest_anchor().id;
23031 this.selections
23032 .all::<OffsetUtf16>(cx)
23033 .iter()
23034 .zip(ranges_to_replace.iter())
23035 .find_map(|(selection, range)| {
23036 if selection.id == newest_selection_id {
23037 Some(
23038 (range.start.0 as isize - selection.head().0 as isize)
23039 ..(range.end.0 as isize - selection.head().0 as isize),
23040 )
23041 } else {
23042 None
23043 }
23044 })
23045 });
23046
23047 cx.emit(EditorEvent::InputHandled {
23048 utf16_range_to_replace: range_to_replace,
23049 text: text.into(),
23050 });
23051
23052 if let Some(new_selected_ranges) = new_selected_ranges {
23053 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23054 selections.select_ranges(new_selected_ranges)
23055 });
23056 this.backspace(&Default::default(), window, cx);
23057 }
23058
23059 this.handle_input(text, window, cx);
23060 });
23061
23062 if let Some(transaction) = self.ime_transaction {
23063 self.buffer.update(cx, |buffer, cx| {
23064 buffer.group_until_transaction(transaction, cx);
23065 });
23066 }
23067
23068 self.unmark_text(window, cx);
23069 }
23070
23071 fn replace_and_mark_text_in_range(
23072 &mut self,
23073 range_utf16: Option<Range<usize>>,
23074 text: &str,
23075 new_selected_range_utf16: Option<Range<usize>>,
23076 window: &mut Window,
23077 cx: &mut Context<Self>,
23078 ) {
23079 if !self.input_enabled {
23080 return;
23081 }
23082
23083 let transaction = self.transact(window, cx, |this, window, cx| {
23084 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23085 let snapshot = this.buffer.read(cx).read(cx);
23086 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23087 for marked_range in &mut marked_ranges {
23088 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23089 marked_range.start.0 += relative_range_utf16.start;
23090 marked_range.start =
23091 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23092 marked_range.end =
23093 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23094 }
23095 }
23096 Some(marked_ranges)
23097 } else if let Some(range_utf16) = range_utf16 {
23098 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23099 Some(this.selection_replacement_ranges(range_utf16, cx))
23100 } else {
23101 None
23102 };
23103
23104 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23105 let newest_selection_id = this.selections.newest_anchor().id;
23106 this.selections
23107 .all::<OffsetUtf16>(cx)
23108 .iter()
23109 .zip(ranges_to_replace.iter())
23110 .find_map(|(selection, range)| {
23111 if selection.id == newest_selection_id {
23112 Some(
23113 (range.start.0 as isize - selection.head().0 as isize)
23114 ..(range.end.0 as isize - selection.head().0 as isize),
23115 )
23116 } else {
23117 None
23118 }
23119 })
23120 });
23121
23122 cx.emit(EditorEvent::InputHandled {
23123 utf16_range_to_replace: range_to_replace,
23124 text: text.into(),
23125 });
23126
23127 if let Some(ranges) = ranges_to_replace {
23128 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23129 s.select_ranges(ranges)
23130 });
23131 }
23132
23133 let marked_ranges = {
23134 let snapshot = this.buffer.read(cx).read(cx);
23135 this.selections
23136 .disjoint_anchors()
23137 .iter()
23138 .map(|selection| {
23139 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23140 })
23141 .collect::<Vec<_>>()
23142 };
23143
23144 if text.is_empty() {
23145 this.unmark_text(window, cx);
23146 } else {
23147 this.highlight_text::<InputComposition>(
23148 marked_ranges.clone(),
23149 HighlightStyle {
23150 underline: Some(UnderlineStyle {
23151 thickness: px(1.),
23152 color: None,
23153 wavy: false,
23154 }),
23155 ..Default::default()
23156 },
23157 cx,
23158 );
23159 }
23160
23161 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23162 let use_autoclose = this.use_autoclose;
23163 let use_auto_surround = this.use_auto_surround;
23164 this.set_use_autoclose(false);
23165 this.set_use_auto_surround(false);
23166 this.handle_input(text, window, cx);
23167 this.set_use_autoclose(use_autoclose);
23168 this.set_use_auto_surround(use_auto_surround);
23169
23170 if let Some(new_selected_range) = new_selected_range_utf16 {
23171 let snapshot = this.buffer.read(cx).read(cx);
23172 let new_selected_ranges = marked_ranges
23173 .into_iter()
23174 .map(|marked_range| {
23175 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23176 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23177 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23178 snapshot.clip_offset_utf16(new_start, Bias::Left)
23179 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23180 })
23181 .collect::<Vec<_>>();
23182
23183 drop(snapshot);
23184 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23185 selections.select_ranges(new_selected_ranges)
23186 });
23187 }
23188 });
23189
23190 self.ime_transaction = self.ime_transaction.or(transaction);
23191 if let Some(transaction) = self.ime_transaction {
23192 self.buffer.update(cx, |buffer, cx| {
23193 buffer.group_until_transaction(transaction, cx);
23194 });
23195 }
23196
23197 if self.text_highlights::<InputComposition>(cx).is_none() {
23198 self.ime_transaction.take();
23199 }
23200 }
23201
23202 fn bounds_for_range(
23203 &mut self,
23204 range_utf16: Range<usize>,
23205 element_bounds: gpui::Bounds<Pixels>,
23206 window: &mut Window,
23207 cx: &mut Context<Self>,
23208 ) -> Option<gpui::Bounds<Pixels>> {
23209 let text_layout_details = self.text_layout_details(window);
23210 let CharacterDimensions {
23211 em_width,
23212 em_advance,
23213 line_height,
23214 } = self.character_dimensions(window);
23215
23216 let snapshot = self.snapshot(window, cx);
23217 let scroll_position = snapshot.scroll_position();
23218 let scroll_left = scroll_position.x * em_advance;
23219
23220 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23221 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23222 + self.gutter_dimensions.full_width();
23223 let y = line_height * (start.row().as_f32() - scroll_position.y);
23224
23225 Some(Bounds {
23226 origin: element_bounds.origin + point(x, y),
23227 size: size(em_width, line_height),
23228 })
23229 }
23230
23231 fn character_index_for_point(
23232 &mut self,
23233 point: gpui::Point<Pixels>,
23234 _window: &mut Window,
23235 _cx: &mut Context<Self>,
23236 ) -> Option<usize> {
23237 let position_map = self.last_position_map.as_ref()?;
23238 if !position_map.text_hitbox.contains(&point) {
23239 return None;
23240 }
23241 let display_point = position_map.point_for_position(point).previous_valid;
23242 let anchor = position_map
23243 .snapshot
23244 .display_point_to_anchor(display_point, Bias::Left);
23245 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23246 Some(utf16_offset.0)
23247 }
23248}
23249
23250trait SelectionExt {
23251 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23252 fn spanned_rows(
23253 &self,
23254 include_end_if_at_line_start: bool,
23255 map: &DisplaySnapshot,
23256 ) -> Range<MultiBufferRow>;
23257}
23258
23259impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23260 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23261 let start = self
23262 .start
23263 .to_point(&map.buffer_snapshot)
23264 .to_display_point(map);
23265 let end = self
23266 .end
23267 .to_point(&map.buffer_snapshot)
23268 .to_display_point(map);
23269 if self.reversed {
23270 end..start
23271 } else {
23272 start..end
23273 }
23274 }
23275
23276 fn spanned_rows(
23277 &self,
23278 include_end_if_at_line_start: bool,
23279 map: &DisplaySnapshot,
23280 ) -> Range<MultiBufferRow> {
23281 let start = self.start.to_point(&map.buffer_snapshot);
23282 let mut end = self.end.to_point(&map.buffer_snapshot);
23283 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23284 end.row -= 1;
23285 }
23286
23287 let buffer_start = map.prev_line_boundary(start).0;
23288 let buffer_end = map.next_line_boundary(end).0;
23289 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23290 }
23291}
23292
23293impl<T: InvalidationRegion> InvalidationStack<T> {
23294 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23295 where
23296 S: Clone + ToOffset,
23297 {
23298 while let Some(region) = self.last() {
23299 let all_selections_inside_invalidation_ranges =
23300 if selections.len() == region.ranges().len() {
23301 selections
23302 .iter()
23303 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23304 .all(|(selection, invalidation_range)| {
23305 let head = selection.head().to_offset(buffer);
23306 invalidation_range.start <= head && invalidation_range.end >= head
23307 })
23308 } else {
23309 false
23310 };
23311
23312 if all_selections_inside_invalidation_ranges {
23313 break;
23314 } else {
23315 self.pop();
23316 }
23317 }
23318 }
23319}
23320
23321impl<T> Default for InvalidationStack<T> {
23322 fn default() -> Self {
23323 Self(Default::default())
23324 }
23325}
23326
23327impl<T> Deref for InvalidationStack<T> {
23328 type Target = Vec<T>;
23329
23330 fn deref(&self) -> &Self::Target {
23331 &self.0
23332 }
23333}
23334
23335impl<T> DerefMut for InvalidationStack<T> {
23336 fn deref_mut(&mut self) -> &mut Self::Target {
23337 &mut self.0
23338 }
23339}
23340
23341impl InvalidationRegion for SnippetState {
23342 fn ranges(&self) -> &[Range<Anchor>] {
23343 &self.ranges[self.active_index]
23344 }
23345}
23346
23347fn edit_prediction_edit_text(
23348 current_snapshot: &BufferSnapshot,
23349 edits: &[(Range<Anchor>, String)],
23350 edit_preview: &EditPreview,
23351 include_deletions: bool,
23352 cx: &App,
23353) -> HighlightedText {
23354 let edits = edits
23355 .iter()
23356 .map(|(anchor, text)| {
23357 (
23358 anchor.start.text_anchor..anchor.end.text_anchor,
23359 text.clone(),
23360 )
23361 })
23362 .collect::<Vec<_>>();
23363
23364 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23365}
23366
23367fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23368 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23369 // Just show the raw edit text with basic styling
23370 let mut text = String::new();
23371 let mut highlights = Vec::new();
23372
23373 let insertion_highlight_style = HighlightStyle {
23374 color: Some(cx.theme().colors().text),
23375 ..Default::default()
23376 };
23377
23378 for (_, edit_text) in edits {
23379 let start_offset = text.len();
23380 text.push_str(edit_text);
23381 let end_offset = text.len();
23382
23383 if start_offset < end_offset {
23384 highlights.push((start_offset..end_offset, insertion_highlight_style));
23385 }
23386 }
23387
23388 HighlightedText {
23389 text: text.into(),
23390 highlights,
23391 }
23392}
23393
23394pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23395 match severity {
23396 lsp::DiagnosticSeverity::ERROR => colors.error,
23397 lsp::DiagnosticSeverity::WARNING => colors.warning,
23398 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23399 lsp::DiagnosticSeverity::HINT => colors.info,
23400 _ => colors.ignored,
23401 }
23402}
23403
23404pub fn styled_runs_for_code_label<'a>(
23405 label: &'a CodeLabel,
23406 syntax_theme: &'a theme::SyntaxTheme,
23407) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23408 let fade_out = HighlightStyle {
23409 fade_out: Some(0.35),
23410 ..Default::default()
23411 };
23412
23413 let mut prev_end = label.filter_range.end;
23414 label
23415 .runs
23416 .iter()
23417 .enumerate()
23418 .flat_map(move |(ix, (range, highlight_id))| {
23419 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23420 style
23421 } else {
23422 return Default::default();
23423 };
23424 let mut muted_style = style;
23425 muted_style.highlight(fade_out);
23426
23427 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23428 if range.start >= label.filter_range.end {
23429 if range.start > prev_end {
23430 runs.push((prev_end..range.start, fade_out));
23431 }
23432 runs.push((range.clone(), muted_style));
23433 } else if range.end <= label.filter_range.end {
23434 runs.push((range.clone(), style));
23435 } else {
23436 runs.push((range.start..label.filter_range.end, style));
23437 runs.push((label.filter_range.end..range.end, muted_style));
23438 }
23439 prev_end = cmp::max(prev_end, range.end);
23440
23441 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23442 runs.push((prev_end..label.text.len(), fade_out));
23443 }
23444
23445 runs
23446 })
23447}
23448
23449pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23450 let mut prev_index = 0;
23451 let mut prev_codepoint: Option<char> = None;
23452 text.char_indices()
23453 .chain([(text.len(), '\0')])
23454 .filter_map(move |(index, codepoint)| {
23455 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23456 let is_boundary = index == text.len()
23457 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23458 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23459 if is_boundary {
23460 let chunk = &text[prev_index..index];
23461 prev_index = index;
23462 Some(chunk)
23463 } else {
23464 None
23465 }
23466 })
23467}
23468
23469pub trait RangeToAnchorExt: Sized {
23470 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23471
23472 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23473 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23474 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23475 }
23476}
23477
23478impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23479 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23480 let start_offset = self.start.to_offset(snapshot);
23481 let end_offset = self.end.to_offset(snapshot);
23482 if start_offset == end_offset {
23483 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23484 } else {
23485 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23486 }
23487 }
23488}
23489
23490pub trait RowExt {
23491 fn as_f32(&self) -> f32;
23492
23493 fn next_row(&self) -> Self;
23494
23495 fn previous_row(&self) -> Self;
23496
23497 fn minus(&self, other: Self) -> u32;
23498}
23499
23500impl RowExt for DisplayRow {
23501 fn as_f32(&self) -> f32 {
23502 self.0 as f32
23503 }
23504
23505 fn next_row(&self) -> Self {
23506 Self(self.0 + 1)
23507 }
23508
23509 fn previous_row(&self) -> Self {
23510 Self(self.0.saturating_sub(1))
23511 }
23512
23513 fn minus(&self, other: Self) -> u32 {
23514 self.0 - other.0
23515 }
23516}
23517
23518impl RowExt for MultiBufferRow {
23519 fn as_f32(&self) -> f32 {
23520 self.0 as f32
23521 }
23522
23523 fn next_row(&self) -> Self {
23524 Self(self.0 + 1)
23525 }
23526
23527 fn previous_row(&self) -> Self {
23528 Self(self.0.saturating_sub(1))
23529 }
23530
23531 fn minus(&self, other: Self) -> u32 {
23532 self.0 - other.0
23533 }
23534}
23535
23536trait RowRangeExt {
23537 type Row;
23538
23539 fn len(&self) -> usize;
23540
23541 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23542}
23543
23544impl RowRangeExt for Range<MultiBufferRow> {
23545 type Row = MultiBufferRow;
23546
23547 fn len(&self) -> usize {
23548 (self.end.0 - self.start.0) as usize
23549 }
23550
23551 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23552 (self.start.0..self.end.0).map(MultiBufferRow)
23553 }
23554}
23555
23556impl RowRangeExt for Range<DisplayRow> {
23557 type Row = DisplayRow;
23558
23559 fn len(&self) -> usize {
23560 (self.end.0 - self.start.0) as usize
23561 }
23562
23563 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23564 (self.start.0..self.end.0).map(DisplayRow)
23565 }
23566}
23567
23568/// If select range has more than one line, we
23569/// just point the cursor to range.start.
23570fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23571 if range.start.row == range.end.row {
23572 range
23573 } else {
23574 range.start..range.start
23575 }
23576}
23577pub struct KillRing(ClipboardItem);
23578impl Global for KillRing {}
23579
23580const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23581
23582enum BreakpointPromptEditAction {
23583 Log,
23584 Condition,
23585 HitCondition,
23586}
23587
23588struct BreakpointPromptEditor {
23589 pub(crate) prompt: Entity<Editor>,
23590 editor: WeakEntity<Editor>,
23591 breakpoint_anchor: Anchor,
23592 breakpoint: Breakpoint,
23593 edit_action: BreakpointPromptEditAction,
23594 block_ids: HashSet<CustomBlockId>,
23595 editor_margins: Arc<Mutex<EditorMargins>>,
23596 _subscriptions: Vec<Subscription>,
23597}
23598
23599impl BreakpointPromptEditor {
23600 const MAX_LINES: u8 = 4;
23601
23602 fn new(
23603 editor: WeakEntity<Editor>,
23604 breakpoint_anchor: Anchor,
23605 breakpoint: Breakpoint,
23606 edit_action: BreakpointPromptEditAction,
23607 window: &mut Window,
23608 cx: &mut Context<Self>,
23609 ) -> Self {
23610 let base_text = match edit_action {
23611 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23612 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23613 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23614 }
23615 .map(|msg| msg.to_string())
23616 .unwrap_or_default();
23617
23618 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23619 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23620
23621 let prompt = cx.new(|cx| {
23622 let mut prompt = Editor::new(
23623 EditorMode::AutoHeight {
23624 min_lines: 1,
23625 max_lines: Some(Self::MAX_LINES as usize),
23626 },
23627 buffer,
23628 None,
23629 window,
23630 cx,
23631 );
23632 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23633 prompt.set_show_cursor_when_unfocused(false, cx);
23634 prompt.set_placeholder_text(
23635 match edit_action {
23636 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23637 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23638 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23639 },
23640 cx,
23641 );
23642
23643 prompt
23644 });
23645
23646 Self {
23647 prompt,
23648 editor,
23649 breakpoint_anchor,
23650 breakpoint,
23651 edit_action,
23652 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23653 block_ids: Default::default(),
23654 _subscriptions: vec![],
23655 }
23656 }
23657
23658 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23659 self.block_ids.extend(block_ids)
23660 }
23661
23662 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23663 if let Some(editor) = self.editor.upgrade() {
23664 let message = self
23665 .prompt
23666 .read(cx)
23667 .buffer
23668 .read(cx)
23669 .as_singleton()
23670 .expect("A multi buffer in breakpoint prompt isn't possible")
23671 .read(cx)
23672 .as_rope()
23673 .to_string();
23674
23675 editor.update(cx, |editor, cx| {
23676 editor.edit_breakpoint_at_anchor(
23677 self.breakpoint_anchor,
23678 self.breakpoint.clone(),
23679 match self.edit_action {
23680 BreakpointPromptEditAction::Log => {
23681 BreakpointEditAction::EditLogMessage(message.into())
23682 }
23683 BreakpointPromptEditAction::Condition => {
23684 BreakpointEditAction::EditCondition(message.into())
23685 }
23686 BreakpointPromptEditAction::HitCondition => {
23687 BreakpointEditAction::EditHitCondition(message.into())
23688 }
23689 },
23690 cx,
23691 );
23692
23693 editor.remove_blocks(self.block_ids.clone(), None, cx);
23694 cx.focus_self(window);
23695 });
23696 }
23697 }
23698
23699 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23700 self.editor
23701 .update(cx, |editor, cx| {
23702 editor.remove_blocks(self.block_ids.clone(), None, cx);
23703 window.focus(&editor.focus_handle);
23704 })
23705 .log_err();
23706 }
23707
23708 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23709 let settings = ThemeSettings::get_global(cx);
23710 let text_style = TextStyle {
23711 color: if self.prompt.read(cx).read_only(cx) {
23712 cx.theme().colors().text_disabled
23713 } else {
23714 cx.theme().colors().text
23715 },
23716 font_family: settings.buffer_font.family.clone(),
23717 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23718 font_size: settings.buffer_font_size(cx).into(),
23719 font_weight: settings.buffer_font.weight,
23720 line_height: relative(settings.buffer_line_height.value()),
23721 ..Default::default()
23722 };
23723 EditorElement::new(
23724 &self.prompt,
23725 EditorStyle {
23726 background: cx.theme().colors().editor_background,
23727 local_player: cx.theme().players().local(),
23728 text: text_style,
23729 ..Default::default()
23730 },
23731 )
23732 }
23733}
23734
23735impl Render for BreakpointPromptEditor {
23736 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23737 let editor_margins = *self.editor_margins.lock();
23738 let gutter_dimensions = editor_margins.gutter;
23739 h_flex()
23740 .key_context("Editor")
23741 .bg(cx.theme().colors().editor_background)
23742 .border_y_1()
23743 .border_color(cx.theme().status().info_border)
23744 .size_full()
23745 .py(window.line_height() / 2.5)
23746 .on_action(cx.listener(Self::confirm))
23747 .on_action(cx.listener(Self::cancel))
23748 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23749 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23750 }
23751}
23752
23753impl Focusable for BreakpointPromptEditor {
23754 fn focus_handle(&self, cx: &App) -> FocusHandle {
23755 self.prompt.focus_handle(cx)
23756 }
23757}
23758
23759fn all_edits_insertions_or_deletions(
23760 edits: &Vec<(Range<Anchor>, String)>,
23761 snapshot: &MultiBufferSnapshot,
23762) -> bool {
23763 let mut all_insertions = true;
23764 let mut all_deletions = true;
23765
23766 for (range, new_text) in edits.iter() {
23767 let range_is_empty = range.to_offset(snapshot).is_empty();
23768 let text_is_empty = new_text.is_empty();
23769
23770 if range_is_empty != text_is_empty {
23771 if range_is_empty {
23772 all_deletions = false;
23773 } else {
23774 all_insertions = false;
23775 }
23776 } else {
23777 return false;
23778 }
23779
23780 if !all_insertions && !all_deletions {
23781 return false;
23782 }
23783 }
23784 all_insertions || all_deletions
23785}
23786
23787struct MissingEditPredictionKeybindingTooltip;
23788
23789impl Render for MissingEditPredictionKeybindingTooltip {
23790 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23791 ui::tooltip_container(window, cx, |container, _, cx| {
23792 container
23793 .flex_shrink_0()
23794 .max_w_80()
23795 .min_h(rems_from_px(124.))
23796 .justify_between()
23797 .child(
23798 v_flex()
23799 .flex_1()
23800 .text_ui_sm(cx)
23801 .child(Label::new("Conflict with Accept Keybinding"))
23802 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23803 )
23804 .child(
23805 h_flex()
23806 .pb_1()
23807 .gap_1()
23808 .items_end()
23809 .w_full()
23810 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23811 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23812 }))
23813 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23814 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23815 })),
23816 )
23817 })
23818 }
23819}
23820
23821#[derive(Debug, Clone, Copy, PartialEq)]
23822pub struct LineHighlight {
23823 pub background: Background,
23824 pub border: Option<gpui::Hsla>,
23825 pub include_gutter: bool,
23826 pub type_id: Option<TypeId>,
23827}
23828
23829struct LineManipulationResult {
23830 pub new_text: String,
23831 pub line_count_before: usize,
23832 pub line_count_after: usize,
23833}
23834
23835fn render_diff_hunk_controls(
23836 row: u32,
23837 status: &DiffHunkStatus,
23838 hunk_range: Range<Anchor>,
23839 is_created_file: bool,
23840 line_height: Pixels,
23841 editor: &Entity<Editor>,
23842 _window: &mut Window,
23843 cx: &mut App,
23844) -> AnyElement {
23845 h_flex()
23846 .h(line_height)
23847 .mr_1()
23848 .gap_1()
23849 .px_0p5()
23850 .pb_1()
23851 .border_x_1()
23852 .border_b_1()
23853 .border_color(cx.theme().colors().border_variant)
23854 .rounded_b_lg()
23855 .bg(cx.theme().colors().editor_background)
23856 .gap_1()
23857 .block_mouse_except_scroll()
23858 .shadow_md()
23859 .child(if status.has_secondary_hunk() {
23860 Button::new(("stage", row as u64), "Stage")
23861 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23862 .tooltip({
23863 let focus_handle = editor.focus_handle(cx);
23864 move |window, cx| {
23865 Tooltip::for_action_in(
23866 "Stage Hunk",
23867 &::git::ToggleStaged,
23868 &focus_handle,
23869 window,
23870 cx,
23871 )
23872 }
23873 })
23874 .on_click({
23875 let editor = editor.clone();
23876 move |_event, _window, cx| {
23877 editor.update(cx, |editor, cx| {
23878 editor.stage_or_unstage_diff_hunks(
23879 true,
23880 vec![hunk_range.start..hunk_range.start],
23881 cx,
23882 );
23883 });
23884 }
23885 })
23886 } else {
23887 Button::new(("unstage", row as u64), "Unstage")
23888 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23889 .tooltip({
23890 let focus_handle = editor.focus_handle(cx);
23891 move |window, cx| {
23892 Tooltip::for_action_in(
23893 "Unstage Hunk",
23894 &::git::ToggleStaged,
23895 &focus_handle,
23896 window,
23897 cx,
23898 )
23899 }
23900 })
23901 .on_click({
23902 let editor = editor.clone();
23903 move |_event, _window, cx| {
23904 editor.update(cx, |editor, cx| {
23905 editor.stage_or_unstage_diff_hunks(
23906 false,
23907 vec![hunk_range.start..hunk_range.start],
23908 cx,
23909 );
23910 });
23911 }
23912 })
23913 })
23914 .child(
23915 Button::new(("restore", row as u64), "Restore")
23916 .tooltip({
23917 let focus_handle = editor.focus_handle(cx);
23918 move |window, cx| {
23919 Tooltip::for_action_in(
23920 "Restore Hunk",
23921 &::git::Restore,
23922 &focus_handle,
23923 window,
23924 cx,
23925 )
23926 }
23927 })
23928 .on_click({
23929 let editor = editor.clone();
23930 move |_event, window, cx| {
23931 editor.update(cx, |editor, cx| {
23932 let snapshot = editor.snapshot(window, cx);
23933 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23934 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23935 });
23936 }
23937 })
23938 .disabled(is_created_file),
23939 )
23940 .when(
23941 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23942 |el| {
23943 el.child(
23944 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23945 .shape(IconButtonShape::Square)
23946 .icon_size(IconSize::Small)
23947 // .disabled(!has_multiple_hunks)
23948 .tooltip({
23949 let focus_handle = editor.focus_handle(cx);
23950 move |window, cx| {
23951 Tooltip::for_action_in(
23952 "Next Hunk",
23953 &GoToHunk,
23954 &focus_handle,
23955 window,
23956 cx,
23957 )
23958 }
23959 })
23960 .on_click({
23961 let editor = editor.clone();
23962 move |_event, window, cx| {
23963 editor.update(cx, |editor, cx| {
23964 let snapshot = editor.snapshot(window, cx);
23965 let position =
23966 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23967 editor.go_to_hunk_before_or_after_position(
23968 &snapshot,
23969 position,
23970 Direction::Next,
23971 window,
23972 cx,
23973 );
23974 editor.expand_selected_diff_hunks(cx);
23975 });
23976 }
23977 }),
23978 )
23979 .child(
23980 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23981 .shape(IconButtonShape::Square)
23982 .icon_size(IconSize::Small)
23983 // .disabled(!has_multiple_hunks)
23984 .tooltip({
23985 let focus_handle = editor.focus_handle(cx);
23986 move |window, cx| {
23987 Tooltip::for_action_in(
23988 "Previous Hunk",
23989 &GoToPreviousHunk,
23990 &focus_handle,
23991 window,
23992 cx,
23993 )
23994 }
23995 })
23996 .on_click({
23997 let editor = editor.clone();
23998 move |_event, window, cx| {
23999 editor.update(cx, |editor, cx| {
24000 let snapshot = editor.snapshot(window, cx);
24001 let point =
24002 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24003 editor.go_to_hunk_before_or_after_position(
24004 &snapshot,
24005 point,
24006 Direction::Prev,
24007 window,
24008 cx,
24009 );
24010 editor.expand_selected_diff_hunks(cx);
24011 });
24012 }
24013 }),
24014 )
24015 },
24016 )
24017 .into_any_element()
24018}