1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
125 DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
126 Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
127 TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionIntent, CompletionResponse,
151 CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, Location, LocationLink,
152 PrepareRenameResponse, Project, ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
153 debugger::breakpoint_store::Breakpoint,
154 debugger::{
155 breakpoint_store::{
156 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
157 BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
164 project_settings::{GitGutterSetting, ProjectSettings},
165};
166use rand::{seq::SliceRandom, thread_rng};
167use rpc::{ErrorCode, ErrorExt, proto::PeerId};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
169use selections_collection::{
170 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
171};
172use serde::{Deserialize, Serialize};
173use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::TypeId,
178 borrow::Cow,
179 cell::OnceCell,
180 cell::RefCell,
181 cmp::{self, Ordering, Reverse},
182 iter::Peekable,
183 mem,
184 num::NonZeroU32,
185 ops::Not,
186 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 sync::Arc,
190 time::{Duration, Instant},
191};
192use sum_tree::TreeMap;
193use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::{
214 code_context_menus::CompletionsMenuSource,
215 editor_settings::MultiCursorModifier,
216 hover_links::{find_url, find_url_from_range},
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
223const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
224const MAX_LINE_LEN: usize = 1024;
225const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
226const MAX_SELECTION_HISTORY_LEN: usize = 1024;
227pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
228#[doc(hidden)]
229pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
230const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
231
232pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
235
236pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
237pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
238pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
239
240pub type RenderDiffHunkControlsFn = Arc<
241 dyn Fn(
242 u32,
243 &DiffHunkStatus,
244 Range<Anchor>,
245 bool,
246 Pixels,
247 &Entity<Editor>,
248 &mut Window,
249 &mut App,
250 ) -> AnyElement,
251>;
252
253enum ReportEditorEvent {
254 Saved { auto_saved: bool },
255 EditorOpened,
256 ZetaTosClicked,
257 Closed,
258}
259
260impl ReportEditorEvent {
261 pub fn event_type(&self) -> &'static str {
262 match self {
263 Self::Saved { .. } => "Editor Saved",
264 Self::EditorOpened => "Editor Opened",
265 Self::ZetaTosClicked => "Edit Prediction Provider ToS Clicked",
266 Self::Closed => "Editor Closed",
267 }
268 }
269}
270
271struct InlineValueCache {
272 enabled: bool,
273 inlays: Vec<InlayId>,
274 refresh_task: Task<Option<()>>,
275}
276
277impl InlineValueCache {
278 fn new(enabled: bool) -> Self {
279 Self {
280 enabled,
281 inlays: Vec::new(),
282 refresh_task: Task::ready(None),
283 }
284 }
285}
286
287#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
288pub enum InlayId {
289 EditPrediction(usize),
290 DebuggerValue(usize),
291 // LSP
292 Hint(usize),
293 Color(usize),
294}
295
296impl InlayId {
297 fn id(&self) -> usize {
298 match self {
299 Self::EditPrediction(id) => *id,
300 Self::DebuggerValue(id) => *id,
301 Self::Hint(id) => *id,
302 Self::Color(id) => *id,
303 }
304 }
305}
306
307pub enum ActiveDebugLine {}
308pub enum DebugStackFrameLine {}
309enum DocumentHighlightRead {}
310enum DocumentHighlightWrite {}
311enum InputComposition {}
312pub enum PendingInput {}
313enum SelectedTextHighlight {}
314
315pub enum ConflictsOuter {}
316pub enum ConflictsOurs {}
317pub enum ConflictsTheirs {}
318pub enum ConflictsOursMarker {}
319pub enum ConflictsTheirsMarker {}
320
321#[derive(Debug, Copy, Clone, PartialEq, Eq)]
322pub enum Navigated {
323 Yes,
324 No,
325}
326
327impl Navigated {
328 pub fn from_bool(yes: bool) -> Navigated {
329 if yes { Navigated::Yes } else { Navigated::No }
330 }
331}
332
333#[derive(Debug, Clone, PartialEq, Eq)]
334enum DisplayDiffHunk {
335 Folded {
336 display_row: DisplayRow,
337 },
338 Unfolded {
339 is_created_file: bool,
340 diff_base_byte_range: Range<usize>,
341 display_row_range: Range<DisplayRow>,
342 multi_buffer_range: Range<Anchor>,
343 status: DiffHunkStatus,
344 },
345}
346
347pub enum HideMouseCursorOrigin {
348 TypingAction,
349 MovementAction,
350}
351
352pub fn init_settings(cx: &mut App) {
353 EditorSettings::register(cx);
354}
355
356pub fn init(cx: &mut App) {
357 init_settings(cx);
358
359 cx.set_global(GlobalBlameRenderer(Arc::new(())));
360
361 workspace::register_project_item::<Editor>(cx);
362 workspace::FollowableViewRegistry::register::<Editor>(cx);
363 workspace::register_serializable_item::<Editor>(cx);
364
365 cx.observe_new(
366 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
367 workspace.register_action(Editor::new_file);
368 workspace.register_action(Editor::new_file_vertical);
369 workspace.register_action(Editor::new_file_horizontal);
370 workspace.register_action(Editor::cancel_language_server_work);
371 workspace.register_action(Editor::toggle_focus);
372 },
373 )
374 .detach();
375
376 cx.on_action(move |_: &workspace::NewFile, cx| {
377 let app_state = workspace::AppState::global(cx);
378 if let Some(app_state) = app_state.upgrade() {
379 workspace::open_new(
380 Default::default(),
381 app_state,
382 cx,
383 |workspace, window, cx| {
384 Editor::new_file(workspace, &Default::default(), window, cx)
385 },
386 )
387 .detach();
388 }
389 });
390 cx.on_action(move |_: &workspace::NewWindow, cx| {
391 let app_state = workspace::AppState::global(cx);
392 if let Some(app_state) = app_state.upgrade() {
393 workspace::open_new(
394 Default::default(),
395 app_state,
396 cx,
397 |workspace, window, cx| {
398 cx.activate(true);
399 Editor::new_file(workspace, &Default::default(), window, cx)
400 },
401 )
402 .detach();
403 }
404 });
405}
406
407pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
408 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
409}
410
411pub trait DiagnosticRenderer {
412 fn render_group(
413 &self,
414 diagnostic_group: Vec<DiagnosticEntry<Point>>,
415 buffer_id: BufferId,
416 snapshot: EditorSnapshot,
417 editor: WeakEntity<Editor>,
418 cx: &mut App,
419 ) -> Vec<BlockProperties<Anchor>>;
420
421 fn render_hover(
422 &self,
423 diagnostic_group: Vec<DiagnosticEntry<Point>>,
424 range: Range<Point>,
425 buffer_id: BufferId,
426 cx: &mut App,
427 ) -> Option<Entity<markdown::Markdown>>;
428
429 fn open_link(
430 &self,
431 editor: &mut Editor,
432 link: SharedString,
433 window: &mut Window,
434 cx: &mut Context<Editor>,
435 );
436}
437
438pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
439
440impl GlobalDiagnosticRenderer {
441 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
442 cx.try_global::<Self>().map(|g| g.0.clone())
443 }
444}
445
446impl gpui::Global for GlobalDiagnosticRenderer {}
447pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
448 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
449}
450
451pub struct SearchWithinRange;
452
453trait InvalidationRegion {
454 fn ranges(&self) -> &[Range<Anchor>];
455}
456
457#[derive(Clone, Debug, PartialEq)]
458pub enum SelectPhase {
459 Begin {
460 position: DisplayPoint,
461 add: bool,
462 click_count: usize,
463 },
464 BeginColumnar {
465 position: DisplayPoint,
466 reset: bool,
467 mode: ColumnarMode,
468 goal_column: u32,
469 },
470 Extend {
471 position: DisplayPoint,
472 click_count: usize,
473 },
474 Update {
475 position: DisplayPoint,
476 goal_column: u32,
477 scroll_delta: gpui::Point<f32>,
478 },
479 End,
480}
481
482#[derive(Clone, Debug, PartialEq)]
483pub enum ColumnarMode {
484 FromMouse,
485 FromSelection,
486}
487
488#[derive(Clone, Debug)]
489pub enum SelectMode {
490 Character,
491 Word(Range<Anchor>),
492 Line(Range<Anchor>),
493 All,
494}
495
496#[derive(Clone, PartialEq, Eq, Debug)]
497pub enum EditorMode {
498 SingleLine,
499 AutoHeight {
500 min_lines: usize,
501 max_lines: Option<usize>,
502 },
503 Full {
504 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
505 scale_ui_elements_with_buffer_font_size: bool,
506 /// When set to `true`, the editor will render a background for the active line.
507 show_active_line_background: bool,
508 /// When set to `true`, the editor's height will be determined by its content.
509 sized_by_content: bool,
510 },
511 Minimap {
512 parent: WeakEntity<Editor>,
513 },
514}
515
516impl EditorMode {
517 pub fn full() -> Self {
518 Self::Full {
519 scale_ui_elements_with_buffer_font_size: true,
520 show_active_line_background: true,
521 sized_by_content: false,
522 }
523 }
524
525 #[inline]
526 pub fn is_full(&self) -> bool {
527 matches!(self, Self::Full { .. })
528 }
529
530 #[inline]
531 pub fn is_single_line(&self) -> bool {
532 matches!(self, Self::SingleLine { .. })
533 }
534
535 #[inline]
536 fn is_minimap(&self) -> bool {
537 matches!(self, Self::Minimap { .. })
538 }
539}
540
541#[derive(Copy, Clone, Debug)]
542pub enum SoftWrap {
543 /// Prefer not to wrap at all.
544 ///
545 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
546 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
547 GitDiff,
548 /// Prefer a single line generally, unless an overly long line is encountered.
549 None,
550 /// Soft wrap lines that exceed the editor width.
551 EditorWidth,
552 /// Soft wrap lines at the preferred line length.
553 Column(u32),
554 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
555 Bounded(u32),
556}
557
558#[derive(Clone)]
559pub struct EditorStyle {
560 pub background: Hsla,
561 pub border: Hsla,
562 pub local_player: PlayerColor,
563 pub text: TextStyle,
564 pub scrollbar_width: Pixels,
565 pub syntax: Arc<SyntaxTheme>,
566 pub status: StatusColors,
567 pub inlay_hints_style: HighlightStyle,
568 pub edit_prediction_styles: EditPredictionStyles,
569 pub unnecessary_code_fade: f32,
570 pub show_underlines: bool,
571}
572
573impl Default for EditorStyle {
574 fn default() -> Self {
575 Self {
576 background: Hsla::default(),
577 border: Hsla::default(),
578 local_player: PlayerColor::default(),
579 text: TextStyle::default(),
580 scrollbar_width: Pixels::default(),
581 syntax: Default::default(),
582 // HACK: Status colors don't have a real default.
583 // We should look into removing the status colors from the editor
584 // style and retrieve them directly from the theme.
585 status: StatusColors::dark(),
586 inlay_hints_style: HighlightStyle::default(),
587 edit_prediction_styles: EditPredictionStyles {
588 insertion: HighlightStyle::default(),
589 whitespace: HighlightStyle::default(),
590 },
591 unnecessary_code_fade: Default::default(),
592 show_underlines: true,
593 }
594 }
595}
596
597pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
598 let show_background = language_settings::language_settings(None, None, cx)
599 .inlay_hints
600 .show_background;
601
602 HighlightStyle {
603 color: Some(cx.theme().status().hint),
604 background_color: show_background.then(|| cx.theme().status().hint_background),
605 ..HighlightStyle::default()
606 }
607}
608
609pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
610 EditPredictionStyles {
611 insertion: HighlightStyle {
612 color: Some(cx.theme().status().predictive),
613 ..HighlightStyle::default()
614 },
615 whitespace: HighlightStyle {
616 background_color: Some(cx.theme().status().created_background),
617 ..HighlightStyle::default()
618 },
619 }
620}
621
622type CompletionId = usize;
623
624pub(crate) enum EditDisplayMode {
625 TabAccept,
626 DiffPopover,
627 Inline,
628}
629
630enum EditPrediction {
631 Edit {
632 edits: Vec<(Range<Anchor>, String)>,
633 edit_preview: Option<EditPreview>,
634 display_mode: EditDisplayMode,
635 snapshot: BufferSnapshot,
636 },
637 Move {
638 target: Anchor,
639 snapshot: BufferSnapshot,
640 },
641}
642
643struct EditPredictionState {
644 inlay_ids: Vec<InlayId>,
645 completion: EditPrediction,
646 completion_id: Option<SharedString>,
647 invalidation_range: Range<Anchor>,
648}
649
650enum EditPredictionSettings {
651 Disabled,
652 Enabled {
653 show_in_menu: bool,
654 preview_requires_modifier: bool,
655 },
656}
657
658enum EditPredictionHighlight {}
659
660#[derive(Debug, Clone)]
661struct InlineDiagnostic {
662 message: SharedString,
663 group_id: usize,
664 is_primary: bool,
665 start: Point,
666 severity: lsp::DiagnosticSeverity,
667}
668
669pub enum MenuEditPredictionsPolicy {
670 Never,
671 ByProvider,
672}
673
674pub enum EditPredictionPreview {
675 /// Modifier is not pressed
676 Inactive { released_too_fast: bool },
677 /// Modifier pressed
678 Active {
679 since: Instant,
680 previous_scroll_position: Option<ScrollAnchor>,
681 },
682}
683
684impl EditPredictionPreview {
685 pub fn released_too_fast(&self) -> bool {
686 match self {
687 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
688 EditPredictionPreview::Active { .. } => false,
689 }
690 }
691
692 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
693 if let EditPredictionPreview::Active {
694 previous_scroll_position,
695 ..
696 } = self
697 {
698 *previous_scroll_position = scroll_position;
699 }
700 }
701}
702
703pub struct ContextMenuOptions {
704 pub min_entries_visible: usize,
705 pub max_entries_visible: usize,
706 pub placement: Option<ContextMenuPlacement>,
707}
708
709#[derive(Debug, Clone, PartialEq, Eq)]
710pub enum ContextMenuPlacement {
711 Above,
712 Below,
713}
714
715#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
716struct EditorActionId(usize);
717
718impl EditorActionId {
719 pub fn post_inc(&mut self) -> Self {
720 let answer = self.0;
721
722 *self = Self(answer + 1);
723
724 Self(answer)
725 }
726}
727
728// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
729// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
730
731type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
732type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
733
734#[derive(Default)]
735struct ScrollbarMarkerState {
736 scrollbar_size: Size<Pixels>,
737 dirty: bool,
738 markers: Arc<[PaintQuad]>,
739 pending_refresh: Option<Task<Result<()>>>,
740}
741
742impl ScrollbarMarkerState {
743 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
744 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
745 }
746}
747
748#[derive(Clone, Copy, PartialEq, Eq)]
749pub enum MinimapVisibility {
750 Disabled,
751 Enabled {
752 /// The configuration currently present in the users settings.
753 setting_configuration: bool,
754 /// Whether to override the currently set visibility from the users setting.
755 toggle_override: bool,
756 },
757}
758
759impl MinimapVisibility {
760 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
761 if mode.is_full() {
762 Self::Enabled {
763 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
764 toggle_override: false,
765 }
766 } else {
767 Self::Disabled
768 }
769 }
770
771 fn hidden(&self) -> Self {
772 match *self {
773 Self::Enabled {
774 setting_configuration,
775 ..
776 } => Self::Enabled {
777 setting_configuration,
778 toggle_override: setting_configuration,
779 },
780 Self::Disabled => Self::Disabled,
781 }
782 }
783
784 fn disabled(&self) -> bool {
785 matches!(*self, Self::Disabled)
786 }
787
788 fn settings_visibility(&self) -> bool {
789 match *self {
790 Self::Enabled {
791 setting_configuration,
792 ..
793 } => setting_configuration,
794 _ => false,
795 }
796 }
797
798 fn visible(&self) -> bool {
799 match *self {
800 Self::Enabled {
801 setting_configuration,
802 toggle_override,
803 } => setting_configuration ^ toggle_override,
804 _ => false,
805 }
806 }
807
808 fn toggle_visibility(&self) -> Self {
809 match *self {
810 Self::Enabled {
811 toggle_override,
812 setting_configuration,
813 } => Self::Enabled {
814 setting_configuration,
815 toggle_override: !toggle_override,
816 },
817 Self::Disabled => Self::Disabled,
818 }
819 }
820}
821
822#[derive(Clone, Debug)]
823struct RunnableTasks {
824 templates: Vec<(TaskSourceKind, TaskTemplate)>,
825 offset: multi_buffer::Anchor,
826 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
827 column: u32,
828 // Values of all named captures, including those starting with '_'
829 extra_variables: HashMap<String, String>,
830 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
831 context_range: Range<BufferOffset>,
832}
833
834impl RunnableTasks {
835 fn resolve<'a>(
836 &'a self,
837 cx: &'a task::TaskContext,
838 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
839 self.templates.iter().filter_map(|(kind, template)| {
840 template
841 .resolve_task(&kind.to_id_base(), cx)
842 .map(|task| (kind.clone(), task))
843 })
844 }
845}
846
847#[derive(Clone)]
848pub struct ResolvedTasks {
849 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
850 position: Anchor,
851}
852
853#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
854struct BufferOffset(usize);
855
856// Addons allow storing per-editor state in other crates (e.g. Vim)
857pub trait Addon: 'static {
858 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
859
860 fn render_buffer_header_controls(
861 &self,
862 _: &ExcerptInfo,
863 _: &Window,
864 _: &App,
865 ) -> Option<AnyElement> {
866 None
867 }
868
869 fn to_any(&self) -> &dyn std::any::Any;
870
871 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
872 None
873 }
874}
875
876struct ChangeLocation {
877 current: Option<Vec<Anchor>>,
878 original: Vec<Anchor>,
879}
880impl ChangeLocation {
881 fn locations(&self) -> &[Anchor] {
882 self.current.as_ref().unwrap_or(&self.original)
883 }
884}
885
886/// A set of caret positions, registered when the editor was edited.
887pub struct ChangeList {
888 changes: Vec<ChangeLocation>,
889 /// Currently "selected" change.
890 position: Option<usize>,
891}
892
893impl ChangeList {
894 pub fn new() -> Self {
895 Self {
896 changes: Vec::new(),
897 position: None,
898 }
899 }
900
901 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
902 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
903 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
904 if self.changes.is_empty() {
905 return None;
906 }
907
908 let prev = self.position.unwrap_or(self.changes.len());
909 let next = if direction == Direction::Prev {
910 prev.saturating_sub(count)
911 } else {
912 (prev + count).min(self.changes.len() - 1)
913 };
914 self.position = Some(next);
915 self.changes.get(next).map(|change| change.locations())
916 }
917
918 /// Adds a new change to the list, resetting the change list position.
919 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
920 self.position.take();
921 if let Some(last) = self.changes.last_mut()
922 && group
923 {
924 last.current = Some(new_positions)
925 } else {
926 self.changes.push(ChangeLocation {
927 original: new_positions,
928 current: None,
929 });
930 }
931 }
932
933 pub fn last(&self) -> Option<&[Anchor]> {
934 self.changes.last().map(|change| change.locations())
935 }
936
937 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
938 self.changes.last().map(|change| change.original.as_slice())
939 }
940
941 pub fn invert_last_group(&mut self) {
942 if let Some(last) = self.changes.last_mut()
943 && let Some(current) = last.current.as_mut()
944 {
945 mem::swap(&mut last.original, current);
946 }
947 }
948}
949
950#[derive(Clone)]
951struct InlineBlamePopoverState {
952 scroll_handle: ScrollHandle,
953 commit_message: Option<ParsedCommitMessage>,
954 markdown: Entity<Markdown>,
955}
956
957struct InlineBlamePopover {
958 position: gpui::Point<Pixels>,
959 hide_task: Option<Task<()>>,
960 popover_bounds: Option<Bounds<Pixels>>,
961 popover_state: InlineBlamePopoverState,
962 keyboard_grace: bool,
963}
964
965enum SelectionDragState {
966 /// State when no drag related activity is detected.
967 None,
968 /// State when the mouse is down on a selection that is about to be dragged.
969 ReadyToDrag {
970 selection: Selection<Anchor>,
971 click_position: gpui::Point<Pixels>,
972 mouse_down_time: Instant,
973 },
974 /// State when the mouse is dragging the selection in the editor.
975 Dragging {
976 selection: Selection<Anchor>,
977 drop_cursor: Selection<Anchor>,
978 hide_drop_cursor: bool,
979 },
980}
981
982enum ColumnarSelectionState {
983 FromMouse {
984 selection_tail: Anchor,
985 display_point: Option<DisplayPoint>,
986 },
987 FromSelection {
988 selection_tail: Anchor,
989 },
990}
991
992/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
993/// a breakpoint on them.
994#[derive(Clone, Copy, Debug, PartialEq, Eq)]
995struct PhantomBreakpointIndicator {
996 display_row: DisplayRow,
997 /// There's a small debounce between hovering over the line and showing the indicator.
998 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
999 is_active: bool,
1000 collides_with_existing_breakpoint: bool,
1001}
1002
1003/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1004///
1005/// See the [module level documentation](self) for more information.
1006pub struct Editor {
1007 focus_handle: FocusHandle,
1008 last_focused_descendant: Option<WeakFocusHandle>,
1009 /// The text buffer being edited
1010 buffer: Entity<MultiBuffer>,
1011 /// Map of how text in the buffer should be displayed.
1012 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1013 pub display_map: Entity<DisplayMap>,
1014 pub selections: SelectionsCollection,
1015 pub scroll_manager: ScrollManager,
1016 /// When inline assist editors are linked, they all render cursors because
1017 /// typing enters text into each of them, even the ones that aren't focused.
1018 pub(crate) show_cursor_when_unfocused: bool,
1019 columnar_selection_state: Option<ColumnarSelectionState>,
1020 add_selections_state: Option<AddSelectionsState>,
1021 select_next_state: Option<SelectNextState>,
1022 select_prev_state: Option<SelectNextState>,
1023 selection_history: SelectionHistory,
1024 defer_selection_effects: bool,
1025 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1026 autoclose_regions: Vec<AutocloseRegion>,
1027 snippet_stack: InvalidationStack<SnippetState>,
1028 select_syntax_node_history: SelectSyntaxNodeHistory,
1029 ime_transaction: Option<TransactionId>,
1030 pub diagnostics_max_severity: DiagnosticSeverity,
1031 active_diagnostics: ActiveDiagnostic,
1032 show_inline_diagnostics: bool,
1033 inline_diagnostics_update: Task<()>,
1034 inline_diagnostics_enabled: bool,
1035 diagnostics_enabled: bool,
1036 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1037 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1038 hard_wrap: Option<usize>,
1039 project: Option<Entity<Project>>,
1040 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1041 completion_provider: Option<Rc<dyn CompletionProvider>>,
1042 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1043 blink_manager: Entity<BlinkManager>,
1044 show_cursor_names: bool,
1045 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1046 pub show_local_selections: bool,
1047 mode: EditorMode,
1048 show_breadcrumbs: bool,
1049 show_gutter: bool,
1050 show_scrollbars: ScrollbarAxes,
1051 minimap_visibility: MinimapVisibility,
1052 offset_content: bool,
1053 disable_expand_excerpt_buttons: bool,
1054 show_line_numbers: Option<bool>,
1055 use_relative_line_numbers: Option<bool>,
1056 show_git_diff_gutter: Option<bool>,
1057 show_code_actions: Option<bool>,
1058 show_runnables: Option<bool>,
1059 show_breakpoints: Option<bool>,
1060 show_wrap_guides: Option<bool>,
1061 show_indent_guides: Option<bool>,
1062 placeholder_text: Option<Arc<str>>,
1063 highlight_order: usize,
1064 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1065 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1066 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1067 scrollbar_marker_state: ScrollbarMarkerState,
1068 active_indent_guides_state: ActiveIndentGuidesState,
1069 nav_history: Option<ItemNavHistory>,
1070 context_menu: RefCell<Option<CodeContextMenu>>,
1071 context_menu_options: Option<ContextMenuOptions>,
1072 mouse_context_menu: Option<MouseContextMenu>,
1073 completion_tasks: Vec<(CompletionId, Task<()>)>,
1074 inline_blame_popover: Option<InlineBlamePopover>,
1075 inline_blame_popover_show_task: Option<Task<()>>,
1076 signature_help_state: SignatureHelpState,
1077 auto_signature_help: Option<bool>,
1078 find_all_references_task_sources: Vec<Anchor>,
1079 next_completion_id: CompletionId,
1080 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1081 code_actions_task: Option<Task<Result<()>>>,
1082 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1083 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1084 document_highlights_task: Option<Task<()>>,
1085 linked_editing_range_task: Option<Task<Option<()>>>,
1086 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1087 pending_rename: Option<RenameState>,
1088 searchable: bool,
1089 cursor_shape: CursorShape,
1090 current_line_highlight: Option<CurrentLineHighlight>,
1091 collapse_matches: bool,
1092 autoindent_mode: Option<AutoindentMode>,
1093 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1094 input_enabled: bool,
1095 use_modal_editing: bool,
1096 read_only: bool,
1097 leader_id: Option<CollaboratorId>,
1098 remote_id: Option<ViewId>,
1099 pub hover_state: HoverState,
1100 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1101 gutter_hovered: bool,
1102 hovered_link_state: Option<HoveredLinkState>,
1103 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1104 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1105 active_edit_prediction: Option<EditPredictionState>,
1106 /// Used to prevent flickering as the user types while the menu is open
1107 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1108 edit_prediction_settings: EditPredictionSettings,
1109 edit_predictions_hidden_for_vim_mode: bool,
1110 show_edit_predictions_override: Option<bool>,
1111 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1112 edit_prediction_preview: EditPredictionPreview,
1113 edit_prediction_indent_conflict: bool,
1114 edit_prediction_requires_modifier_in_indent_conflict: bool,
1115 inlay_hint_cache: InlayHintCache,
1116 next_inlay_id: usize,
1117 _subscriptions: Vec<Subscription>,
1118 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1119 gutter_dimensions: GutterDimensions,
1120 style: Option<EditorStyle>,
1121 text_style_refinement: Option<TextStyleRefinement>,
1122 next_editor_action_id: EditorActionId,
1123 editor_actions: Rc<
1124 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1125 >,
1126 use_autoclose: bool,
1127 use_auto_surround: bool,
1128 auto_replace_emoji_shortcode: bool,
1129 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1130 show_git_blame_gutter: bool,
1131 show_git_blame_inline: bool,
1132 show_git_blame_inline_delay_task: Option<Task<()>>,
1133 git_blame_inline_enabled: bool,
1134 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1135 serialize_dirty_buffers: bool,
1136 show_selection_menu: Option<bool>,
1137 blame: Option<Entity<GitBlame>>,
1138 blame_subscription: Option<Subscription>,
1139 custom_context_menu: Option<
1140 Box<
1141 dyn 'static
1142 + Fn(
1143 &mut Self,
1144 DisplayPoint,
1145 &mut Window,
1146 &mut Context<Self>,
1147 ) -> Option<Entity<ui::ContextMenu>>,
1148 >,
1149 >,
1150 last_bounds: Option<Bounds<Pixels>>,
1151 last_position_map: Option<Rc<PositionMap>>,
1152 expect_bounds_change: Option<Bounds<Pixels>>,
1153 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1154 tasks_update_task: Option<Task<()>>,
1155 breakpoint_store: Option<Entity<BreakpointStore>>,
1156 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1157 hovered_diff_hunk_row: Option<DisplayRow>,
1158 pull_diagnostics_task: Task<()>,
1159 in_project_search: bool,
1160 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1161 breadcrumb_header: Option<String>,
1162 focused_block: Option<FocusedBlock>,
1163 next_scroll_position: NextScrollCursorCenterTopBottom,
1164 addons: HashMap<TypeId, Box<dyn Addon>>,
1165 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1166 load_diff_task: Option<Shared<Task<()>>>,
1167 /// Whether we are temporarily displaying a diff other than git's
1168 temporary_diff_override: bool,
1169 selection_mark_mode: bool,
1170 toggle_fold_multiple_buffers: Task<()>,
1171 _scroll_cursor_center_top_bottom_task: Task<()>,
1172 serialize_selections: Task<()>,
1173 serialize_folds: Task<()>,
1174 mouse_cursor_hidden: bool,
1175 minimap: Option<Entity<Self>>,
1176 hide_mouse_mode: HideMouseMode,
1177 pub change_list: ChangeList,
1178 inline_value_cache: InlineValueCache,
1179 selection_drag_state: SelectionDragState,
1180 next_color_inlay_id: usize,
1181 colors: Option<LspColorData>,
1182 folding_newlines: Task<()>,
1183}
1184
1185#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1186enum NextScrollCursorCenterTopBottom {
1187 #[default]
1188 Center,
1189 Top,
1190 Bottom,
1191}
1192
1193impl NextScrollCursorCenterTopBottom {
1194 fn next(&self) -> Self {
1195 match self {
1196 Self::Center => Self::Top,
1197 Self::Top => Self::Bottom,
1198 Self::Bottom => Self::Center,
1199 }
1200 }
1201}
1202
1203#[derive(Clone)]
1204pub struct EditorSnapshot {
1205 pub mode: EditorMode,
1206 show_gutter: bool,
1207 show_line_numbers: Option<bool>,
1208 show_git_diff_gutter: Option<bool>,
1209 show_code_actions: Option<bool>,
1210 show_runnables: Option<bool>,
1211 show_breakpoints: Option<bool>,
1212 git_blame_gutter_max_author_length: Option<usize>,
1213 pub display_snapshot: DisplaySnapshot,
1214 pub placeholder_text: Option<Arc<str>>,
1215 is_focused: bool,
1216 scroll_anchor: ScrollAnchor,
1217 ongoing_scroll: OngoingScroll,
1218 current_line_highlight: CurrentLineHighlight,
1219 gutter_hovered: bool,
1220}
1221
1222#[derive(Default, Debug, Clone, Copy)]
1223pub struct GutterDimensions {
1224 pub left_padding: Pixels,
1225 pub right_padding: Pixels,
1226 pub width: Pixels,
1227 pub margin: Pixels,
1228 pub git_blame_entries_width: Option<Pixels>,
1229}
1230
1231impl GutterDimensions {
1232 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1233 Self {
1234 margin: Self::default_gutter_margin(font_id, font_size, cx),
1235 ..Default::default()
1236 }
1237 }
1238
1239 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1240 -cx.text_system().descent(font_id, font_size)
1241 }
1242 /// The full width of the space taken up by the gutter.
1243 pub fn full_width(&self) -> Pixels {
1244 self.margin + self.width
1245 }
1246
1247 /// The width of the space reserved for the fold indicators,
1248 /// use alongside 'justify_end' and `gutter_width` to
1249 /// right align content with the line numbers
1250 pub fn fold_area_width(&self) -> Pixels {
1251 self.margin + self.right_padding
1252 }
1253}
1254
1255struct CharacterDimensions {
1256 em_width: Pixels,
1257 em_advance: Pixels,
1258 line_height: Pixels,
1259}
1260
1261#[derive(Debug)]
1262pub struct RemoteSelection {
1263 pub replica_id: ReplicaId,
1264 pub selection: Selection<Anchor>,
1265 pub cursor_shape: CursorShape,
1266 pub collaborator_id: CollaboratorId,
1267 pub line_mode: bool,
1268 pub user_name: Option<SharedString>,
1269 pub color: PlayerColor,
1270}
1271
1272#[derive(Clone, Debug)]
1273struct SelectionHistoryEntry {
1274 selections: Arc<[Selection<Anchor>]>,
1275 select_next_state: Option<SelectNextState>,
1276 select_prev_state: Option<SelectNextState>,
1277 add_selections_state: Option<AddSelectionsState>,
1278}
1279
1280#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1281enum SelectionHistoryMode {
1282 Normal,
1283 Undoing,
1284 Redoing,
1285 Skipping,
1286}
1287
1288#[derive(Clone, PartialEq, Eq, Hash)]
1289struct HoveredCursor {
1290 replica_id: u16,
1291 selection_id: usize,
1292}
1293
1294impl Default for SelectionHistoryMode {
1295 fn default() -> Self {
1296 Self::Normal
1297 }
1298}
1299
1300#[derive(Debug)]
1301/// SelectionEffects controls the side-effects of updating the selection.
1302///
1303/// The default behaviour does "what you mostly want":
1304/// - it pushes to the nav history if the cursor moved by >10 lines
1305/// - it re-triggers completion requests
1306/// - it scrolls to fit
1307///
1308/// You might want to modify these behaviours. For example when doing a "jump"
1309/// like go to definition, we always want to add to nav history; but when scrolling
1310/// in vim mode we never do.
1311///
1312/// Similarly, you might want to disable scrolling if you don't want the viewport to
1313/// move.
1314#[derive(Clone)]
1315pub struct SelectionEffects {
1316 nav_history: Option<bool>,
1317 completions: bool,
1318 scroll: Option<Autoscroll>,
1319}
1320
1321impl Default for SelectionEffects {
1322 fn default() -> Self {
1323 Self {
1324 nav_history: None,
1325 completions: true,
1326 scroll: Some(Autoscroll::fit()),
1327 }
1328 }
1329}
1330impl SelectionEffects {
1331 pub fn scroll(scroll: Autoscroll) -> Self {
1332 Self {
1333 scroll: Some(scroll),
1334 ..Default::default()
1335 }
1336 }
1337
1338 pub fn no_scroll() -> Self {
1339 Self {
1340 scroll: None,
1341 ..Default::default()
1342 }
1343 }
1344
1345 pub fn completions(self, completions: bool) -> Self {
1346 Self {
1347 completions,
1348 ..self
1349 }
1350 }
1351
1352 pub fn nav_history(self, nav_history: bool) -> Self {
1353 Self {
1354 nav_history: Some(nav_history),
1355 ..self
1356 }
1357 }
1358}
1359
1360struct DeferredSelectionEffectsState {
1361 changed: bool,
1362 effects: SelectionEffects,
1363 old_cursor_position: Anchor,
1364 history_entry: SelectionHistoryEntry,
1365}
1366
1367#[derive(Default)]
1368struct SelectionHistory {
1369 #[allow(clippy::type_complexity)]
1370 selections_by_transaction:
1371 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1372 mode: SelectionHistoryMode,
1373 undo_stack: VecDeque<SelectionHistoryEntry>,
1374 redo_stack: VecDeque<SelectionHistoryEntry>,
1375}
1376
1377impl SelectionHistory {
1378 #[track_caller]
1379 fn insert_transaction(
1380 &mut self,
1381 transaction_id: TransactionId,
1382 selections: Arc<[Selection<Anchor>]>,
1383 ) {
1384 if selections.is_empty() {
1385 log::error!(
1386 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1387 std::panic::Location::caller()
1388 );
1389 return;
1390 }
1391 self.selections_by_transaction
1392 .insert(transaction_id, (selections, None));
1393 }
1394
1395 #[allow(clippy::type_complexity)]
1396 fn transaction(
1397 &self,
1398 transaction_id: TransactionId,
1399 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1400 self.selections_by_transaction.get(&transaction_id)
1401 }
1402
1403 #[allow(clippy::type_complexity)]
1404 fn transaction_mut(
1405 &mut self,
1406 transaction_id: TransactionId,
1407 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1408 self.selections_by_transaction.get_mut(&transaction_id)
1409 }
1410
1411 fn push(&mut self, entry: SelectionHistoryEntry) {
1412 if !entry.selections.is_empty() {
1413 match self.mode {
1414 SelectionHistoryMode::Normal => {
1415 self.push_undo(entry);
1416 self.redo_stack.clear();
1417 }
1418 SelectionHistoryMode::Undoing => self.push_redo(entry),
1419 SelectionHistoryMode::Redoing => self.push_undo(entry),
1420 SelectionHistoryMode::Skipping => {}
1421 }
1422 }
1423 }
1424
1425 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1426 if self
1427 .undo_stack
1428 .back()
1429 .is_none_or(|e| e.selections != entry.selections)
1430 {
1431 self.undo_stack.push_back(entry);
1432 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1433 self.undo_stack.pop_front();
1434 }
1435 }
1436 }
1437
1438 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1439 if self
1440 .redo_stack
1441 .back()
1442 .is_none_or(|e| e.selections != entry.selections)
1443 {
1444 self.redo_stack.push_back(entry);
1445 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1446 self.redo_stack.pop_front();
1447 }
1448 }
1449 }
1450}
1451
1452#[derive(Clone, Copy)]
1453pub struct RowHighlightOptions {
1454 pub autoscroll: bool,
1455 pub include_gutter: bool,
1456}
1457
1458impl Default for RowHighlightOptions {
1459 fn default() -> Self {
1460 Self {
1461 autoscroll: Default::default(),
1462 include_gutter: true,
1463 }
1464 }
1465}
1466
1467struct RowHighlight {
1468 index: usize,
1469 range: Range<Anchor>,
1470 color: Hsla,
1471 options: RowHighlightOptions,
1472 type_id: TypeId,
1473}
1474
1475#[derive(Clone, Debug)]
1476struct AddSelectionsState {
1477 groups: Vec<AddSelectionsGroup>,
1478}
1479
1480#[derive(Clone, Debug)]
1481struct AddSelectionsGroup {
1482 above: bool,
1483 stack: Vec<usize>,
1484}
1485
1486#[derive(Clone)]
1487struct SelectNextState {
1488 query: AhoCorasick,
1489 wordwise: bool,
1490 done: bool,
1491}
1492
1493impl std::fmt::Debug for SelectNextState {
1494 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1495 f.debug_struct(std::any::type_name::<Self>())
1496 .field("wordwise", &self.wordwise)
1497 .field("done", &self.done)
1498 .finish()
1499 }
1500}
1501
1502#[derive(Debug)]
1503struct AutocloseRegion {
1504 selection_id: usize,
1505 range: Range<Anchor>,
1506 pair: BracketPair,
1507}
1508
1509#[derive(Debug)]
1510struct SnippetState {
1511 ranges: Vec<Vec<Range<Anchor>>>,
1512 active_index: usize,
1513 choices: Vec<Option<Vec<String>>>,
1514}
1515
1516#[doc(hidden)]
1517pub struct RenameState {
1518 pub range: Range<Anchor>,
1519 pub old_name: Arc<str>,
1520 pub editor: Entity<Editor>,
1521 block_id: CustomBlockId,
1522}
1523
1524struct InvalidationStack<T>(Vec<T>);
1525
1526struct RegisteredEditPredictionProvider {
1527 provider: Arc<dyn EditPredictionProviderHandle>,
1528 _subscription: Subscription,
1529}
1530
1531#[derive(Debug, PartialEq, Eq)]
1532pub struct ActiveDiagnosticGroup {
1533 pub active_range: Range<Anchor>,
1534 pub active_message: String,
1535 pub group_id: usize,
1536 pub blocks: HashSet<CustomBlockId>,
1537}
1538
1539#[derive(Debug, PartialEq, Eq)]
1540
1541pub(crate) enum ActiveDiagnostic {
1542 None,
1543 All,
1544 Group(ActiveDiagnosticGroup),
1545}
1546
1547#[derive(Serialize, Deserialize, Clone, Debug)]
1548pub struct ClipboardSelection {
1549 /// The number of bytes in this selection.
1550 pub len: usize,
1551 /// Whether this was a full-line selection.
1552 pub is_entire_line: bool,
1553 /// The indentation of the first line when this content was originally copied.
1554 pub first_line_indent: u32,
1555}
1556
1557// selections, scroll behavior, was newest selection reversed
1558type SelectSyntaxNodeHistoryState = (
1559 Box<[Selection<usize>]>,
1560 SelectSyntaxNodeScrollBehavior,
1561 bool,
1562);
1563
1564#[derive(Default)]
1565struct SelectSyntaxNodeHistory {
1566 stack: Vec<SelectSyntaxNodeHistoryState>,
1567 // disable temporarily to allow changing selections without losing the stack
1568 pub disable_clearing: bool,
1569}
1570
1571impl SelectSyntaxNodeHistory {
1572 pub fn try_clear(&mut self) {
1573 if !self.disable_clearing {
1574 self.stack.clear();
1575 }
1576 }
1577
1578 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1579 self.stack.push(selection);
1580 }
1581
1582 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1583 self.stack.pop()
1584 }
1585}
1586
1587enum SelectSyntaxNodeScrollBehavior {
1588 CursorTop,
1589 FitSelection,
1590 CursorBottom,
1591}
1592
1593#[derive(Debug)]
1594pub(crate) struct NavigationData {
1595 cursor_anchor: Anchor,
1596 cursor_position: Point,
1597 scroll_anchor: ScrollAnchor,
1598 scroll_top_row: u32,
1599}
1600
1601#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1602pub enum GotoDefinitionKind {
1603 Symbol,
1604 Declaration,
1605 Type,
1606 Implementation,
1607}
1608
1609#[derive(Debug, Clone)]
1610enum InlayHintRefreshReason {
1611 ModifiersChanged(bool),
1612 Toggle(bool),
1613 SettingsChange(InlayHintSettings),
1614 NewLinesShown,
1615 BufferEdited(HashSet<Arc<Language>>),
1616 RefreshRequested,
1617 ExcerptsRemoved(Vec<ExcerptId>),
1618}
1619
1620impl InlayHintRefreshReason {
1621 fn description(&self) -> &'static str {
1622 match self {
1623 Self::ModifiersChanged(_) => "modifiers changed",
1624 Self::Toggle(_) => "toggle",
1625 Self::SettingsChange(_) => "settings change",
1626 Self::NewLinesShown => "new lines shown",
1627 Self::BufferEdited(_) => "buffer edited",
1628 Self::RefreshRequested => "refresh requested",
1629 Self::ExcerptsRemoved(_) => "excerpts removed",
1630 }
1631 }
1632}
1633
1634pub enum FormatTarget {
1635 Buffers(HashSet<Entity<Buffer>>),
1636 Ranges(Vec<Range<MultiBufferPoint>>),
1637}
1638
1639pub(crate) struct FocusedBlock {
1640 id: BlockId,
1641 focus_handle: WeakFocusHandle,
1642}
1643
1644#[derive(Clone)]
1645enum JumpData {
1646 MultiBufferRow {
1647 row: MultiBufferRow,
1648 line_offset_from_top: u32,
1649 },
1650 MultiBufferPoint {
1651 excerpt_id: ExcerptId,
1652 position: Point,
1653 anchor: text::Anchor,
1654 line_offset_from_top: u32,
1655 },
1656}
1657
1658pub enum MultibufferSelectionMode {
1659 First,
1660 All,
1661}
1662
1663#[derive(Clone, Copy, Debug, Default)]
1664pub struct RewrapOptions {
1665 pub override_language_settings: bool,
1666 pub preserve_existing_whitespace: bool,
1667}
1668
1669impl Editor {
1670 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1671 let buffer = cx.new(|cx| Buffer::local("", cx));
1672 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1673 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1674 }
1675
1676 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1677 let buffer = cx.new(|cx| Buffer::local("", cx));
1678 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1679 Self::new(EditorMode::full(), buffer, None, window, cx)
1680 }
1681
1682 pub fn auto_height(
1683 min_lines: usize,
1684 max_lines: usize,
1685 window: &mut Window,
1686 cx: &mut Context<Self>,
1687 ) -> Self {
1688 let buffer = cx.new(|cx| Buffer::local("", cx));
1689 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1690 Self::new(
1691 EditorMode::AutoHeight {
1692 min_lines,
1693 max_lines: Some(max_lines),
1694 },
1695 buffer,
1696 None,
1697 window,
1698 cx,
1699 )
1700 }
1701
1702 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1703 /// The editor grows as tall as needed to fit its content.
1704 pub fn auto_height_unbounded(
1705 min_lines: usize,
1706 window: &mut Window,
1707 cx: &mut Context<Self>,
1708 ) -> Self {
1709 let buffer = cx.new(|cx| Buffer::local("", cx));
1710 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1711 Self::new(
1712 EditorMode::AutoHeight {
1713 min_lines,
1714 max_lines: None,
1715 },
1716 buffer,
1717 None,
1718 window,
1719 cx,
1720 )
1721 }
1722
1723 pub fn for_buffer(
1724 buffer: Entity<Buffer>,
1725 project: Option<Entity<Project>>,
1726 window: &mut Window,
1727 cx: &mut Context<Self>,
1728 ) -> Self {
1729 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1730 Self::new(EditorMode::full(), buffer, project, window, cx)
1731 }
1732
1733 pub fn for_multibuffer(
1734 buffer: Entity<MultiBuffer>,
1735 project: Option<Entity<Project>>,
1736 window: &mut Window,
1737 cx: &mut Context<Self>,
1738 ) -> Self {
1739 Self::new(EditorMode::full(), buffer, project, window, cx)
1740 }
1741
1742 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1743 let mut clone = Self::new(
1744 self.mode.clone(),
1745 self.buffer.clone(),
1746 self.project.clone(),
1747 window,
1748 cx,
1749 );
1750 self.display_map.update(cx, |display_map, cx| {
1751 let snapshot = display_map.snapshot(cx);
1752 clone.display_map.update(cx, |display_map, cx| {
1753 display_map.set_state(&snapshot, cx);
1754 });
1755 });
1756 clone.folds_did_change(cx);
1757 clone.selections.clone_state(&self.selections);
1758 clone.scroll_manager.clone_state(&self.scroll_manager);
1759 clone.searchable = self.searchable;
1760 clone.read_only = self.read_only;
1761 clone
1762 }
1763
1764 pub fn new(
1765 mode: EditorMode,
1766 buffer: Entity<MultiBuffer>,
1767 project: Option<Entity<Project>>,
1768 window: &mut Window,
1769 cx: &mut Context<Self>,
1770 ) -> Self {
1771 Editor::new_internal(mode, buffer, project, None, window, cx)
1772 }
1773
1774 fn new_internal(
1775 mode: EditorMode,
1776 buffer: Entity<MultiBuffer>,
1777 project: Option<Entity<Project>>,
1778 display_map: Option<Entity<DisplayMap>>,
1779 window: &mut Window,
1780 cx: &mut Context<Self>,
1781 ) -> Self {
1782 debug_assert!(
1783 display_map.is_none() || mode.is_minimap(),
1784 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1785 );
1786
1787 let full_mode = mode.is_full();
1788 let is_minimap = mode.is_minimap();
1789 let diagnostics_max_severity = if full_mode {
1790 EditorSettings::get_global(cx)
1791 .diagnostics_max_severity
1792 .unwrap_or(DiagnosticSeverity::Hint)
1793 } else {
1794 DiagnosticSeverity::Off
1795 };
1796 let style = window.text_style();
1797 let font_size = style.font_size.to_pixels(window.rem_size());
1798 let editor = cx.entity().downgrade();
1799 let fold_placeholder = FoldPlaceholder {
1800 constrain_width: true,
1801 render: Arc::new(move |fold_id, fold_range, cx| {
1802 let editor = editor.clone();
1803 div()
1804 .id(fold_id)
1805 .bg(cx.theme().colors().ghost_element_background)
1806 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1807 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1808 .rounded_xs()
1809 .size_full()
1810 .cursor_pointer()
1811 .child("⋯")
1812 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1813 .on_click(move |_, _window, cx| {
1814 editor
1815 .update(cx, |editor, cx| {
1816 editor.unfold_ranges(
1817 &[fold_range.start..fold_range.end],
1818 true,
1819 false,
1820 cx,
1821 );
1822 cx.stop_propagation();
1823 })
1824 .ok();
1825 })
1826 .into_any()
1827 }),
1828 merge_adjacent: true,
1829 ..FoldPlaceholder::default()
1830 };
1831 let display_map = display_map.unwrap_or_else(|| {
1832 cx.new(|cx| {
1833 DisplayMap::new(
1834 buffer.clone(),
1835 style.font(),
1836 font_size,
1837 None,
1838 FILE_HEADER_HEIGHT,
1839 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1840 fold_placeholder,
1841 diagnostics_max_severity,
1842 cx,
1843 )
1844 })
1845 });
1846
1847 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1848
1849 let blink_manager = cx.new(|cx| {
1850 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1851 if is_minimap {
1852 blink_manager.disable(cx);
1853 }
1854 blink_manager
1855 });
1856
1857 let soft_wrap_mode_override =
1858 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1859
1860 let mut project_subscriptions = Vec::new();
1861 if full_mode && let Some(project) = project.as_ref() {
1862 project_subscriptions.push(cx.subscribe_in(
1863 project,
1864 window,
1865 |editor, _, event, window, cx| match event {
1866 project::Event::RefreshCodeLens => {
1867 // we always query lens with actions, without storing them, always refreshing them
1868 }
1869 project::Event::RefreshInlayHints => {
1870 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1871 }
1872 project::Event::LanguageServerAdded(..)
1873 | project::Event::LanguageServerRemoved(..) => {
1874 if editor.tasks_update_task.is_none() {
1875 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1876 }
1877 }
1878 project::Event::SnippetEdit(id, snippet_edits) => {
1879 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1880 let focus_handle = editor.focus_handle(cx);
1881 if focus_handle.is_focused(window) {
1882 let snapshot = buffer.read(cx).snapshot();
1883 for (range, snippet) in snippet_edits {
1884 let editor_range =
1885 language::range_from_lsp(*range).to_offset(&snapshot);
1886 editor
1887 .insert_snippet(
1888 &[editor_range],
1889 snippet.clone(),
1890 window,
1891 cx,
1892 )
1893 .ok();
1894 }
1895 }
1896 }
1897 }
1898 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1899 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1900 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1901 }
1902 }
1903
1904 project::Event::EntryRenamed(transaction) => {
1905 let Some(workspace) = editor.workspace() else {
1906 return;
1907 };
1908 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1909 else {
1910 return;
1911 };
1912 if active_editor.entity_id() == cx.entity_id() {
1913 let edited_buffers_already_open = {
1914 let other_editors: Vec<Entity<Editor>> = workspace
1915 .read(cx)
1916 .panes()
1917 .iter()
1918 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1919 .filter(|editor| editor.entity_id() != cx.entity_id())
1920 .collect();
1921
1922 transaction.0.keys().all(|buffer| {
1923 other_editors.iter().any(|editor| {
1924 let multi_buffer = editor.read(cx).buffer();
1925 multi_buffer.read(cx).is_singleton()
1926 && multi_buffer.read(cx).as_singleton().map_or(
1927 false,
1928 |singleton| {
1929 singleton.entity_id() == buffer.entity_id()
1930 },
1931 )
1932 })
1933 })
1934 };
1935
1936 if !edited_buffers_already_open {
1937 let workspace = workspace.downgrade();
1938 let transaction = transaction.clone();
1939 cx.defer_in(window, move |_, window, cx| {
1940 cx.spawn_in(window, async move |editor, cx| {
1941 Self::open_project_transaction(
1942 &editor,
1943 workspace,
1944 transaction,
1945 "Rename".to_string(),
1946 cx,
1947 )
1948 .await
1949 .ok()
1950 })
1951 .detach();
1952 });
1953 }
1954 }
1955 }
1956
1957 _ => {}
1958 },
1959 ));
1960 if let Some(task_inventory) = project
1961 .read(cx)
1962 .task_store()
1963 .read(cx)
1964 .task_inventory()
1965 .cloned()
1966 {
1967 project_subscriptions.push(cx.observe_in(
1968 &task_inventory,
1969 window,
1970 |editor, _, window, cx| {
1971 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1972 },
1973 ));
1974 };
1975
1976 project_subscriptions.push(cx.subscribe_in(
1977 &project.read(cx).breakpoint_store(),
1978 window,
1979 |editor, _, event, window, cx| match event {
1980 BreakpointStoreEvent::ClearDebugLines => {
1981 editor.clear_row_highlights::<ActiveDebugLine>();
1982 editor.refresh_inline_values(cx);
1983 }
1984 BreakpointStoreEvent::SetDebugLine => {
1985 if editor.go_to_active_debug_line(window, cx) {
1986 cx.stop_propagation();
1987 }
1988
1989 editor.refresh_inline_values(cx);
1990 }
1991 _ => {}
1992 },
1993 ));
1994 let git_store = project.read(cx).git_store().clone();
1995 let project = project.clone();
1996 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1997 if let GitStoreEvent::RepositoryUpdated(
1998 _,
1999 RepositoryEvent::Updated {
2000 new_instance: true, ..
2001 },
2002 _,
2003 ) = event
2004 {
2005 this.load_diff_task = Some(
2006 update_uncommitted_diff_for_buffer(
2007 cx.entity(),
2008 &project,
2009 this.buffer.read(cx).all_buffers(),
2010 this.buffer.clone(),
2011 cx,
2012 )
2013 .shared(),
2014 );
2015 }
2016 }));
2017 }
2018
2019 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2020
2021 let inlay_hint_settings =
2022 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2023 let focus_handle = cx.focus_handle();
2024 if !is_minimap {
2025 cx.on_focus(&focus_handle, window, Self::handle_focus)
2026 .detach();
2027 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2028 .detach();
2029 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2030 .detach();
2031 cx.on_blur(&focus_handle, window, Self::handle_blur)
2032 .detach();
2033 cx.observe_pending_input(window, Self::observe_pending_input)
2034 .detach();
2035 }
2036
2037 let show_indent_guides =
2038 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2039 Some(false)
2040 } else {
2041 None
2042 };
2043
2044 let breakpoint_store = match (&mode, project.as_ref()) {
2045 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2046 _ => None,
2047 };
2048
2049 let mut code_action_providers = Vec::new();
2050 let mut load_uncommitted_diff = None;
2051 if let Some(project) = project.clone() {
2052 load_uncommitted_diff = Some(
2053 update_uncommitted_diff_for_buffer(
2054 cx.entity(),
2055 &project,
2056 buffer.read(cx).all_buffers(),
2057 buffer.clone(),
2058 cx,
2059 )
2060 .shared(),
2061 );
2062 code_action_providers.push(Rc::new(project) as Rc<_>);
2063 }
2064
2065 let mut editor = Self {
2066 focus_handle,
2067 show_cursor_when_unfocused: false,
2068 last_focused_descendant: None,
2069 buffer: buffer.clone(),
2070 display_map: display_map.clone(),
2071 selections,
2072 scroll_manager: ScrollManager::new(cx),
2073 columnar_selection_state: None,
2074 add_selections_state: None,
2075 select_next_state: None,
2076 select_prev_state: None,
2077 selection_history: SelectionHistory::default(),
2078 defer_selection_effects: false,
2079 deferred_selection_effects_state: None,
2080 autoclose_regions: Vec::new(),
2081 snippet_stack: InvalidationStack::default(),
2082 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2083 ime_transaction: None,
2084 active_diagnostics: ActiveDiagnostic::None,
2085 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2086 inline_diagnostics_update: Task::ready(()),
2087 inline_diagnostics: Vec::new(),
2088 soft_wrap_mode_override,
2089 diagnostics_max_severity,
2090 hard_wrap: None,
2091 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2092 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2093 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2094 project,
2095 blink_manager: blink_manager.clone(),
2096 show_local_selections: true,
2097 show_scrollbars: ScrollbarAxes {
2098 horizontal: full_mode,
2099 vertical: full_mode,
2100 },
2101 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2102 offset_content: !matches!(mode, EditorMode::SingleLine),
2103 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2104 show_gutter: full_mode,
2105 show_line_numbers: (!full_mode).then_some(false),
2106 use_relative_line_numbers: None,
2107 disable_expand_excerpt_buttons: !full_mode,
2108 show_git_diff_gutter: None,
2109 show_code_actions: None,
2110 show_runnables: None,
2111 show_breakpoints: None,
2112 show_wrap_guides: None,
2113 show_indent_guides,
2114 placeholder_text: None,
2115 highlight_order: 0,
2116 highlighted_rows: HashMap::default(),
2117 background_highlights: TreeMap::default(),
2118 gutter_highlights: TreeMap::default(),
2119 scrollbar_marker_state: ScrollbarMarkerState::default(),
2120 active_indent_guides_state: ActiveIndentGuidesState::default(),
2121 nav_history: None,
2122 context_menu: RefCell::new(None),
2123 context_menu_options: None,
2124 mouse_context_menu: None,
2125 completion_tasks: Vec::new(),
2126 inline_blame_popover: None,
2127 inline_blame_popover_show_task: None,
2128 signature_help_state: SignatureHelpState::default(),
2129 auto_signature_help: None,
2130 find_all_references_task_sources: Vec::new(),
2131 next_completion_id: 0,
2132 next_inlay_id: 0,
2133 code_action_providers,
2134 available_code_actions: None,
2135 code_actions_task: None,
2136 quick_selection_highlight_task: None,
2137 debounced_selection_highlight_task: None,
2138 document_highlights_task: None,
2139 linked_editing_range_task: None,
2140 pending_rename: None,
2141 searchable: !is_minimap,
2142 cursor_shape: EditorSettings::get_global(cx)
2143 .cursor_shape
2144 .unwrap_or_default(),
2145 current_line_highlight: None,
2146 autoindent_mode: Some(AutoindentMode::EachLine),
2147 collapse_matches: false,
2148 workspace: None,
2149 input_enabled: !is_minimap,
2150 use_modal_editing: full_mode,
2151 read_only: is_minimap,
2152 use_autoclose: true,
2153 use_auto_surround: true,
2154 auto_replace_emoji_shortcode: false,
2155 jsx_tag_auto_close_enabled_in_any_buffer: false,
2156 leader_id: None,
2157 remote_id: None,
2158 hover_state: HoverState::default(),
2159 pending_mouse_down: None,
2160 hovered_link_state: None,
2161 edit_prediction_provider: None,
2162 active_edit_prediction: None,
2163 stale_edit_prediction_in_menu: None,
2164 edit_prediction_preview: EditPredictionPreview::Inactive {
2165 released_too_fast: false,
2166 },
2167 inline_diagnostics_enabled: full_mode,
2168 diagnostics_enabled: full_mode,
2169 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2170 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2171 gutter_hovered: false,
2172 pixel_position_of_newest_cursor: None,
2173 last_bounds: None,
2174 last_position_map: None,
2175 expect_bounds_change: None,
2176 gutter_dimensions: GutterDimensions::default(),
2177 style: None,
2178 show_cursor_names: false,
2179 hovered_cursors: HashMap::default(),
2180 next_editor_action_id: EditorActionId::default(),
2181 editor_actions: Rc::default(),
2182 edit_predictions_hidden_for_vim_mode: false,
2183 show_edit_predictions_override: None,
2184 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2185 edit_prediction_settings: EditPredictionSettings::Disabled,
2186 edit_prediction_indent_conflict: false,
2187 edit_prediction_requires_modifier_in_indent_conflict: true,
2188 custom_context_menu: None,
2189 show_git_blame_gutter: false,
2190 show_git_blame_inline: false,
2191 show_selection_menu: None,
2192 show_git_blame_inline_delay_task: None,
2193 git_blame_inline_enabled: full_mode
2194 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2195 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2196 serialize_dirty_buffers: !is_minimap
2197 && ProjectSettings::get_global(cx)
2198 .session
2199 .restore_unsaved_buffers,
2200 blame: None,
2201 blame_subscription: None,
2202 tasks: BTreeMap::default(),
2203
2204 breakpoint_store,
2205 gutter_breakpoint_indicator: (None, None),
2206 hovered_diff_hunk_row: None,
2207 _subscriptions: (!is_minimap)
2208 .then(|| {
2209 vec![
2210 cx.observe(&buffer, Self::on_buffer_changed),
2211 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2212 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2213 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2214 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2215 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2216 cx.observe_window_activation(window, |editor, window, cx| {
2217 let active = window.is_window_active();
2218 editor.blink_manager.update(cx, |blink_manager, cx| {
2219 if active {
2220 blink_manager.enable(cx);
2221 } else {
2222 blink_manager.disable(cx);
2223 }
2224 });
2225 if active {
2226 editor.show_mouse_cursor(cx);
2227 }
2228 }),
2229 ]
2230 })
2231 .unwrap_or_default(),
2232 tasks_update_task: None,
2233 pull_diagnostics_task: Task::ready(()),
2234 colors: None,
2235 next_color_inlay_id: 0,
2236 linked_edit_ranges: Default::default(),
2237 in_project_search: false,
2238 previous_search_ranges: None,
2239 breadcrumb_header: None,
2240 focused_block: None,
2241 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2242 addons: HashMap::default(),
2243 registered_buffers: HashMap::default(),
2244 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2245 selection_mark_mode: false,
2246 toggle_fold_multiple_buffers: Task::ready(()),
2247 serialize_selections: Task::ready(()),
2248 serialize_folds: Task::ready(()),
2249 text_style_refinement: None,
2250 load_diff_task: load_uncommitted_diff,
2251 temporary_diff_override: false,
2252 mouse_cursor_hidden: false,
2253 minimap: None,
2254 hide_mouse_mode: EditorSettings::get_global(cx)
2255 .hide_mouse
2256 .unwrap_or_default(),
2257 change_list: ChangeList::new(),
2258 mode,
2259 selection_drag_state: SelectionDragState::None,
2260 folding_newlines: Task::ready(()),
2261 };
2262
2263 if is_minimap {
2264 return editor;
2265 }
2266
2267 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2268 editor
2269 ._subscriptions
2270 .push(cx.observe(breakpoints, |_, _, cx| {
2271 cx.notify();
2272 }));
2273 }
2274 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2275 editor._subscriptions.extend(project_subscriptions);
2276
2277 editor._subscriptions.push(cx.subscribe_in(
2278 &cx.entity(),
2279 window,
2280 |editor, _, e: &EditorEvent, window, cx| match e {
2281 EditorEvent::ScrollPositionChanged { local, .. } => {
2282 if *local {
2283 let new_anchor = editor.scroll_manager.anchor();
2284 let snapshot = editor.snapshot(window, cx);
2285 editor.update_restoration_data(cx, move |data| {
2286 data.scroll_position = (
2287 new_anchor.top_row(&snapshot.buffer_snapshot),
2288 new_anchor.offset,
2289 );
2290 });
2291 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2292 editor.inline_blame_popover.take();
2293 }
2294 }
2295 EditorEvent::Edited { .. } => {
2296 if !vim_enabled(cx) {
2297 let (map, selections) = editor.selections.all_adjusted_display(cx);
2298 let pop_state = editor
2299 .change_list
2300 .last()
2301 .map(|previous| {
2302 previous.len() == selections.len()
2303 && previous.iter().enumerate().all(|(ix, p)| {
2304 p.to_display_point(&map).row()
2305 == selections[ix].head().row()
2306 })
2307 })
2308 .unwrap_or(false);
2309 let new_positions = selections
2310 .into_iter()
2311 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2312 .collect();
2313 editor
2314 .change_list
2315 .push_to_change_list(pop_state, new_positions);
2316 }
2317 }
2318 _ => (),
2319 },
2320 ));
2321
2322 if let Some(dap_store) = editor
2323 .project
2324 .as_ref()
2325 .map(|project| project.read(cx).dap_store())
2326 {
2327 let weak_editor = cx.weak_entity();
2328
2329 editor
2330 ._subscriptions
2331 .push(
2332 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2333 let session_entity = cx.entity();
2334 weak_editor
2335 .update(cx, |editor, cx| {
2336 editor._subscriptions.push(
2337 cx.subscribe(&session_entity, Self::on_debug_session_event),
2338 );
2339 })
2340 .ok();
2341 }),
2342 );
2343
2344 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2345 editor
2346 ._subscriptions
2347 .push(cx.subscribe(&session, Self::on_debug_session_event));
2348 }
2349 }
2350
2351 // skip adding the initial selection to selection history
2352 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2353 editor.end_selection(window, cx);
2354 editor.selection_history.mode = SelectionHistoryMode::Normal;
2355
2356 editor.scroll_manager.show_scrollbars(window, cx);
2357 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2358
2359 if full_mode {
2360 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2361 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2362
2363 if editor.git_blame_inline_enabled {
2364 editor.start_git_blame_inline(false, window, cx);
2365 }
2366
2367 editor.go_to_active_debug_line(window, cx);
2368
2369 if let Some(buffer) = buffer.read(cx).as_singleton()
2370 && let Some(project) = editor.project()
2371 {
2372 let handle = project.update(cx, |project, cx| {
2373 project.register_buffer_with_language_servers(&buffer, cx)
2374 });
2375 editor
2376 .registered_buffers
2377 .insert(buffer.read(cx).remote_id(), handle);
2378 }
2379
2380 editor.minimap =
2381 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2382 editor.colors = Some(LspColorData::new(cx));
2383 editor.update_lsp_data(false, None, window, cx);
2384 }
2385
2386 if editor.mode.is_full() {
2387 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2388 }
2389
2390 editor
2391 }
2392
2393 pub fn deploy_mouse_context_menu(
2394 &mut self,
2395 position: gpui::Point<Pixels>,
2396 context_menu: Entity<ContextMenu>,
2397 window: &mut Window,
2398 cx: &mut Context<Self>,
2399 ) {
2400 self.mouse_context_menu = Some(MouseContextMenu::new(
2401 self,
2402 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2403 context_menu,
2404 window,
2405 cx,
2406 ));
2407 }
2408
2409 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2410 self.mouse_context_menu
2411 .as_ref()
2412 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2413 }
2414
2415 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2416 if self
2417 .selections
2418 .pending
2419 .as_ref()
2420 .is_some_and(|pending_selection| {
2421 let snapshot = self.buffer().read(cx).snapshot(cx);
2422 pending_selection
2423 .selection
2424 .range()
2425 .includes(range, &snapshot)
2426 })
2427 {
2428 return true;
2429 }
2430
2431 self.selections
2432 .disjoint_in_range::<usize>(range.clone(), cx)
2433 .into_iter()
2434 .any(|selection| {
2435 // This is needed to cover a corner case, if we just check for an existing
2436 // selection in the fold range, having a cursor at the start of the fold
2437 // marks it as selected. Non-empty selections don't cause this.
2438 let length = selection.end - selection.start;
2439 length > 0
2440 })
2441 }
2442
2443 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2444 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2445 }
2446
2447 fn key_context_internal(
2448 &self,
2449 has_active_edit_prediction: bool,
2450 window: &Window,
2451 cx: &App,
2452 ) -> KeyContext {
2453 let mut key_context = KeyContext::new_with_defaults();
2454 key_context.add("Editor");
2455 let mode = match self.mode {
2456 EditorMode::SingleLine => "single_line",
2457 EditorMode::AutoHeight { .. } => "auto_height",
2458 EditorMode::Minimap { .. } => "minimap",
2459 EditorMode::Full { .. } => "full",
2460 };
2461
2462 if EditorSettings::jupyter_enabled(cx) {
2463 key_context.add("jupyter");
2464 }
2465
2466 key_context.set("mode", mode);
2467 if self.pending_rename.is_some() {
2468 key_context.add("renaming");
2469 }
2470
2471 match self.context_menu.borrow().as_ref() {
2472 Some(CodeContextMenu::Completions(menu)) => {
2473 if menu.visible() {
2474 key_context.add("menu");
2475 key_context.add("showing_completions");
2476 }
2477 }
2478 Some(CodeContextMenu::CodeActions(menu)) => {
2479 if menu.visible() {
2480 key_context.add("menu");
2481 key_context.add("showing_code_actions")
2482 }
2483 }
2484 None => {}
2485 }
2486
2487 if self.signature_help_state.has_multiple_signatures() {
2488 key_context.add("showing_signature_help");
2489 }
2490
2491 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2492 if !self.focus_handle(cx).contains_focused(window, cx)
2493 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2494 {
2495 for addon in self.addons.values() {
2496 addon.extend_key_context(&mut key_context, cx)
2497 }
2498 }
2499
2500 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2501 if let Some(extension) = singleton_buffer
2502 .read(cx)
2503 .file()
2504 .and_then(|file| file.path().extension()?.to_str())
2505 {
2506 key_context.set("extension", extension.to_string());
2507 }
2508 } else {
2509 key_context.add("multibuffer");
2510 }
2511
2512 if has_active_edit_prediction {
2513 if self.edit_prediction_in_conflict() {
2514 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2515 } else {
2516 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2517 key_context.add("copilot_suggestion");
2518 }
2519 }
2520
2521 if self.selection_mark_mode {
2522 key_context.add("selection_mode");
2523 }
2524
2525 key_context
2526 }
2527
2528 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2529 if self.mouse_cursor_hidden {
2530 self.mouse_cursor_hidden = false;
2531 cx.notify();
2532 }
2533 }
2534
2535 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2536 let hide_mouse_cursor = match origin {
2537 HideMouseCursorOrigin::TypingAction => {
2538 matches!(
2539 self.hide_mouse_mode,
2540 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2541 )
2542 }
2543 HideMouseCursorOrigin::MovementAction => {
2544 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2545 }
2546 };
2547 if self.mouse_cursor_hidden != hide_mouse_cursor {
2548 self.mouse_cursor_hidden = hide_mouse_cursor;
2549 cx.notify();
2550 }
2551 }
2552
2553 pub fn edit_prediction_in_conflict(&self) -> bool {
2554 if !self.show_edit_predictions_in_menu() {
2555 return false;
2556 }
2557
2558 let showing_completions = self
2559 .context_menu
2560 .borrow()
2561 .as_ref()
2562 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2563
2564 showing_completions
2565 || self.edit_prediction_requires_modifier()
2566 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2567 // bindings to insert tab characters.
2568 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2569 }
2570
2571 pub fn accept_edit_prediction_keybind(
2572 &self,
2573 accept_partial: bool,
2574 window: &Window,
2575 cx: &App,
2576 ) -> AcceptEditPredictionBinding {
2577 let key_context = self.key_context_internal(true, window, cx);
2578 let in_conflict = self.edit_prediction_in_conflict();
2579
2580 let bindings = if accept_partial {
2581 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2582 } else {
2583 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2584 };
2585
2586 // TODO: if the binding contains multiple keystrokes, display all of them, not
2587 // just the first one.
2588 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2589 !in_conflict
2590 || binding
2591 .keystrokes()
2592 .first()
2593 .is_some_and(|keystroke| keystroke.modifiers.modified())
2594 }))
2595 }
2596
2597 pub fn new_file(
2598 workspace: &mut Workspace,
2599 _: &workspace::NewFile,
2600 window: &mut Window,
2601 cx: &mut Context<Workspace>,
2602 ) {
2603 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2604 "Failed to create buffer",
2605 window,
2606 cx,
2607 |e, _, _| match e.error_code() {
2608 ErrorCode::RemoteUpgradeRequired => Some(format!(
2609 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2610 e.error_tag("required").unwrap_or("the latest version")
2611 )),
2612 _ => None,
2613 },
2614 );
2615 }
2616
2617 pub fn new_in_workspace(
2618 workspace: &mut Workspace,
2619 window: &mut Window,
2620 cx: &mut Context<Workspace>,
2621 ) -> Task<Result<Entity<Editor>>> {
2622 let project = workspace.project().clone();
2623 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2624
2625 cx.spawn_in(window, async move |workspace, cx| {
2626 let buffer = create.await?;
2627 workspace.update_in(cx, |workspace, window, cx| {
2628 let editor =
2629 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2630 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2631 editor
2632 })
2633 })
2634 }
2635
2636 fn new_file_vertical(
2637 workspace: &mut Workspace,
2638 _: &workspace::NewFileSplitVertical,
2639 window: &mut Window,
2640 cx: &mut Context<Workspace>,
2641 ) {
2642 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2643 }
2644
2645 fn new_file_horizontal(
2646 workspace: &mut Workspace,
2647 _: &workspace::NewFileSplitHorizontal,
2648 window: &mut Window,
2649 cx: &mut Context<Workspace>,
2650 ) {
2651 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2652 }
2653
2654 fn new_file_in_direction(
2655 workspace: &mut Workspace,
2656 direction: SplitDirection,
2657 window: &mut Window,
2658 cx: &mut Context<Workspace>,
2659 ) {
2660 let project = workspace.project().clone();
2661 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2662
2663 cx.spawn_in(window, async move |workspace, cx| {
2664 let buffer = create.await?;
2665 workspace.update_in(cx, move |workspace, window, cx| {
2666 workspace.split_item(
2667 direction,
2668 Box::new(
2669 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2670 ),
2671 window,
2672 cx,
2673 )
2674 })?;
2675 anyhow::Ok(())
2676 })
2677 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2678 match e.error_code() {
2679 ErrorCode::RemoteUpgradeRequired => Some(format!(
2680 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2681 e.error_tag("required").unwrap_or("the latest version")
2682 )),
2683 _ => None,
2684 }
2685 });
2686 }
2687
2688 pub fn leader_id(&self) -> Option<CollaboratorId> {
2689 self.leader_id
2690 }
2691
2692 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2693 &self.buffer
2694 }
2695
2696 pub fn project(&self) -> Option<&Entity<Project>> {
2697 self.project.as_ref()
2698 }
2699
2700 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2701 self.workspace.as_ref()?.0.upgrade()
2702 }
2703
2704 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2705 self.buffer().read(cx).title(cx)
2706 }
2707
2708 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2709 let git_blame_gutter_max_author_length = self
2710 .render_git_blame_gutter(cx)
2711 .then(|| {
2712 if let Some(blame) = self.blame.as_ref() {
2713 let max_author_length =
2714 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2715 Some(max_author_length)
2716 } else {
2717 None
2718 }
2719 })
2720 .flatten();
2721
2722 EditorSnapshot {
2723 mode: self.mode.clone(),
2724 show_gutter: self.show_gutter,
2725 show_line_numbers: self.show_line_numbers,
2726 show_git_diff_gutter: self.show_git_diff_gutter,
2727 show_code_actions: self.show_code_actions,
2728 show_runnables: self.show_runnables,
2729 show_breakpoints: self.show_breakpoints,
2730 git_blame_gutter_max_author_length,
2731 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2732 scroll_anchor: self.scroll_manager.anchor(),
2733 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2734 placeholder_text: self.placeholder_text.clone(),
2735 is_focused: self.focus_handle.is_focused(window),
2736 current_line_highlight: self
2737 .current_line_highlight
2738 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2739 gutter_hovered: self.gutter_hovered,
2740 }
2741 }
2742
2743 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2744 self.buffer.read(cx).language_at(point, cx)
2745 }
2746
2747 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2748 self.buffer.read(cx).read(cx).file_at(point).cloned()
2749 }
2750
2751 pub fn active_excerpt(
2752 &self,
2753 cx: &App,
2754 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2755 self.buffer
2756 .read(cx)
2757 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2758 }
2759
2760 pub fn mode(&self) -> &EditorMode {
2761 &self.mode
2762 }
2763
2764 pub fn set_mode(&mut self, mode: EditorMode) {
2765 self.mode = mode;
2766 }
2767
2768 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2769 self.collaboration_hub.as_deref()
2770 }
2771
2772 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2773 self.collaboration_hub = Some(hub);
2774 }
2775
2776 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2777 self.in_project_search = in_project_search;
2778 }
2779
2780 pub fn set_custom_context_menu(
2781 &mut self,
2782 f: impl 'static
2783 + Fn(
2784 &mut Self,
2785 DisplayPoint,
2786 &mut Window,
2787 &mut Context<Self>,
2788 ) -> Option<Entity<ui::ContextMenu>>,
2789 ) {
2790 self.custom_context_menu = Some(Box::new(f))
2791 }
2792
2793 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2794 self.completion_provider = provider;
2795 }
2796
2797 #[cfg(any(test, feature = "test-support"))]
2798 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2799 self.completion_provider.clone()
2800 }
2801
2802 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2803 self.semantics_provider.clone()
2804 }
2805
2806 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2807 self.semantics_provider = provider;
2808 }
2809
2810 pub fn set_edit_prediction_provider<T>(
2811 &mut self,
2812 provider: Option<Entity<T>>,
2813 window: &mut Window,
2814 cx: &mut Context<Self>,
2815 ) where
2816 T: EditPredictionProvider,
2817 {
2818 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2819 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2820 if this.focus_handle.is_focused(window) {
2821 this.update_visible_edit_prediction(window, cx);
2822 }
2823 }),
2824 provider: Arc::new(provider),
2825 });
2826 self.update_edit_prediction_settings(cx);
2827 self.refresh_edit_prediction(false, false, window, cx);
2828 }
2829
2830 pub fn placeholder_text(&self) -> Option<&str> {
2831 self.placeholder_text.as_deref()
2832 }
2833
2834 pub fn set_placeholder_text(
2835 &mut self,
2836 placeholder_text: impl Into<Arc<str>>,
2837 cx: &mut Context<Self>,
2838 ) {
2839 let placeholder_text = Some(placeholder_text.into());
2840 if self.placeholder_text != placeholder_text {
2841 self.placeholder_text = placeholder_text;
2842 cx.notify();
2843 }
2844 }
2845
2846 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2847 self.cursor_shape = cursor_shape;
2848
2849 // Disrupt blink for immediate user feedback that the cursor shape has changed
2850 self.blink_manager.update(cx, BlinkManager::show_cursor);
2851
2852 cx.notify();
2853 }
2854
2855 pub fn set_current_line_highlight(
2856 &mut self,
2857 current_line_highlight: Option<CurrentLineHighlight>,
2858 ) {
2859 self.current_line_highlight = current_line_highlight;
2860 }
2861
2862 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2863 self.collapse_matches = collapse_matches;
2864 }
2865
2866 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2867 let buffers = self.buffer.read(cx).all_buffers();
2868 let Some(project) = self.project.as_ref() else {
2869 return;
2870 };
2871 project.update(cx, |project, cx| {
2872 for buffer in buffers {
2873 self.registered_buffers
2874 .entry(buffer.read(cx).remote_id())
2875 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2876 }
2877 })
2878 }
2879
2880 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2881 if self.collapse_matches {
2882 return range.start..range.start;
2883 }
2884 range.clone()
2885 }
2886
2887 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2888 if self.display_map.read(cx).clip_at_line_ends != clip {
2889 self.display_map
2890 .update(cx, |map, _| map.clip_at_line_ends = clip);
2891 }
2892 }
2893
2894 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2895 self.input_enabled = input_enabled;
2896 }
2897
2898 pub fn set_edit_predictions_hidden_for_vim_mode(
2899 &mut self,
2900 hidden: bool,
2901 window: &mut Window,
2902 cx: &mut Context<Self>,
2903 ) {
2904 if hidden != self.edit_predictions_hidden_for_vim_mode {
2905 self.edit_predictions_hidden_for_vim_mode = hidden;
2906 if hidden {
2907 self.update_visible_edit_prediction(window, cx);
2908 } else {
2909 self.refresh_edit_prediction(true, false, window, cx);
2910 }
2911 }
2912 }
2913
2914 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2915 self.menu_edit_predictions_policy = value;
2916 }
2917
2918 pub fn set_autoindent(&mut self, autoindent: bool) {
2919 if autoindent {
2920 self.autoindent_mode = Some(AutoindentMode::EachLine);
2921 } else {
2922 self.autoindent_mode = None;
2923 }
2924 }
2925
2926 pub fn read_only(&self, cx: &App) -> bool {
2927 self.read_only || self.buffer.read(cx).read_only()
2928 }
2929
2930 pub fn set_read_only(&mut self, read_only: bool) {
2931 self.read_only = read_only;
2932 }
2933
2934 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2935 self.use_autoclose = autoclose;
2936 }
2937
2938 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2939 self.use_auto_surround = auto_surround;
2940 }
2941
2942 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2943 self.auto_replace_emoji_shortcode = auto_replace;
2944 }
2945
2946 pub fn toggle_edit_predictions(
2947 &mut self,
2948 _: &ToggleEditPrediction,
2949 window: &mut Window,
2950 cx: &mut Context<Self>,
2951 ) {
2952 if self.show_edit_predictions_override.is_some() {
2953 self.set_show_edit_predictions(None, window, cx);
2954 } else {
2955 let show_edit_predictions = !self.edit_predictions_enabled();
2956 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2957 }
2958 }
2959
2960 pub fn set_show_edit_predictions(
2961 &mut self,
2962 show_edit_predictions: Option<bool>,
2963 window: &mut Window,
2964 cx: &mut Context<Self>,
2965 ) {
2966 self.show_edit_predictions_override = show_edit_predictions;
2967 self.update_edit_prediction_settings(cx);
2968
2969 if let Some(false) = show_edit_predictions {
2970 self.discard_edit_prediction(false, cx);
2971 } else {
2972 self.refresh_edit_prediction(false, true, window, cx);
2973 }
2974 }
2975
2976 fn edit_predictions_disabled_in_scope(
2977 &self,
2978 buffer: &Entity<Buffer>,
2979 buffer_position: language::Anchor,
2980 cx: &App,
2981 ) -> bool {
2982 let snapshot = buffer.read(cx).snapshot();
2983 let settings = snapshot.settings_at(buffer_position, cx);
2984
2985 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2986 return false;
2987 };
2988
2989 scope.override_name().is_some_and(|scope_name| {
2990 settings
2991 .edit_predictions_disabled_in
2992 .iter()
2993 .any(|s| s == scope_name)
2994 })
2995 }
2996
2997 pub fn set_use_modal_editing(&mut self, to: bool) {
2998 self.use_modal_editing = to;
2999 }
3000
3001 pub fn use_modal_editing(&self) -> bool {
3002 self.use_modal_editing
3003 }
3004
3005 fn selections_did_change(
3006 &mut self,
3007 local: bool,
3008 old_cursor_position: &Anchor,
3009 effects: SelectionEffects,
3010 window: &mut Window,
3011 cx: &mut Context<Self>,
3012 ) {
3013 window.invalidate_character_coordinates();
3014
3015 // Copy selections to primary selection buffer
3016 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3017 if local {
3018 let selections = self.selections.all::<usize>(cx);
3019 let buffer_handle = self.buffer.read(cx).read(cx);
3020
3021 let mut text = String::new();
3022 for (index, selection) in selections.iter().enumerate() {
3023 let text_for_selection = buffer_handle
3024 .text_for_range(selection.start..selection.end)
3025 .collect::<String>();
3026
3027 text.push_str(&text_for_selection);
3028 if index != selections.len() - 1 {
3029 text.push('\n');
3030 }
3031 }
3032
3033 if !text.is_empty() {
3034 cx.write_to_primary(ClipboardItem::new_string(text));
3035 }
3036 }
3037
3038 let selection_anchors = self.selections.disjoint_anchors();
3039
3040 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3041 self.buffer.update(cx, |buffer, cx| {
3042 buffer.set_active_selections(
3043 &selection_anchors,
3044 self.selections.line_mode,
3045 self.cursor_shape,
3046 cx,
3047 )
3048 });
3049 }
3050 let display_map = self
3051 .display_map
3052 .update(cx, |display_map, cx| display_map.snapshot(cx));
3053 let buffer = &display_map.buffer_snapshot;
3054 if self.selections.count() == 1 {
3055 self.add_selections_state = None;
3056 }
3057 self.select_next_state = None;
3058 self.select_prev_state = None;
3059 self.select_syntax_node_history.try_clear();
3060 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3061 self.snippet_stack.invalidate(&selection_anchors, buffer);
3062 self.take_rename(false, window, cx);
3063
3064 let newest_selection = self.selections.newest_anchor();
3065 let new_cursor_position = newest_selection.head();
3066 let selection_start = newest_selection.start;
3067
3068 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3069 self.push_to_nav_history(
3070 *old_cursor_position,
3071 Some(new_cursor_position.to_point(buffer)),
3072 false,
3073 effects.nav_history == Some(true),
3074 cx,
3075 );
3076 }
3077
3078 if local {
3079 if let Some(buffer_id) = new_cursor_position.buffer_id
3080 && !self.registered_buffers.contains_key(&buffer_id)
3081 && let Some(project) = self.project.as_ref()
3082 {
3083 project.update(cx, |project, cx| {
3084 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3085 return;
3086 };
3087 self.registered_buffers.insert(
3088 buffer_id,
3089 project.register_buffer_with_language_servers(&buffer, cx),
3090 );
3091 })
3092 }
3093
3094 let mut context_menu = self.context_menu.borrow_mut();
3095 let completion_menu = match context_menu.as_ref() {
3096 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3097 Some(CodeContextMenu::CodeActions(_)) => {
3098 *context_menu = None;
3099 None
3100 }
3101 None => None,
3102 };
3103 let completion_position = completion_menu.map(|menu| menu.initial_position);
3104 drop(context_menu);
3105
3106 if effects.completions
3107 && let Some(completion_position) = completion_position
3108 {
3109 let start_offset = selection_start.to_offset(buffer);
3110 let position_matches = start_offset == completion_position.to_offset(buffer);
3111 let continue_showing = if position_matches {
3112 if self.snippet_stack.is_empty() {
3113 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3114 } else {
3115 // Snippet choices can be shown even when the cursor is in whitespace.
3116 // Dismissing the menu with actions like backspace is handled by
3117 // invalidation regions.
3118 true
3119 }
3120 } else {
3121 false
3122 };
3123
3124 if continue_showing {
3125 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3126 } else {
3127 self.hide_context_menu(window, cx);
3128 }
3129 }
3130
3131 hide_hover(self, cx);
3132
3133 if old_cursor_position.to_display_point(&display_map).row()
3134 != new_cursor_position.to_display_point(&display_map).row()
3135 {
3136 self.available_code_actions.take();
3137 }
3138 self.refresh_code_actions(window, cx);
3139 self.refresh_document_highlights(cx);
3140 self.refresh_selected_text_highlights(false, window, cx);
3141 refresh_matching_bracket_highlights(self, window, cx);
3142 self.update_visible_edit_prediction(window, cx);
3143 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3144 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3145 self.inline_blame_popover.take();
3146 if self.git_blame_inline_enabled {
3147 self.start_inline_blame_timer(window, cx);
3148 }
3149 }
3150
3151 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3152 cx.emit(EditorEvent::SelectionsChanged { local });
3153
3154 let selections = &self.selections.disjoint;
3155 if selections.len() == 1 {
3156 cx.emit(SearchEvent::ActiveMatchChanged)
3157 }
3158 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3159 let inmemory_selections = selections
3160 .iter()
3161 .map(|s| {
3162 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3163 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3164 })
3165 .collect();
3166 self.update_restoration_data(cx, |data| {
3167 data.selections = inmemory_selections;
3168 });
3169
3170 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3171 && let Some(workspace_id) =
3172 self.workspace.as_ref().and_then(|workspace| workspace.1)
3173 {
3174 let snapshot = self.buffer().read(cx).snapshot(cx);
3175 let selections = selections.clone();
3176 let background_executor = cx.background_executor().clone();
3177 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3178 self.serialize_selections = cx.background_spawn(async move {
3179 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3180 let db_selections = selections
3181 .iter()
3182 .map(|selection| {
3183 (
3184 selection.start.to_offset(&snapshot),
3185 selection.end.to_offset(&snapshot),
3186 )
3187 })
3188 .collect();
3189
3190 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3191 .await
3192 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3193 .log_err();
3194 });
3195 }
3196 }
3197
3198 cx.notify();
3199 }
3200
3201 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3202 use text::ToOffset as _;
3203 use text::ToPoint as _;
3204
3205 if self.mode.is_minimap()
3206 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3207 {
3208 return;
3209 }
3210
3211 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3212 return;
3213 };
3214
3215 let snapshot = singleton.read(cx).snapshot();
3216 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3217 let display_snapshot = display_map.snapshot(cx);
3218
3219 display_snapshot
3220 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3221 .map(|fold| {
3222 fold.range.start.text_anchor.to_point(&snapshot)
3223 ..fold.range.end.text_anchor.to_point(&snapshot)
3224 })
3225 .collect()
3226 });
3227 self.update_restoration_data(cx, |data| {
3228 data.folds = inmemory_folds;
3229 });
3230
3231 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3232 return;
3233 };
3234 let background_executor = cx.background_executor().clone();
3235 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3236 let db_folds = self.display_map.update(cx, |display_map, cx| {
3237 display_map
3238 .snapshot(cx)
3239 .folds_in_range(0..snapshot.len())
3240 .map(|fold| {
3241 (
3242 fold.range.start.text_anchor.to_offset(&snapshot),
3243 fold.range.end.text_anchor.to_offset(&snapshot),
3244 )
3245 })
3246 .collect()
3247 });
3248 self.serialize_folds = cx.background_spawn(async move {
3249 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3250 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3251 .await
3252 .with_context(|| {
3253 format!(
3254 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3255 )
3256 })
3257 .log_err();
3258 });
3259 }
3260
3261 pub fn sync_selections(
3262 &mut self,
3263 other: Entity<Editor>,
3264 cx: &mut Context<Self>,
3265 ) -> gpui::Subscription {
3266 let other_selections = other.read(cx).selections.disjoint.to_vec();
3267 self.selections.change_with(cx, |selections| {
3268 selections.select_anchors(other_selections);
3269 });
3270
3271 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3272 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3273 let other_selections = other.read(cx).selections.disjoint.to_vec();
3274 if other_selections.is_empty() {
3275 return;
3276 }
3277 this.selections.change_with(cx, |selections| {
3278 selections.select_anchors(other_selections);
3279 });
3280 }
3281 });
3282
3283 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3284 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3285 let these_selections = this.selections.disjoint.to_vec();
3286 if these_selections.is_empty() {
3287 return;
3288 }
3289 other.update(cx, |other_editor, cx| {
3290 other_editor.selections.change_with(cx, |selections| {
3291 selections.select_anchors(these_selections);
3292 })
3293 });
3294 }
3295 });
3296
3297 Subscription::join(other_subscription, this_subscription)
3298 }
3299
3300 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3301 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3302 /// effects of selection change occur at the end of the transaction.
3303 pub fn change_selections<R>(
3304 &mut self,
3305 effects: SelectionEffects,
3306 window: &mut Window,
3307 cx: &mut Context<Self>,
3308 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3309 ) -> R {
3310 if let Some(state) = &mut self.deferred_selection_effects_state {
3311 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3312 state.effects.completions = effects.completions;
3313 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3314 let (changed, result) = self.selections.change_with(cx, change);
3315 state.changed |= changed;
3316 return result;
3317 }
3318 let mut state = DeferredSelectionEffectsState {
3319 changed: false,
3320 effects,
3321 old_cursor_position: self.selections.newest_anchor().head(),
3322 history_entry: SelectionHistoryEntry {
3323 selections: self.selections.disjoint_anchors(),
3324 select_next_state: self.select_next_state.clone(),
3325 select_prev_state: self.select_prev_state.clone(),
3326 add_selections_state: self.add_selections_state.clone(),
3327 },
3328 };
3329 let (changed, result) = self.selections.change_with(cx, change);
3330 state.changed = state.changed || changed;
3331 if self.defer_selection_effects {
3332 self.deferred_selection_effects_state = Some(state);
3333 } else {
3334 self.apply_selection_effects(state, window, cx);
3335 }
3336 result
3337 }
3338
3339 /// Defers the effects of selection change, so that the effects of multiple calls to
3340 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3341 /// to selection history and the state of popovers based on selection position aren't
3342 /// erroneously updated.
3343 pub fn with_selection_effects_deferred<R>(
3344 &mut self,
3345 window: &mut Window,
3346 cx: &mut Context<Self>,
3347 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3348 ) -> R {
3349 let already_deferred = self.defer_selection_effects;
3350 self.defer_selection_effects = true;
3351 let result = update(self, window, cx);
3352 if !already_deferred {
3353 self.defer_selection_effects = false;
3354 if let Some(state) = self.deferred_selection_effects_state.take() {
3355 self.apply_selection_effects(state, window, cx);
3356 }
3357 }
3358 result
3359 }
3360
3361 fn apply_selection_effects(
3362 &mut self,
3363 state: DeferredSelectionEffectsState,
3364 window: &mut Window,
3365 cx: &mut Context<Self>,
3366 ) {
3367 if state.changed {
3368 self.selection_history.push(state.history_entry);
3369
3370 if let Some(autoscroll) = state.effects.scroll {
3371 self.request_autoscroll(autoscroll, cx);
3372 }
3373
3374 let old_cursor_position = &state.old_cursor_position;
3375
3376 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3377
3378 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3379 self.show_signature_help(&ShowSignatureHelp, window, cx);
3380 }
3381 }
3382 }
3383
3384 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3385 where
3386 I: IntoIterator<Item = (Range<S>, T)>,
3387 S: ToOffset,
3388 T: Into<Arc<str>>,
3389 {
3390 if self.read_only(cx) {
3391 return;
3392 }
3393
3394 self.buffer
3395 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3396 }
3397
3398 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3399 where
3400 I: IntoIterator<Item = (Range<S>, T)>,
3401 S: ToOffset,
3402 T: Into<Arc<str>>,
3403 {
3404 if self.read_only(cx) {
3405 return;
3406 }
3407
3408 self.buffer.update(cx, |buffer, cx| {
3409 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3410 });
3411 }
3412
3413 pub fn edit_with_block_indent<I, S, T>(
3414 &mut self,
3415 edits: I,
3416 original_indent_columns: Vec<Option<u32>>,
3417 cx: &mut Context<Self>,
3418 ) where
3419 I: IntoIterator<Item = (Range<S>, T)>,
3420 S: ToOffset,
3421 T: Into<Arc<str>>,
3422 {
3423 if self.read_only(cx) {
3424 return;
3425 }
3426
3427 self.buffer.update(cx, |buffer, cx| {
3428 buffer.edit(
3429 edits,
3430 Some(AutoindentMode::Block {
3431 original_indent_columns,
3432 }),
3433 cx,
3434 )
3435 });
3436 }
3437
3438 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3439 self.hide_context_menu(window, cx);
3440
3441 match phase {
3442 SelectPhase::Begin {
3443 position,
3444 add,
3445 click_count,
3446 } => self.begin_selection(position, add, click_count, window, cx),
3447 SelectPhase::BeginColumnar {
3448 position,
3449 goal_column,
3450 reset,
3451 mode,
3452 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3453 SelectPhase::Extend {
3454 position,
3455 click_count,
3456 } => self.extend_selection(position, click_count, window, cx),
3457 SelectPhase::Update {
3458 position,
3459 goal_column,
3460 scroll_delta,
3461 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3462 SelectPhase::End => self.end_selection(window, cx),
3463 }
3464 }
3465
3466 fn extend_selection(
3467 &mut self,
3468 position: DisplayPoint,
3469 click_count: usize,
3470 window: &mut Window,
3471 cx: &mut Context<Self>,
3472 ) {
3473 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3474 let tail = self.selections.newest::<usize>(cx).tail();
3475 self.begin_selection(position, false, click_count, window, cx);
3476
3477 let position = position.to_offset(&display_map, Bias::Left);
3478 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3479
3480 let mut pending_selection = self
3481 .selections
3482 .pending_anchor()
3483 .expect("extend_selection not called with pending selection");
3484 if position >= tail {
3485 pending_selection.start = tail_anchor;
3486 } else {
3487 pending_selection.end = tail_anchor;
3488 pending_selection.reversed = true;
3489 }
3490
3491 let mut pending_mode = self.selections.pending_mode().unwrap();
3492 match &mut pending_mode {
3493 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3494 _ => {}
3495 }
3496
3497 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3498 SelectionEffects::scroll(Autoscroll::fit())
3499 } else {
3500 SelectionEffects::no_scroll()
3501 };
3502
3503 self.change_selections(effects, window, cx, |s| {
3504 s.set_pending(pending_selection, pending_mode)
3505 });
3506 }
3507
3508 fn begin_selection(
3509 &mut self,
3510 position: DisplayPoint,
3511 add: bool,
3512 click_count: usize,
3513 window: &mut Window,
3514 cx: &mut Context<Self>,
3515 ) {
3516 if !self.focus_handle.is_focused(window) {
3517 self.last_focused_descendant = None;
3518 window.focus(&self.focus_handle);
3519 }
3520
3521 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3522 let buffer = &display_map.buffer_snapshot;
3523 let position = display_map.clip_point(position, Bias::Left);
3524
3525 let start;
3526 let end;
3527 let mode;
3528 let mut auto_scroll;
3529 match click_count {
3530 1 => {
3531 start = buffer.anchor_before(position.to_point(&display_map));
3532 end = start;
3533 mode = SelectMode::Character;
3534 auto_scroll = true;
3535 }
3536 2 => {
3537 let position = display_map
3538 .clip_point(position, Bias::Left)
3539 .to_offset(&display_map, Bias::Left);
3540 let (range, _) = buffer.surrounding_word(position, false);
3541 start = buffer.anchor_before(range.start);
3542 end = buffer.anchor_before(range.end);
3543 mode = SelectMode::Word(start..end);
3544 auto_scroll = true;
3545 }
3546 3 => {
3547 let position = display_map
3548 .clip_point(position, Bias::Left)
3549 .to_point(&display_map);
3550 let line_start = display_map.prev_line_boundary(position).0;
3551 let next_line_start = buffer.clip_point(
3552 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3553 Bias::Left,
3554 );
3555 start = buffer.anchor_before(line_start);
3556 end = buffer.anchor_before(next_line_start);
3557 mode = SelectMode::Line(start..end);
3558 auto_scroll = true;
3559 }
3560 _ => {
3561 start = buffer.anchor_before(0);
3562 end = buffer.anchor_before(buffer.len());
3563 mode = SelectMode::All;
3564 auto_scroll = false;
3565 }
3566 }
3567 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3568
3569 let point_to_delete: Option<usize> = {
3570 let selected_points: Vec<Selection<Point>> =
3571 self.selections.disjoint_in_range(start..end, cx);
3572
3573 if !add || click_count > 1 {
3574 None
3575 } else if !selected_points.is_empty() {
3576 Some(selected_points[0].id)
3577 } else {
3578 let clicked_point_already_selected =
3579 self.selections.disjoint.iter().find(|selection| {
3580 selection.start.to_point(buffer) == start.to_point(buffer)
3581 || selection.end.to_point(buffer) == end.to_point(buffer)
3582 });
3583
3584 clicked_point_already_selected.map(|selection| selection.id)
3585 }
3586 };
3587
3588 let selections_count = self.selections.count();
3589 let effects = if auto_scroll {
3590 SelectionEffects::default()
3591 } else {
3592 SelectionEffects::no_scroll()
3593 };
3594
3595 self.change_selections(effects, window, cx, |s| {
3596 if let Some(point_to_delete) = point_to_delete {
3597 s.delete(point_to_delete);
3598
3599 if selections_count == 1 {
3600 s.set_pending_anchor_range(start..end, mode);
3601 }
3602 } else {
3603 if !add {
3604 s.clear_disjoint();
3605 }
3606
3607 s.set_pending_anchor_range(start..end, mode);
3608 }
3609 });
3610 }
3611
3612 fn begin_columnar_selection(
3613 &mut self,
3614 position: DisplayPoint,
3615 goal_column: u32,
3616 reset: bool,
3617 mode: ColumnarMode,
3618 window: &mut Window,
3619 cx: &mut Context<Self>,
3620 ) {
3621 if !self.focus_handle.is_focused(window) {
3622 self.last_focused_descendant = None;
3623 window.focus(&self.focus_handle);
3624 }
3625
3626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3627
3628 if reset {
3629 let pointer_position = display_map
3630 .buffer_snapshot
3631 .anchor_before(position.to_point(&display_map));
3632
3633 self.change_selections(
3634 SelectionEffects::scroll(Autoscroll::newest()),
3635 window,
3636 cx,
3637 |s| {
3638 s.clear_disjoint();
3639 s.set_pending_anchor_range(
3640 pointer_position..pointer_position,
3641 SelectMode::Character,
3642 );
3643 },
3644 );
3645 };
3646
3647 let tail = self.selections.newest::<Point>(cx).tail();
3648 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3649 self.columnar_selection_state = match mode {
3650 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3651 selection_tail: selection_anchor,
3652 display_point: if reset {
3653 if position.column() != goal_column {
3654 Some(DisplayPoint::new(position.row(), goal_column))
3655 } else {
3656 None
3657 }
3658 } else {
3659 None
3660 },
3661 }),
3662 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3663 selection_tail: selection_anchor,
3664 }),
3665 };
3666
3667 if !reset {
3668 self.select_columns(position, goal_column, &display_map, window, cx);
3669 }
3670 }
3671
3672 fn update_selection(
3673 &mut self,
3674 position: DisplayPoint,
3675 goal_column: u32,
3676 scroll_delta: gpui::Point<f32>,
3677 window: &mut Window,
3678 cx: &mut Context<Self>,
3679 ) {
3680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3681
3682 if self.columnar_selection_state.is_some() {
3683 self.select_columns(position, goal_column, &display_map, window, cx);
3684 } else if let Some(mut pending) = self.selections.pending_anchor() {
3685 let buffer = &display_map.buffer_snapshot;
3686 let head;
3687 let tail;
3688 let mode = self.selections.pending_mode().unwrap();
3689 match &mode {
3690 SelectMode::Character => {
3691 head = position.to_point(&display_map);
3692 tail = pending.tail().to_point(buffer);
3693 }
3694 SelectMode::Word(original_range) => {
3695 let offset = display_map
3696 .clip_point(position, Bias::Left)
3697 .to_offset(&display_map, Bias::Left);
3698 let original_range = original_range.to_offset(buffer);
3699
3700 let head_offset = if buffer.is_inside_word(offset, false)
3701 || original_range.contains(&offset)
3702 {
3703 let (word_range, _) = buffer.surrounding_word(offset, false);
3704 if word_range.start < original_range.start {
3705 word_range.start
3706 } else {
3707 word_range.end
3708 }
3709 } else {
3710 offset
3711 };
3712
3713 head = head_offset.to_point(buffer);
3714 if head_offset <= original_range.start {
3715 tail = original_range.end.to_point(buffer);
3716 } else {
3717 tail = original_range.start.to_point(buffer);
3718 }
3719 }
3720 SelectMode::Line(original_range) => {
3721 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3722
3723 let position = display_map
3724 .clip_point(position, Bias::Left)
3725 .to_point(&display_map);
3726 let line_start = display_map.prev_line_boundary(position).0;
3727 let next_line_start = buffer.clip_point(
3728 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3729 Bias::Left,
3730 );
3731
3732 if line_start < original_range.start {
3733 head = line_start
3734 } else {
3735 head = next_line_start
3736 }
3737
3738 if head <= original_range.start {
3739 tail = original_range.end;
3740 } else {
3741 tail = original_range.start;
3742 }
3743 }
3744 SelectMode::All => {
3745 return;
3746 }
3747 };
3748
3749 if head < tail {
3750 pending.start = buffer.anchor_before(head);
3751 pending.end = buffer.anchor_before(tail);
3752 pending.reversed = true;
3753 } else {
3754 pending.start = buffer.anchor_before(tail);
3755 pending.end = buffer.anchor_before(head);
3756 pending.reversed = false;
3757 }
3758
3759 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3760 s.set_pending(pending, mode);
3761 });
3762 } else {
3763 log::error!("update_selection dispatched with no pending selection");
3764 return;
3765 }
3766
3767 self.apply_scroll_delta(scroll_delta, window, cx);
3768 cx.notify();
3769 }
3770
3771 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3772 self.columnar_selection_state.take();
3773 if self.selections.pending_anchor().is_some() {
3774 let selections = self.selections.all::<usize>(cx);
3775 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3776 s.select(selections);
3777 s.clear_pending();
3778 });
3779 }
3780 }
3781
3782 fn select_columns(
3783 &mut self,
3784 head: DisplayPoint,
3785 goal_column: u32,
3786 display_map: &DisplaySnapshot,
3787 window: &mut Window,
3788 cx: &mut Context<Self>,
3789 ) {
3790 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3791 return;
3792 };
3793
3794 let tail = match columnar_state {
3795 ColumnarSelectionState::FromMouse {
3796 selection_tail,
3797 display_point,
3798 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3799 ColumnarSelectionState::FromSelection { selection_tail } => {
3800 selection_tail.to_display_point(display_map)
3801 }
3802 };
3803
3804 let start_row = cmp::min(tail.row(), head.row());
3805 let end_row = cmp::max(tail.row(), head.row());
3806 let start_column = cmp::min(tail.column(), goal_column);
3807 let end_column = cmp::max(tail.column(), goal_column);
3808 let reversed = start_column < tail.column();
3809
3810 let selection_ranges = (start_row.0..=end_row.0)
3811 .map(DisplayRow)
3812 .filter_map(|row| {
3813 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3814 || start_column <= display_map.line_len(row))
3815 && !display_map.is_block_line(row)
3816 {
3817 let start = display_map
3818 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3819 .to_point(display_map);
3820 let end = display_map
3821 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3822 .to_point(display_map);
3823 if reversed {
3824 Some(end..start)
3825 } else {
3826 Some(start..end)
3827 }
3828 } else {
3829 None
3830 }
3831 })
3832 .collect::<Vec<_>>();
3833
3834 let ranges = match columnar_state {
3835 ColumnarSelectionState::FromMouse { .. } => {
3836 let mut non_empty_ranges = selection_ranges
3837 .iter()
3838 .filter(|selection_range| selection_range.start != selection_range.end)
3839 .peekable();
3840 if non_empty_ranges.peek().is_some() {
3841 non_empty_ranges.cloned().collect()
3842 } else {
3843 selection_ranges
3844 }
3845 }
3846 _ => selection_ranges,
3847 };
3848
3849 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3850 s.select_ranges(ranges);
3851 });
3852 cx.notify();
3853 }
3854
3855 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3856 self.selections
3857 .all_adjusted(cx)
3858 .iter()
3859 .any(|selection| !selection.is_empty())
3860 }
3861
3862 pub fn has_pending_nonempty_selection(&self) -> bool {
3863 let pending_nonempty_selection = match self.selections.pending_anchor() {
3864 Some(Selection { start, end, .. }) => start != end,
3865 None => false,
3866 };
3867
3868 pending_nonempty_selection
3869 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3870 }
3871
3872 pub fn has_pending_selection(&self) -> bool {
3873 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3874 }
3875
3876 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3877 self.selection_mark_mode = false;
3878 self.selection_drag_state = SelectionDragState::None;
3879
3880 if self.clear_expanded_diff_hunks(cx) {
3881 cx.notify();
3882 return;
3883 }
3884 if self.dismiss_menus_and_popups(true, window, cx) {
3885 return;
3886 }
3887
3888 if self.mode.is_full()
3889 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3890 {
3891 return;
3892 }
3893
3894 cx.propagate();
3895 }
3896
3897 pub fn dismiss_menus_and_popups(
3898 &mut self,
3899 is_user_requested: bool,
3900 window: &mut Window,
3901 cx: &mut Context<Self>,
3902 ) -> bool {
3903 if self.take_rename(false, window, cx).is_some() {
3904 return true;
3905 }
3906
3907 if hide_hover(self, cx) {
3908 return true;
3909 }
3910
3911 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3912 return true;
3913 }
3914
3915 if self.hide_context_menu(window, cx).is_some() {
3916 return true;
3917 }
3918
3919 if self.mouse_context_menu.take().is_some() {
3920 return true;
3921 }
3922
3923 if is_user_requested && self.discard_edit_prediction(true, cx) {
3924 return true;
3925 }
3926
3927 if self.snippet_stack.pop().is_some() {
3928 return true;
3929 }
3930
3931 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3932 self.dismiss_diagnostics(cx);
3933 return true;
3934 }
3935
3936 false
3937 }
3938
3939 fn linked_editing_ranges_for(
3940 &self,
3941 selection: Range<text::Anchor>,
3942 cx: &App,
3943 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3944 if self.linked_edit_ranges.is_empty() {
3945 return None;
3946 }
3947 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3948 selection.end.buffer_id.and_then(|end_buffer_id| {
3949 if selection.start.buffer_id != Some(end_buffer_id) {
3950 return None;
3951 }
3952 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3953 let snapshot = buffer.read(cx).snapshot();
3954 self.linked_edit_ranges
3955 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3956 .map(|ranges| (ranges, snapshot, buffer))
3957 })?;
3958 use text::ToOffset as TO;
3959 // find offset from the start of current range to current cursor position
3960 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3961
3962 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3963 let start_difference = start_offset - start_byte_offset;
3964 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3965 let end_difference = end_offset - start_byte_offset;
3966 // Current range has associated linked ranges.
3967 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3968 for range in linked_ranges.iter() {
3969 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3970 let end_offset = start_offset + end_difference;
3971 let start_offset = start_offset + start_difference;
3972 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3973 continue;
3974 }
3975 if self.selections.disjoint_anchor_ranges().any(|s| {
3976 if s.start.buffer_id != selection.start.buffer_id
3977 || s.end.buffer_id != selection.end.buffer_id
3978 {
3979 return false;
3980 }
3981 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3982 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3983 }) {
3984 continue;
3985 }
3986 let start = buffer_snapshot.anchor_after(start_offset);
3987 let end = buffer_snapshot.anchor_after(end_offset);
3988 linked_edits
3989 .entry(buffer.clone())
3990 .or_default()
3991 .push(start..end);
3992 }
3993 Some(linked_edits)
3994 }
3995
3996 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3997 let text: Arc<str> = text.into();
3998
3999 if self.read_only(cx) {
4000 return;
4001 }
4002
4003 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4004
4005 let selections = self.selections.all_adjusted(cx);
4006 let mut bracket_inserted = false;
4007 let mut edits = Vec::new();
4008 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4009 let mut new_selections = Vec::with_capacity(selections.len());
4010 let mut new_autoclose_regions = Vec::new();
4011 let snapshot = self.buffer.read(cx).read(cx);
4012 let mut clear_linked_edit_ranges = false;
4013
4014 for (selection, autoclose_region) in
4015 self.selections_with_autoclose_regions(selections, &snapshot)
4016 {
4017 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4018 // Determine if the inserted text matches the opening or closing
4019 // bracket of any of this language's bracket pairs.
4020 let mut bracket_pair = None;
4021 let mut is_bracket_pair_start = false;
4022 let mut is_bracket_pair_end = false;
4023 if !text.is_empty() {
4024 let mut bracket_pair_matching_end = None;
4025 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4026 // and they are removing the character that triggered IME popup.
4027 for (pair, enabled) in scope.brackets() {
4028 if !pair.close && !pair.surround {
4029 continue;
4030 }
4031
4032 if enabled && pair.start.ends_with(text.as_ref()) {
4033 let prefix_len = pair.start.len() - text.len();
4034 let preceding_text_matches_prefix = prefix_len == 0
4035 || (selection.start.column >= (prefix_len as u32)
4036 && snapshot.contains_str_at(
4037 Point::new(
4038 selection.start.row,
4039 selection.start.column - (prefix_len as u32),
4040 ),
4041 &pair.start[..prefix_len],
4042 ));
4043 if preceding_text_matches_prefix {
4044 bracket_pair = Some(pair.clone());
4045 is_bracket_pair_start = true;
4046 break;
4047 }
4048 }
4049 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4050 {
4051 // take first bracket pair matching end, but don't break in case a later bracket
4052 // pair matches start
4053 bracket_pair_matching_end = Some(pair.clone());
4054 }
4055 }
4056 if let Some(end) = bracket_pair_matching_end
4057 && bracket_pair.is_none()
4058 {
4059 bracket_pair = Some(end);
4060 is_bracket_pair_end = true;
4061 }
4062 }
4063
4064 if let Some(bracket_pair) = bracket_pair {
4065 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4066 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4067 let auto_surround =
4068 self.use_auto_surround && snapshot_settings.use_auto_surround;
4069 if selection.is_empty() {
4070 if is_bracket_pair_start {
4071 // If the inserted text is a suffix of an opening bracket and the
4072 // selection is preceded by the rest of the opening bracket, then
4073 // insert the closing bracket.
4074 let following_text_allows_autoclose = snapshot
4075 .chars_at(selection.start)
4076 .next()
4077 .is_none_or(|c| scope.should_autoclose_before(c));
4078
4079 let preceding_text_allows_autoclose = selection.start.column == 0
4080 || snapshot
4081 .reversed_chars_at(selection.start)
4082 .next()
4083 .is_none_or(|c| {
4084 bracket_pair.start != bracket_pair.end
4085 || !snapshot
4086 .char_classifier_at(selection.start)
4087 .is_word(c)
4088 });
4089
4090 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4091 && bracket_pair.start.len() == 1
4092 {
4093 let target = bracket_pair.start.chars().next().unwrap();
4094 let current_line_count = snapshot
4095 .reversed_chars_at(selection.start)
4096 .take_while(|&c| c != '\n')
4097 .filter(|&c| c == target)
4098 .count();
4099 current_line_count % 2 == 1
4100 } else {
4101 false
4102 };
4103
4104 if autoclose
4105 && bracket_pair.close
4106 && following_text_allows_autoclose
4107 && preceding_text_allows_autoclose
4108 && !is_closing_quote
4109 {
4110 let anchor = snapshot.anchor_before(selection.end);
4111 new_selections.push((selection.map(|_| anchor), text.len()));
4112 new_autoclose_regions.push((
4113 anchor,
4114 text.len(),
4115 selection.id,
4116 bracket_pair.clone(),
4117 ));
4118 edits.push((
4119 selection.range(),
4120 format!("{}{}", text, bracket_pair.end).into(),
4121 ));
4122 bracket_inserted = true;
4123 continue;
4124 }
4125 }
4126
4127 if let Some(region) = autoclose_region {
4128 // If the selection is followed by an auto-inserted closing bracket,
4129 // then don't insert that closing bracket again; just move the selection
4130 // past the closing bracket.
4131 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4132 && text.as_ref() == region.pair.end.as_str()
4133 && snapshot.contains_str_at(region.range.end, text.as_ref());
4134 if should_skip {
4135 let anchor = snapshot.anchor_after(selection.end);
4136 new_selections
4137 .push((selection.map(|_| anchor), region.pair.end.len()));
4138 continue;
4139 }
4140 }
4141
4142 let always_treat_brackets_as_autoclosed = snapshot
4143 .language_settings_at(selection.start, cx)
4144 .always_treat_brackets_as_autoclosed;
4145 if always_treat_brackets_as_autoclosed
4146 && is_bracket_pair_end
4147 && snapshot.contains_str_at(selection.end, text.as_ref())
4148 {
4149 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4150 // and the inserted text is a closing bracket and the selection is followed
4151 // by the closing bracket then move the selection past the closing bracket.
4152 let anchor = snapshot.anchor_after(selection.end);
4153 new_selections.push((selection.map(|_| anchor), text.len()));
4154 continue;
4155 }
4156 }
4157 // If an opening bracket is 1 character long and is typed while
4158 // text is selected, then surround that text with the bracket pair.
4159 else if auto_surround
4160 && bracket_pair.surround
4161 && is_bracket_pair_start
4162 && bracket_pair.start.chars().count() == 1
4163 {
4164 edits.push((selection.start..selection.start, text.clone()));
4165 edits.push((
4166 selection.end..selection.end,
4167 bracket_pair.end.as_str().into(),
4168 ));
4169 bracket_inserted = true;
4170 new_selections.push((
4171 Selection {
4172 id: selection.id,
4173 start: snapshot.anchor_after(selection.start),
4174 end: snapshot.anchor_before(selection.end),
4175 reversed: selection.reversed,
4176 goal: selection.goal,
4177 },
4178 0,
4179 ));
4180 continue;
4181 }
4182 }
4183 }
4184
4185 if self.auto_replace_emoji_shortcode
4186 && selection.is_empty()
4187 && text.as_ref().ends_with(':')
4188 && let Some(possible_emoji_short_code) =
4189 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4190 && !possible_emoji_short_code.is_empty()
4191 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4192 {
4193 let emoji_shortcode_start = Point::new(
4194 selection.start.row,
4195 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4196 );
4197
4198 // Remove shortcode from buffer
4199 edits.push((
4200 emoji_shortcode_start..selection.start,
4201 "".to_string().into(),
4202 ));
4203 new_selections.push((
4204 Selection {
4205 id: selection.id,
4206 start: snapshot.anchor_after(emoji_shortcode_start),
4207 end: snapshot.anchor_before(selection.start),
4208 reversed: selection.reversed,
4209 goal: selection.goal,
4210 },
4211 0,
4212 ));
4213
4214 // Insert emoji
4215 let selection_start_anchor = snapshot.anchor_after(selection.start);
4216 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4217 edits.push((selection.start..selection.end, emoji.to_string().into()));
4218
4219 continue;
4220 }
4221
4222 // If not handling any auto-close operation, then just replace the selected
4223 // text with the given input and move the selection to the end of the
4224 // newly inserted text.
4225 let anchor = snapshot.anchor_after(selection.end);
4226 if !self.linked_edit_ranges.is_empty() {
4227 let start_anchor = snapshot.anchor_before(selection.start);
4228
4229 let is_word_char = text.chars().next().is_none_or(|char| {
4230 let classifier = snapshot
4231 .char_classifier_at(start_anchor.to_offset(&snapshot))
4232 .ignore_punctuation(true);
4233 classifier.is_word(char)
4234 });
4235
4236 if is_word_char {
4237 if let Some(ranges) = self
4238 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4239 {
4240 for (buffer, edits) in ranges {
4241 linked_edits
4242 .entry(buffer.clone())
4243 .or_default()
4244 .extend(edits.into_iter().map(|range| (range, text.clone())));
4245 }
4246 }
4247 } else {
4248 clear_linked_edit_ranges = true;
4249 }
4250 }
4251
4252 new_selections.push((selection.map(|_| anchor), 0));
4253 edits.push((selection.start..selection.end, text.clone()));
4254 }
4255
4256 drop(snapshot);
4257
4258 self.transact(window, cx, |this, window, cx| {
4259 if clear_linked_edit_ranges {
4260 this.linked_edit_ranges.clear();
4261 }
4262 let initial_buffer_versions =
4263 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4264
4265 this.buffer.update(cx, |buffer, cx| {
4266 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4267 });
4268 for (buffer, edits) in linked_edits {
4269 buffer.update(cx, |buffer, cx| {
4270 let snapshot = buffer.snapshot();
4271 let edits = edits
4272 .into_iter()
4273 .map(|(range, text)| {
4274 use text::ToPoint as TP;
4275 let end_point = TP::to_point(&range.end, &snapshot);
4276 let start_point = TP::to_point(&range.start, &snapshot);
4277 (start_point..end_point, text)
4278 })
4279 .sorted_by_key(|(range, _)| range.start);
4280 buffer.edit(edits, None, cx);
4281 })
4282 }
4283 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4284 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4285 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4286 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4287 .zip(new_selection_deltas)
4288 .map(|(selection, delta)| Selection {
4289 id: selection.id,
4290 start: selection.start + delta,
4291 end: selection.end + delta,
4292 reversed: selection.reversed,
4293 goal: SelectionGoal::None,
4294 })
4295 .collect::<Vec<_>>();
4296
4297 let mut i = 0;
4298 for (position, delta, selection_id, pair) in new_autoclose_regions {
4299 let position = position.to_offset(&map.buffer_snapshot) + delta;
4300 let start = map.buffer_snapshot.anchor_before(position);
4301 let end = map.buffer_snapshot.anchor_after(position);
4302 while let Some(existing_state) = this.autoclose_regions.get(i) {
4303 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4304 Ordering::Less => i += 1,
4305 Ordering::Greater => break,
4306 Ordering::Equal => {
4307 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4308 Ordering::Less => i += 1,
4309 Ordering::Equal => break,
4310 Ordering::Greater => break,
4311 }
4312 }
4313 }
4314 }
4315 this.autoclose_regions.insert(
4316 i,
4317 AutocloseRegion {
4318 selection_id,
4319 range: start..end,
4320 pair,
4321 },
4322 );
4323 }
4324
4325 let had_active_edit_prediction = this.has_active_edit_prediction();
4326 this.change_selections(
4327 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4328 window,
4329 cx,
4330 |s| s.select(new_selections),
4331 );
4332
4333 if !bracket_inserted
4334 && let Some(on_type_format_task) =
4335 this.trigger_on_type_formatting(text.to_string(), window, cx)
4336 {
4337 on_type_format_task.detach_and_log_err(cx);
4338 }
4339
4340 let editor_settings = EditorSettings::get_global(cx);
4341 if bracket_inserted
4342 && (editor_settings.auto_signature_help
4343 || editor_settings.show_signature_help_after_edits)
4344 {
4345 this.show_signature_help(&ShowSignatureHelp, window, cx);
4346 }
4347
4348 let trigger_in_words =
4349 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4350 if this.hard_wrap.is_some() {
4351 let latest: Range<Point> = this.selections.newest(cx).range();
4352 if latest.is_empty()
4353 && this
4354 .buffer()
4355 .read(cx)
4356 .snapshot(cx)
4357 .line_len(MultiBufferRow(latest.start.row))
4358 == latest.start.column
4359 {
4360 this.rewrap_impl(
4361 RewrapOptions {
4362 override_language_settings: true,
4363 preserve_existing_whitespace: true,
4364 },
4365 cx,
4366 )
4367 }
4368 }
4369 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4370 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4371 this.refresh_edit_prediction(true, false, window, cx);
4372 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4373 });
4374 }
4375
4376 fn find_possible_emoji_shortcode_at_position(
4377 snapshot: &MultiBufferSnapshot,
4378 position: Point,
4379 ) -> Option<String> {
4380 let mut chars = Vec::new();
4381 let mut found_colon = false;
4382 for char in snapshot.reversed_chars_at(position).take(100) {
4383 // Found a possible emoji shortcode in the middle of the buffer
4384 if found_colon {
4385 if char.is_whitespace() {
4386 chars.reverse();
4387 return Some(chars.iter().collect());
4388 }
4389 // If the previous character is not a whitespace, we are in the middle of a word
4390 // and we only want to complete the shortcode if the word is made up of other emojis
4391 let mut containing_word = String::new();
4392 for ch in snapshot
4393 .reversed_chars_at(position)
4394 .skip(chars.len() + 1)
4395 .take(100)
4396 {
4397 if ch.is_whitespace() {
4398 break;
4399 }
4400 containing_word.push(ch);
4401 }
4402 let containing_word = containing_word.chars().rev().collect::<String>();
4403 if util::word_consists_of_emojis(containing_word.as_str()) {
4404 chars.reverse();
4405 return Some(chars.iter().collect());
4406 }
4407 }
4408
4409 if char.is_whitespace() || !char.is_ascii() {
4410 return None;
4411 }
4412 if char == ':' {
4413 found_colon = true;
4414 } else {
4415 chars.push(char);
4416 }
4417 }
4418 // Found a possible emoji shortcode at the beginning of the buffer
4419 chars.reverse();
4420 Some(chars.iter().collect())
4421 }
4422
4423 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4424 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4425 self.transact(window, cx, |this, window, cx| {
4426 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4427 let selections = this.selections.all::<usize>(cx);
4428 let multi_buffer = this.buffer.read(cx);
4429 let buffer = multi_buffer.snapshot(cx);
4430 selections
4431 .iter()
4432 .map(|selection| {
4433 let start_point = selection.start.to_point(&buffer);
4434 let mut existing_indent =
4435 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4436 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4437 let start = selection.start;
4438 let end = selection.end;
4439 let selection_is_empty = start == end;
4440 let language_scope = buffer.language_scope_at(start);
4441 let (
4442 comment_delimiter,
4443 doc_delimiter,
4444 insert_extra_newline,
4445 indent_on_newline,
4446 indent_on_extra_newline,
4447 ) = if let Some(language) = &language_scope {
4448 let mut insert_extra_newline =
4449 insert_extra_newline_brackets(&buffer, start..end, language)
4450 || insert_extra_newline_tree_sitter(&buffer, start..end);
4451
4452 // Comment extension on newline is allowed only for cursor selections
4453 let comment_delimiter = maybe!({
4454 if !selection_is_empty {
4455 return None;
4456 }
4457
4458 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4459 return None;
4460 }
4461
4462 let delimiters = language.line_comment_prefixes();
4463 let max_len_of_delimiter =
4464 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4465 let (snapshot, range) =
4466 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4467
4468 let num_of_whitespaces = snapshot
4469 .chars_for_range(range.clone())
4470 .take_while(|c| c.is_whitespace())
4471 .count();
4472 let comment_candidate = snapshot
4473 .chars_for_range(range.clone())
4474 .skip(num_of_whitespaces)
4475 .take(max_len_of_delimiter)
4476 .collect::<String>();
4477 let (delimiter, trimmed_len) = delimiters
4478 .iter()
4479 .filter_map(|delimiter| {
4480 let prefix = delimiter.trim_end();
4481 if comment_candidate.starts_with(prefix) {
4482 Some((delimiter, prefix.len()))
4483 } else {
4484 None
4485 }
4486 })
4487 .max_by_key(|(_, len)| *len)?;
4488
4489 if let Some(BlockCommentConfig {
4490 start: block_start, ..
4491 }) = language.block_comment()
4492 {
4493 let block_start_trimmed = block_start.trim_end();
4494 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4495 let line_content = snapshot
4496 .chars_for_range(range)
4497 .skip(num_of_whitespaces)
4498 .take(block_start_trimmed.len())
4499 .collect::<String>();
4500
4501 if line_content.starts_with(block_start_trimmed) {
4502 return None;
4503 }
4504 }
4505 }
4506
4507 let cursor_is_placed_after_comment_marker =
4508 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4509 if cursor_is_placed_after_comment_marker {
4510 Some(delimiter.clone())
4511 } else {
4512 None
4513 }
4514 });
4515
4516 let mut indent_on_newline = IndentSize::spaces(0);
4517 let mut indent_on_extra_newline = IndentSize::spaces(0);
4518
4519 let doc_delimiter = maybe!({
4520 if !selection_is_empty {
4521 return None;
4522 }
4523
4524 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4525 return None;
4526 }
4527
4528 let BlockCommentConfig {
4529 start: start_tag,
4530 end: end_tag,
4531 prefix: delimiter,
4532 tab_size: len,
4533 } = language.documentation_comment()?;
4534 let is_within_block_comment = buffer
4535 .language_scope_at(start_point)
4536 .is_some_and(|scope| scope.override_name() == Some("comment"));
4537 if !is_within_block_comment {
4538 return None;
4539 }
4540
4541 let (snapshot, range) =
4542 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4543
4544 let num_of_whitespaces = snapshot
4545 .chars_for_range(range.clone())
4546 .take_while(|c| c.is_whitespace())
4547 .count();
4548
4549 // 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.
4550 let column = start_point.column;
4551 let cursor_is_after_start_tag = {
4552 let start_tag_len = start_tag.len();
4553 let start_tag_line = snapshot
4554 .chars_for_range(range.clone())
4555 .skip(num_of_whitespaces)
4556 .take(start_tag_len)
4557 .collect::<String>();
4558 if start_tag_line.starts_with(start_tag.as_ref()) {
4559 num_of_whitespaces + start_tag_len <= column as usize
4560 } else {
4561 false
4562 }
4563 };
4564
4565 let cursor_is_after_delimiter = {
4566 let delimiter_trim = delimiter.trim_end();
4567 let delimiter_line = snapshot
4568 .chars_for_range(range.clone())
4569 .skip(num_of_whitespaces)
4570 .take(delimiter_trim.len())
4571 .collect::<String>();
4572 if delimiter_line.starts_with(delimiter_trim) {
4573 num_of_whitespaces + delimiter_trim.len() <= column as usize
4574 } else {
4575 false
4576 }
4577 };
4578
4579 let cursor_is_before_end_tag_if_exists = {
4580 let mut char_position = 0u32;
4581 let mut end_tag_offset = None;
4582
4583 'outer: for chunk in snapshot.text_for_range(range) {
4584 if let Some(byte_pos) = chunk.find(&**end_tag) {
4585 let chars_before_match =
4586 chunk[..byte_pos].chars().count() as u32;
4587 end_tag_offset =
4588 Some(char_position + chars_before_match);
4589 break 'outer;
4590 }
4591 char_position += chunk.chars().count() as u32;
4592 }
4593
4594 if let Some(end_tag_offset) = end_tag_offset {
4595 let cursor_is_before_end_tag = column <= end_tag_offset;
4596 if cursor_is_after_start_tag {
4597 if cursor_is_before_end_tag {
4598 insert_extra_newline = true;
4599 }
4600 let cursor_is_at_start_of_end_tag =
4601 column == end_tag_offset;
4602 if cursor_is_at_start_of_end_tag {
4603 indent_on_extra_newline.len = *len;
4604 }
4605 }
4606 cursor_is_before_end_tag
4607 } else {
4608 true
4609 }
4610 };
4611
4612 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4613 && cursor_is_before_end_tag_if_exists
4614 {
4615 if cursor_is_after_start_tag {
4616 indent_on_newline.len = *len;
4617 }
4618 Some(delimiter.clone())
4619 } else {
4620 None
4621 }
4622 });
4623
4624 (
4625 comment_delimiter,
4626 doc_delimiter,
4627 insert_extra_newline,
4628 indent_on_newline,
4629 indent_on_extra_newline,
4630 )
4631 } else {
4632 (
4633 None,
4634 None,
4635 false,
4636 IndentSize::default(),
4637 IndentSize::default(),
4638 )
4639 };
4640
4641 let prevent_auto_indent = doc_delimiter.is_some();
4642 let delimiter = comment_delimiter.or(doc_delimiter);
4643
4644 let capacity_for_delimiter =
4645 delimiter.as_deref().map(str::len).unwrap_or_default();
4646 let mut new_text = String::with_capacity(
4647 1 + capacity_for_delimiter
4648 + existing_indent.len as usize
4649 + indent_on_newline.len as usize
4650 + indent_on_extra_newline.len as usize,
4651 );
4652 new_text.push('\n');
4653 new_text.extend(existing_indent.chars());
4654 new_text.extend(indent_on_newline.chars());
4655
4656 if let Some(delimiter) = &delimiter {
4657 new_text.push_str(delimiter);
4658 }
4659
4660 if insert_extra_newline {
4661 new_text.push('\n');
4662 new_text.extend(existing_indent.chars());
4663 new_text.extend(indent_on_extra_newline.chars());
4664 }
4665
4666 let anchor = buffer.anchor_after(end);
4667 let new_selection = selection.map(|_| anchor);
4668 (
4669 ((start..end, new_text), prevent_auto_indent),
4670 (insert_extra_newline, new_selection),
4671 )
4672 })
4673 .unzip()
4674 };
4675
4676 let mut auto_indent_edits = Vec::new();
4677 let mut edits = Vec::new();
4678 for (edit, prevent_auto_indent) in edits_with_flags {
4679 if prevent_auto_indent {
4680 edits.push(edit);
4681 } else {
4682 auto_indent_edits.push(edit);
4683 }
4684 }
4685 if !edits.is_empty() {
4686 this.edit(edits, cx);
4687 }
4688 if !auto_indent_edits.is_empty() {
4689 this.edit_with_autoindent(auto_indent_edits, cx);
4690 }
4691
4692 let buffer = this.buffer.read(cx).snapshot(cx);
4693 let new_selections = selection_info
4694 .into_iter()
4695 .map(|(extra_newline_inserted, new_selection)| {
4696 let mut cursor = new_selection.end.to_point(&buffer);
4697 if extra_newline_inserted {
4698 cursor.row -= 1;
4699 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4700 }
4701 new_selection.map(|_| cursor)
4702 })
4703 .collect();
4704
4705 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4706 this.refresh_edit_prediction(true, false, window, cx);
4707 });
4708 }
4709
4710 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4712
4713 let buffer = self.buffer.read(cx);
4714 let snapshot = buffer.snapshot(cx);
4715
4716 let mut edits = Vec::new();
4717 let mut rows = Vec::new();
4718
4719 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4720 let cursor = selection.head();
4721 let row = cursor.row;
4722
4723 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4724
4725 let newline = "\n".to_string();
4726 edits.push((start_of_line..start_of_line, newline));
4727
4728 rows.push(row + rows_inserted as u32);
4729 }
4730
4731 self.transact(window, cx, |editor, window, cx| {
4732 editor.edit(edits, cx);
4733
4734 editor.change_selections(Default::default(), window, cx, |s| {
4735 let mut index = 0;
4736 s.move_cursors_with(|map, _, _| {
4737 let row = rows[index];
4738 index += 1;
4739
4740 let point = Point::new(row, 0);
4741 let boundary = map.next_line_boundary(point).1;
4742 let clipped = map.clip_point(boundary, Bias::Left);
4743
4744 (clipped, SelectionGoal::None)
4745 });
4746 });
4747
4748 let mut indent_edits = Vec::new();
4749 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4750 for row in rows {
4751 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4752 for (row, indent) in indents {
4753 if indent.len == 0 {
4754 continue;
4755 }
4756
4757 let text = match indent.kind {
4758 IndentKind::Space => " ".repeat(indent.len as usize),
4759 IndentKind::Tab => "\t".repeat(indent.len as usize),
4760 };
4761 let point = Point::new(row.0, 0);
4762 indent_edits.push((point..point, text));
4763 }
4764 }
4765 editor.edit(indent_edits, cx);
4766 });
4767 }
4768
4769 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4770 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4771
4772 let buffer = self.buffer.read(cx);
4773 let snapshot = buffer.snapshot(cx);
4774
4775 let mut edits = Vec::new();
4776 let mut rows = Vec::new();
4777 let mut rows_inserted = 0;
4778
4779 for selection in self.selections.all_adjusted(cx) {
4780 let cursor = selection.head();
4781 let row = cursor.row;
4782
4783 let point = Point::new(row + 1, 0);
4784 let start_of_line = snapshot.clip_point(point, Bias::Left);
4785
4786 let newline = "\n".to_string();
4787 edits.push((start_of_line..start_of_line, newline));
4788
4789 rows_inserted += 1;
4790 rows.push(row + rows_inserted);
4791 }
4792
4793 self.transact(window, cx, |editor, window, cx| {
4794 editor.edit(edits, cx);
4795
4796 editor.change_selections(Default::default(), window, cx, |s| {
4797 let mut index = 0;
4798 s.move_cursors_with(|map, _, _| {
4799 let row = rows[index];
4800 index += 1;
4801
4802 let point = Point::new(row, 0);
4803 let boundary = map.next_line_boundary(point).1;
4804 let clipped = map.clip_point(boundary, Bias::Left);
4805
4806 (clipped, SelectionGoal::None)
4807 });
4808 });
4809
4810 let mut indent_edits = Vec::new();
4811 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4812 for row in rows {
4813 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4814 for (row, indent) in indents {
4815 if indent.len == 0 {
4816 continue;
4817 }
4818
4819 let text = match indent.kind {
4820 IndentKind::Space => " ".repeat(indent.len as usize),
4821 IndentKind::Tab => "\t".repeat(indent.len as usize),
4822 };
4823 let point = Point::new(row.0, 0);
4824 indent_edits.push((point..point, text));
4825 }
4826 }
4827 editor.edit(indent_edits, cx);
4828 });
4829 }
4830
4831 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4832 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4833 original_indent_columns: Vec::new(),
4834 });
4835 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4836 }
4837
4838 fn insert_with_autoindent_mode(
4839 &mut self,
4840 text: &str,
4841 autoindent_mode: Option<AutoindentMode>,
4842 window: &mut Window,
4843 cx: &mut Context<Self>,
4844 ) {
4845 if self.read_only(cx) {
4846 return;
4847 }
4848
4849 let text: Arc<str> = text.into();
4850 self.transact(window, cx, |this, window, cx| {
4851 let old_selections = this.selections.all_adjusted(cx);
4852 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4853 let anchors = {
4854 let snapshot = buffer.read(cx);
4855 old_selections
4856 .iter()
4857 .map(|s| {
4858 let anchor = snapshot.anchor_after(s.head());
4859 s.map(|_| anchor)
4860 })
4861 .collect::<Vec<_>>()
4862 };
4863 buffer.edit(
4864 old_selections
4865 .iter()
4866 .map(|s| (s.start..s.end, text.clone())),
4867 autoindent_mode,
4868 cx,
4869 );
4870 anchors
4871 });
4872
4873 this.change_selections(Default::default(), window, cx, |s| {
4874 s.select_anchors(selection_anchors);
4875 });
4876
4877 cx.notify();
4878 });
4879 }
4880
4881 fn trigger_completion_on_input(
4882 &mut self,
4883 text: &str,
4884 trigger_in_words: bool,
4885 window: &mut Window,
4886 cx: &mut Context<Self>,
4887 ) {
4888 let completions_source = self
4889 .context_menu
4890 .borrow()
4891 .as_ref()
4892 .and_then(|menu| match menu {
4893 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4894 CodeContextMenu::CodeActions(_) => None,
4895 });
4896
4897 match completions_source {
4898 Some(CompletionsMenuSource::Words) => {
4899 self.show_word_completions(&ShowWordCompletions, window, cx)
4900 }
4901 Some(CompletionsMenuSource::Normal)
4902 | Some(CompletionsMenuSource::SnippetChoices)
4903 | None
4904 if self.is_completion_trigger(
4905 text,
4906 trigger_in_words,
4907 completions_source.is_some(),
4908 cx,
4909 ) =>
4910 {
4911 self.show_completions(
4912 &ShowCompletions {
4913 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4914 },
4915 window,
4916 cx,
4917 )
4918 }
4919 _ => {
4920 self.hide_context_menu(window, cx);
4921 }
4922 }
4923 }
4924
4925 fn is_completion_trigger(
4926 &self,
4927 text: &str,
4928 trigger_in_words: bool,
4929 menu_is_open: bool,
4930 cx: &mut Context<Self>,
4931 ) -> bool {
4932 let position = self.selections.newest_anchor().head();
4933 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4934 return false;
4935 };
4936
4937 if let Some(completion_provider) = &self.completion_provider {
4938 completion_provider.is_completion_trigger(
4939 &buffer,
4940 position.text_anchor,
4941 text,
4942 trigger_in_words,
4943 menu_is_open,
4944 cx,
4945 )
4946 } else {
4947 false
4948 }
4949 }
4950
4951 /// If any empty selections is touching the start of its innermost containing autoclose
4952 /// region, expand it to select the brackets.
4953 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4954 let selections = self.selections.all::<usize>(cx);
4955 let buffer = self.buffer.read(cx).read(cx);
4956 let new_selections = self
4957 .selections_with_autoclose_regions(selections, &buffer)
4958 .map(|(mut selection, region)| {
4959 if !selection.is_empty() {
4960 return selection;
4961 }
4962
4963 if let Some(region) = region {
4964 let mut range = region.range.to_offset(&buffer);
4965 if selection.start == range.start && range.start >= region.pair.start.len() {
4966 range.start -= region.pair.start.len();
4967 if buffer.contains_str_at(range.start, ®ion.pair.start)
4968 && buffer.contains_str_at(range.end, ®ion.pair.end)
4969 {
4970 range.end += region.pair.end.len();
4971 selection.start = range.start;
4972 selection.end = range.end;
4973
4974 return selection;
4975 }
4976 }
4977 }
4978
4979 let always_treat_brackets_as_autoclosed = buffer
4980 .language_settings_at(selection.start, cx)
4981 .always_treat_brackets_as_autoclosed;
4982
4983 if !always_treat_brackets_as_autoclosed {
4984 return selection;
4985 }
4986
4987 if let Some(scope) = buffer.language_scope_at(selection.start) {
4988 for (pair, enabled) in scope.brackets() {
4989 if !enabled || !pair.close {
4990 continue;
4991 }
4992
4993 if buffer.contains_str_at(selection.start, &pair.end) {
4994 let pair_start_len = pair.start.len();
4995 if buffer.contains_str_at(
4996 selection.start.saturating_sub(pair_start_len),
4997 &pair.start,
4998 ) {
4999 selection.start -= pair_start_len;
5000 selection.end += pair.end.len();
5001
5002 return selection;
5003 }
5004 }
5005 }
5006 }
5007
5008 selection
5009 })
5010 .collect();
5011
5012 drop(buffer);
5013 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5014 selections.select(new_selections)
5015 });
5016 }
5017
5018 /// Iterate the given selections, and for each one, find the smallest surrounding
5019 /// autoclose region. This uses the ordering of the selections and the autoclose
5020 /// regions to avoid repeated comparisons.
5021 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5022 &'a self,
5023 selections: impl IntoIterator<Item = Selection<D>>,
5024 buffer: &'a MultiBufferSnapshot,
5025 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5026 let mut i = 0;
5027 let mut regions = self.autoclose_regions.as_slice();
5028 selections.into_iter().map(move |selection| {
5029 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5030
5031 let mut enclosing = None;
5032 while let Some(pair_state) = regions.get(i) {
5033 if pair_state.range.end.to_offset(buffer) < range.start {
5034 regions = ®ions[i + 1..];
5035 i = 0;
5036 } else if pair_state.range.start.to_offset(buffer) > range.end {
5037 break;
5038 } else {
5039 if pair_state.selection_id == selection.id {
5040 enclosing = Some(pair_state);
5041 }
5042 i += 1;
5043 }
5044 }
5045
5046 (selection, enclosing)
5047 })
5048 }
5049
5050 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5051 fn invalidate_autoclose_regions(
5052 &mut self,
5053 mut selections: &[Selection<Anchor>],
5054 buffer: &MultiBufferSnapshot,
5055 ) {
5056 self.autoclose_regions.retain(|state| {
5057 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5058 return false;
5059 }
5060
5061 let mut i = 0;
5062 while let Some(selection) = selections.get(i) {
5063 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5064 selections = &selections[1..];
5065 continue;
5066 }
5067 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5068 break;
5069 }
5070 if selection.id == state.selection_id {
5071 return true;
5072 } else {
5073 i += 1;
5074 }
5075 }
5076 false
5077 });
5078 }
5079
5080 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5081 let offset = position.to_offset(buffer);
5082 let (word_range, kind) = buffer.surrounding_word(offset, true);
5083 if offset > word_range.start && kind == Some(CharKind::Word) {
5084 Some(
5085 buffer
5086 .text_for_range(word_range.start..offset)
5087 .collect::<String>(),
5088 )
5089 } else {
5090 None
5091 }
5092 }
5093
5094 pub fn toggle_inline_values(
5095 &mut self,
5096 _: &ToggleInlineValues,
5097 _: &mut Window,
5098 cx: &mut Context<Self>,
5099 ) {
5100 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5101
5102 self.refresh_inline_values(cx);
5103 }
5104
5105 pub fn toggle_inlay_hints(
5106 &mut self,
5107 _: &ToggleInlayHints,
5108 _: &mut Window,
5109 cx: &mut Context<Self>,
5110 ) {
5111 self.refresh_inlay_hints(
5112 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5113 cx,
5114 );
5115 }
5116
5117 pub fn inlay_hints_enabled(&self) -> bool {
5118 self.inlay_hint_cache.enabled
5119 }
5120
5121 pub fn inline_values_enabled(&self) -> bool {
5122 self.inline_value_cache.enabled
5123 }
5124
5125 #[cfg(any(test, feature = "test-support"))]
5126 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5127 self.display_map
5128 .read(cx)
5129 .current_inlays()
5130 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5131 .cloned()
5132 .collect()
5133 }
5134
5135 #[cfg(any(test, feature = "test-support"))]
5136 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5137 self.display_map
5138 .read(cx)
5139 .current_inlays()
5140 .cloned()
5141 .collect()
5142 }
5143
5144 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5145 if self.semantics_provider.is_none() || !self.mode.is_full() {
5146 return;
5147 }
5148
5149 let reason_description = reason.description();
5150 let ignore_debounce = matches!(
5151 reason,
5152 InlayHintRefreshReason::SettingsChange(_)
5153 | InlayHintRefreshReason::Toggle(_)
5154 | InlayHintRefreshReason::ExcerptsRemoved(_)
5155 | InlayHintRefreshReason::ModifiersChanged(_)
5156 );
5157 let (invalidate_cache, required_languages) = match reason {
5158 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5159 match self.inlay_hint_cache.modifiers_override(enabled) {
5160 Some(enabled) => {
5161 if enabled {
5162 (InvalidationStrategy::RefreshRequested, None)
5163 } else {
5164 self.splice_inlays(
5165 &self
5166 .visible_inlay_hints(cx)
5167 .iter()
5168 .map(|inlay| inlay.id)
5169 .collect::<Vec<InlayId>>(),
5170 Vec::new(),
5171 cx,
5172 );
5173 return;
5174 }
5175 }
5176 None => return,
5177 }
5178 }
5179 InlayHintRefreshReason::Toggle(enabled) => {
5180 if self.inlay_hint_cache.toggle(enabled) {
5181 if enabled {
5182 (InvalidationStrategy::RefreshRequested, None)
5183 } else {
5184 self.splice_inlays(
5185 &self
5186 .visible_inlay_hints(cx)
5187 .iter()
5188 .map(|inlay| inlay.id)
5189 .collect::<Vec<InlayId>>(),
5190 Vec::new(),
5191 cx,
5192 );
5193 return;
5194 }
5195 } else {
5196 return;
5197 }
5198 }
5199 InlayHintRefreshReason::SettingsChange(new_settings) => {
5200 match self.inlay_hint_cache.update_settings(
5201 &self.buffer,
5202 new_settings,
5203 self.visible_inlay_hints(cx),
5204 cx,
5205 ) {
5206 ControlFlow::Break(Some(InlaySplice {
5207 to_remove,
5208 to_insert,
5209 })) => {
5210 self.splice_inlays(&to_remove, to_insert, cx);
5211 return;
5212 }
5213 ControlFlow::Break(None) => return,
5214 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5215 }
5216 }
5217 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5218 if let Some(InlaySplice {
5219 to_remove,
5220 to_insert,
5221 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5222 {
5223 self.splice_inlays(&to_remove, to_insert, cx);
5224 }
5225 self.display_map.update(cx, |display_map, _| {
5226 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5227 });
5228 return;
5229 }
5230 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5231 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5232 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5233 }
5234 InlayHintRefreshReason::RefreshRequested => {
5235 (InvalidationStrategy::RefreshRequested, None)
5236 }
5237 };
5238
5239 if let Some(InlaySplice {
5240 to_remove,
5241 to_insert,
5242 }) = self.inlay_hint_cache.spawn_hint_refresh(
5243 reason_description,
5244 self.visible_excerpts(required_languages.as_ref(), cx),
5245 invalidate_cache,
5246 ignore_debounce,
5247 cx,
5248 ) {
5249 self.splice_inlays(&to_remove, to_insert, cx);
5250 }
5251 }
5252
5253 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5254 self.display_map
5255 .read(cx)
5256 .current_inlays()
5257 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5258 .cloned()
5259 .collect()
5260 }
5261
5262 pub fn visible_excerpts(
5263 &self,
5264 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5265 cx: &mut Context<Editor>,
5266 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5267 let Some(project) = self.project() else {
5268 return HashMap::default();
5269 };
5270 let project = project.read(cx);
5271 let multi_buffer = self.buffer().read(cx);
5272 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5273 let multi_buffer_visible_start = self
5274 .scroll_manager
5275 .anchor()
5276 .anchor
5277 .to_point(&multi_buffer_snapshot);
5278 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5279 multi_buffer_visible_start
5280 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5281 Bias::Left,
5282 );
5283 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5284 multi_buffer_snapshot
5285 .range_to_buffer_ranges(multi_buffer_visible_range)
5286 .into_iter()
5287 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5288 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5289 let buffer_file = project::File::from_dyn(buffer.file())?;
5290 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5291 let worktree_entry = buffer_worktree
5292 .read(cx)
5293 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5294 if worktree_entry.is_ignored {
5295 return None;
5296 }
5297
5298 let language = buffer.language()?;
5299 if let Some(restrict_to_languages) = restrict_to_languages
5300 && !restrict_to_languages.contains(language)
5301 {
5302 return None;
5303 }
5304 Some((
5305 excerpt_id,
5306 (
5307 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5308 buffer.version().clone(),
5309 excerpt_visible_range,
5310 ),
5311 ))
5312 })
5313 .collect()
5314 }
5315
5316 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5317 TextLayoutDetails {
5318 text_system: window.text_system().clone(),
5319 editor_style: self.style.clone().unwrap(),
5320 rem_size: window.rem_size(),
5321 scroll_anchor: self.scroll_manager.anchor(),
5322 visible_rows: self.visible_line_count(),
5323 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5324 }
5325 }
5326
5327 pub fn splice_inlays(
5328 &self,
5329 to_remove: &[InlayId],
5330 to_insert: Vec<Inlay>,
5331 cx: &mut Context<Self>,
5332 ) {
5333 self.display_map.update(cx, |display_map, cx| {
5334 display_map.splice_inlays(to_remove, to_insert, cx)
5335 });
5336 cx.notify();
5337 }
5338
5339 fn trigger_on_type_formatting(
5340 &self,
5341 input: String,
5342 window: &mut Window,
5343 cx: &mut Context<Self>,
5344 ) -> Option<Task<Result<()>>> {
5345 if input.len() != 1 {
5346 return None;
5347 }
5348
5349 let project = self.project()?;
5350 let position = self.selections.newest_anchor().head();
5351 let (buffer, buffer_position) = self
5352 .buffer
5353 .read(cx)
5354 .text_anchor_for_position(position, cx)?;
5355
5356 let settings = language_settings::language_settings(
5357 buffer
5358 .read(cx)
5359 .language_at(buffer_position)
5360 .map(|l| l.name()),
5361 buffer.read(cx).file(),
5362 cx,
5363 );
5364 if !settings.use_on_type_format {
5365 return None;
5366 }
5367
5368 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5369 // hence we do LSP request & edit on host side only — add formats to host's history.
5370 let push_to_lsp_host_history = true;
5371 // If this is not the host, append its history with new edits.
5372 let push_to_client_history = project.read(cx).is_via_collab();
5373
5374 let on_type_formatting = project.update(cx, |project, cx| {
5375 project.on_type_format(
5376 buffer.clone(),
5377 buffer_position,
5378 input,
5379 push_to_lsp_host_history,
5380 cx,
5381 )
5382 });
5383 Some(cx.spawn_in(window, async move |editor, cx| {
5384 if let Some(transaction) = on_type_formatting.await? {
5385 if push_to_client_history {
5386 buffer
5387 .update(cx, |buffer, _| {
5388 buffer.push_transaction(transaction, Instant::now());
5389 buffer.finalize_last_transaction();
5390 })
5391 .ok();
5392 }
5393 editor.update(cx, |editor, cx| {
5394 editor.refresh_document_highlights(cx);
5395 })?;
5396 }
5397 Ok(())
5398 }))
5399 }
5400
5401 pub fn show_word_completions(
5402 &mut self,
5403 _: &ShowWordCompletions,
5404 window: &mut Window,
5405 cx: &mut Context<Self>,
5406 ) {
5407 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5408 }
5409
5410 pub fn show_completions(
5411 &mut self,
5412 options: &ShowCompletions,
5413 window: &mut Window,
5414 cx: &mut Context<Self>,
5415 ) {
5416 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5417 }
5418
5419 fn open_or_update_completions_menu(
5420 &mut self,
5421 requested_source: Option<CompletionsMenuSource>,
5422 trigger: Option<&str>,
5423 window: &mut Window,
5424 cx: &mut Context<Self>,
5425 ) {
5426 if self.pending_rename.is_some() {
5427 return;
5428 }
5429
5430 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5431
5432 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5433 // inserted and selected. To handle that case, the start of the selection is used so that
5434 // the menu starts with all choices.
5435 let position = self
5436 .selections
5437 .newest_anchor()
5438 .start
5439 .bias_right(&multibuffer_snapshot);
5440 if position.diff_base_anchor.is_some() {
5441 return;
5442 }
5443 let (buffer, buffer_position) =
5444 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5445 output
5446 } else {
5447 return;
5448 };
5449 let buffer_snapshot = buffer.read(cx).snapshot();
5450
5451 let query: Option<Arc<String>> =
5452 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5453
5454 drop(multibuffer_snapshot);
5455
5456 let provider = match requested_source {
5457 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5458 Some(CompletionsMenuSource::Words) => None,
5459 Some(CompletionsMenuSource::SnippetChoices) => {
5460 log::error!("bug: SnippetChoices requested_source is not handled");
5461 None
5462 }
5463 };
5464
5465 let sort_completions = provider
5466 .as_ref()
5467 .is_some_and(|provider| provider.sort_completions());
5468
5469 let filter_completions = provider
5470 .as_ref()
5471 .is_none_or(|provider| provider.filter_completions());
5472
5473 let trigger_kind = match trigger {
5474 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5475 CompletionTriggerKind::TRIGGER_CHARACTER
5476 }
5477 _ => CompletionTriggerKind::INVOKED,
5478 };
5479 let completion_context = CompletionContext {
5480 trigger_character: trigger.and_then(|trigger| {
5481 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5482 Some(String::from(trigger))
5483 } else {
5484 None
5485 }
5486 }),
5487 trigger_kind,
5488 };
5489
5490 // Hide the current completions menu when a trigger char is typed. Without this, cached
5491 // completions from before the trigger char may be reused (#32774). Snippet choices could
5492 // involve trigger chars, so this is skipped in that case.
5493 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5494 {
5495 let menu_is_open = matches!(
5496 self.context_menu.borrow().as_ref(),
5497 Some(CodeContextMenu::Completions(_))
5498 );
5499 if menu_is_open {
5500 self.hide_context_menu(window, cx);
5501 }
5502 }
5503
5504 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5505 if filter_completions {
5506 menu.filter(query.clone(), provider.clone(), window, cx);
5507 }
5508 // When `is_incomplete` is false, no need to re-query completions when the current query
5509 // is a suffix of the initial query.
5510 if !menu.is_incomplete {
5511 // If the new query is a suffix of the old query (typing more characters) and
5512 // the previous result was complete, the existing completions can be filtered.
5513 //
5514 // Note that this is always true for snippet completions.
5515 let query_matches = match (&menu.initial_query, &query) {
5516 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5517 (None, _) => true,
5518 _ => false,
5519 };
5520 if query_matches {
5521 let position_matches = if menu.initial_position == position {
5522 true
5523 } else {
5524 let snapshot = self.buffer.read(cx).read(cx);
5525 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5526 };
5527 if position_matches {
5528 return;
5529 }
5530 }
5531 }
5532 };
5533
5534 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5535 buffer_snapshot.surrounding_word(buffer_position, false)
5536 {
5537 let word_to_exclude = buffer_snapshot
5538 .text_for_range(word_range.clone())
5539 .collect::<String>();
5540 (
5541 buffer_snapshot.anchor_before(word_range.start)
5542 ..buffer_snapshot.anchor_after(buffer_position),
5543 Some(word_to_exclude),
5544 )
5545 } else {
5546 (buffer_position..buffer_position, None)
5547 };
5548
5549 let language = buffer_snapshot
5550 .language_at(buffer_position)
5551 .map(|language| language.name());
5552
5553 let completion_settings =
5554 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5555
5556 let show_completion_documentation = buffer_snapshot
5557 .settings_at(buffer_position, cx)
5558 .show_completion_documentation;
5559
5560 // The document can be large, so stay in reasonable bounds when searching for words,
5561 // otherwise completion pop-up might be slow to appear.
5562 const WORD_LOOKUP_ROWS: u32 = 5_000;
5563 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5564 let min_word_search = buffer_snapshot.clip_point(
5565 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5566 Bias::Left,
5567 );
5568 let max_word_search = buffer_snapshot.clip_point(
5569 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5570 Bias::Right,
5571 );
5572 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5573 ..buffer_snapshot.point_to_offset(max_word_search);
5574
5575 let skip_digits = query
5576 .as_ref()
5577 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5578
5579 let (mut words, provider_responses) = match &provider {
5580 Some(provider) => {
5581 let provider_responses = provider.completions(
5582 position.excerpt_id,
5583 &buffer,
5584 buffer_position,
5585 completion_context,
5586 window,
5587 cx,
5588 );
5589
5590 let words = match completion_settings.words {
5591 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5592 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5593 .background_spawn(async move {
5594 buffer_snapshot.words_in_range(WordsQuery {
5595 fuzzy_contents: None,
5596 range: word_search_range,
5597 skip_digits,
5598 })
5599 }),
5600 };
5601
5602 (words, provider_responses)
5603 }
5604 None => (
5605 cx.background_spawn(async move {
5606 buffer_snapshot.words_in_range(WordsQuery {
5607 fuzzy_contents: None,
5608 range: word_search_range,
5609 skip_digits,
5610 })
5611 }),
5612 Task::ready(Ok(Vec::new())),
5613 ),
5614 };
5615
5616 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5617
5618 let id = post_inc(&mut self.next_completion_id);
5619 let task = cx.spawn_in(window, async move |editor, cx| {
5620 let Ok(()) = editor.update(cx, |this, _| {
5621 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5622 }) else {
5623 return;
5624 };
5625
5626 // TODO: Ideally completions from different sources would be selectively re-queried, so
5627 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5628 let mut completions = Vec::new();
5629 let mut is_incomplete = false;
5630 if let Some(provider_responses) = provider_responses.await.log_err()
5631 && !provider_responses.is_empty()
5632 {
5633 for response in provider_responses {
5634 completions.extend(response.completions);
5635 is_incomplete = is_incomplete || response.is_incomplete;
5636 }
5637 if completion_settings.words == WordsCompletionMode::Fallback {
5638 words = Task::ready(BTreeMap::default());
5639 }
5640 }
5641
5642 let mut words = words.await;
5643 if let Some(word_to_exclude) = &word_to_exclude {
5644 words.remove(word_to_exclude);
5645 }
5646 for lsp_completion in &completions {
5647 words.remove(&lsp_completion.new_text);
5648 }
5649 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5650 replace_range: word_replace_range.clone(),
5651 new_text: word.clone(),
5652 label: CodeLabel::plain(word, None),
5653 icon_path: None,
5654 documentation: None,
5655 source: CompletionSource::BufferWord {
5656 word_range,
5657 resolved: false,
5658 },
5659 insert_text_mode: Some(InsertTextMode::AS_IS),
5660 confirm: None,
5661 }));
5662
5663 let menu = if completions.is_empty() {
5664 None
5665 } else {
5666 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5667 let languages = editor
5668 .workspace
5669 .as_ref()
5670 .and_then(|(workspace, _)| workspace.upgrade())
5671 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5672 let menu = CompletionsMenu::new(
5673 id,
5674 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5675 sort_completions,
5676 show_completion_documentation,
5677 position,
5678 query.clone(),
5679 is_incomplete,
5680 buffer.clone(),
5681 completions.into(),
5682 snippet_sort_order,
5683 languages,
5684 language,
5685 cx,
5686 );
5687
5688 let query = if filter_completions { query } else { None };
5689 let matches_task = if let Some(query) = query {
5690 menu.do_async_filtering(query, cx)
5691 } else {
5692 Task::ready(menu.unfiltered_matches())
5693 };
5694 (menu, matches_task)
5695 }) else {
5696 return;
5697 };
5698
5699 let matches = matches_task.await;
5700
5701 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5702 // Newer menu already set, so exit.
5703 if let Some(CodeContextMenu::Completions(prev_menu)) =
5704 editor.context_menu.borrow().as_ref()
5705 && prev_menu.id > id
5706 {
5707 return;
5708 };
5709
5710 // Only valid to take prev_menu because it the new menu is immediately set
5711 // below, or the menu is hidden.
5712 if let Some(CodeContextMenu::Completions(prev_menu)) =
5713 editor.context_menu.borrow_mut().take()
5714 {
5715 let position_matches =
5716 if prev_menu.initial_position == menu.initial_position {
5717 true
5718 } else {
5719 let snapshot = editor.buffer.read(cx).read(cx);
5720 prev_menu.initial_position.to_offset(&snapshot)
5721 == menu.initial_position.to_offset(&snapshot)
5722 };
5723 if position_matches {
5724 // Preserve markdown cache before `set_filter_results` because it will
5725 // try to populate the documentation cache.
5726 menu.preserve_markdown_cache(prev_menu);
5727 }
5728 };
5729
5730 menu.set_filter_results(matches, provider, window, cx);
5731 }) else {
5732 return;
5733 };
5734
5735 menu.visible().then_some(menu)
5736 };
5737
5738 editor
5739 .update_in(cx, |editor, window, cx| {
5740 if editor.focus_handle.is_focused(window)
5741 && let Some(menu) = menu
5742 {
5743 *editor.context_menu.borrow_mut() =
5744 Some(CodeContextMenu::Completions(menu));
5745
5746 crate::hover_popover::hide_hover(editor, cx);
5747 if editor.show_edit_predictions_in_menu() {
5748 editor.update_visible_edit_prediction(window, cx);
5749 } else {
5750 editor.discard_edit_prediction(false, cx);
5751 }
5752
5753 cx.notify();
5754 return;
5755 }
5756
5757 if editor.completion_tasks.len() <= 1 {
5758 // If there are no more completion tasks and the last menu was empty, we should hide it.
5759 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5760 // If it was already hidden and we don't show edit predictions in the menu,
5761 // we should also show the edit prediction when available.
5762 if was_hidden && editor.show_edit_predictions_in_menu() {
5763 editor.update_visible_edit_prediction(window, cx);
5764 }
5765 }
5766 })
5767 .ok();
5768 });
5769
5770 self.completion_tasks.push((id, task));
5771 }
5772
5773 #[cfg(feature = "test-support")]
5774 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5775 let menu = self.context_menu.borrow();
5776 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5777 let completions = menu.completions.borrow();
5778 Some(completions.to_vec())
5779 } else {
5780 None
5781 }
5782 }
5783
5784 pub fn with_completions_menu_matching_id<R>(
5785 &self,
5786 id: CompletionId,
5787 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5788 ) -> R {
5789 let mut context_menu = self.context_menu.borrow_mut();
5790 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5791 return f(None);
5792 };
5793 if completions_menu.id != id {
5794 return f(None);
5795 }
5796 f(Some(completions_menu))
5797 }
5798
5799 pub fn confirm_completion(
5800 &mut self,
5801 action: &ConfirmCompletion,
5802 window: &mut Window,
5803 cx: &mut Context<Self>,
5804 ) -> Option<Task<Result<()>>> {
5805 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5806 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5807 }
5808
5809 pub fn confirm_completion_insert(
5810 &mut self,
5811 _: &ConfirmCompletionInsert,
5812 window: &mut Window,
5813 cx: &mut Context<Self>,
5814 ) -> Option<Task<Result<()>>> {
5815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5816 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5817 }
5818
5819 pub fn confirm_completion_replace(
5820 &mut self,
5821 _: &ConfirmCompletionReplace,
5822 window: &mut Window,
5823 cx: &mut Context<Self>,
5824 ) -> Option<Task<Result<()>>> {
5825 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5826 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5827 }
5828
5829 pub fn compose_completion(
5830 &mut self,
5831 action: &ComposeCompletion,
5832 window: &mut Window,
5833 cx: &mut Context<Self>,
5834 ) -> Option<Task<Result<()>>> {
5835 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5836 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5837 }
5838
5839 fn do_completion(
5840 &mut self,
5841 item_ix: Option<usize>,
5842 intent: CompletionIntent,
5843 window: &mut Window,
5844 cx: &mut Context<Editor>,
5845 ) -> Option<Task<Result<()>>> {
5846 use language::ToOffset as _;
5847
5848 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5849 else {
5850 return None;
5851 };
5852
5853 let candidate_id = {
5854 let entries = completions_menu.entries.borrow();
5855 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5856 if self.show_edit_predictions_in_menu() {
5857 self.discard_edit_prediction(true, cx);
5858 }
5859 mat.candidate_id
5860 };
5861
5862 let completion = completions_menu
5863 .completions
5864 .borrow()
5865 .get(candidate_id)?
5866 .clone();
5867 cx.stop_propagation();
5868
5869 let buffer_handle = completions_menu.buffer.clone();
5870
5871 let CompletionEdit {
5872 new_text,
5873 snippet,
5874 replace_range,
5875 } = process_completion_for_edit(
5876 &completion,
5877 intent,
5878 &buffer_handle,
5879 &completions_menu.initial_position.text_anchor,
5880 cx,
5881 );
5882
5883 let buffer = buffer_handle.read(cx);
5884 let snapshot = self.buffer.read(cx).snapshot(cx);
5885 let newest_anchor = self.selections.newest_anchor();
5886 let replace_range_multibuffer = {
5887 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5888 let multibuffer_anchor = snapshot
5889 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5890 .unwrap()
5891 ..snapshot
5892 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5893 .unwrap();
5894 multibuffer_anchor.start.to_offset(&snapshot)
5895 ..multibuffer_anchor.end.to_offset(&snapshot)
5896 };
5897 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5898 return None;
5899 }
5900
5901 let old_text = buffer
5902 .text_for_range(replace_range.clone())
5903 .collect::<String>();
5904 let lookbehind = newest_anchor
5905 .start
5906 .text_anchor
5907 .to_offset(buffer)
5908 .saturating_sub(replace_range.start);
5909 let lookahead = replace_range
5910 .end
5911 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5912 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5913 let suffix = &old_text[lookbehind.min(old_text.len())..];
5914
5915 let selections = self.selections.all::<usize>(cx);
5916 let mut ranges = Vec::new();
5917 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5918
5919 for selection in &selections {
5920 let range = if selection.id == newest_anchor.id {
5921 replace_range_multibuffer.clone()
5922 } else {
5923 let mut range = selection.range();
5924
5925 // if prefix is present, don't duplicate it
5926 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5927 range.start = range.start.saturating_sub(lookbehind);
5928
5929 // if suffix is also present, mimic the newest cursor and replace it
5930 if selection.id != newest_anchor.id
5931 && snapshot.contains_str_at(range.end, suffix)
5932 {
5933 range.end += lookahead;
5934 }
5935 }
5936 range
5937 };
5938
5939 ranges.push(range.clone());
5940
5941 if !self.linked_edit_ranges.is_empty() {
5942 let start_anchor = snapshot.anchor_before(range.start);
5943 let end_anchor = snapshot.anchor_after(range.end);
5944 if let Some(ranges) = self
5945 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5946 {
5947 for (buffer, edits) in ranges {
5948 linked_edits
5949 .entry(buffer.clone())
5950 .or_default()
5951 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5952 }
5953 }
5954 }
5955 }
5956
5957 let common_prefix_len = old_text
5958 .chars()
5959 .zip(new_text.chars())
5960 .take_while(|(a, b)| a == b)
5961 .map(|(a, _)| a.len_utf8())
5962 .sum::<usize>();
5963
5964 cx.emit(EditorEvent::InputHandled {
5965 utf16_range_to_replace: None,
5966 text: new_text[common_prefix_len..].into(),
5967 });
5968
5969 self.transact(window, cx, |editor, window, cx| {
5970 if let Some(mut snippet) = snippet {
5971 snippet.text = new_text.to_string();
5972 editor
5973 .insert_snippet(&ranges, snippet, window, cx)
5974 .log_err();
5975 } else {
5976 editor.buffer.update(cx, |multi_buffer, cx| {
5977 let auto_indent = match completion.insert_text_mode {
5978 Some(InsertTextMode::AS_IS) => None,
5979 _ => editor.autoindent_mode.clone(),
5980 };
5981 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5982 multi_buffer.edit(edits, auto_indent, cx);
5983 });
5984 }
5985 for (buffer, edits) in linked_edits {
5986 buffer.update(cx, |buffer, cx| {
5987 let snapshot = buffer.snapshot();
5988 let edits = edits
5989 .into_iter()
5990 .map(|(range, text)| {
5991 use text::ToPoint as TP;
5992 let end_point = TP::to_point(&range.end, &snapshot);
5993 let start_point = TP::to_point(&range.start, &snapshot);
5994 (start_point..end_point, text)
5995 })
5996 .sorted_by_key(|(range, _)| range.start);
5997 buffer.edit(edits, None, cx);
5998 })
5999 }
6000
6001 editor.refresh_edit_prediction(true, false, window, cx);
6002 });
6003 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6004
6005 let show_new_completions_on_confirm = completion
6006 .confirm
6007 .as_ref()
6008 .is_some_and(|confirm| confirm(intent, window, cx));
6009 if show_new_completions_on_confirm {
6010 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6011 }
6012
6013 let provider = self.completion_provider.as_ref()?;
6014 drop(completion);
6015 let apply_edits = provider.apply_additional_edits_for_completion(
6016 buffer_handle,
6017 completions_menu.completions.clone(),
6018 candidate_id,
6019 true,
6020 cx,
6021 );
6022
6023 let editor_settings = EditorSettings::get_global(cx);
6024 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6025 // After the code completion is finished, users often want to know what signatures are needed.
6026 // so we should automatically call signature_help
6027 self.show_signature_help(&ShowSignatureHelp, window, cx);
6028 }
6029
6030 Some(cx.foreground_executor().spawn(async move {
6031 apply_edits.await?;
6032 Ok(())
6033 }))
6034 }
6035
6036 pub fn toggle_code_actions(
6037 &mut self,
6038 action: &ToggleCodeActions,
6039 window: &mut Window,
6040 cx: &mut Context<Self>,
6041 ) {
6042 let quick_launch = action.quick_launch;
6043 let mut context_menu = self.context_menu.borrow_mut();
6044 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6045 if code_actions.deployed_from == action.deployed_from {
6046 // Toggle if we're selecting the same one
6047 *context_menu = None;
6048 cx.notify();
6049 return;
6050 } else {
6051 // Otherwise, clear it and start a new one
6052 *context_menu = None;
6053 cx.notify();
6054 }
6055 }
6056 drop(context_menu);
6057 let snapshot = self.snapshot(window, cx);
6058 let deployed_from = action.deployed_from.clone();
6059 let action = action.clone();
6060 self.completion_tasks.clear();
6061 self.discard_edit_prediction(false, cx);
6062
6063 let multibuffer_point = match &action.deployed_from {
6064 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6065 DisplayPoint::new(*row, 0).to_point(&snapshot)
6066 }
6067 _ => self.selections.newest::<Point>(cx).head(),
6068 };
6069 let Some((buffer, buffer_row)) = snapshot
6070 .buffer_snapshot
6071 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6072 .and_then(|(buffer_snapshot, range)| {
6073 self.buffer()
6074 .read(cx)
6075 .buffer(buffer_snapshot.remote_id())
6076 .map(|buffer| (buffer, range.start.row))
6077 })
6078 else {
6079 return;
6080 };
6081 let buffer_id = buffer.read(cx).remote_id();
6082 let tasks = self
6083 .tasks
6084 .get(&(buffer_id, buffer_row))
6085 .map(|t| Arc::new(t.to_owned()));
6086
6087 if !self.focus_handle.is_focused(window) {
6088 return;
6089 }
6090 let project = self.project.clone();
6091
6092 let code_actions_task = match deployed_from {
6093 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6094 _ => self.code_actions(buffer_row, window, cx),
6095 };
6096
6097 let runnable_task = match deployed_from {
6098 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6099 _ => {
6100 let mut task_context_task = Task::ready(None);
6101 if let Some(tasks) = &tasks
6102 && let Some(project) = project
6103 {
6104 task_context_task =
6105 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6106 }
6107
6108 cx.spawn_in(window, {
6109 let buffer = buffer.clone();
6110 async move |editor, cx| {
6111 let task_context = task_context_task.await;
6112
6113 let resolved_tasks =
6114 tasks
6115 .zip(task_context.clone())
6116 .map(|(tasks, task_context)| ResolvedTasks {
6117 templates: tasks.resolve(&task_context).collect(),
6118 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6119 multibuffer_point.row,
6120 tasks.column,
6121 )),
6122 });
6123 let debug_scenarios = editor
6124 .update(cx, |editor, cx| {
6125 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6126 })?
6127 .await;
6128 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6129 }
6130 })
6131 }
6132 };
6133
6134 cx.spawn_in(window, async move |editor, cx| {
6135 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6136 let code_actions = code_actions_task.await;
6137 let spawn_straight_away = quick_launch
6138 && resolved_tasks
6139 .as_ref()
6140 .is_some_and(|tasks| tasks.templates.len() == 1)
6141 && code_actions
6142 .as_ref()
6143 .is_none_or(|actions| actions.is_empty())
6144 && debug_scenarios.is_empty();
6145
6146 editor.update_in(cx, |editor, window, cx| {
6147 crate::hover_popover::hide_hover(editor, cx);
6148 let actions = CodeActionContents::new(
6149 resolved_tasks,
6150 code_actions,
6151 debug_scenarios,
6152 task_context.unwrap_or_default(),
6153 );
6154
6155 // Don't show the menu if there are no actions available
6156 if actions.is_empty() {
6157 cx.notify();
6158 return Task::ready(Ok(()));
6159 }
6160
6161 *editor.context_menu.borrow_mut() =
6162 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6163 buffer,
6164 actions,
6165 selected_item: Default::default(),
6166 scroll_handle: UniformListScrollHandle::default(),
6167 deployed_from,
6168 }));
6169 cx.notify();
6170 if spawn_straight_away
6171 && let Some(task) = editor.confirm_code_action(
6172 &ConfirmCodeAction { item_ix: Some(0) },
6173 window,
6174 cx,
6175 )
6176 {
6177 return task;
6178 }
6179
6180 Task::ready(Ok(()))
6181 })
6182 })
6183 .detach_and_log_err(cx);
6184 }
6185
6186 fn debug_scenarios(
6187 &mut self,
6188 resolved_tasks: &Option<ResolvedTasks>,
6189 buffer: &Entity<Buffer>,
6190 cx: &mut App,
6191 ) -> Task<Vec<task::DebugScenario>> {
6192 maybe!({
6193 let project = self.project()?;
6194 let dap_store = project.read(cx).dap_store();
6195 let mut scenarios = vec![];
6196 let resolved_tasks = resolved_tasks.as_ref()?;
6197 let buffer = buffer.read(cx);
6198 let language = buffer.language()?;
6199 let file = buffer.file();
6200 let debug_adapter = language_settings(language.name().into(), file, cx)
6201 .debuggers
6202 .first()
6203 .map(SharedString::from)
6204 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6205
6206 dap_store.update(cx, |dap_store, cx| {
6207 for (_, task) in &resolved_tasks.templates {
6208 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6209 task.original_task().clone(),
6210 debug_adapter.clone().into(),
6211 task.display_label().to_owned().into(),
6212 cx,
6213 );
6214 scenarios.push(maybe_scenario);
6215 }
6216 });
6217 Some(cx.background_spawn(async move {
6218 futures::future::join_all(scenarios)
6219 .await
6220 .into_iter()
6221 .flatten()
6222 .collect::<Vec<_>>()
6223 }))
6224 })
6225 .unwrap_or_else(|| Task::ready(vec![]))
6226 }
6227
6228 fn code_actions(
6229 &mut self,
6230 buffer_row: u32,
6231 window: &mut Window,
6232 cx: &mut Context<Self>,
6233 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6234 let mut task = self.code_actions_task.take();
6235 cx.spawn_in(window, async move |editor, cx| {
6236 while let Some(prev_task) = task {
6237 prev_task.await.log_err();
6238 task = editor
6239 .update(cx, |this, _| this.code_actions_task.take())
6240 .ok()?;
6241 }
6242
6243 editor
6244 .update(cx, |editor, cx| {
6245 editor
6246 .available_code_actions
6247 .clone()
6248 .and_then(|(location, code_actions)| {
6249 let snapshot = location.buffer.read(cx).snapshot();
6250 let point_range = location.range.to_point(&snapshot);
6251 let point_range = point_range.start.row..=point_range.end.row;
6252 if point_range.contains(&buffer_row) {
6253 Some(code_actions)
6254 } else {
6255 None
6256 }
6257 })
6258 })
6259 .ok()
6260 .flatten()
6261 })
6262 }
6263
6264 pub fn confirm_code_action(
6265 &mut self,
6266 action: &ConfirmCodeAction,
6267 window: &mut Window,
6268 cx: &mut Context<Self>,
6269 ) -> Option<Task<Result<()>>> {
6270 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6271
6272 let actions_menu =
6273 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6274 menu
6275 } else {
6276 return None;
6277 };
6278
6279 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6280 let action = actions_menu.actions.get(action_ix)?;
6281 let title = action.label();
6282 let buffer = actions_menu.buffer;
6283 let workspace = self.workspace()?;
6284
6285 match action {
6286 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6287 workspace.update(cx, |workspace, cx| {
6288 workspace.schedule_resolved_task(
6289 task_source_kind,
6290 resolved_task,
6291 false,
6292 window,
6293 cx,
6294 );
6295
6296 Some(Task::ready(Ok(())))
6297 })
6298 }
6299 CodeActionsItem::CodeAction {
6300 excerpt_id,
6301 action,
6302 provider,
6303 } => {
6304 let apply_code_action =
6305 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6306 let workspace = workspace.downgrade();
6307 Some(cx.spawn_in(window, async move |editor, cx| {
6308 let project_transaction = apply_code_action.await?;
6309 Self::open_project_transaction(
6310 &editor,
6311 workspace,
6312 project_transaction,
6313 title,
6314 cx,
6315 )
6316 .await
6317 }))
6318 }
6319 CodeActionsItem::DebugScenario(scenario) => {
6320 let context = actions_menu.actions.context;
6321
6322 workspace.update(cx, |workspace, cx| {
6323 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6324 workspace.start_debug_session(
6325 scenario,
6326 context,
6327 Some(buffer),
6328 None,
6329 window,
6330 cx,
6331 );
6332 });
6333 Some(Task::ready(Ok(())))
6334 }
6335 }
6336 }
6337
6338 pub async fn open_project_transaction(
6339 editor: &WeakEntity<Editor>,
6340 workspace: WeakEntity<Workspace>,
6341 transaction: ProjectTransaction,
6342 title: String,
6343 cx: &mut AsyncWindowContext,
6344 ) -> Result<()> {
6345 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6346 cx.update(|_, cx| {
6347 entries.sort_unstable_by_key(|(buffer, _)| {
6348 buffer.read(cx).file().map(|f| f.path().clone())
6349 });
6350 })?;
6351
6352 // If the project transaction's edits are all contained within this editor, then
6353 // avoid opening a new editor to display them.
6354
6355 if let Some((buffer, transaction)) = entries.first() {
6356 if entries.len() == 1 {
6357 let excerpt = editor.update(cx, |editor, cx| {
6358 editor
6359 .buffer()
6360 .read(cx)
6361 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6362 })?;
6363 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6364 && excerpted_buffer == *buffer
6365 {
6366 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6367 let excerpt_range = excerpt_range.to_offset(buffer);
6368 buffer
6369 .edited_ranges_for_transaction::<usize>(transaction)
6370 .all(|range| {
6371 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6372 })
6373 })?;
6374
6375 if all_edits_within_excerpt {
6376 return Ok(());
6377 }
6378 }
6379 }
6380 } else {
6381 return Ok(());
6382 }
6383
6384 let mut ranges_to_highlight = Vec::new();
6385 let excerpt_buffer = cx.new(|cx| {
6386 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6387 for (buffer_handle, transaction) in &entries {
6388 let edited_ranges = buffer_handle
6389 .read(cx)
6390 .edited_ranges_for_transaction::<Point>(transaction)
6391 .collect::<Vec<_>>();
6392 let (ranges, _) = multibuffer.set_excerpts_for_path(
6393 PathKey::for_buffer(buffer_handle, cx),
6394 buffer_handle.clone(),
6395 edited_ranges,
6396 DEFAULT_MULTIBUFFER_CONTEXT,
6397 cx,
6398 );
6399
6400 ranges_to_highlight.extend(ranges);
6401 }
6402 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6403 multibuffer
6404 })?;
6405
6406 workspace.update_in(cx, |workspace, window, cx| {
6407 let project = workspace.project().clone();
6408 let editor =
6409 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6410 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6411 editor.update(cx, |editor, cx| {
6412 editor.highlight_background::<Self>(
6413 &ranges_to_highlight,
6414 |theme| theme.colors().editor_highlighted_line_background,
6415 cx,
6416 );
6417 });
6418 })?;
6419
6420 Ok(())
6421 }
6422
6423 pub fn clear_code_action_providers(&mut self) {
6424 self.code_action_providers.clear();
6425 self.available_code_actions.take();
6426 }
6427
6428 pub fn add_code_action_provider(
6429 &mut self,
6430 provider: Rc<dyn CodeActionProvider>,
6431 window: &mut Window,
6432 cx: &mut Context<Self>,
6433 ) {
6434 if self
6435 .code_action_providers
6436 .iter()
6437 .any(|existing_provider| existing_provider.id() == provider.id())
6438 {
6439 return;
6440 }
6441
6442 self.code_action_providers.push(provider);
6443 self.refresh_code_actions(window, cx);
6444 }
6445
6446 pub fn remove_code_action_provider(
6447 &mut self,
6448 id: Arc<str>,
6449 window: &mut Window,
6450 cx: &mut Context<Self>,
6451 ) {
6452 self.code_action_providers
6453 .retain(|provider| provider.id() != id);
6454 self.refresh_code_actions(window, cx);
6455 }
6456
6457 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6458 !self.code_action_providers.is_empty()
6459 && EditorSettings::get_global(cx).toolbar.code_actions
6460 }
6461
6462 pub fn has_available_code_actions(&self) -> bool {
6463 self.available_code_actions
6464 .as_ref()
6465 .is_some_and(|(_, actions)| !actions.is_empty())
6466 }
6467
6468 fn render_inline_code_actions(
6469 &self,
6470 icon_size: ui::IconSize,
6471 display_row: DisplayRow,
6472 is_active: bool,
6473 cx: &mut Context<Self>,
6474 ) -> AnyElement {
6475 let show_tooltip = !self.context_menu_visible();
6476 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6477 .icon_size(icon_size)
6478 .shape(ui::IconButtonShape::Square)
6479 .icon_color(ui::Color::Hidden)
6480 .toggle_state(is_active)
6481 .when(show_tooltip, |this| {
6482 this.tooltip({
6483 let focus_handle = self.focus_handle.clone();
6484 move |window, cx| {
6485 Tooltip::for_action_in(
6486 "Toggle Code Actions",
6487 &ToggleCodeActions {
6488 deployed_from: None,
6489 quick_launch: false,
6490 },
6491 &focus_handle,
6492 window,
6493 cx,
6494 )
6495 }
6496 })
6497 })
6498 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6499 window.focus(&editor.focus_handle(cx));
6500 editor.toggle_code_actions(
6501 &crate::actions::ToggleCodeActions {
6502 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6503 display_row,
6504 )),
6505 quick_launch: false,
6506 },
6507 window,
6508 cx,
6509 );
6510 }))
6511 .into_any_element()
6512 }
6513
6514 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6515 &self.context_menu
6516 }
6517
6518 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6519 let newest_selection = self.selections.newest_anchor().clone();
6520 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6521 let buffer = self.buffer.read(cx);
6522 if newest_selection.head().diff_base_anchor.is_some() {
6523 return None;
6524 }
6525 let (start_buffer, start) =
6526 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6527 let (end_buffer, end) =
6528 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6529 if start_buffer != end_buffer {
6530 return None;
6531 }
6532
6533 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6534 cx.background_executor()
6535 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6536 .await;
6537
6538 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6539 let providers = this.code_action_providers.clone();
6540 let tasks = this
6541 .code_action_providers
6542 .iter()
6543 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6544 .collect::<Vec<_>>();
6545 (providers, tasks)
6546 })?;
6547
6548 let mut actions = Vec::new();
6549 for (provider, provider_actions) in
6550 providers.into_iter().zip(future::join_all(tasks).await)
6551 {
6552 if let Some(provider_actions) = provider_actions.log_err() {
6553 actions.extend(provider_actions.into_iter().map(|action| {
6554 AvailableCodeAction {
6555 excerpt_id: newest_selection.start.excerpt_id,
6556 action,
6557 provider: provider.clone(),
6558 }
6559 }));
6560 }
6561 }
6562
6563 this.update(cx, |this, cx| {
6564 this.available_code_actions = if actions.is_empty() {
6565 None
6566 } else {
6567 Some((
6568 Location {
6569 buffer: start_buffer,
6570 range: start..end,
6571 },
6572 actions.into(),
6573 ))
6574 };
6575 cx.notify();
6576 })
6577 }));
6578 None
6579 }
6580
6581 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6582 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6583 self.show_git_blame_inline = false;
6584
6585 self.show_git_blame_inline_delay_task =
6586 Some(cx.spawn_in(window, async move |this, cx| {
6587 cx.background_executor().timer(delay).await;
6588
6589 this.update(cx, |this, cx| {
6590 this.show_git_blame_inline = true;
6591 cx.notify();
6592 })
6593 .log_err();
6594 }));
6595 }
6596 }
6597
6598 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6599 let snapshot = self.snapshot(window, cx);
6600 let cursor = self.selections.newest::<Point>(cx).head();
6601 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6602 else {
6603 return;
6604 };
6605
6606 let Some(blame) = self.blame.as_ref() else {
6607 return;
6608 };
6609
6610 let row_info = RowInfo {
6611 buffer_id: Some(buffer.remote_id()),
6612 buffer_row: Some(point.row),
6613 ..Default::default()
6614 };
6615 let Some(blame_entry) = blame
6616 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6617 .flatten()
6618 else {
6619 return;
6620 };
6621
6622 let anchor = self.selections.newest_anchor().head();
6623 let position = self.to_pixel_point(anchor, &snapshot, window);
6624 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6625 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6626 };
6627 }
6628
6629 fn show_blame_popover(
6630 &mut self,
6631 blame_entry: &BlameEntry,
6632 position: gpui::Point<Pixels>,
6633 ignore_timeout: bool,
6634 cx: &mut Context<Self>,
6635 ) {
6636 if let Some(state) = &mut self.inline_blame_popover {
6637 state.hide_task.take();
6638 } else {
6639 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6640 let blame_entry = blame_entry.clone();
6641 let show_task = cx.spawn(async move |editor, cx| {
6642 if !ignore_timeout {
6643 cx.background_executor()
6644 .timer(std::time::Duration::from_millis(blame_popover_delay))
6645 .await;
6646 }
6647 editor
6648 .update(cx, |editor, cx| {
6649 editor.inline_blame_popover_show_task.take();
6650 let Some(blame) = editor.blame.as_ref() else {
6651 return;
6652 };
6653 let blame = blame.read(cx);
6654 let details = blame.details_for_entry(&blame_entry);
6655 let markdown = cx.new(|cx| {
6656 Markdown::new(
6657 details
6658 .as_ref()
6659 .map(|message| message.message.clone())
6660 .unwrap_or_default(),
6661 None,
6662 None,
6663 cx,
6664 )
6665 });
6666 editor.inline_blame_popover = Some(InlineBlamePopover {
6667 position,
6668 hide_task: None,
6669 popover_bounds: None,
6670 popover_state: InlineBlamePopoverState {
6671 scroll_handle: ScrollHandle::new(),
6672 commit_message: details,
6673 markdown,
6674 },
6675 keyboard_grace: ignore_timeout,
6676 });
6677 cx.notify();
6678 })
6679 .ok();
6680 });
6681 self.inline_blame_popover_show_task = Some(show_task);
6682 }
6683 }
6684
6685 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6686 self.inline_blame_popover_show_task.take();
6687 if let Some(state) = &mut self.inline_blame_popover {
6688 let hide_task = cx.spawn(async move |editor, cx| {
6689 cx.background_executor()
6690 .timer(std::time::Duration::from_millis(100))
6691 .await;
6692 editor
6693 .update(cx, |editor, cx| {
6694 editor.inline_blame_popover.take();
6695 cx.notify();
6696 })
6697 .ok();
6698 });
6699 state.hide_task = Some(hide_task);
6700 }
6701 }
6702
6703 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6704 if self.pending_rename.is_some() {
6705 return None;
6706 }
6707
6708 let provider = self.semantics_provider.clone()?;
6709 let buffer = self.buffer.read(cx);
6710 let newest_selection = self.selections.newest_anchor().clone();
6711 let cursor_position = newest_selection.head();
6712 let (cursor_buffer, cursor_buffer_position) =
6713 buffer.text_anchor_for_position(cursor_position, cx)?;
6714 let (tail_buffer, tail_buffer_position) =
6715 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6716 if cursor_buffer != tail_buffer {
6717 return None;
6718 }
6719
6720 let snapshot = cursor_buffer.read(cx).snapshot();
6721 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6722 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6723 if start_word_range != end_word_range {
6724 self.document_highlights_task.take();
6725 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6726 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6727 return None;
6728 }
6729
6730 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6731 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6732 cx.background_executor()
6733 .timer(Duration::from_millis(debounce))
6734 .await;
6735
6736 let highlights = if let Some(highlights) = cx
6737 .update(|cx| {
6738 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6739 })
6740 .ok()
6741 .flatten()
6742 {
6743 highlights.await.log_err()
6744 } else {
6745 None
6746 };
6747
6748 if let Some(highlights) = highlights {
6749 this.update(cx, |this, cx| {
6750 if this.pending_rename.is_some() {
6751 return;
6752 }
6753
6754 let buffer = this.buffer.read(cx);
6755 if buffer
6756 .text_anchor_for_position(cursor_position, cx)
6757 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6758 {
6759 return;
6760 }
6761
6762 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6763 let mut write_ranges = Vec::new();
6764 let mut read_ranges = Vec::new();
6765 for highlight in highlights {
6766 let buffer_id = cursor_buffer.read(cx).remote_id();
6767 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6768 {
6769 let start = highlight
6770 .range
6771 .start
6772 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6773 let end = highlight
6774 .range
6775 .end
6776 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6777 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6778 continue;
6779 }
6780
6781 let range = Anchor {
6782 buffer_id: Some(buffer_id),
6783 excerpt_id,
6784 text_anchor: start,
6785 diff_base_anchor: None,
6786 }..Anchor {
6787 buffer_id: Some(buffer_id),
6788 excerpt_id,
6789 text_anchor: end,
6790 diff_base_anchor: None,
6791 };
6792 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6793 write_ranges.push(range);
6794 } else {
6795 read_ranges.push(range);
6796 }
6797 }
6798 }
6799
6800 this.highlight_background::<DocumentHighlightRead>(
6801 &read_ranges,
6802 |theme| theme.colors().editor_document_highlight_read_background,
6803 cx,
6804 );
6805 this.highlight_background::<DocumentHighlightWrite>(
6806 &write_ranges,
6807 |theme| theme.colors().editor_document_highlight_write_background,
6808 cx,
6809 );
6810 cx.notify();
6811 })
6812 .log_err();
6813 }
6814 }));
6815 None
6816 }
6817
6818 fn prepare_highlight_query_from_selection(
6819 &mut self,
6820 cx: &mut Context<Editor>,
6821 ) -> Option<(String, Range<Anchor>)> {
6822 if matches!(self.mode, EditorMode::SingleLine) {
6823 return None;
6824 }
6825 if !EditorSettings::get_global(cx).selection_highlight {
6826 return None;
6827 }
6828 if self.selections.count() != 1 || self.selections.line_mode {
6829 return None;
6830 }
6831 let selection = self.selections.newest::<Point>(cx);
6832 if selection.is_empty() || selection.start.row != selection.end.row {
6833 return None;
6834 }
6835 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6836 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6837 let query = multi_buffer_snapshot
6838 .text_for_range(selection_anchor_range.clone())
6839 .collect::<String>();
6840 if query.trim().is_empty() {
6841 return None;
6842 }
6843 Some((query, selection_anchor_range))
6844 }
6845
6846 fn update_selection_occurrence_highlights(
6847 &mut self,
6848 query_text: String,
6849 query_range: Range<Anchor>,
6850 multi_buffer_range_to_query: Range<Point>,
6851 use_debounce: bool,
6852 window: &mut Window,
6853 cx: &mut Context<Editor>,
6854 ) -> Task<()> {
6855 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6856 cx.spawn_in(window, async move |editor, cx| {
6857 if use_debounce {
6858 cx.background_executor()
6859 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6860 .await;
6861 }
6862 let match_task = cx.background_spawn(async move {
6863 let buffer_ranges = multi_buffer_snapshot
6864 .range_to_buffer_ranges(multi_buffer_range_to_query)
6865 .into_iter()
6866 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6867 let mut match_ranges = Vec::new();
6868 let Ok(regex) = project::search::SearchQuery::text(
6869 query_text.clone(),
6870 false,
6871 false,
6872 false,
6873 Default::default(),
6874 Default::default(),
6875 false,
6876 None,
6877 ) else {
6878 return Vec::default();
6879 };
6880 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6881 match_ranges.extend(
6882 regex
6883 .search(buffer_snapshot, Some(search_range.clone()))
6884 .await
6885 .into_iter()
6886 .filter_map(|match_range| {
6887 let match_start = buffer_snapshot
6888 .anchor_after(search_range.start + match_range.start);
6889 let match_end = buffer_snapshot
6890 .anchor_before(search_range.start + match_range.end);
6891 let match_anchor_range = Anchor::range_in_buffer(
6892 excerpt_id,
6893 buffer_snapshot.remote_id(),
6894 match_start..match_end,
6895 );
6896 (match_anchor_range != query_range).then_some(match_anchor_range)
6897 }),
6898 );
6899 }
6900 match_ranges
6901 });
6902 let match_ranges = match_task.await;
6903 editor
6904 .update_in(cx, |editor, _, cx| {
6905 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6906 if !match_ranges.is_empty() {
6907 editor.highlight_background::<SelectedTextHighlight>(
6908 &match_ranges,
6909 |theme| theme.colors().editor_document_highlight_bracket_background,
6910 cx,
6911 )
6912 }
6913 })
6914 .log_err();
6915 })
6916 }
6917
6918 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6919 struct NewlineFold;
6920 let type_id = std::any::TypeId::of::<NewlineFold>();
6921 if !self.mode.is_single_line() {
6922 return;
6923 }
6924 let snapshot = self.snapshot(window, cx);
6925 if snapshot.buffer_snapshot.max_point().row == 0 {
6926 return;
6927 }
6928 let task = cx.background_spawn(async move {
6929 let new_newlines = snapshot
6930 .buffer_chars_at(0)
6931 .filter_map(|(c, i)| {
6932 if c == '\n' {
6933 Some(
6934 snapshot.buffer_snapshot.anchor_after(i)
6935 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6936 )
6937 } else {
6938 None
6939 }
6940 })
6941 .collect::<Vec<_>>();
6942 let existing_newlines = snapshot
6943 .folds_in_range(0..snapshot.buffer_snapshot.len())
6944 .filter_map(|fold| {
6945 if fold.placeholder.type_tag == Some(type_id) {
6946 Some(fold.range.start..fold.range.end)
6947 } else {
6948 None
6949 }
6950 })
6951 .collect::<Vec<_>>();
6952
6953 (new_newlines, existing_newlines)
6954 });
6955 self.folding_newlines = cx.spawn(async move |this, cx| {
6956 let (new_newlines, existing_newlines) = task.await;
6957 if new_newlines == existing_newlines {
6958 return;
6959 }
6960 let placeholder = FoldPlaceholder {
6961 render: Arc::new(move |_, _, cx| {
6962 div()
6963 .bg(cx.theme().status().hint_background)
6964 .border_b_1()
6965 .size_full()
6966 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6967 .border_color(cx.theme().status().hint)
6968 .child("\\n")
6969 .into_any()
6970 }),
6971 constrain_width: false,
6972 merge_adjacent: false,
6973 type_tag: Some(type_id),
6974 };
6975 let creases = new_newlines
6976 .into_iter()
6977 .map(|range| Crease::simple(range, placeholder.clone()))
6978 .collect();
6979 this.update(cx, |this, cx| {
6980 this.display_map.update(cx, |display_map, cx| {
6981 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6982 display_map.fold(creases, cx);
6983 });
6984 })
6985 .ok();
6986 });
6987 }
6988
6989 fn refresh_selected_text_highlights(
6990 &mut self,
6991 on_buffer_edit: bool,
6992 window: &mut Window,
6993 cx: &mut Context<Editor>,
6994 ) {
6995 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6996 else {
6997 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6998 self.quick_selection_highlight_task.take();
6999 self.debounced_selection_highlight_task.take();
7000 return;
7001 };
7002 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7003 if on_buffer_edit
7004 || self
7005 .quick_selection_highlight_task
7006 .as_ref()
7007 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7008 {
7009 let multi_buffer_visible_start = self
7010 .scroll_manager
7011 .anchor()
7012 .anchor
7013 .to_point(&multi_buffer_snapshot);
7014 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7015 multi_buffer_visible_start
7016 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7017 Bias::Left,
7018 );
7019 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7020 self.quick_selection_highlight_task = Some((
7021 query_range.clone(),
7022 self.update_selection_occurrence_highlights(
7023 query_text.clone(),
7024 query_range.clone(),
7025 multi_buffer_visible_range,
7026 false,
7027 window,
7028 cx,
7029 ),
7030 ));
7031 }
7032 if on_buffer_edit
7033 || self
7034 .debounced_selection_highlight_task
7035 .as_ref()
7036 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7037 {
7038 let multi_buffer_start = multi_buffer_snapshot
7039 .anchor_before(0)
7040 .to_point(&multi_buffer_snapshot);
7041 let multi_buffer_end = multi_buffer_snapshot
7042 .anchor_after(multi_buffer_snapshot.len())
7043 .to_point(&multi_buffer_snapshot);
7044 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7045 self.debounced_selection_highlight_task = Some((
7046 query_range.clone(),
7047 self.update_selection_occurrence_highlights(
7048 query_text,
7049 query_range,
7050 multi_buffer_full_range,
7051 true,
7052 window,
7053 cx,
7054 ),
7055 ));
7056 }
7057 }
7058
7059 pub fn refresh_edit_prediction(
7060 &mut self,
7061 debounce: bool,
7062 user_requested: bool,
7063 window: &mut Window,
7064 cx: &mut Context<Self>,
7065 ) -> Option<()> {
7066 if DisableAiSettings::get_global(cx).disable_ai {
7067 return None;
7068 }
7069
7070 let provider = self.edit_prediction_provider()?;
7071 let cursor = self.selections.newest_anchor().head();
7072 let (buffer, cursor_buffer_position) =
7073 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7074
7075 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7076 self.discard_edit_prediction(false, cx);
7077 return None;
7078 }
7079
7080 if !user_requested
7081 && (!self.should_show_edit_predictions()
7082 || !self.is_focused(window)
7083 || buffer.read(cx).is_empty())
7084 {
7085 self.discard_edit_prediction(false, cx);
7086 return None;
7087 }
7088
7089 self.update_visible_edit_prediction(window, cx);
7090 provider.refresh(
7091 self.project.clone(),
7092 buffer,
7093 cursor_buffer_position,
7094 debounce,
7095 cx,
7096 );
7097 Some(())
7098 }
7099
7100 fn show_edit_predictions_in_menu(&self) -> bool {
7101 match self.edit_prediction_settings {
7102 EditPredictionSettings::Disabled => false,
7103 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7104 }
7105 }
7106
7107 pub fn edit_predictions_enabled(&self) -> bool {
7108 match self.edit_prediction_settings {
7109 EditPredictionSettings::Disabled => false,
7110 EditPredictionSettings::Enabled { .. } => true,
7111 }
7112 }
7113
7114 fn edit_prediction_requires_modifier(&self) -> bool {
7115 match self.edit_prediction_settings {
7116 EditPredictionSettings::Disabled => false,
7117 EditPredictionSettings::Enabled {
7118 preview_requires_modifier,
7119 ..
7120 } => preview_requires_modifier,
7121 }
7122 }
7123
7124 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7125 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7126 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7127 self.discard_edit_prediction(false, cx);
7128 } else {
7129 let selection = self.selections.newest_anchor();
7130 let cursor = selection.head();
7131
7132 if let Some((buffer, cursor_buffer_position)) =
7133 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7134 {
7135 self.edit_prediction_settings =
7136 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7137 }
7138 }
7139 }
7140
7141 fn edit_prediction_settings_at_position(
7142 &self,
7143 buffer: &Entity<Buffer>,
7144 buffer_position: language::Anchor,
7145 cx: &App,
7146 ) -> EditPredictionSettings {
7147 if !self.mode.is_full()
7148 || !self.show_edit_predictions_override.unwrap_or(true)
7149 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7150 {
7151 return EditPredictionSettings::Disabled;
7152 }
7153
7154 let buffer = buffer.read(cx);
7155
7156 let file = buffer.file();
7157
7158 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7159 return EditPredictionSettings::Disabled;
7160 };
7161
7162 let by_provider = matches!(
7163 self.menu_edit_predictions_policy,
7164 MenuEditPredictionsPolicy::ByProvider
7165 );
7166
7167 let show_in_menu = by_provider
7168 && self
7169 .edit_prediction_provider
7170 .as_ref()
7171 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7172
7173 let preview_requires_modifier =
7174 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7175
7176 EditPredictionSettings::Enabled {
7177 show_in_menu,
7178 preview_requires_modifier,
7179 }
7180 }
7181
7182 fn should_show_edit_predictions(&self) -> bool {
7183 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7184 }
7185
7186 pub fn edit_prediction_preview_is_active(&self) -> bool {
7187 matches!(
7188 self.edit_prediction_preview,
7189 EditPredictionPreview::Active { .. }
7190 )
7191 }
7192
7193 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7194 let cursor = self.selections.newest_anchor().head();
7195 if let Some((buffer, cursor_position)) =
7196 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7197 {
7198 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7199 } else {
7200 false
7201 }
7202 }
7203
7204 pub fn supports_minimap(&self, cx: &App) -> bool {
7205 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7206 }
7207
7208 fn edit_predictions_enabled_in_buffer(
7209 &self,
7210 buffer: &Entity<Buffer>,
7211 buffer_position: language::Anchor,
7212 cx: &App,
7213 ) -> bool {
7214 maybe!({
7215 if self.read_only(cx) {
7216 return Some(false);
7217 }
7218 let provider = self.edit_prediction_provider()?;
7219 if !provider.is_enabled(buffer, buffer_position, cx) {
7220 return Some(false);
7221 }
7222 let buffer = buffer.read(cx);
7223 let Some(file) = buffer.file() else {
7224 return Some(true);
7225 };
7226 let settings = all_language_settings(Some(file), cx);
7227 Some(settings.edit_predictions_enabled_for_file(file, cx))
7228 })
7229 .unwrap_or(false)
7230 }
7231
7232 fn cycle_edit_prediction(
7233 &mut self,
7234 direction: Direction,
7235 window: &mut Window,
7236 cx: &mut Context<Self>,
7237 ) -> Option<()> {
7238 let provider = self.edit_prediction_provider()?;
7239 let cursor = self.selections.newest_anchor().head();
7240 let (buffer, cursor_buffer_position) =
7241 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7242 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7243 return None;
7244 }
7245
7246 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7247 self.update_visible_edit_prediction(window, cx);
7248
7249 Some(())
7250 }
7251
7252 pub fn show_edit_prediction(
7253 &mut self,
7254 _: &ShowEditPrediction,
7255 window: &mut Window,
7256 cx: &mut Context<Self>,
7257 ) {
7258 if !self.has_active_edit_prediction() {
7259 self.refresh_edit_prediction(false, true, window, cx);
7260 return;
7261 }
7262
7263 self.update_visible_edit_prediction(window, cx);
7264 }
7265
7266 pub fn display_cursor_names(
7267 &mut self,
7268 _: &DisplayCursorNames,
7269 window: &mut Window,
7270 cx: &mut Context<Self>,
7271 ) {
7272 self.show_cursor_names(window, cx);
7273 }
7274
7275 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7276 self.show_cursor_names = true;
7277 cx.notify();
7278 cx.spawn_in(window, async move |this, cx| {
7279 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7280 this.update(cx, |this, cx| {
7281 this.show_cursor_names = false;
7282 cx.notify()
7283 })
7284 .ok()
7285 })
7286 .detach();
7287 }
7288
7289 pub fn next_edit_prediction(
7290 &mut self,
7291 _: &NextEditPrediction,
7292 window: &mut Window,
7293 cx: &mut Context<Self>,
7294 ) {
7295 if self.has_active_edit_prediction() {
7296 self.cycle_edit_prediction(Direction::Next, window, cx);
7297 } else {
7298 let is_copilot_disabled = self
7299 .refresh_edit_prediction(false, true, window, cx)
7300 .is_none();
7301 if is_copilot_disabled {
7302 cx.propagate();
7303 }
7304 }
7305 }
7306
7307 pub fn previous_edit_prediction(
7308 &mut self,
7309 _: &PreviousEditPrediction,
7310 window: &mut Window,
7311 cx: &mut Context<Self>,
7312 ) {
7313 if self.has_active_edit_prediction() {
7314 self.cycle_edit_prediction(Direction::Prev, window, cx);
7315 } else {
7316 let is_copilot_disabled = self
7317 .refresh_edit_prediction(false, true, window, cx)
7318 .is_none();
7319 if is_copilot_disabled {
7320 cx.propagate();
7321 }
7322 }
7323 }
7324
7325 pub fn accept_edit_prediction(
7326 &mut self,
7327 _: &AcceptEditPrediction,
7328 window: &mut Window,
7329 cx: &mut Context<Self>,
7330 ) {
7331 if self.show_edit_predictions_in_menu() {
7332 self.hide_context_menu(window, cx);
7333 }
7334
7335 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7336 return;
7337 };
7338
7339 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7340
7341 match &active_edit_prediction.completion {
7342 EditPrediction::Move { target, .. } => {
7343 let target = *target;
7344
7345 if let Some(position_map) = &self.last_position_map {
7346 if position_map
7347 .visible_row_range
7348 .contains(&target.to_display_point(&position_map.snapshot).row())
7349 || !self.edit_prediction_requires_modifier()
7350 {
7351 self.unfold_ranges(&[target..target], true, false, cx);
7352 // Note that this is also done in vim's handler of the Tab action.
7353 self.change_selections(
7354 SelectionEffects::scroll(Autoscroll::newest()),
7355 window,
7356 cx,
7357 |selections| {
7358 selections.select_anchor_ranges([target..target]);
7359 },
7360 );
7361 self.clear_row_highlights::<EditPredictionPreview>();
7362
7363 self.edit_prediction_preview
7364 .set_previous_scroll_position(None);
7365 } else {
7366 self.edit_prediction_preview
7367 .set_previous_scroll_position(Some(
7368 position_map.snapshot.scroll_anchor,
7369 ));
7370
7371 self.highlight_rows::<EditPredictionPreview>(
7372 target..target,
7373 cx.theme().colors().editor_highlighted_line_background,
7374 RowHighlightOptions {
7375 autoscroll: true,
7376 ..Default::default()
7377 },
7378 cx,
7379 );
7380 self.request_autoscroll(Autoscroll::fit(), cx);
7381 }
7382 }
7383 }
7384 EditPrediction::Edit { edits, .. } => {
7385 if let Some(provider) = self.edit_prediction_provider() {
7386 provider.accept(cx);
7387 }
7388
7389 // Store the transaction ID and selections before applying the edit
7390 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7391
7392 let snapshot = self.buffer.read(cx).snapshot(cx);
7393 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7394
7395 self.buffer.update(cx, |buffer, cx| {
7396 buffer.edit(edits.iter().cloned(), None, cx)
7397 });
7398
7399 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7400 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7401 });
7402
7403 let selections = self.selections.disjoint_anchors();
7404 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7405 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7406 if has_new_transaction {
7407 self.selection_history
7408 .insert_transaction(transaction_id_now, selections);
7409 }
7410 }
7411
7412 self.update_visible_edit_prediction(window, cx);
7413 if self.active_edit_prediction.is_none() {
7414 self.refresh_edit_prediction(true, true, window, cx);
7415 }
7416
7417 cx.notify();
7418 }
7419 }
7420
7421 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7422 }
7423
7424 pub fn accept_partial_edit_prediction(
7425 &mut self,
7426 _: &AcceptPartialEditPrediction,
7427 window: &mut Window,
7428 cx: &mut Context<Self>,
7429 ) {
7430 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7431 return;
7432 };
7433 if self.selections.count() != 1 {
7434 return;
7435 }
7436
7437 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7438
7439 match &active_edit_prediction.completion {
7440 EditPrediction::Move { target, .. } => {
7441 let target = *target;
7442 self.change_selections(
7443 SelectionEffects::scroll(Autoscroll::newest()),
7444 window,
7445 cx,
7446 |selections| {
7447 selections.select_anchor_ranges([target..target]);
7448 },
7449 );
7450 }
7451 EditPrediction::Edit { edits, .. } => {
7452 // Find an insertion that starts at the cursor position.
7453 let snapshot = self.buffer.read(cx).snapshot(cx);
7454 let cursor_offset = self.selections.newest::<usize>(cx).head();
7455 let insertion = edits.iter().find_map(|(range, text)| {
7456 let range = range.to_offset(&snapshot);
7457 if range.is_empty() && range.start == cursor_offset {
7458 Some(text)
7459 } else {
7460 None
7461 }
7462 });
7463
7464 if let Some(text) = insertion {
7465 let mut partial_completion = text
7466 .chars()
7467 .by_ref()
7468 .take_while(|c| c.is_alphabetic())
7469 .collect::<String>();
7470 if partial_completion.is_empty() {
7471 partial_completion = text
7472 .chars()
7473 .by_ref()
7474 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7475 .collect::<String>();
7476 }
7477
7478 cx.emit(EditorEvent::InputHandled {
7479 utf16_range_to_replace: None,
7480 text: partial_completion.clone().into(),
7481 });
7482
7483 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7484
7485 self.refresh_edit_prediction(true, true, window, cx);
7486 cx.notify();
7487 } else {
7488 self.accept_edit_prediction(&Default::default(), window, cx);
7489 }
7490 }
7491 }
7492 }
7493
7494 fn discard_edit_prediction(
7495 &mut self,
7496 should_report_edit_prediction_event: bool,
7497 cx: &mut Context<Self>,
7498 ) -> bool {
7499 if should_report_edit_prediction_event {
7500 let completion_id = self
7501 .active_edit_prediction
7502 .as_ref()
7503 .and_then(|active_completion| active_completion.completion_id.clone());
7504
7505 self.report_edit_prediction_event(completion_id, false, cx);
7506 }
7507
7508 if let Some(provider) = self.edit_prediction_provider() {
7509 provider.discard(cx);
7510 }
7511
7512 self.take_active_edit_prediction(cx)
7513 }
7514
7515 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7516 let Some(provider) = self.edit_prediction_provider() else {
7517 return;
7518 };
7519
7520 let Some((_, buffer, _)) = self
7521 .buffer
7522 .read(cx)
7523 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7524 else {
7525 return;
7526 };
7527
7528 let extension = buffer
7529 .read(cx)
7530 .file()
7531 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7532
7533 let event_type = match accepted {
7534 true => "Edit Prediction Accepted",
7535 false => "Edit Prediction Discarded",
7536 };
7537 telemetry::event!(
7538 event_type,
7539 provider = provider.name(),
7540 prediction_id = id,
7541 suggestion_accepted = accepted,
7542 file_extension = extension,
7543 );
7544 }
7545
7546 pub fn has_active_edit_prediction(&self) -> bool {
7547 self.active_edit_prediction.is_some()
7548 }
7549
7550 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7551 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7552 return false;
7553 };
7554
7555 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7556 self.clear_highlights::<EditPredictionHighlight>(cx);
7557 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7558 true
7559 }
7560
7561 /// Returns true when we're displaying the edit prediction popover below the cursor
7562 /// like we are not previewing and the LSP autocomplete menu is visible
7563 /// or we are in `when_holding_modifier` mode.
7564 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7565 if self.edit_prediction_preview_is_active()
7566 || !self.show_edit_predictions_in_menu()
7567 || !self.edit_predictions_enabled()
7568 {
7569 return false;
7570 }
7571
7572 if self.has_visible_completions_menu() {
7573 return true;
7574 }
7575
7576 has_completion && self.edit_prediction_requires_modifier()
7577 }
7578
7579 fn handle_modifiers_changed(
7580 &mut self,
7581 modifiers: Modifiers,
7582 position_map: &PositionMap,
7583 window: &mut Window,
7584 cx: &mut Context<Self>,
7585 ) {
7586 if self.show_edit_predictions_in_menu() {
7587 self.update_edit_prediction_preview(&modifiers, window, cx);
7588 }
7589
7590 self.update_selection_mode(&modifiers, position_map, window, cx);
7591
7592 let mouse_position = window.mouse_position();
7593 if !position_map.text_hitbox.is_hovered(window) {
7594 return;
7595 }
7596
7597 self.update_hovered_link(
7598 position_map.point_for_position(mouse_position),
7599 &position_map.snapshot,
7600 modifiers,
7601 window,
7602 cx,
7603 )
7604 }
7605
7606 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7607 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7608 if invert {
7609 match multi_cursor_setting {
7610 MultiCursorModifier::Alt => modifiers.alt,
7611 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7612 }
7613 } else {
7614 match multi_cursor_setting {
7615 MultiCursorModifier::Alt => modifiers.secondary(),
7616 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7617 }
7618 }
7619 }
7620
7621 fn columnar_selection_mode(
7622 modifiers: &Modifiers,
7623 cx: &mut Context<Self>,
7624 ) -> Option<ColumnarMode> {
7625 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7626 if Self::multi_cursor_modifier(false, modifiers, cx) {
7627 Some(ColumnarMode::FromMouse)
7628 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7629 Some(ColumnarMode::FromSelection)
7630 } else {
7631 None
7632 }
7633 } else {
7634 None
7635 }
7636 }
7637
7638 fn update_selection_mode(
7639 &mut self,
7640 modifiers: &Modifiers,
7641 position_map: &PositionMap,
7642 window: &mut Window,
7643 cx: &mut Context<Self>,
7644 ) {
7645 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7646 return;
7647 };
7648 if self.selections.pending.is_none() {
7649 return;
7650 }
7651
7652 let mouse_position = window.mouse_position();
7653 let point_for_position = position_map.point_for_position(mouse_position);
7654 let position = point_for_position.previous_valid;
7655
7656 self.select(
7657 SelectPhase::BeginColumnar {
7658 position,
7659 reset: false,
7660 mode,
7661 goal_column: point_for_position.exact_unclipped.column(),
7662 },
7663 window,
7664 cx,
7665 );
7666 }
7667
7668 fn update_edit_prediction_preview(
7669 &mut self,
7670 modifiers: &Modifiers,
7671 window: &mut Window,
7672 cx: &mut Context<Self>,
7673 ) {
7674 let mut modifiers_held = false;
7675 if let Some(accept_keystroke) = self
7676 .accept_edit_prediction_keybind(false, window, cx)
7677 .keystroke()
7678 {
7679 modifiers_held = modifiers_held
7680 || (&accept_keystroke.modifiers == modifiers
7681 && accept_keystroke.modifiers.modified());
7682 };
7683 if let Some(accept_partial_keystroke) = self
7684 .accept_edit_prediction_keybind(true, window, cx)
7685 .keystroke()
7686 {
7687 modifiers_held = modifiers_held
7688 || (&accept_partial_keystroke.modifiers == modifiers
7689 && accept_partial_keystroke.modifiers.modified());
7690 }
7691
7692 if modifiers_held {
7693 if matches!(
7694 self.edit_prediction_preview,
7695 EditPredictionPreview::Inactive { .. }
7696 ) {
7697 self.edit_prediction_preview = EditPredictionPreview::Active {
7698 previous_scroll_position: None,
7699 since: Instant::now(),
7700 };
7701
7702 self.update_visible_edit_prediction(window, cx);
7703 cx.notify();
7704 }
7705 } else if let EditPredictionPreview::Active {
7706 previous_scroll_position,
7707 since,
7708 } = self.edit_prediction_preview
7709 {
7710 if let (Some(previous_scroll_position), Some(position_map)) =
7711 (previous_scroll_position, self.last_position_map.as_ref())
7712 {
7713 self.set_scroll_position(
7714 previous_scroll_position
7715 .scroll_position(&position_map.snapshot.display_snapshot),
7716 window,
7717 cx,
7718 );
7719 }
7720
7721 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7722 released_too_fast: since.elapsed() < Duration::from_millis(200),
7723 };
7724 self.clear_row_highlights::<EditPredictionPreview>();
7725 self.update_visible_edit_prediction(window, cx);
7726 cx.notify();
7727 }
7728 }
7729
7730 fn update_visible_edit_prediction(
7731 &mut self,
7732 _window: &mut Window,
7733 cx: &mut Context<Self>,
7734 ) -> Option<()> {
7735 if DisableAiSettings::get_global(cx).disable_ai {
7736 return None;
7737 }
7738
7739 let selection = self.selections.newest_anchor();
7740 let cursor = selection.head();
7741 let multibuffer = self.buffer.read(cx).snapshot(cx);
7742 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7743 let excerpt_id = cursor.excerpt_id;
7744
7745 let show_in_menu = self.show_edit_predictions_in_menu();
7746 let completions_menu_has_precedence = !show_in_menu
7747 && (self.context_menu.borrow().is_some()
7748 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7749
7750 if completions_menu_has_precedence
7751 || !offset_selection.is_empty()
7752 || self
7753 .active_edit_prediction
7754 .as_ref()
7755 .is_some_and(|completion| {
7756 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7757 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7758 !invalidation_range.contains(&offset_selection.head())
7759 })
7760 {
7761 self.discard_edit_prediction(false, cx);
7762 return None;
7763 }
7764
7765 self.take_active_edit_prediction(cx);
7766 let Some(provider) = self.edit_prediction_provider() else {
7767 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7768 return None;
7769 };
7770
7771 let (buffer, cursor_buffer_position) =
7772 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7773
7774 self.edit_prediction_settings =
7775 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7776
7777 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7778 self.discard_edit_prediction(false, cx);
7779 return None;
7780 };
7781
7782 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7783
7784 if self.edit_prediction_indent_conflict {
7785 let cursor_point = cursor.to_point(&multibuffer);
7786
7787 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7788
7789 if let Some((_, indent)) = indents.iter().next()
7790 && indent.len == cursor_point.column
7791 {
7792 self.edit_prediction_indent_conflict = false;
7793 }
7794 }
7795
7796 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7797 let edits = edit_prediction
7798 .edits
7799 .into_iter()
7800 .flat_map(|(range, new_text)| {
7801 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7802 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7803 Some((start..end, new_text))
7804 })
7805 .collect::<Vec<_>>();
7806 if edits.is_empty() {
7807 return None;
7808 }
7809
7810 let first_edit_start = edits.first().unwrap().0.start;
7811 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7812 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7813
7814 let last_edit_end = edits.last().unwrap().0.end;
7815 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7816 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7817
7818 let cursor_row = cursor.to_point(&multibuffer).row;
7819
7820 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7821
7822 let mut inlay_ids = Vec::new();
7823 let invalidation_row_range;
7824 let move_invalidation_row_range = if cursor_row < edit_start_row {
7825 Some(cursor_row..edit_end_row)
7826 } else if cursor_row > edit_end_row {
7827 Some(edit_start_row..cursor_row)
7828 } else {
7829 None
7830 };
7831 let supports_jump = self
7832 .edit_prediction_provider
7833 .as_ref()
7834 .map(|provider| provider.provider.supports_jump_to_edit())
7835 .unwrap_or(true);
7836
7837 let is_move = supports_jump
7838 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7839 let completion = if is_move {
7840 invalidation_row_range =
7841 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7842 let target = first_edit_start;
7843 EditPrediction::Move { target, snapshot }
7844 } else {
7845 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7846 && !self.edit_predictions_hidden_for_vim_mode;
7847
7848 if show_completions_in_buffer {
7849 if edits
7850 .iter()
7851 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7852 {
7853 let mut inlays = Vec::new();
7854 for (range, new_text) in &edits {
7855 let inlay = Inlay::edit_prediction(
7856 post_inc(&mut self.next_inlay_id),
7857 range.start,
7858 new_text.as_str(),
7859 );
7860 inlay_ids.push(inlay.id);
7861 inlays.push(inlay);
7862 }
7863
7864 self.splice_inlays(&[], inlays, cx);
7865 } else {
7866 let background_color = cx.theme().status().deleted_background;
7867 self.highlight_text::<EditPredictionHighlight>(
7868 edits.iter().map(|(range, _)| range.clone()).collect(),
7869 HighlightStyle {
7870 background_color: Some(background_color),
7871 ..Default::default()
7872 },
7873 cx,
7874 );
7875 }
7876 }
7877
7878 invalidation_row_range = edit_start_row..edit_end_row;
7879
7880 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7881 if provider.show_tab_accept_marker() {
7882 EditDisplayMode::TabAccept
7883 } else {
7884 EditDisplayMode::Inline
7885 }
7886 } else {
7887 EditDisplayMode::DiffPopover
7888 };
7889
7890 EditPrediction::Edit {
7891 edits,
7892 edit_preview: edit_prediction.edit_preview,
7893 display_mode,
7894 snapshot,
7895 }
7896 };
7897
7898 let invalidation_range = multibuffer
7899 .anchor_before(Point::new(invalidation_row_range.start, 0))
7900 ..multibuffer.anchor_after(Point::new(
7901 invalidation_row_range.end,
7902 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7903 ));
7904
7905 self.stale_edit_prediction_in_menu = None;
7906 self.active_edit_prediction = Some(EditPredictionState {
7907 inlay_ids,
7908 completion,
7909 completion_id: edit_prediction.id,
7910 invalidation_range,
7911 });
7912
7913 cx.notify();
7914
7915 Some(())
7916 }
7917
7918 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7919 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7920 }
7921
7922 fn clear_tasks(&mut self) {
7923 self.tasks.clear()
7924 }
7925
7926 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7927 if self.tasks.insert(key, value).is_some() {
7928 // This case should hopefully be rare, but just in case...
7929 log::error!(
7930 "multiple different run targets found on a single line, only the last target will be rendered"
7931 )
7932 }
7933 }
7934
7935 /// Get all display points of breakpoints that will be rendered within editor
7936 ///
7937 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7938 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7939 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7940 fn active_breakpoints(
7941 &self,
7942 range: Range<DisplayRow>,
7943 window: &mut Window,
7944 cx: &mut Context<Self>,
7945 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7946 let mut breakpoint_display_points = HashMap::default();
7947
7948 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7949 return breakpoint_display_points;
7950 };
7951
7952 let snapshot = self.snapshot(window, cx);
7953
7954 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7955 let Some(project) = self.project() else {
7956 return breakpoint_display_points;
7957 };
7958
7959 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7960 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7961
7962 for (buffer_snapshot, range, excerpt_id) in
7963 multi_buffer_snapshot.range_to_buffer_ranges(range)
7964 {
7965 let Some(buffer) = project
7966 .read(cx)
7967 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7968 else {
7969 continue;
7970 };
7971 let breakpoints = breakpoint_store.read(cx).breakpoints(
7972 &buffer,
7973 Some(
7974 buffer_snapshot.anchor_before(range.start)
7975 ..buffer_snapshot.anchor_after(range.end),
7976 ),
7977 buffer_snapshot,
7978 cx,
7979 );
7980 for (breakpoint, state) in breakpoints {
7981 let multi_buffer_anchor =
7982 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7983 let position = multi_buffer_anchor
7984 .to_point(multi_buffer_snapshot)
7985 .to_display_point(&snapshot);
7986
7987 breakpoint_display_points.insert(
7988 position.row(),
7989 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7990 );
7991 }
7992 }
7993
7994 breakpoint_display_points
7995 }
7996
7997 fn breakpoint_context_menu(
7998 &self,
7999 anchor: Anchor,
8000 window: &mut Window,
8001 cx: &mut Context<Self>,
8002 ) -> Entity<ui::ContextMenu> {
8003 let weak_editor = cx.weak_entity();
8004 let focus_handle = self.focus_handle(cx);
8005
8006 let row = self
8007 .buffer
8008 .read(cx)
8009 .snapshot(cx)
8010 .summary_for_anchor::<Point>(&anchor)
8011 .row;
8012
8013 let breakpoint = self
8014 .breakpoint_at_row(row, window, cx)
8015 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8016
8017 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8018 "Edit Log Breakpoint"
8019 } else {
8020 "Set Log Breakpoint"
8021 };
8022
8023 let condition_breakpoint_msg = if breakpoint
8024 .as_ref()
8025 .is_some_and(|bp| bp.1.condition.is_some())
8026 {
8027 "Edit Condition Breakpoint"
8028 } else {
8029 "Set Condition Breakpoint"
8030 };
8031
8032 let hit_condition_breakpoint_msg = if breakpoint
8033 .as_ref()
8034 .is_some_and(|bp| bp.1.hit_condition.is_some())
8035 {
8036 "Edit Hit Condition Breakpoint"
8037 } else {
8038 "Set Hit Condition Breakpoint"
8039 };
8040
8041 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8042 "Unset Breakpoint"
8043 } else {
8044 "Set Breakpoint"
8045 };
8046
8047 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8048
8049 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8050 BreakpointState::Enabled => Some("Disable"),
8051 BreakpointState::Disabled => Some("Enable"),
8052 });
8053
8054 let (anchor, breakpoint) =
8055 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8056
8057 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8058 menu.on_blur_subscription(Subscription::new(|| {}))
8059 .context(focus_handle)
8060 .when(run_to_cursor, |this| {
8061 let weak_editor = weak_editor.clone();
8062 this.entry("Run to cursor", None, move |window, cx| {
8063 weak_editor
8064 .update(cx, |editor, cx| {
8065 editor.change_selections(
8066 SelectionEffects::no_scroll(),
8067 window,
8068 cx,
8069 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8070 );
8071 })
8072 .ok();
8073
8074 window.dispatch_action(Box::new(RunToCursor), cx);
8075 })
8076 .separator()
8077 })
8078 .when_some(toggle_state_msg, |this, msg| {
8079 this.entry(msg, None, {
8080 let weak_editor = weak_editor.clone();
8081 let breakpoint = breakpoint.clone();
8082 move |_window, cx| {
8083 weak_editor
8084 .update(cx, |this, cx| {
8085 this.edit_breakpoint_at_anchor(
8086 anchor,
8087 breakpoint.as_ref().clone(),
8088 BreakpointEditAction::InvertState,
8089 cx,
8090 );
8091 })
8092 .log_err();
8093 }
8094 })
8095 })
8096 .entry(set_breakpoint_msg, None, {
8097 let weak_editor = weak_editor.clone();
8098 let breakpoint = breakpoint.clone();
8099 move |_window, cx| {
8100 weak_editor
8101 .update(cx, |this, cx| {
8102 this.edit_breakpoint_at_anchor(
8103 anchor,
8104 breakpoint.as_ref().clone(),
8105 BreakpointEditAction::Toggle,
8106 cx,
8107 );
8108 })
8109 .log_err();
8110 }
8111 })
8112 .entry(log_breakpoint_msg, None, {
8113 let breakpoint = breakpoint.clone();
8114 let weak_editor = weak_editor.clone();
8115 move |window, cx| {
8116 weak_editor
8117 .update(cx, |this, cx| {
8118 this.add_edit_breakpoint_block(
8119 anchor,
8120 breakpoint.as_ref(),
8121 BreakpointPromptEditAction::Log,
8122 window,
8123 cx,
8124 );
8125 })
8126 .log_err();
8127 }
8128 })
8129 .entry(condition_breakpoint_msg, None, {
8130 let breakpoint = breakpoint.clone();
8131 let weak_editor = weak_editor.clone();
8132 move |window, cx| {
8133 weak_editor
8134 .update(cx, |this, cx| {
8135 this.add_edit_breakpoint_block(
8136 anchor,
8137 breakpoint.as_ref(),
8138 BreakpointPromptEditAction::Condition,
8139 window,
8140 cx,
8141 );
8142 })
8143 .log_err();
8144 }
8145 })
8146 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8147 weak_editor
8148 .update(cx, |this, cx| {
8149 this.add_edit_breakpoint_block(
8150 anchor,
8151 breakpoint.as_ref(),
8152 BreakpointPromptEditAction::HitCondition,
8153 window,
8154 cx,
8155 );
8156 })
8157 .log_err();
8158 })
8159 })
8160 }
8161
8162 fn render_breakpoint(
8163 &self,
8164 position: Anchor,
8165 row: DisplayRow,
8166 breakpoint: &Breakpoint,
8167 state: Option<BreakpointSessionState>,
8168 cx: &mut Context<Self>,
8169 ) -> IconButton {
8170 let is_rejected = state.is_some_and(|s| !s.verified);
8171 // Is it a breakpoint that shows up when hovering over gutter?
8172 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8173 (false, false),
8174 |PhantomBreakpointIndicator {
8175 is_active,
8176 display_row,
8177 collides_with_existing_breakpoint,
8178 }| {
8179 (
8180 is_active && display_row == row,
8181 collides_with_existing_breakpoint,
8182 )
8183 },
8184 );
8185
8186 let (color, icon) = {
8187 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8188 (false, false) => ui::IconName::DebugBreakpoint,
8189 (true, false) => ui::IconName::DebugLogBreakpoint,
8190 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8191 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8192 };
8193
8194 let color = if is_phantom {
8195 Color::Hint
8196 } else if is_rejected {
8197 Color::Disabled
8198 } else {
8199 Color::Debugger
8200 };
8201
8202 (color, icon)
8203 };
8204
8205 let breakpoint = Arc::from(breakpoint.clone());
8206
8207 let alt_as_text = gpui::Keystroke {
8208 modifiers: Modifiers::secondary_key(),
8209 ..Default::default()
8210 };
8211 let primary_action_text = if breakpoint.is_disabled() {
8212 "Enable breakpoint"
8213 } else if is_phantom && !collides_with_existing {
8214 "Set breakpoint"
8215 } else {
8216 "Unset breakpoint"
8217 };
8218 let focus_handle = self.focus_handle.clone();
8219
8220 let meta = if is_rejected {
8221 SharedString::from("No executable code is associated with this line.")
8222 } else if collides_with_existing && !breakpoint.is_disabled() {
8223 SharedString::from(format!(
8224 "{alt_as_text}-click to disable,\nright-click for more options."
8225 ))
8226 } else {
8227 SharedString::from("Right-click for more options.")
8228 };
8229 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8230 .icon_size(IconSize::XSmall)
8231 .size(ui::ButtonSize::None)
8232 .when(is_rejected, |this| {
8233 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8234 })
8235 .icon_color(color)
8236 .style(ButtonStyle::Transparent)
8237 .on_click(cx.listener({
8238 move |editor, event: &ClickEvent, window, cx| {
8239 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8240 BreakpointEditAction::InvertState
8241 } else {
8242 BreakpointEditAction::Toggle
8243 };
8244
8245 window.focus(&editor.focus_handle(cx));
8246 editor.edit_breakpoint_at_anchor(
8247 position,
8248 breakpoint.as_ref().clone(),
8249 edit_action,
8250 cx,
8251 );
8252 }
8253 }))
8254 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8255 editor.set_breakpoint_context_menu(
8256 row,
8257 Some(position),
8258 event.position(),
8259 window,
8260 cx,
8261 );
8262 }))
8263 .tooltip(move |window, cx| {
8264 Tooltip::with_meta_in(
8265 primary_action_text,
8266 Some(&ToggleBreakpoint),
8267 meta.clone(),
8268 &focus_handle,
8269 window,
8270 cx,
8271 )
8272 })
8273 }
8274
8275 fn build_tasks_context(
8276 project: &Entity<Project>,
8277 buffer: &Entity<Buffer>,
8278 buffer_row: u32,
8279 tasks: &Arc<RunnableTasks>,
8280 cx: &mut Context<Self>,
8281 ) -> Task<Option<task::TaskContext>> {
8282 let position = Point::new(buffer_row, tasks.column);
8283 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8284 let location = Location {
8285 buffer: buffer.clone(),
8286 range: range_start..range_start,
8287 };
8288 // Fill in the environmental variables from the tree-sitter captures
8289 let mut captured_task_variables = TaskVariables::default();
8290 for (capture_name, value) in tasks.extra_variables.clone() {
8291 captured_task_variables.insert(
8292 task::VariableName::Custom(capture_name.into()),
8293 value.clone(),
8294 );
8295 }
8296 project.update(cx, |project, cx| {
8297 project.task_store().update(cx, |task_store, cx| {
8298 task_store.task_context_for_location(captured_task_variables, location, cx)
8299 })
8300 })
8301 }
8302
8303 pub fn spawn_nearest_task(
8304 &mut self,
8305 action: &SpawnNearestTask,
8306 window: &mut Window,
8307 cx: &mut Context<Self>,
8308 ) {
8309 let Some((workspace, _)) = self.workspace.clone() else {
8310 return;
8311 };
8312 let Some(project) = self.project.clone() else {
8313 return;
8314 };
8315
8316 // Try to find a closest, enclosing node using tree-sitter that has a task
8317 let Some((buffer, buffer_row, tasks)) = self
8318 .find_enclosing_node_task(cx)
8319 // Or find the task that's closest in row-distance.
8320 .or_else(|| self.find_closest_task(cx))
8321 else {
8322 return;
8323 };
8324
8325 let reveal_strategy = action.reveal;
8326 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8327 cx.spawn_in(window, async move |_, cx| {
8328 let context = task_context.await?;
8329 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8330
8331 let resolved = &mut resolved_task.resolved;
8332 resolved.reveal = reveal_strategy;
8333
8334 workspace
8335 .update_in(cx, |workspace, window, cx| {
8336 workspace.schedule_resolved_task(
8337 task_source_kind,
8338 resolved_task,
8339 false,
8340 window,
8341 cx,
8342 );
8343 })
8344 .ok()
8345 })
8346 .detach();
8347 }
8348
8349 fn find_closest_task(
8350 &mut self,
8351 cx: &mut Context<Self>,
8352 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8353 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8354
8355 let ((buffer_id, row), tasks) = self
8356 .tasks
8357 .iter()
8358 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8359
8360 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8361 let tasks = Arc::new(tasks.to_owned());
8362 Some((buffer, *row, tasks))
8363 }
8364
8365 fn find_enclosing_node_task(
8366 &mut self,
8367 cx: &mut Context<Self>,
8368 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8369 let snapshot = self.buffer.read(cx).snapshot(cx);
8370 let offset = self.selections.newest::<usize>(cx).head();
8371 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8372 let buffer_id = excerpt.buffer().remote_id();
8373
8374 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8375 let mut cursor = layer.node().walk();
8376
8377 while cursor.goto_first_child_for_byte(offset).is_some() {
8378 if cursor.node().end_byte() == offset {
8379 cursor.goto_next_sibling();
8380 }
8381 }
8382
8383 // Ascend to the smallest ancestor that contains the range and has a task.
8384 loop {
8385 let node = cursor.node();
8386 let node_range = node.byte_range();
8387 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8388
8389 // Check if this node contains our offset
8390 if node_range.start <= offset && node_range.end >= offset {
8391 // If it contains offset, check for task
8392 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8393 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8394 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8395 }
8396 }
8397
8398 if !cursor.goto_parent() {
8399 break;
8400 }
8401 }
8402 None
8403 }
8404
8405 fn render_run_indicator(
8406 &self,
8407 _style: &EditorStyle,
8408 is_active: bool,
8409 row: DisplayRow,
8410 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8411 cx: &mut Context<Self>,
8412 ) -> IconButton {
8413 let color = Color::Muted;
8414 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8415
8416 IconButton::new(
8417 ("run_indicator", row.0 as usize),
8418 ui::IconName::PlayOutlined,
8419 )
8420 .shape(ui::IconButtonShape::Square)
8421 .icon_size(IconSize::XSmall)
8422 .icon_color(color)
8423 .toggle_state(is_active)
8424 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8425 let quick_launch = match e {
8426 ClickEvent::Keyboard(_) => true,
8427 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8428 };
8429
8430 window.focus(&editor.focus_handle(cx));
8431 editor.toggle_code_actions(
8432 &ToggleCodeActions {
8433 deployed_from: Some(CodeActionSource::RunMenu(row)),
8434 quick_launch,
8435 },
8436 window,
8437 cx,
8438 );
8439 }))
8440 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8441 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8442 }))
8443 }
8444
8445 pub fn context_menu_visible(&self) -> bool {
8446 !self.edit_prediction_preview_is_active()
8447 && self
8448 .context_menu
8449 .borrow()
8450 .as_ref()
8451 .is_some_and(|menu| menu.visible())
8452 }
8453
8454 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8455 self.context_menu
8456 .borrow()
8457 .as_ref()
8458 .map(|menu| menu.origin())
8459 }
8460
8461 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8462 self.context_menu_options = Some(options);
8463 }
8464
8465 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8466 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8467
8468 fn render_edit_prediction_popover(
8469 &mut self,
8470 text_bounds: &Bounds<Pixels>,
8471 content_origin: gpui::Point<Pixels>,
8472 right_margin: Pixels,
8473 editor_snapshot: &EditorSnapshot,
8474 visible_row_range: Range<DisplayRow>,
8475 scroll_top: f32,
8476 scroll_bottom: f32,
8477 line_layouts: &[LineWithInvisibles],
8478 line_height: Pixels,
8479 scroll_pixel_position: gpui::Point<Pixels>,
8480 newest_selection_head: Option<DisplayPoint>,
8481 editor_width: Pixels,
8482 style: &EditorStyle,
8483 window: &mut Window,
8484 cx: &mut App,
8485 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8486 if self.mode().is_minimap() {
8487 return None;
8488 }
8489 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8490
8491 if self.edit_prediction_visible_in_cursor_popover(true) {
8492 return None;
8493 }
8494
8495 match &active_edit_prediction.completion {
8496 EditPrediction::Move { target, .. } => {
8497 let target_display_point = target.to_display_point(editor_snapshot);
8498
8499 if self.edit_prediction_requires_modifier() {
8500 if !self.edit_prediction_preview_is_active() {
8501 return None;
8502 }
8503
8504 self.render_edit_prediction_modifier_jump_popover(
8505 text_bounds,
8506 content_origin,
8507 visible_row_range,
8508 line_layouts,
8509 line_height,
8510 scroll_pixel_position,
8511 newest_selection_head,
8512 target_display_point,
8513 window,
8514 cx,
8515 )
8516 } else {
8517 self.render_edit_prediction_eager_jump_popover(
8518 text_bounds,
8519 content_origin,
8520 editor_snapshot,
8521 visible_row_range,
8522 scroll_top,
8523 scroll_bottom,
8524 line_height,
8525 scroll_pixel_position,
8526 target_display_point,
8527 editor_width,
8528 window,
8529 cx,
8530 )
8531 }
8532 }
8533 EditPrediction::Edit {
8534 display_mode: EditDisplayMode::Inline,
8535 ..
8536 } => None,
8537 EditPrediction::Edit {
8538 display_mode: EditDisplayMode::TabAccept,
8539 edits,
8540 ..
8541 } => {
8542 let range = &edits.first()?.0;
8543 let target_display_point = range.end.to_display_point(editor_snapshot);
8544
8545 self.render_edit_prediction_end_of_line_popover(
8546 "Accept",
8547 editor_snapshot,
8548 visible_row_range,
8549 target_display_point,
8550 line_height,
8551 scroll_pixel_position,
8552 content_origin,
8553 editor_width,
8554 window,
8555 cx,
8556 )
8557 }
8558 EditPrediction::Edit {
8559 edits,
8560 edit_preview,
8561 display_mode: EditDisplayMode::DiffPopover,
8562 snapshot,
8563 } => self.render_edit_prediction_diff_popover(
8564 text_bounds,
8565 content_origin,
8566 right_margin,
8567 editor_snapshot,
8568 visible_row_range,
8569 line_layouts,
8570 line_height,
8571 scroll_pixel_position,
8572 newest_selection_head,
8573 editor_width,
8574 style,
8575 edits,
8576 edit_preview,
8577 snapshot,
8578 window,
8579 cx,
8580 ),
8581 }
8582 }
8583
8584 fn render_edit_prediction_modifier_jump_popover(
8585 &mut self,
8586 text_bounds: &Bounds<Pixels>,
8587 content_origin: gpui::Point<Pixels>,
8588 visible_row_range: Range<DisplayRow>,
8589 line_layouts: &[LineWithInvisibles],
8590 line_height: Pixels,
8591 scroll_pixel_position: gpui::Point<Pixels>,
8592 newest_selection_head: Option<DisplayPoint>,
8593 target_display_point: DisplayPoint,
8594 window: &mut Window,
8595 cx: &mut App,
8596 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8597 let scrolled_content_origin =
8598 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8599
8600 const SCROLL_PADDING_Y: Pixels = px(12.);
8601
8602 if target_display_point.row() < visible_row_range.start {
8603 return self.render_edit_prediction_scroll_popover(
8604 |_| SCROLL_PADDING_Y,
8605 IconName::ArrowUp,
8606 visible_row_range,
8607 line_layouts,
8608 newest_selection_head,
8609 scrolled_content_origin,
8610 window,
8611 cx,
8612 );
8613 } else if target_display_point.row() >= visible_row_range.end {
8614 return self.render_edit_prediction_scroll_popover(
8615 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8616 IconName::ArrowDown,
8617 visible_row_range,
8618 line_layouts,
8619 newest_selection_head,
8620 scrolled_content_origin,
8621 window,
8622 cx,
8623 );
8624 }
8625
8626 const POLE_WIDTH: Pixels = px(2.);
8627
8628 let line_layout =
8629 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8630 let target_column = target_display_point.column() as usize;
8631
8632 let target_x = line_layout.x_for_index(target_column);
8633 let target_y =
8634 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8635
8636 let flag_on_right = target_x < text_bounds.size.width / 2.;
8637
8638 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8639 border_color.l += 0.001;
8640
8641 let mut element = v_flex()
8642 .items_end()
8643 .when(flag_on_right, |el| el.items_start())
8644 .child(if flag_on_right {
8645 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8646 .rounded_bl(px(0.))
8647 .rounded_tl(px(0.))
8648 .border_l_2()
8649 .border_color(border_color)
8650 } else {
8651 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8652 .rounded_br(px(0.))
8653 .rounded_tr(px(0.))
8654 .border_r_2()
8655 .border_color(border_color)
8656 })
8657 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8658 .into_any();
8659
8660 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8661
8662 let mut origin = scrolled_content_origin + point(target_x, target_y)
8663 - point(
8664 if flag_on_right {
8665 POLE_WIDTH
8666 } else {
8667 size.width - POLE_WIDTH
8668 },
8669 size.height - line_height,
8670 );
8671
8672 origin.x = origin.x.max(content_origin.x);
8673
8674 element.prepaint_at(origin, window, cx);
8675
8676 Some((element, origin))
8677 }
8678
8679 fn render_edit_prediction_scroll_popover(
8680 &mut self,
8681 to_y: impl Fn(Size<Pixels>) -> Pixels,
8682 scroll_icon: IconName,
8683 visible_row_range: Range<DisplayRow>,
8684 line_layouts: &[LineWithInvisibles],
8685 newest_selection_head: Option<DisplayPoint>,
8686 scrolled_content_origin: gpui::Point<Pixels>,
8687 window: &mut Window,
8688 cx: &mut App,
8689 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8690 let mut element = self
8691 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8692 .into_any();
8693
8694 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8695
8696 let cursor = newest_selection_head?;
8697 let cursor_row_layout =
8698 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8699 let cursor_column = cursor.column() as usize;
8700
8701 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8702
8703 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8704
8705 element.prepaint_at(origin, window, cx);
8706 Some((element, origin))
8707 }
8708
8709 fn render_edit_prediction_eager_jump_popover(
8710 &mut self,
8711 text_bounds: &Bounds<Pixels>,
8712 content_origin: gpui::Point<Pixels>,
8713 editor_snapshot: &EditorSnapshot,
8714 visible_row_range: Range<DisplayRow>,
8715 scroll_top: f32,
8716 scroll_bottom: f32,
8717 line_height: Pixels,
8718 scroll_pixel_position: gpui::Point<Pixels>,
8719 target_display_point: DisplayPoint,
8720 editor_width: Pixels,
8721 window: &mut Window,
8722 cx: &mut App,
8723 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8724 if target_display_point.row().as_f32() < scroll_top {
8725 let mut element = self
8726 .render_edit_prediction_line_popover(
8727 "Jump to Edit",
8728 Some(IconName::ArrowUp),
8729 window,
8730 cx,
8731 )?
8732 .into_any();
8733
8734 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8735 let offset = point(
8736 (text_bounds.size.width - size.width) / 2.,
8737 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8738 );
8739
8740 let origin = text_bounds.origin + offset;
8741 element.prepaint_at(origin, window, cx);
8742 Some((element, origin))
8743 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8744 let mut element = self
8745 .render_edit_prediction_line_popover(
8746 "Jump to Edit",
8747 Some(IconName::ArrowDown),
8748 window,
8749 cx,
8750 )?
8751 .into_any();
8752
8753 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8754 let offset = point(
8755 (text_bounds.size.width - size.width) / 2.,
8756 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8757 );
8758
8759 let origin = text_bounds.origin + offset;
8760 element.prepaint_at(origin, window, cx);
8761 Some((element, origin))
8762 } else {
8763 self.render_edit_prediction_end_of_line_popover(
8764 "Jump to Edit",
8765 editor_snapshot,
8766 visible_row_range,
8767 target_display_point,
8768 line_height,
8769 scroll_pixel_position,
8770 content_origin,
8771 editor_width,
8772 window,
8773 cx,
8774 )
8775 }
8776 }
8777
8778 fn render_edit_prediction_end_of_line_popover(
8779 self: &mut Editor,
8780 label: &'static str,
8781 editor_snapshot: &EditorSnapshot,
8782 visible_row_range: Range<DisplayRow>,
8783 target_display_point: DisplayPoint,
8784 line_height: Pixels,
8785 scroll_pixel_position: gpui::Point<Pixels>,
8786 content_origin: gpui::Point<Pixels>,
8787 editor_width: Pixels,
8788 window: &mut Window,
8789 cx: &mut App,
8790 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8791 let target_line_end = DisplayPoint::new(
8792 target_display_point.row(),
8793 editor_snapshot.line_len(target_display_point.row()),
8794 );
8795
8796 let mut element = self
8797 .render_edit_prediction_line_popover(label, None, window, cx)?
8798 .into_any();
8799
8800 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8801
8802 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8803
8804 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8805 let mut origin = start_point
8806 + line_origin
8807 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8808 origin.x = origin.x.max(content_origin.x);
8809
8810 let max_x = content_origin.x + editor_width - size.width;
8811
8812 if origin.x > max_x {
8813 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8814
8815 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8816 origin.y += offset;
8817 IconName::ArrowUp
8818 } else {
8819 origin.y -= offset;
8820 IconName::ArrowDown
8821 };
8822
8823 element = self
8824 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8825 .into_any();
8826
8827 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8828
8829 origin.x = content_origin.x + editor_width - size.width - px(2.);
8830 }
8831
8832 element.prepaint_at(origin, window, cx);
8833 Some((element, origin))
8834 }
8835
8836 fn render_edit_prediction_diff_popover(
8837 self: &Editor,
8838 text_bounds: &Bounds<Pixels>,
8839 content_origin: gpui::Point<Pixels>,
8840 right_margin: Pixels,
8841 editor_snapshot: &EditorSnapshot,
8842 visible_row_range: Range<DisplayRow>,
8843 line_layouts: &[LineWithInvisibles],
8844 line_height: Pixels,
8845 scroll_pixel_position: gpui::Point<Pixels>,
8846 newest_selection_head: Option<DisplayPoint>,
8847 editor_width: Pixels,
8848 style: &EditorStyle,
8849 edits: &Vec<(Range<Anchor>, String)>,
8850 edit_preview: &Option<language::EditPreview>,
8851 snapshot: &language::BufferSnapshot,
8852 window: &mut Window,
8853 cx: &mut App,
8854 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8855 let edit_start = edits
8856 .first()
8857 .unwrap()
8858 .0
8859 .start
8860 .to_display_point(editor_snapshot);
8861 let edit_end = edits
8862 .last()
8863 .unwrap()
8864 .0
8865 .end
8866 .to_display_point(editor_snapshot);
8867
8868 let is_visible = visible_row_range.contains(&edit_start.row())
8869 || visible_row_range.contains(&edit_end.row());
8870 if !is_visible {
8871 return None;
8872 }
8873
8874 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8875 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8876 } else {
8877 // Fallback for providers without edit_preview
8878 crate::edit_prediction_fallback_text(edits, cx)
8879 };
8880
8881 let styled_text = highlighted_edits.to_styled_text(&style.text);
8882 let line_count = highlighted_edits.text.lines().count();
8883
8884 const BORDER_WIDTH: Pixels = px(1.);
8885
8886 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8887 let has_keybind = keybind.is_some();
8888
8889 let mut element = h_flex()
8890 .items_start()
8891 .child(
8892 h_flex()
8893 .bg(cx.theme().colors().editor_background)
8894 .border(BORDER_WIDTH)
8895 .shadow_xs()
8896 .border_color(cx.theme().colors().border)
8897 .rounded_l_lg()
8898 .when(line_count > 1, |el| el.rounded_br_lg())
8899 .pr_1()
8900 .child(styled_text),
8901 )
8902 .child(
8903 h_flex()
8904 .h(line_height + BORDER_WIDTH * 2.)
8905 .px_1p5()
8906 .gap_1()
8907 // Workaround: For some reason, there's a gap if we don't do this
8908 .ml(-BORDER_WIDTH)
8909 .shadow(vec![gpui::BoxShadow {
8910 color: gpui::black().opacity(0.05),
8911 offset: point(px(1.), px(1.)),
8912 blur_radius: px(2.),
8913 spread_radius: px(0.),
8914 }])
8915 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8916 .border(BORDER_WIDTH)
8917 .border_color(cx.theme().colors().border)
8918 .rounded_r_lg()
8919 .id("edit_prediction_diff_popover_keybind")
8920 .when(!has_keybind, |el| {
8921 let status_colors = cx.theme().status();
8922
8923 el.bg(status_colors.error_background)
8924 .border_color(status_colors.error.opacity(0.6))
8925 .child(Icon::new(IconName::Info).color(Color::Error))
8926 .cursor_default()
8927 .hoverable_tooltip(move |_window, cx| {
8928 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8929 })
8930 })
8931 .children(keybind),
8932 )
8933 .into_any();
8934
8935 let longest_row =
8936 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8937 let longest_line_width = if visible_row_range.contains(&longest_row) {
8938 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8939 } else {
8940 layout_line(
8941 longest_row,
8942 editor_snapshot,
8943 style,
8944 editor_width,
8945 |_| false,
8946 window,
8947 cx,
8948 )
8949 .width
8950 };
8951
8952 let viewport_bounds =
8953 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8954 right: -right_margin,
8955 ..Default::default()
8956 });
8957
8958 let x_after_longest =
8959 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8960 - scroll_pixel_position.x;
8961
8962 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8963
8964 // Fully visible if it can be displayed within the window (allow overlapping other
8965 // panes). However, this is only allowed if the popover starts within text_bounds.
8966 let can_position_to_the_right = x_after_longest < text_bounds.right()
8967 && x_after_longest + element_bounds.width < viewport_bounds.right();
8968
8969 let mut origin = if can_position_to_the_right {
8970 point(
8971 x_after_longest,
8972 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8973 - scroll_pixel_position.y,
8974 )
8975 } else {
8976 let cursor_row = newest_selection_head.map(|head| head.row());
8977 let above_edit = edit_start
8978 .row()
8979 .0
8980 .checked_sub(line_count as u32)
8981 .map(DisplayRow);
8982 let below_edit = Some(edit_end.row() + 1);
8983 let above_cursor =
8984 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8985 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8986
8987 // Place the edit popover adjacent to the edit if there is a location
8988 // available that is onscreen and does not obscure the cursor. Otherwise,
8989 // place it adjacent to the cursor.
8990 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8991 .into_iter()
8992 .flatten()
8993 .find(|&start_row| {
8994 let end_row = start_row + line_count as u32;
8995 visible_row_range.contains(&start_row)
8996 && visible_row_range.contains(&end_row)
8997 && cursor_row
8998 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
8999 })?;
9000
9001 content_origin
9002 + point(
9003 -scroll_pixel_position.x,
9004 row_target.as_f32() * line_height - scroll_pixel_position.y,
9005 )
9006 };
9007
9008 origin.x -= BORDER_WIDTH;
9009
9010 window.defer_draw(element, origin, 1);
9011
9012 // Do not return an element, since it will already be drawn due to defer_draw.
9013 None
9014 }
9015
9016 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9017 px(30.)
9018 }
9019
9020 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9021 if self.read_only(cx) {
9022 cx.theme().players().read_only()
9023 } else {
9024 self.style.as_ref().unwrap().local_player
9025 }
9026 }
9027
9028 fn render_edit_prediction_accept_keybind(
9029 &self,
9030 window: &mut Window,
9031 cx: &App,
9032 ) -> Option<AnyElement> {
9033 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9034 let accept_keystroke = accept_binding.keystroke()?;
9035
9036 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9037
9038 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
9039 Color::Accent
9040 } else {
9041 Color::Muted
9042 };
9043
9044 h_flex()
9045 .px_0p5()
9046 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9047 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9048 .text_size(TextSize::XSmall.rems(cx))
9049 .child(h_flex().children(ui::render_modifiers(
9050 &accept_keystroke.modifiers,
9051 PlatformStyle::platform(),
9052 Some(modifiers_color),
9053 Some(IconSize::XSmall.rems().into()),
9054 true,
9055 )))
9056 .when(is_platform_style_mac, |parent| {
9057 parent.child(accept_keystroke.key.clone())
9058 })
9059 .when(!is_platform_style_mac, |parent| {
9060 parent.child(
9061 Key::new(
9062 util::capitalize(&accept_keystroke.key),
9063 Some(Color::Default),
9064 )
9065 .size(Some(IconSize::XSmall.rems().into())),
9066 )
9067 })
9068 .into_any()
9069 .into()
9070 }
9071
9072 fn render_edit_prediction_line_popover(
9073 &self,
9074 label: impl Into<SharedString>,
9075 icon: Option<IconName>,
9076 window: &mut Window,
9077 cx: &App,
9078 ) -> Option<Stateful<Div>> {
9079 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9080
9081 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9082 let has_keybind = keybind.is_some();
9083
9084 let result = h_flex()
9085 .id("ep-line-popover")
9086 .py_0p5()
9087 .pl_1()
9088 .pr(padding_right)
9089 .gap_1()
9090 .rounded_md()
9091 .border_1()
9092 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9093 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9094 .shadow_xs()
9095 .when(!has_keybind, |el| {
9096 let status_colors = cx.theme().status();
9097
9098 el.bg(status_colors.error_background)
9099 .border_color(status_colors.error.opacity(0.6))
9100 .pl_2()
9101 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9102 .cursor_default()
9103 .hoverable_tooltip(move |_window, cx| {
9104 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9105 })
9106 })
9107 .children(keybind)
9108 .child(
9109 Label::new(label)
9110 .size(LabelSize::Small)
9111 .when(!has_keybind, |el| {
9112 el.color(cx.theme().status().error.into()).strikethrough()
9113 }),
9114 )
9115 .when(!has_keybind, |el| {
9116 el.child(
9117 h_flex().ml_1().child(
9118 Icon::new(IconName::Info)
9119 .size(IconSize::Small)
9120 .color(cx.theme().status().error.into()),
9121 ),
9122 )
9123 })
9124 .when_some(icon, |element, icon| {
9125 element.child(
9126 div()
9127 .mt(px(1.5))
9128 .child(Icon::new(icon).size(IconSize::Small)),
9129 )
9130 });
9131
9132 Some(result)
9133 }
9134
9135 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9136 let accent_color = cx.theme().colors().text_accent;
9137 let editor_bg_color = cx.theme().colors().editor_background;
9138 editor_bg_color.blend(accent_color.opacity(0.1))
9139 }
9140
9141 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9142 let accent_color = cx.theme().colors().text_accent;
9143 let editor_bg_color = cx.theme().colors().editor_background;
9144 editor_bg_color.blend(accent_color.opacity(0.6))
9145 }
9146 fn get_prediction_provider_icon_name(
9147 provider: &Option<RegisteredEditPredictionProvider>,
9148 ) -> IconName {
9149 match provider {
9150 Some(provider) => match provider.provider.name() {
9151 "copilot" => IconName::Copilot,
9152 "supermaven" => IconName::Supermaven,
9153 _ => IconName::ZedPredict,
9154 },
9155 None => IconName::ZedPredict,
9156 }
9157 }
9158
9159 fn render_edit_prediction_cursor_popover(
9160 &self,
9161 min_width: Pixels,
9162 max_width: Pixels,
9163 cursor_point: Point,
9164 style: &EditorStyle,
9165 accept_keystroke: Option<&gpui::Keystroke>,
9166 _window: &Window,
9167 cx: &mut Context<Editor>,
9168 ) -> Option<AnyElement> {
9169 let provider = self.edit_prediction_provider.as_ref()?;
9170 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9171
9172 if provider.provider.needs_terms_acceptance(cx) {
9173 return Some(
9174 h_flex()
9175 .min_w(min_width)
9176 .flex_1()
9177 .px_2()
9178 .py_1()
9179 .gap_3()
9180 .elevation_2(cx)
9181 .hover(|style| style.bg(cx.theme().colors().element_hover))
9182 .id("accept-terms")
9183 .cursor_pointer()
9184 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9185 .on_click(cx.listener(|this, _event, window, cx| {
9186 cx.stop_propagation();
9187 this.report_editor_event(ReportEditorEvent::ZetaTosClicked, None, cx);
9188 window.dispatch_action(
9189 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9190 cx,
9191 );
9192 }))
9193 .child(
9194 h_flex()
9195 .flex_1()
9196 .gap_2()
9197 .child(Icon::new(provider_icon))
9198 .child(Label::new("Accept Terms of Service"))
9199 .child(div().w_full())
9200 .child(
9201 Icon::new(IconName::ArrowUpRight)
9202 .color(Color::Muted)
9203 .size(IconSize::Small),
9204 )
9205 .into_any_element(),
9206 )
9207 .into_any(),
9208 );
9209 }
9210
9211 let is_refreshing = provider.provider.is_refreshing(cx);
9212
9213 fn pending_completion_container(icon: IconName) -> Div {
9214 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9215 }
9216
9217 let completion = match &self.active_edit_prediction {
9218 Some(prediction) => {
9219 if !self.has_visible_completions_menu() {
9220 const RADIUS: Pixels = px(6.);
9221 const BORDER_WIDTH: Pixels = px(1.);
9222
9223 return Some(
9224 h_flex()
9225 .elevation_2(cx)
9226 .border(BORDER_WIDTH)
9227 .border_color(cx.theme().colors().border)
9228 .when(accept_keystroke.is_none(), |el| {
9229 el.border_color(cx.theme().status().error)
9230 })
9231 .rounded(RADIUS)
9232 .rounded_tl(px(0.))
9233 .overflow_hidden()
9234 .child(div().px_1p5().child(match &prediction.completion {
9235 EditPrediction::Move { target, snapshot } => {
9236 use text::ToPoint as _;
9237 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9238 {
9239 Icon::new(IconName::ZedPredictDown)
9240 } else {
9241 Icon::new(IconName::ZedPredictUp)
9242 }
9243 }
9244 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9245 }))
9246 .child(
9247 h_flex()
9248 .gap_1()
9249 .py_1()
9250 .px_2()
9251 .rounded_r(RADIUS - BORDER_WIDTH)
9252 .border_l_1()
9253 .border_color(cx.theme().colors().border)
9254 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9255 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9256 el.child(
9257 Label::new("Hold")
9258 .size(LabelSize::Small)
9259 .when(accept_keystroke.is_none(), |el| {
9260 el.strikethrough()
9261 })
9262 .line_height_style(LineHeightStyle::UiLabel),
9263 )
9264 })
9265 .id("edit_prediction_cursor_popover_keybind")
9266 .when(accept_keystroke.is_none(), |el| {
9267 let status_colors = cx.theme().status();
9268
9269 el.bg(status_colors.error_background)
9270 .border_color(status_colors.error.opacity(0.6))
9271 .child(Icon::new(IconName::Info).color(Color::Error))
9272 .cursor_default()
9273 .hoverable_tooltip(move |_window, cx| {
9274 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9275 .into()
9276 })
9277 })
9278 .when_some(
9279 accept_keystroke.as_ref(),
9280 |el, accept_keystroke| {
9281 el.child(h_flex().children(ui::render_modifiers(
9282 &accept_keystroke.modifiers,
9283 PlatformStyle::platform(),
9284 Some(Color::Default),
9285 Some(IconSize::XSmall.rems().into()),
9286 false,
9287 )))
9288 },
9289 ),
9290 )
9291 .into_any(),
9292 );
9293 }
9294
9295 self.render_edit_prediction_cursor_popover_preview(
9296 prediction,
9297 cursor_point,
9298 style,
9299 cx,
9300 )?
9301 }
9302
9303 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9304 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9305 stale_completion,
9306 cursor_point,
9307 style,
9308 cx,
9309 )?,
9310
9311 None => pending_completion_container(provider_icon)
9312 .child(Label::new("...").size(LabelSize::Small)),
9313 },
9314
9315 None => pending_completion_container(provider_icon)
9316 .child(Label::new("...").size(LabelSize::Small)),
9317 };
9318
9319 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9320 completion
9321 .with_animation(
9322 "loading-completion",
9323 Animation::new(Duration::from_secs(2))
9324 .repeat()
9325 .with_easing(pulsating_between(0.4, 0.8)),
9326 |label, delta| label.opacity(delta),
9327 )
9328 .into_any_element()
9329 } else {
9330 completion.into_any_element()
9331 };
9332
9333 let has_completion = self.active_edit_prediction.is_some();
9334
9335 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9336 Some(
9337 h_flex()
9338 .min_w(min_width)
9339 .max_w(max_width)
9340 .flex_1()
9341 .elevation_2(cx)
9342 .border_color(cx.theme().colors().border)
9343 .child(
9344 div()
9345 .flex_1()
9346 .py_1()
9347 .px_2()
9348 .overflow_hidden()
9349 .child(completion),
9350 )
9351 .when_some(accept_keystroke, |el, accept_keystroke| {
9352 if !accept_keystroke.modifiers.modified() {
9353 return el;
9354 }
9355
9356 el.child(
9357 h_flex()
9358 .h_full()
9359 .border_l_1()
9360 .rounded_r_lg()
9361 .border_color(cx.theme().colors().border)
9362 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9363 .gap_1()
9364 .py_1()
9365 .px_2()
9366 .child(
9367 h_flex()
9368 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9369 .when(is_platform_style_mac, |parent| parent.gap_1())
9370 .child(h_flex().children(ui::render_modifiers(
9371 &accept_keystroke.modifiers,
9372 PlatformStyle::platform(),
9373 Some(if !has_completion {
9374 Color::Muted
9375 } else {
9376 Color::Default
9377 }),
9378 None,
9379 false,
9380 ))),
9381 )
9382 .child(Label::new("Preview").into_any_element())
9383 .opacity(if has_completion { 1.0 } else { 0.4 }),
9384 )
9385 })
9386 .into_any(),
9387 )
9388 }
9389
9390 fn render_edit_prediction_cursor_popover_preview(
9391 &self,
9392 completion: &EditPredictionState,
9393 cursor_point: Point,
9394 style: &EditorStyle,
9395 cx: &mut Context<Editor>,
9396 ) -> Option<Div> {
9397 use text::ToPoint as _;
9398
9399 fn render_relative_row_jump(
9400 prefix: impl Into<String>,
9401 current_row: u32,
9402 target_row: u32,
9403 ) -> Div {
9404 let (row_diff, arrow) = if target_row < current_row {
9405 (current_row - target_row, IconName::ArrowUp)
9406 } else {
9407 (target_row - current_row, IconName::ArrowDown)
9408 };
9409
9410 h_flex()
9411 .child(
9412 Label::new(format!("{}{}", prefix.into(), row_diff))
9413 .color(Color::Muted)
9414 .size(LabelSize::Small),
9415 )
9416 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9417 }
9418
9419 let supports_jump = self
9420 .edit_prediction_provider
9421 .as_ref()
9422 .map(|provider| provider.provider.supports_jump_to_edit())
9423 .unwrap_or(true);
9424
9425 match &completion.completion {
9426 EditPrediction::Move {
9427 target, snapshot, ..
9428 } => {
9429 if !supports_jump {
9430 return None;
9431 }
9432
9433 Some(
9434 h_flex()
9435 .px_2()
9436 .gap_2()
9437 .flex_1()
9438 .child(
9439 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9440 Icon::new(IconName::ZedPredictDown)
9441 } else {
9442 Icon::new(IconName::ZedPredictUp)
9443 },
9444 )
9445 .child(Label::new("Jump to Edit")),
9446 )
9447 }
9448
9449 EditPrediction::Edit {
9450 edits,
9451 edit_preview,
9452 snapshot,
9453 display_mode: _,
9454 } => {
9455 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9456
9457 let (highlighted_edits, has_more_lines) =
9458 if let Some(edit_preview) = edit_preview.as_ref() {
9459 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9460 .first_line_preview()
9461 } else {
9462 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9463 };
9464
9465 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9466 .with_default_highlights(&style.text, highlighted_edits.highlights);
9467
9468 let preview = h_flex()
9469 .gap_1()
9470 .min_w_16()
9471 .child(styled_text)
9472 .when(has_more_lines, |parent| parent.child("…"));
9473
9474 let left = if supports_jump && first_edit_row != cursor_point.row {
9475 render_relative_row_jump("", cursor_point.row, first_edit_row)
9476 .into_any_element()
9477 } else {
9478 let icon_name =
9479 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9480 Icon::new(icon_name).into_any_element()
9481 };
9482
9483 Some(
9484 h_flex()
9485 .h_full()
9486 .flex_1()
9487 .gap_2()
9488 .pr_1()
9489 .overflow_x_hidden()
9490 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9491 .child(left)
9492 .child(preview),
9493 )
9494 }
9495 }
9496 }
9497
9498 pub fn render_context_menu(
9499 &self,
9500 style: &EditorStyle,
9501 max_height_in_lines: u32,
9502 window: &mut Window,
9503 cx: &mut Context<Editor>,
9504 ) -> Option<AnyElement> {
9505 let menu = self.context_menu.borrow();
9506 let menu = menu.as_ref()?;
9507 if !menu.visible() {
9508 return None;
9509 };
9510 Some(menu.render(style, max_height_in_lines, window, cx))
9511 }
9512
9513 fn render_context_menu_aside(
9514 &mut self,
9515 max_size: Size<Pixels>,
9516 window: &mut Window,
9517 cx: &mut Context<Editor>,
9518 ) -> Option<AnyElement> {
9519 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9520 if menu.visible() {
9521 menu.render_aside(max_size, window, cx)
9522 } else {
9523 None
9524 }
9525 })
9526 }
9527
9528 fn hide_context_menu(
9529 &mut self,
9530 window: &mut Window,
9531 cx: &mut Context<Self>,
9532 ) -> Option<CodeContextMenu> {
9533 cx.notify();
9534 self.completion_tasks.clear();
9535 let context_menu = self.context_menu.borrow_mut().take();
9536 self.stale_edit_prediction_in_menu.take();
9537 self.update_visible_edit_prediction(window, cx);
9538 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9539 && let Some(completion_provider) = &self.completion_provider
9540 {
9541 completion_provider.selection_changed(None, window, cx);
9542 }
9543 context_menu
9544 }
9545
9546 fn show_snippet_choices(
9547 &mut self,
9548 choices: &Vec<String>,
9549 selection: Range<Anchor>,
9550 cx: &mut Context<Self>,
9551 ) {
9552 let Some((_, buffer, _)) = self
9553 .buffer()
9554 .read(cx)
9555 .excerpt_containing(selection.start, cx)
9556 else {
9557 return;
9558 };
9559 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9560 else {
9561 return;
9562 };
9563 if buffer != end_buffer {
9564 log::error!("expected anchor range to have matching buffer IDs");
9565 return;
9566 }
9567
9568 let id = post_inc(&mut self.next_completion_id);
9569 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9570 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9571 CompletionsMenu::new_snippet_choices(
9572 id,
9573 true,
9574 choices,
9575 selection,
9576 buffer,
9577 snippet_sort_order,
9578 ),
9579 ));
9580 }
9581
9582 pub fn insert_snippet(
9583 &mut self,
9584 insertion_ranges: &[Range<usize>],
9585 snippet: Snippet,
9586 window: &mut Window,
9587 cx: &mut Context<Self>,
9588 ) -> Result<()> {
9589 struct Tabstop<T> {
9590 is_end_tabstop: bool,
9591 ranges: Vec<Range<T>>,
9592 choices: Option<Vec<String>>,
9593 }
9594
9595 let tabstops = self.buffer.update(cx, |buffer, cx| {
9596 let snippet_text: Arc<str> = snippet.text.clone().into();
9597 let edits = insertion_ranges
9598 .iter()
9599 .cloned()
9600 .map(|range| (range, snippet_text.clone()));
9601 let autoindent_mode = AutoindentMode::Block {
9602 original_indent_columns: Vec::new(),
9603 };
9604 buffer.edit(edits, Some(autoindent_mode), cx);
9605
9606 let snapshot = &*buffer.read(cx);
9607 let snippet = &snippet;
9608 snippet
9609 .tabstops
9610 .iter()
9611 .map(|tabstop| {
9612 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9613 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9614 });
9615 let mut tabstop_ranges = tabstop
9616 .ranges
9617 .iter()
9618 .flat_map(|tabstop_range| {
9619 let mut delta = 0_isize;
9620 insertion_ranges.iter().map(move |insertion_range| {
9621 let insertion_start = insertion_range.start as isize + delta;
9622 delta +=
9623 snippet.text.len() as isize - insertion_range.len() as isize;
9624
9625 let start = ((insertion_start + tabstop_range.start) as usize)
9626 .min(snapshot.len());
9627 let end = ((insertion_start + tabstop_range.end) as usize)
9628 .min(snapshot.len());
9629 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9630 })
9631 })
9632 .collect::<Vec<_>>();
9633 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9634
9635 Tabstop {
9636 is_end_tabstop,
9637 ranges: tabstop_ranges,
9638 choices: tabstop.choices.clone(),
9639 }
9640 })
9641 .collect::<Vec<_>>()
9642 });
9643 if let Some(tabstop) = tabstops.first() {
9644 self.change_selections(Default::default(), window, cx, |s| {
9645 // Reverse order so that the first range is the newest created selection.
9646 // Completions will use it and autoscroll will prioritize it.
9647 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9648 });
9649
9650 if let Some(choices) = &tabstop.choices
9651 && let Some(selection) = tabstop.ranges.first()
9652 {
9653 self.show_snippet_choices(choices, selection.clone(), cx)
9654 }
9655
9656 // If we're already at the last tabstop and it's at the end of the snippet,
9657 // we're done, we don't need to keep the state around.
9658 if !tabstop.is_end_tabstop {
9659 let choices = tabstops
9660 .iter()
9661 .map(|tabstop| tabstop.choices.clone())
9662 .collect();
9663
9664 let ranges = tabstops
9665 .into_iter()
9666 .map(|tabstop| tabstop.ranges)
9667 .collect::<Vec<_>>();
9668
9669 self.snippet_stack.push(SnippetState {
9670 active_index: 0,
9671 ranges,
9672 choices,
9673 });
9674 }
9675
9676 // Check whether the just-entered snippet ends with an auto-closable bracket.
9677 if self.autoclose_regions.is_empty() {
9678 let snapshot = self.buffer.read(cx).snapshot(cx);
9679 let mut all_selections = self.selections.all::<Point>(cx);
9680 for selection in &mut all_selections {
9681 let selection_head = selection.head();
9682 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9683 continue;
9684 };
9685
9686 let mut bracket_pair = None;
9687 let max_lookup_length = scope
9688 .brackets()
9689 .map(|(pair, _)| {
9690 pair.start
9691 .as_str()
9692 .chars()
9693 .count()
9694 .max(pair.end.as_str().chars().count())
9695 })
9696 .max();
9697 if let Some(max_lookup_length) = max_lookup_length {
9698 let next_text = snapshot
9699 .chars_at(selection_head)
9700 .take(max_lookup_length)
9701 .collect::<String>();
9702 let prev_text = snapshot
9703 .reversed_chars_at(selection_head)
9704 .take(max_lookup_length)
9705 .collect::<String>();
9706
9707 for (pair, enabled) in scope.brackets() {
9708 if enabled
9709 && pair.close
9710 && prev_text.starts_with(pair.start.as_str())
9711 && next_text.starts_with(pair.end.as_str())
9712 {
9713 bracket_pair = Some(pair.clone());
9714 break;
9715 }
9716 }
9717 }
9718
9719 if let Some(pair) = bracket_pair {
9720 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9721 let autoclose_enabled =
9722 self.use_autoclose && snapshot_settings.use_autoclose;
9723 if autoclose_enabled {
9724 let start = snapshot.anchor_after(selection_head);
9725 let end = snapshot.anchor_after(selection_head);
9726 self.autoclose_regions.push(AutocloseRegion {
9727 selection_id: selection.id,
9728 range: start..end,
9729 pair,
9730 });
9731 }
9732 }
9733 }
9734 }
9735 }
9736 Ok(())
9737 }
9738
9739 pub fn move_to_next_snippet_tabstop(
9740 &mut self,
9741 window: &mut Window,
9742 cx: &mut Context<Self>,
9743 ) -> bool {
9744 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9745 }
9746
9747 pub fn move_to_prev_snippet_tabstop(
9748 &mut self,
9749 window: &mut Window,
9750 cx: &mut Context<Self>,
9751 ) -> bool {
9752 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9753 }
9754
9755 pub fn move_to_snippet_tabstop(
9756 &mut self,
9757 bias: Bias,
9758 window: &mut Window,
9759 cx: &mut Context<Self>,
9760 ) -> bool {
9761 if let Some(mut snippet) = self.snippet_stack.pop() {
9762 match bias {
9763 Bias::Left => {
9764 if snippet.active_index > 0 {
9765 snippet.active_index -= 1;
9766 } else {
9767 self.snippet_stack.push(snippet);
9768 return false;
9769 }
9770 }
9771 Bias::Right => {
9772 if snippet.active_index + 1 < snippet.ranges.len() {
9773 snippet.active_index += 1;
9774 } else {
9775 self.snippet_stack.push(snippet);
9776 return false;
9777 }
9778 }
9779 }
9780 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9781 self.change_selections(Default::default(), window, cx, |s| {
9782 // Reverse order so that the first range is the newest created selection.
9783 // Completions will use it and autoscroll will prioritize it.
9784 s.select_ranges(current_ranges.iter().rev().cloned())
9785 });
9786
9787 if let Some(choices) = &snippet.choices[snippet.active_index]
9788 && let Some(selection) = current_ranges.first()
9789 {
9790 self.show_snippet_choices(choices, selection.clone(), cx);
9791 }
9792
9793 // If snippet state is not at the last tabstop, push it back on the stack
9794 if snippet.active_index + 1 < snippet.ranges.len() {
9795 self.snippet_stack.push(snippet);
9796 }
9797 return true;
9798 }
9799 }
9800
9801 false
9802 }
9803
9804 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9805 self.transact(window, cx, |this, window, cx| {
9806 this.select_all(&SelectAll, window, cx);
9807 this.insert("", window, cx);
9808 });
9809 }
9810
9811 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9812 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9813 self.transact(window, cx, |this, window, cx| {
9814 this.select_autoclose_pair(window, cx);
9815 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9816 if !this.linked_edit_ranges.is_empty() {
9817 let selections = this.selections.all::<MultiBufferPoint>(cx);
9818 let snapshot = this.buffer.read(cx).snapshot(cx);
9819
9820 for selection in selections.iter() {
9821 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9822 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9823 if selection_start.buffer_id != selection_end.buffer_id {
9824 continue;
9825 }
9826 if let Some(ranges) =
9827 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9828 {
9829 for (buffer, entries) in ranges {
9830 linked_ranges.entry(buffer).or_default().extend(entries);
9831 }
9832 }
9833 }
9834 }
9835
9836 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9837 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9838 for selection in &mut selections {
9839 if selection.is_empty() {
9840 let old_head = selection.head();
9841 let mut new_head =
9842 movement::left(&display_map, old_head.to_display_point(&display_map))
9843 .to_point(&display_map);
9844 if let Some((buffer, line_buffer_range)) = display_map
9845 .buffer_snapshot
9846 .buffer_line_for_row(MultiBufferRow(old_head.row))
9847 {
9848 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9849 let indent_len = match indent_size.kind {
9850 IndentKind::Space => {
9851 buffer.settings_at(line_buffer_range.start, cx).tab_size
9852 }
9853 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9854 };
9855 if old_head.column <= indent_size.len && old_head.column > 0 {
9856 let indent_len = indent_len.get();
9857 new_head = cmp::min(
9858 new_head,
9859 MultiBufferPoint::new(
9860 old_head.row,
9861 ((old_head.column - 1) / indent_len) * indent_len,
9862 ),
9863 );
9864 }
9865 }
9866
9867 selection.set_head(new_head, SelectionGoal::None);
9868 }
9869 }
9870
9871 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9872 this.insert("", window, cx);
9873 let empty_str: Arc<str> = Arc::from("");
9874 for (buffer, edits) in linked_ranges {
9875 let snapshot = buffer.read(cx).snapshot();
9876 use text::ToPoint as TP;
9877
9878 let edits = edits
9879 .into_iter()
9880 .map(|range| {
9881 let end_point = TP::to_point(&range.end, &snapshot);
9882 let mut start_point = TP::to_point(&range.start, &snapshot);
9883
9884 if end_point == start_point {
9885 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9886 .saturating_sub(1);
9887 start_point =
9888 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9889 };
9890
9891 (start_point..end_point, empty_str.clone())
9892 })
9893 .sorted_by_key(|(range, _)| range.start)
9894 .collect::<Vec<_>>();
9895 buffer.update(cx, |this, cx| {
9896 this.edit(edits, None, cx);
9897 })
9898 }
9899 this.refresh_edit_prediction(true, false, window, cx);
9900 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9901 });
9902 }
9903
9904 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9905 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9906 self.transact(window, cx, |this, window, cx| {
9907 this.change_selections(Default::default(), window, cx, |s| {
9908 s.move_with(|map, selection| {
9909 if selection.is_empty() {
9910 let cursor = movement::right(map, selection.head());
9911 selection.end = cursor;
9912 selection.reversed = true;
9913 selection.goal = SelectionGoal::None;
9914 }
9915 })
9916 });
9917 this.insert("", window, cx);
9918 this.refresh_edit_prediction(true, false, window, cx);
9919 });
9920 }
9921
9922 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9923 if self.mode.is_single_line() {
9924 cx.propagate();
9925 return;
9926 }
9927
9928 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9929 if self.move_to_prev_snippet_tabstop(window, cx) {
9930 return;
9931 }
9932 self.outdent(&Outdent, window, cx);
9933 }
9934
9935 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9936 if self.mode.is_single_line() {
9937 cx.propagate();
9938 return;
9939 }
9940
9941 if self.move_to_next_snippet_tabstop(window, cx) {
9942 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9943 return;
9944 }
9945 if self.read_only(cx) {
9946 return;
9947 }
9948 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9949 let mut selections = self.selections.all_adjusted(cx);
9950 let buffer = self.buffer.read(cx);
9951 let snapshot = buffer.snapshot(cx);
9952 let rows_iter = selections.iter().map(|s| s.head().row);
9953 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9954
9955 let has_some_cursor_in_whitespace = selections
9956 .iter()
9957 .filter(|selection| selection.is_empty())
9958 .any(|selection| {
9959 let cursor = selection.head();
9960 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9961 cursor.column < current_indent.len
9962 });
9963
9964 let mut edits = Vec::new();
9965 let mut prev_edited_row = 0;
9966 let mut row_delta = 0;
9967 for selection in &mut selections {
9968 if selection.start.row != prev_edited_row {
9969 row_delta = 0;
9970 }
9971 prev_edited_row = selection.end.row;
9972
9973 // If the selection is non-empty, then increase the indentation of the selected lines.
9974 if !selection.is_empty() {
9975 row_delta =
9976 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9977 continue;
9978 }
9979
9980 let cursor = selection.head();
9981 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9982 if let Some(suggested_indent) =
9983 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9984 {
9985 // Don't do anything if already at suggested indent
9986 // and there is any other cursor which is not
9987 if has_some_cursor_in_whitespace
9988 && cursor.column == current_indent.len
9989 && current_indent.len == suggested_indent.len
9990 {
9991 continue;
9992 }
9993
9994 // Adjust line and move cursor to suggested indent
9995 // if cursor is not at suggested indent
9996 if cursor.column < suggested_indent.len
9997 && cursor.column <= current_indent.len
9998 && current_indent.len <= suggested_indent.len
9999 {
10000 selection.start = Point::new(cursor.row, suggested_indent.len);
10001 selection.end = selection.start;
10002 if row_delta == 0 {
10003 edits.extend(Buffer::edit_for_indent_size_adjustment(
10004 cursor.row,
10005 current_indent,
10006 suggested_indent,
10007 ));
10008 row_delta = suggested_indent.len - current_indent.len;
10009 }
10010 continue;
10011 }
10012
10013 // If current indent is more than suggested indent
10014 // only move cursor to current indent and skip indent
10015 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10016 selection.start = Point::new(cursor.row, current_indent.len);
10017 selection.end = selection.start;
10018 continue;
10019 }
10020 }
10021
10022 // Otherwise, insert a hard or soft tab.
10023 let settings = buffer.language_settings_at(cursor, cx);
10024 let tab_size = if settings.hard_tabs {
10025 IndentSize::tab()
10026 } else {
10027 let tab_size = settings.tab_size.get();
10028 let indent_remainder = snapshot
10029 .text_for_range(Point::new(cursor.row, 0)..cursor)
10030 .flat_map(str::chars)
10031 .fold(row_delta % tab_size, |counter: u32, c| {
10032 if c == '\t' {
10033 0
10034 } else {
10035 (counter + 1) % tab_size
10036 }
10037 });
10038
10039 let chars_to_next_tab_stop = tab_size - indent_remainder;
10040 IndentSize::spaces(chars_to_next_tab_stop)
10041 };
10042 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10043 selection.end = selection.start;
10044 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10045 row_delta += tab_size.len;
10046 }
10047
10048 self.transact(window, cx, |this, window, cx| {
10049 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10050 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10051 this.refresh_edit_prediction(true, false, window, cx);
10052 });
10053 }
10054
10055 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10056 if self.read_only(cx) {
10057 return;
10058 }
10059 if self.mode.is_single_line() {
10060 cx.propagate();
10061 return;
10062 }
10063
10064 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10065 let mut selections = self.selections.all::<Point>(cx);
10066 let mut prev_edited_row = 0;
10067 let mut row_delta = 0;
10068 let mut edits = Vec::new();
10069 let buffer = self.buffer.read(cx);
10070 let snapshot = buffer.snapshot(cx);
10071 for selection in &mut selections {
10072 if selection.start.row != prev_edited_row {
10073 row_delta = 0;
10074 }
10075 prev_edited_row = selection.end.row;
10076
10077 row_delta =
10078 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10079 }
10080
10081 self.transact(window, cx, |this, window, cx| {
10082 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10083 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10084 });
10085 }
10086
10087 fn indent_selection(
10088 buffer: &MultiBuffer,
10089 snapshot: &MultiBufferSnapshot,
10090 selection: &mut Selection<Point>,
10091 edits: &mut Vec<(Range<Point>, String)>,
10092 delta_for_start_row: u32,
10093 cx: &App,
10094 ) -> u32 {
10095 let settings = buffer.language_settings_at(selection.start, cx);
10096 let tab_size = settings.tab_size.get();
10097 let indent_kind = if settings.hard_tabs {
10098 IndentKind::Tab
10099 } else {
10100 IndentKind::Space
10101 };
10102 let mut start_row = selection.start.row;
10103 let mut end_row = selection.end.row + 1;
10104
10105 // If a selection ends at the beginning of a line, don't indent
10106 // that last line.
10107 if selection.end.column == 0 && selection.end.row > selection.start.row {
10108 end_row -= 1;
10109 }
10110
10111 // Avoid re-indenting a row that has already been indented by a
10112 // previous selection, but still update this selection's column
10113 // to reflect that indentation.
10114 if delta_for_start_row > 0 {
10115 start_row += 1;
10116 selection.start.column += delta_for_start_row;
10117 if selection.end.row == selection.start.row {
10118 selection.end.column += delta_for_start_row;
10119 }
10120 }
10121
10122 let mut delta_for_end_row = 0;
10123 let has_multiple_rows = start_row + 1 != end_row;
10124 for row in start_row..end_row {
10125 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10126 let indent_delta = match (current_indent.kind, indent_kind) {
10127 (IndentKind::Space, IndentKind::Space) => {
10128 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10129 IndentSize::spaces(columns_to_next_tab_stop)
10130 }
10131 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10132 (_, IndentKind::Tab) => IndentSize::tab(),
10133 };
10134
10135 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10136 0
10137 } else {
10138 selection.start.column
10139 };
10140 let row_start = Point::new(row, start);
10141 edits.push((
10142 row_start..row_start,
10143 indent_delta.chars().collect::<String>(),
10144 ));
10145
10146 // Update this selection's endpoints to reflect the indentation.
10147 if row == selection.start.row {
10148 selection.start.column += indent_delta.len;
10149 }
10150 if row == selection.end.row {
10151 selection.end.column += indent_delta.len;
10152 delta_for_end_row = indent_delta.len;
10153 }
10154 }
10155
10156 if selection.start.row == selection.end.row {
10157 delta_for_start_row + delta_for_end_row
10158 } else {
10159 delta_for_end_row
10160 }
10161 }
10162
10163 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10164 if self.read_only(cx) {
10165 return;
10166 }
10167 if self.mode.is_single_line() {
10168 cx.propagate();
10169 return;
10170 }
10171
10172 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10173 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10174 let selections = self.selections.all::<Point>(cx);
10175 let mut deletion_ranges = Vec::new();
10176 let mut last_outdent = None;
10177 {
10178 let buffer = self.buffer.read(cx);
10179 let snapshot = buffer.snapshot(cx);
10180 for selection in &selections {
10181 let settings = buffer.language_settings_at(selection.start, cx);
10182 let tab_size = settings.tab_size.get();
10183 let mut rows = selection.spanned_rows(false, &display_map);
10184
10185 // Avoid re-outdenting a row that has already been outdented by a
10186 // previous selection.
10187 if let Some(last_row) = last_outdent
10188 && last_row == rows.start
10189 {
10190 rows.start = rows.start.next_row();
10191 }
10192 let has_multiple_rows = rows.len() > 1;
10193 for row in rows.iter_rows() {
10194 let indent_size = snapshot.indent_size_for_line(row);
10195 if indent_size.len > 0 {
10196 let deletion_len = match indent_size.kind {
10197 IndentKind::Space => {
10198 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10199 if columns_to_prev_tab_stop == 0 {
10200 tab_size
10201 } else {
10202 columns_to_prev_tab_stop
10203 }
10204 }
10205 IndentKind::Tab => 1,
10206 };
10207 let start = if has_multiple_rows
10208 || deletion_len > selection.start.column
10209 || indent_size.len < selection.start.column
10210 {
10211 0
10212 } else {
10213 selection.start.column - deletion_len
10214 };
10215 deletion_ranges.push(
10216 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10217 );
10218 last_outdent = Some(row);
10219 }
10220 }
10221 }
10222 }
10223
10224 self.transact(window, cx, |this, window, cx| {
10225 this.buffer.update(cx, |buffer, cx| {
10226 let empty_str: Arc<str> = Arc::default();
10227 buffer.edit(
10228 deletion_ranges
10229 .into_iter()
10230 .map(|range| (range, empty_str.clone())),
10231 None,
10232 cx,
10233 );
10234 });
10235 let selections = this.selections.all::<usize>(cx);
10236 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10237 });
10238 }
10239
10240 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10241 if self.read_only(cx) {
10242 return;
10243 }
10244 if self.mode.is_single_line() {
10245 cx.propagate();
10246 return;
10247 }
10248
10249 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10250 let selections = self
10251 .selections
10252 .all::<usize>(cx)
10253 .into_iter()
10254 .map(|s| s.range());
10255
10256 self.transact(window, cx, |this, window, cx| {
10257 this.buffer.update(cx, |buffer, cx| {
10258 buffer.autoindent_ranges(selections, cx);
10259 });
10260 let selections = this.selections.all::<usize>(cx);
10261 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10262 });
10263 }
10264
10265 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10266 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10267 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10268 let selections = self.selections.all::<Point>(cx);
10269
10270 let mut new_cursors = Vec::new();
10271 let mut edit_ranges = Vec::new();
10272 let mut selections = selections.iter().peekable();
10273 while let Some(selection) = selections.next() {
10274 let mut rows = selection.spanned_rows(false, &display_map);
10275 let goal_display_column = selection.head().to_display_point(&display_map).column();
10276
10277 // Accumulate contiguous regions of rows that we want to delete.
10278 while let Some(next_selection) = selections.peek() {
10279 let next_rows = next_selection.spanned_rows(false, &display_map);
10280 if next_rows.start <= rows.end {
10281 rows.end = next_rows.end;
10282 selections.next().unwrap();
10283 } else {
10284 break;
10285 }
10286 }
10287
10288 let buffer = &display_map.buffer_snapshot;
10289 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10290 let edit_end;
10291 let cursor_buffer_row;
10292 if buffer.max_point().row >= rows.end.0 {
10293 // If there's a line after the range, delete the \n from the end of the row range
10294 // and position the cursor on the next line.
10295 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10296 cursor_buffer_row = rows.end;
10297 } else {
10298 // If there isn't a line after the range, delete the \n from the line before the
10299 // start of the row range and position the cursor there.
10300 edit_start = edit_start.saturating_sub(1);
10301 edit_end = buffer.len();
10302 cursor_buffer_row = rows.start.previous_row();
10303 }
10304
10305 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10306 *cursor.column_mut() =
10307 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10308
10309 new_cursors.push((
10310 selection.id,
10311 buffer.anchor_after(cursor.to_point(&display_map)),
10312 ));
10313 edit_ranges.push(edit_start..edit_end);
10314 }
10315
10316 self.transact(window, cx, |this, window, cx| {
10317 let buffer = this.buffer.update(cx, |buffer, cx| {
10318 let empty_str: Arc<str> = Arc::default();
10319 buffer.edit(
10320 edit_ranges
10321 .into_iter()
10322 .map(|range| (range, empty_str.clone())),
10323 None,
10324 cx,
10325 );
10326 buffer.snapshot(cx)
10327 });
10328 let new_selections = new_cursors
10329 .into_iter()
10330 .map(|(id, cursor)| {
10331 let cursor = cursor.to_point(&buffer);
10332 Selection {
10333 id,
10334 start: cursor,
10335 end: cursor,
10336 reversed: false,
10337 goal: SelectionGoal::None,
10338 }
10339 })
10340 .collect();
10341
10342 this.change_selections(Default::default(), window, cx, |s| {
10343 s.select(new_selections);
10344 });
10345 });
10346 }
10347
10348 pub fn join_lines_impl(
10349 &mut self,
10350 insert_whitespace: bool,
10351 window: &mut Window,
10352 cx: &mut Context<Self>,
10353 ) {
10354 if self.read_only(cx) {
10355 return;
10356 }
10357 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10358 for selection in self.selections.all::<Point>(cx) {
10359 let start = MultiBufferRow(selection.start.row);
10360 // Treat single line selections as if they include the next line. Otherwise this action
10361 // would do nothing for single line selections individual cursors.
10362 let end = if selection.start.row == selection.end.row {
10363 MultiBufferRow(selection.start.row + 1)
10364 } else {
10365 MultiBufferRow(selection.end.row)
10366 };
10367
10368 if let Some(last_row_range) = row_ranges.last_mut()
10369 && start <= last_row_range.end
10370 {
10371 last_row_range.end = end;
10372 continue;
10373 }
10374 row_ranges.push(start..end);
10375 }
10376
10377 let snapshot = self.buffer.read(cx).snapshot(cx);
10378 let mut cursor_positions = Vec::new();
10379 for row_range in &row_ranges {
10380 let anchor = snapshot.anchor_before(Point::new(
10381 row_range.end.previous_row().0,
10382 snapshot.line_len(row_range.end.previous_row()),
10383 ));
10384 cursor_positions.push(anchor..anchor);
10385 }
10386
10387 self.transact(window, cx, |this, window, cx| {
10388 for row_range in row_ranges.into_iter().rev() {
10389 for row in row_range.iter_rows().rev() {
10390 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10391 let next_line_row = row.next_row();
10392 let indent = snapshot.indent_size_for_line(next_line_row);
10393 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10394
10395 let replace =
10396 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10397 " "
10398 } else {
10399 ""
10400 };
10401
10402 this.buffer.update(cx, |buffer, cx| {
10403 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10404 });
10405 }
10406 }
10407
10408 this.change_selections(Default::default(), window, cx, |s| {
10409 s.select_anchor_ranges(cursor_positions)
10410 });
10411 });
10412 }
10413
10414 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10415 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10416 self.join_lines_impl(true, window, cx);
10417 }
10418
10419 pub fn sort_lines_case_sensitive(
10420 &mut self,
10421 _: &SortLinesCaseSensitive,
10422 window: &mut Window,
10423 cx: &mut Context<Self>,
10424 ) {
10425 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10426 }
10427
10428 pub fn sort_lines_by_length(
10429 &mut self,
10430 _: &SortLinesByLength,
10431 window: &mut Window,
10432 cx: &mut Context<Self>,
10433 ) {
10434 self.manipulate_immutable_lines(window, cx, |lines| {
10435 lines.sort_by_key(|&line| line.chars().count())
10436 })
10437 }
10438
10439 pub fn sort_lines_case_insensitive(
10440 &mut self,
10441 _: &SortLinesCaseInsensitive,
10442 window: &mut Window,
10443 cx: &mut Context<Self>,
10444 ) {
10445 self.manipulate_immutable_lines(window, cx, |lines| {
10446 lines.sort_by_key(|line| line.to_lowercase())
10447 })
10448 }
10449
10450 pub fn unique_lines_case_insensitive(
10451 &mut self,
10452 _: &UniqueLinesCaseInsensitive,
10453 window: &mut Window,
10454 cx: &mut Context<Self>,
10455 ) {
10456 self.manipulate_immutable_lines(window, cx, |lines| {
10457 let mut seen = HashSet::default();
10458 lines.retain(|line| seen.insert(line.to_lowercase()));
10459 })
10460 }
10461
10462 pub fn unique_lines_case_sensitive(
10463 &mut self,
10464 _: &UniqueLinesCaseSensitive,
10465 window: &mut Window,
10466 cx: &mut Context<Self>,
10467 ) {
10468 self.manipulate_immutable_lines(window, cx, |lines| {
10469 let mut seen = HashSet::default();
10470 lines.retain(|line| seen.insert(*line));
10471 })
10472 }
10473
10474 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10475 let Some(project) = self.project.clone() else {
10476 return;
10477 };
10478 self.reload(project, window, cx)
10479 .detach_and_notify_err(window, cx);
10480 }
10481
10482 pub fn restore_file(
10483 &mut self,
10484 _: &::git::RestoreFile,
10485 window: &mut Window,
10486 cx: &mut Context<Self>,
10487 ) {
10488 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10489 let mut buffer_ids = HashSet::default();
10490 let snapshot = self.buffer().read(cx).snapshot(cx);
10491 for selection in self.selections.all::<usize>(cx) {
10492 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10493 }
10494
10495 let buffer = self.buffer().read(cx);
10496 let ranges = buffer_ids
10497 .into_iter()
10498 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10499 .collect::<Vec<_>>();
10500
10501 self.restore_hunks_in_ranges(ranges, window, cx);
10502 }
10503
10504 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10505 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10506 let selections = self
10507 .selections
10508 .all(cx)
10509 .into_iter()
10510 .map(|s| s.range())
10511 .collect();
10512 self.restore_hunks_in_ranges(selections, window, cx);
10513 }
10514
10515 pub fn restore_hunks_in_ranges(
10516 &mut self,
10517 ranges: Vec<Range<Point>>,
10518 window: &mut Window,
10519 cx: &mut Context<Editor>,
10520 ) {
10521 let mut revert_changes = HashMap::default();
10522 let chunk_by = self
10523 .snapshot(window, cx)
10524 .hunks_for_ranges(ranges)
10525 .into_iter()
10526 .chunk_by(|hunk| hunk.buffer_id);
10527 for (buffer_id, hunks) in &chunk_by {
10528 let hunks = hunks.collect::<Vec<_>>();
10529 for hunk in &hunks {
10530 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10531 }
10532 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10533 }
10534 drop(chunk_by);
10535 if !revert_changes.is_empty() {
10536 self.transact(window, cx, |editor, window, cx| {
10537 editor.restore(revert_changes, window, cx);
10538 });
10539 }
10540 }
10541
10542 pub fn open_active_item_in_terminal(
10543 &mut self,
10544 _: &OpenInTerminal,
10545 window: &mut Window,
10546 cx: &mut Context<Self>,
10547 ) {
10548 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10549 let project_path = buffer.read(cx).project_path(cx)?;
10550 let project = self.project()?.read(cx);
10551 let entry = project.entry_for_path(&project_path, cx)?;
10552 let parent = match &entry.canonical_path {
10553 Some(canonical_path) => canonical_path.to_path_buf(),
10554 None => project.absolute_path(&project_path, cx)?,
10555 }
10556 .parent()?
10557 .to_path_buf();
10558 Some(parent)
10559 }) {
10560 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10561 }
10562 }
10563
10564 fn set_breakpoint_context_menu(
10565 &mut self,
10566 display_row: DisplayRow,
10567 position: Option<Anchor>,
10568 clicked_point: gpui::Point<Pixels>,
10569 window: &mut Window,
10570 cx: &mut Context<Self>,
10571 ) {
10572 let source = self
10573 .buffer
10574 .read(cx)
10575 .snapshot(cx)
10576 .anchor_before(Point::new(display_row.0, 0u32));
10577
10578 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10579
10580 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10581 self,
10582 source,
10583 clicked_point,
10584 context_menu,
10585 window,
10586 cx,
10587 );
10588 }
10589
10590 fn add_edit_breakpoint_block(
10591 &mut self,
10592 anchor: Anchor,
10593 breakpoint: &Breakpoint,
10594 edit_action: BreakpointPromptEditAction,
10595 window: &mut Window,
10596 cx: &mut Context<Self>,
10597 ) {
10598 let weak_editor = cx.weak_entity();
10599 let bp_prompt = cx.new(|cx| {
10600 BreakpointPromptEditor::new(
10601 weak_editor,
10602 anchor,
10603 breakpoint.clone(),
10604 edit_action,
10605 window,
10606 cx,
10607 )
10608 });
10609
10610 let height = bp_prompt.update(cx, |this, cx| {
10611 this.prompt
10612 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10613 });
10614 let cloned_prompt = bp_prompt.clone();
10615 let blocks = vec![BlockProperties {
10616 style: BlockStyle::Sticky,
10617 placement: BlockPlacement::Above(anchor),
10618 height: Some(height),
10619 render: Arc::new(move |cx| {
10620 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10621 cloned_prompt.clone().into_any_element()
10622 }),
10623 priority: 0,
10624 }];
10625
10626 let focus_handle = bp_prompt.focus_handle(cx);
10627 window.focus(&focus_handle);
10628
10629 let block_ids = self.insert_blocks(blocks, None, cx);
10630 bp_prompt.update(cx, |prompt, _| {
10631 prompt.add_block_ids(block_ids);
10632 });
10633 }
10634
10635 pub(crate) fn breakpoint_at_row(
10636 &self,
10637 row: u32,
10638 window: &mut Window,
10639 cx: &mut Context<Self>,
10640 ) -> Option<(Anchor, Breakpoint)> {
10641 let snapshot = self.snapshot(window, cx);
10642 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10643
10644 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10645 }
10646
10647 pub(crate) fn breakpoint_at_anchor(
10648 &self,
10649 breakpoint_position: Anchor,
10650 snapshot: &EditorSnapshot,
10651 cx: &mut Context<Self>,
10652 ) -> Option<(Anchor, Breakpoint)> {
10653 let buffer = self
10654 .buffer
10655 .read(cx)
10656 .buffer_for_anchor(breakpoint_position, cx)?;
10657
10658 let enclosing_excerpt = breakpoint_position.excerpt_id;
10659 let buffer_snapshot = buffer.read(cx).snapshot();
10660
10661 let row = buffer_snapshot
10662 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10663 .row;
10664
10665 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10666 let anchor_end = snapshot
10667 .buffer_snapshot
10668 .anchor_after(Point::new(row, line_len));
10669
10670 self.breakpoint_store
10671 .as_ref()?
10672 .read_with(cx, |breakpoint_store, cx| {
10673 breakpoint_store
10674 .breakpoints(
10675 &buffer,
10676 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10677 &buffer_snapshot,
10678 cx,
10679 )
10680 .next()
10681 .and_then(|(bp, _)| {
10682 let breakpoint_row = buffer_snapshot
10683 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10684 .row;
10685
10686 if breakpoint_row == row {
10687 snapshot
10688 .buffer_snapshot
10689 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10690 .map(|position| (position, bp.bp.clone()))
10691 } else {
10692 None
10693 }
10694 })
10695 })
10696 }
10697
10698 pub fn edit_log_breakpoint(
10699 &mut self,
10700 _: &EditLogBreakpoint,
10701 window: &mut Window,
10702 cx: &mut Context<Self>,
10703 ) {
10704 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10705 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10706 message: None,
10707 state: BreakpointState::Enabled,
10708 condition: None,
10709 hit_condition: None,
10710 });
10711
10712 self.add_edit_breakpoint_block(
10713 anchor,
10714 &breakpoint,
10715 BreakpointPromptEditAction::Log,
10716 window,
10717 cx,
10718 );
10719 }
10720 }
10721
10722 fn breakpoints_at_cursors(
10723 &self,
10724 window: &mut Window,
10725 cx: &mut Context<Self>,
10726 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10727 let snapshot = self.snapshot(window, cx);
10728 let cursors = self
10729 .selections
10730 .disjoint_anchors()
10731 .iter()
10732 .map(|selection| {
10733 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10734
10735 let breakpoint_position = self
10736 .breakpoint_at_row(cursor_position.row, window, cx)
10737 .map(|bp| bp.0)
10738 .unwrap_or_else(|| {
10739 snapshot
10740 .display_snapshot
10741 .buffer_snapshot
10742 .anchor_after(Point::new(cursor_position.row, 0))
10743 });
10744
10745 let breakpoint = self
10746 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10747 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10748
10749 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10750 })
10751 // 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.
10752 .collect::<HashMap<Anchor, _>>();
10753
10754 cursors.into_iter().collect()
10755 }
10756
10757 pub fn enable_breakpoint(
10758 &mut self,
10759 _: &crate::actions::EnableBreakpoint,
10760 window: &mut Window,
10761 cx: &mut Context<Self>,
10762 ) {
10763 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10764 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10765 continue;
10766 };
10767 self.edit_breakpoint_at_anchor(
10768 anchor,
10769 breakpoint,
10770 BreakpointEditAction::InvertState,
10771 cx,
10772 );
10773 }
10774 }
10775
10776 pub fn disable_breakpoint(
10777 &mut self,
10778 _: &crate::actions::DisableBreakpoint,
10779 window: &mut Window,
10780 cx: &mut Context<Self>,
10781 ) {
10782 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10783 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10784 continue;
10785 };
10786 self.edit_breakpoint_at_anchor(
10787 anchor,
10788 breakpoint,
10789 BreakpointEditAction::InvertState,
10790 cx,
10791 );
10792 }
10793 }
10794
10795 pub fn toggle_breakpoint(
10796 &mut self,
10797 _: &crate::actions::ToggleBreakpoint,
10798 window: &mut Window,
10799 cx: &mut Context<Self>,
10800 ) {
10801 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10802 if let Some(breakpoint) = breakpoint {
10803 self.edit_breakpoint_at_anchor(
10804 anchor,
10805 breakpoint,
10806 BreakpointEditAction::Toggle,
10807 cx,
10808 );
10809 } else {
10810 self.edit_breakpoint_at_anchor(
10811 anchor,
10812 Breakpoint::new_standard(),
10813 BreakpointEditAction::Toggle,
10814 cx,
10815 );
10816 }
10817 }
10818 }
10819
10820 pub fn edit_breakpoint_at_anchor(
10821 &mut self,
10822 breakpoint_position: Anchor,
10823 breakpoint: Breakpoint,
10824 edit_action: BreakpointEditAction,
10825 cx: &mut Context<Self>,
10826 ) {
10827 let Some(breakpoint_store) = &self.breakpoint_store else {
10828 return;
10829 };
10830
10831 let Some(buffer) = self
10832 .buffer
10833 .read(cx)
10834 .buffer_for_anchor(breakpoint_position, cx)
10835 else {
10836 return;
10837 };
10838
10839 breakpoint_store.update(cx, |breakpoint_store, cx| {
10840 breakpoint_store.toggle_breakpoint(
10841 buffer,
10842 BreakpointWithPosition {
10843 position: breakpoint_position.text_anchor,
10844 bp: breakpoint,
10845 },
10846 edit_action,
10847 cx,
10848 );
10849 });
10850
10851 cx.notify();
10852 }
10853
10854 #[cfg(any(test, feature = "test-support"))]
10855 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10856 self.breakpoint_store.clone()
10857 }
10858
10859 pub fn prepare_restore_change(
10860 &self,
10861 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10862 hunk: &MultiBufferDiffHunk,
10863 cx: &mut App,
10864 ) -> Option<()> {
10865 if hunk.is_created_file() {
10866 return None;
10867 }
10868 let buffer = self.buffer.read(cx);
10869 let diff = buffer.diff_for(hunk.buffer_id)?;
10870 let buffer = buffer.buffer(hunk.buffer_id)?;
10871 let buffer = buffer.read(cx);
10872 let original_text = diff
10873 .read(cx)
10874 .base_text()
10875 .as_rope()
10876 .slice(hunk.diff_base_byte_range.clone());
10877 let buffer_snapshot = buffer.snapshot();
10878 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10879 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10880 probe
10881 .0
10882 .start
10883 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10884 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10885 }) {
10886 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10887 Some(())
10888 } else {
10889 None
10890 }
10891 }
10892
10893 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10894 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10895 }
10896
10897 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10898 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10899 }
10900
10901 fn manipulate_lines<M>(
10902 &mut self,
10903 window: &mut Window,
10904 cx: &mut Context<Self>,
10905 mut manipulate: M,
10906 ) where
10907 M: FnMut(&str) -> LineManipulationResult,
10908 {
10909 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10910
10911 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10912 let buffer = self.buffer.read(cx).snapshot(cx);
10913
10914 let mut edits = Vec::new();
10915
10916 let selections = self.selections.all::<Point>(cx);
10917 let mut selections = selections.iter().peekable();
10918 let mut contiguous_row_selections = Vec::new();
10919 let mut new_selections = Vec::new();
10920 let mut added_lines = 0;
10921 let mut removed_lines = 0;
10922
10923 while let Some(selection) = selections.next() {
10924 let (start_row, end_row) = consume_contiguous_rows(
10925 &mut contiguous_row_selections,
10926 selection,
10927 &display_map,
10928 &mut selections,
10929 );
10930
10931 let start_point = Point::new(start_row.0, 0);
10932 let end_point = Point::new(
10933 end_row.previous_row().0,
10934 buffer.line_len(end_row.previous_row()),
10935 );
10936 let text = buffer
10937 .text_for_range(start_point..end_point)
10938 .collect::<String>();
10939
10940 let LineManipulationResult {
10941 new_text,
10942 line_count_before,
10943 line_count_after,
10944 } = manipulate(&text);
10945
10946 edits.push((start_point..end_point, new_text));
10947
10948 // Selections must change based on added and removed line count
10949 let start_row =
10950 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10951 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10952 new_selections.push(Selection {
10953 id: selection.id,
10954 start: start_row,
10955 end: end_row,
10956 goal: SelectionGoal::None,
10957 reversed: selection.reversed,
10958 });
10959
10960 if line_count_after > line_count_before {
10961 added_lines += line_count_after - line_count_before;
10962 } else if line_count_before > line_count_after {
10963 removed_lines += line_count_before - line_count_after;
10964 }
10965 }
10966
10967 self.transact(window, cx, |this, window, cx| {
10968 let buffer = this.buffer.update(cx, |buffer, cx| {
10969 buffer.edit(edits, None, cx);
10970 buffer.snapshot(cx)
10971 });
10972
10973 // Recalculate offsets on newly edited buffer
10974 let new_selections = new_selections
10975 .iter()
10976 .map(|s| {
10977 let start_point = Point::new(s.start.0, 0);
10978 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10979 Selection {
10980 id: s.id,
10981 start: buffer.point_to_offset(start_point),
10982 end: buffer.point_to_offset(end_point),
10983 goal: s.goal,
10984 reversed: s.reversed,
10985 }
10986 })
10987 .collect();
10988
10989 this.change_selections(Default::default(), window, cx, |s| {
10990 s.select(new_selections);
10991 });
10992
10993 this.request_autoscroll(Autoscroll::fit(), cx);
10994 });
10995 }
10996
10997 fn manipulate_immutable_lines<Fn>(
10998 &mut self,
10999 window: &mut Window,
11000 cx: &mut Context<Self>,
11001 mut callback: Fn,
11002 ) where
11003 Fn: FnMut(&mut Vec<&str>),
11004 {
11005 self.manipulate_lines(window, cx, |text| {
11006 let mut lines: Vec<&str> = text.split('\n').collect();
11007 let line_count_before = lines.len();
11008
11009 callback(&mut lines);
11010
11011 LineManipulationResult {
11012 new_text: lines.join("\n"),
11013 line_count_before,
11014 line_count_after: lines.len(),
11015 }
11016 });
11017 }
11018
11019 fn manipulate_mutable_lines<Fn>(
11020 &mut self,
11021 window: &mut Window,
11022 cx: &mut Context<Self>,
11023 mut callback: Fn,
11024 ) where
11025 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11026 {
11027 self.manipulate_lines(window, cx, |text| {
11028 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11029 let line_count_before = lines.len();
11030
11031 callback(&mut lines);
11032
11033 LineManipulationResult {
11034 new_text: lines.join("\n"),
11035 line_count_before,
11036 line_count_after: lines.len(),
11037 }
11038 });
11039 }
11040
11041 pub fn convert_indentation_to_spaces(
11042 &mut self,
11043 _: &ConvertIndentationToSpaces,
11044 window: &mut Window,
11045 cx: &mut Context<Self>,
11046 ) {
11047 let settings = self.buffer.read(cx).language_settings(cx);
11048 let tab_size = settings.tab_size.get() as usize;
11049
11050 self.manipulate_mutable_lines(window, cx, |lines| {
11051 // Allocates a reasonably sized scratch buffer once for the whole loop
11052 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11053 // Avoids recomputing spaces that could be inserted many times
11054 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11055 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11056 .collect();
11057
11058 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11059 let mut chars = line.as_ref().chars();
11060 let mut col = 0;
11061 let mut changed = false;
11062
11063 for ch in chars.by_ref() {
11064 match ch {
11065 ' ' => {
11066 reindented_line.push(' ');
11067 col += 1;
11068 }
11069 '\t' => {
11070 // \t are converted to spaces depending on the current column
11071 let spaces_len = tab_size - (col % tab_size);
11072 reindented_line.extend(&space_cache[spaces_len - 1]);
11073 col += spaces_len;
11074 changed = true;
11075 }
11076 _ => {
11077 // If we dont append before break, the character is consumed
11078 reindented_line.push(ch);
11079 break;
11080 }
11081 }
11082 }
11083
11084 if !changed {
11085 reindented_line.clear();
11086 continue;
11087 }
11088 // Append the rest of the line and replace old reference with new one
11089 reindented_line.extend(chars);
11090 *line = Cow::Owned(reindented_line.clone());
11091 reindented_line.clear();
11092 }
11093 });
11094 }
11095
11096 pub fn convert_indentation_to_tabs(
11097 &mut self,
11098 _: &ConvertIndentationToTabs,
11099 window: &mut Window,
11100 cx: &mut Context<Self>,
11101 ) {
11102 let settings = self.buffer.read(cx).language_settings(cx);
11103 let tab_size = settings.tab_size.get() as usize;
11104
11105 self.manipulate_mutable_lines(window, cx, |lines| {
11106 // Allocates a reasonably sized buffer once for the whole loop
11107 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11108 // Avoids recomputing spaces that could be inserted many times
11109 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11110 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11111 .collect();
11112
11113 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11114 let mut chars = line.chars();
11115 let mut spaces_count = 0;
11116 let mut first_non_indent_char = None;
11117 let mut changed = false;
11118
11119 for ch in chars.by_ref() {
11120 match ch {
11121 ' ' => {
11122 // Keep track of spaces. Append \t when we reach tab_size
11123 spaces_count += 1;
11124 changed = true;
11125 if spaces_count == tab_size {
11126 reindented_line.push('\t');
11127 spaces_count = 0;
11128 }
11129 }
11130 '\t' => {
11131 reindented_line.push('\t');
11132 spaces_count = 0;
11133 }
11134 _ => {
11135 // Dont append it yet, we might have remaining spaces
11136 first_non_indent_char = Some(ch);
11137 break;
11138 }
11139 }
11140 }
11141
11142 if !changed {
11143 reindented_line.clear();
11144 continue;
11145 }
11146 // Remaining spaces that didn't make a full tab stop
11147 if spaces_count > 0 {
11148 reindented_line.extend(&space_cache[spaces_count - 1]);
11149 }
11150 // If we consume an extra character that was not indentation, add it back
11151 if let Some(extra_char) = first_non_indent_char {
11152 reindented_line.push(extra_char);
11153 }
11154 // Append the rest of the line and replace old reference with new one
11155 reindented_line.extend(chars);
11156 *line = Cow::Owned(reindented_line.clone());
11157 reindented_line.clear();
11158 }
11159 });
11160 }
11161
11162 pub fn convert_to_upper_case(
11163 &mut self,
11164 _: &ConvertToUpperCase,
11165 window: &mut Window,
11166 cx: &mut Context<Self>,
11167 ) {
11168 self.manipulate_text(window, cx, |text| text.to_uppercase())
11169 }
11170
11171 pub fn convert_to_lower_case(
11172 &mut self,
11173 _: &ConvertToLowerCase,
11174 window: &mut Window,
11175 cx: &mut Context<Self>,
11176 ) {
11177 self.manipulate_text(window, cx, |text| text.to_lowercase())
11178 }
11179
11180 pub fn convert_to_title_case(
11181 &mut self,
11182 _: &ConvertToTitleCase,
11183 window: &mut Window,
11184 cx: &mut Context<Self>,
11185 ) {
11186 self.manipulate_text(window, cx, |text| {
11187 text.split('\n')
11188 .map(|line| line.to_case(Case::Title))
11189 .join("\n")
11190 })
11191 }
11192
11193 pub fn convert_to_snake_case(
11194 &mut self,
11195 _: &ConvertToSnakeCase,
11196 window: &mut Window,
11197 cx: &mut Context<Self>,
11198 ) {
11199 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11200 }
11201
11202 pub fn convert_to_kebab_case(
11203 &mut self,
11204 _: &ConvertToKebabCase,
11205 window: &mut Window,
11206 cx: &mut Context<Self>,
11207 ) {
11208 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11209 }
11210
11211 pub fn convert_to_upper_camel_case(
11212 &mut self,
11213 _: &ConvertToUpperCamelCase,
11214 window: &mut Window,
11215 cx: &mut Context<Self>,
11216 ) {
11217 self.manipulate_text(window, cx, |text| {
11218 text.split('\n')
11219 .map(|line| line.to_case(Case::UpperCamel))
11220 .join("\n")
11221 })
11222 }
11223
11224 pub fn convert_to_lower_camel_case(
11225 &mut self,
11226 _: &ConvertToLowerCamelCase,
11227 window: &mut Window,
11228 cx: &mut Context<Self>,
11229 ) {
11230 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11231 }
11232
11233 pub fn convert_to_opposite_case(
11234 &mut self,
11235 _: &ConvertToOppositeCase,
11236 window: &mut Window,
11237 cx: &mut Context<Self>,
11238 ) {
11239 self.manipulate_text(window, cx, |text| {
11240 text.chars()
11241 .fold(String::with_capacity(text.len()), |mut t, c| {
11242 if c.is_uppercase() {
11243 t.extend(c.to_lowercase());
11244 } else {
11245 t.extend(c.to_uppercase());
11246 }
11247 t
11248 })
11249 })
11250 }
11251
11252 pub fn convert_to_sentence_case(
11253 &mut self,
11254 _: &ConvertToSentenceCase,
11255 window: &mut Window,
11256 cx: &mut Context<Self>,
11257 ) {
11258 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11259 }
11260
11261 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11262 self.manipulate_text(window, cx, |text| {
11263 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11264 if has_upper_case_characters {
11265 text.to_lowercase()
11266 } else {
11267 text.to_uppercase()
11268 }
11269 })
11270 }
11271
11272 pub fn convert_to_rot13(
11273 &mut self,
11274 _: &ConvertToRot13,
11275 window: &mut Window,
11276 cx: &mut Context<Self>,
11277 ) {
11278 self.manipulate_text(window, cx, |text| {
11279 text.chars()
11280 .map(|c| match c {
11281 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11282 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11283 _ => c,
11284 })
11285 .collect()
11286 })
11287 }
11288
11289 pub fn convert_to_rot47(
11290 &mut self,
11291 _: &ConvertToRot47,
11292 window: &mut Window,
11293 cx: &mut Context<Self>,
11294 ) {
11295 self.manipulate_text(window, cx, |text| {
11296 text.chars()
11297 .map(|c| {
11298 let code_point = c as u32;
11299 if code_point >= 33 && code_point <= 126 {
11300 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11301 }
11302 c
11303 })
11304 .collect()
11305 })
11306 }
11307
11308 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11309 where
11310 Fn: FnMut(&str) -> String,
11311 {
11312 let buffer = self.buffer.read(cx).snapshot(cx);
11313
11314 let mut new_selections = Vec::new();
11315 let mut edits = Vec::new();
11316 let mut selection_adjustment = 0i32;
11317
11318 for selection in self.selections.all::<usize>(cx) {
11319 let selection_is_empty = selection.is_empty();
11320
11321 let (start, end) = if selection_is_empty {
11322 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11323 (word_range.start, word_range.end)
11324 } else {
11325 (selection.start, selection.end)
11326 };
11327
11328 let text = buffer.text_for_range(start..end).collect::<String>();
11329 let old_length = text.len() as i32;
11330 let text = callback(&text);
11331
11332 new_selections.push(Selection {
11333 start: (start as i32 - selection_adjustment) as usize,
11334 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11335 goal: SelectionGoal::None,
11336 ..selection
11337 });
11338
11339 selection_adjustment += old_length - text.len() as i32;
11340
11341 edits.push((start..end, text));
11342 }
11343
11344 self.transact(window, cx, |this, window, cx| {
11345 this.buffer.update(cx, |buffer, cx| {
11346 buffer.edit(edits, None, cx);
11347 });
11348
11349 this.change_selections(Default::default(), window, cx, |s| {
11350 s.select(new_selections);
11351 });
11352
11353 this.request_autoscroll(Autoscroll::fit(), cx);
11354 });
11355 }
11356
11357 pub fn move_selection_on_drop(
11358 &mut self,
11359 selection: &Selection<Anchor>,
11360 target: DisplayPoint,
11361 is_cut: bool,
11362 window: &mut Window,
11363 cx: &mut Context<Self>,
11364 ) {
11365 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11366 let buffer = &display_map.buffer_snapshot;
11367 let mut edits = Vec::new();
11368 let insert_point = display_map
11369 .clip_point(target, Bias::Left)
11370 .to_point(&display_map);
11371 let text = buffer
11372 .text_for_range(selection.start..selection.end)
11373 .collect::<String>();
11374 if is_cut {
11375 edits.push(((selection.start..selection.end), String::new()));
11376 }
11377 let insert_anchor = buffer.anchor_before(insert_point);
11378 edits.push(((insert_anchor..insert_anchor), text));
11379 let last_edit_start = insert_anchor.bias_left(buffer);
11380 let last_edit_end = insert_anchor.bias_right(buffer);
11381 self.transact(window, cx, |this, window, cx| {
11382 this.buffer.update(cx, |buffer, cx| {
11383 buffer.edit(edits, None, cx);
11384 });
11385 this.change_selections(Default::default(), window, cx, |s| {
11386 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11387 });
11388 });
11389 }
11390
11391 pub fn clear_selection_drag_state(&mut self) {
11392 self.selection_drag_state = SelectionDragState::None;
11393 }
11394
11395 pub fn duplicate(
11396 &mut self,
11397 upwards: bool,
11398 whole_lines: bool,
11399 window: &mut Window,
11400 cx: &mut Context<Self>,
11401 ) {
11402 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11403
11404 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11405 let buffer = &display_map.buffer_snapshot;
11406 let selections = self.selections.all::<Point>(cx);
11407
11408 let mut edits = Vec::new();
11409 let mut selections_iter = selections.iter().peekable();
11410 while let Some(selection) = selections_iter.next() {
11411 let mut rows = selection.spanned_rows(false, &display_map);
11412 // duplicate line-wise
11413 if whole_lines || selection.start == selection.end {
11414 // Avoid duplicating the same lines twice.
11415 while let Some(next_selection) = selections_iter.peek() {
11416 let next_rows = next_selection.spanned_rows(false, &display_map);
11417 if next_rows.start < rows.end {
11418 rows.end = next_rows.end;
11419 selections_iter.next().unwrap();
11420 } else {
11421 break;
11422 }
11423 }
11424
11425 // Copy the text from the selected row region and splice it either at the start
11426 // or end of the region.
11427 let start = Point::new(rows.start.0, 0);
11428 let end = Point::new(
11429 rows.end.previous_row().0,
11430 buffer.line_len(rows.end.previous_row()),
11431 );
11432 let text = buffer
11433 .text_for_range(start..end)
11434 .chain(Some("\n"))
11435 .collect::<String>();
11436 let insert_location = if upwards {
11437 Point::new(rows.end.0, 0)
11438 } else {
11439 start
11440 };
11441 edits.push((insert_location..insert_location, text));
11442 } else {
11443 // duplicate character-wise
11444 let start = selection.start;
11445 let end = selection.end;
11446 let text = buffer.text_for_range(start..end).collect::<String>();
11447 edits.push((selection.end..selection.end, text));
11448 }
11449 }
11450
11451 self.transact(window, cx, |this, _, cx| {
11452 this.buffer.update(cx, |buffer, cx| {
11453 buffer.edit(edits, None, cx);
11454 });
11455
11456 this.request_autoscroll(Autoscroll::fit(), cx);
11457 });
11458 }
11459
11460 pub fn duplicate_line_up(
11461 &mut self,
11462 _: &DuplicateLineUp,
11463 window: &mut Window,
11464 cx: &mut Context<Self>,
11465 ) {
11466 self.duplicate(true, true, window, cx);
11467 }
11468
11469 pub fn duplicate_line_down(
11470 &mut self,
11471 _: &DuplicateLineDown,
11472 window: &mut Window,
11473 cx: &mut Context<Self>,
11474 ) {
11475 self.duplicate(false, true, window, cx);
11476 }
11477
11478 pub fn duplicate_selection(
11479 &mut self,
11480 _: &DuplicateSelection,
11481 window: &mut Window,
11482 cx: &mut Context<Self>,
11483 ) {
11484 self.duplicate(false, false, window, cx);
11485 }
11486
11487 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11488 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11489 if self.mode.is_single_line() {
11490 cx.propagate();
11491 return;
11492 }
11493
11494 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11495 let buffer = self.buffer.read(cx).snapshot(cx);
11496
11497 let mut edits = Vec::new();
11498 let mut unfold_ranges = Vec::new();
11499 let mut refold_creases = Vec::new();
11500
11501 let selections = self.selections.all::<Point>(cx);
11502 let mut selections = selections.iter().peekable();
11503 let mut contiguous_row_selections = Vec::new();
11504 let mut new_selections = Vec::new();
11505
11506 while let Some(selection) = selections.next() {
11507 // Find all the selections that span a contiguous row range
11508 let (start_row, end_row) = consume_contiguous_rows(
11509 &mut contiguous_row_selections,
11510 selection,
11511 &display_map,
11512 &mut selections,
11513 );
11514
11515 // Move the text spanned by the row range to be before the line preceding the row range
11516 if start_row.0 > 0 {
11517 let range_to_move = Point::new(
11518 start_row.previous_row().0,
11519 buffer.line_len(start_row.previous_row()),
11520 )
11521 ..Point::new(
11522 end_row.previous_row().0,
11523 buffer.line_len(end_row.previous_row()),
11524 );
11525 let insertion_point = display_map
11526 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11527 .0;
11528
11529 // Don't move lines across excerpts
11530 if buffer
11531 .excerpt_containing(insertion_point..range_to_move.end)
11532 .is_some()
11533 {
11534 let text = buffer
11535 .text_for_range(range_to_move.clone())
11536 .flat_map(|s| s.chars())
11537 .skip(1)
11538 .chain(['\n'])
11539 .collect::<String>();
11540
11541 edits.push((
11542 buffer.anchor_after(range_to_move.start)
11543 ..buffer.anchor_before(range_to_move.end),
11544 String::new(),
11545 ));
11546 let insertion_anchor = buffer.anchor_after(insertion_point);
11547 edits.push((insertion_anchor..insertion_anchor, text));
11548
11549 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11550
11551 // Move selections up
11552 new_selections.extend(contiguous_row_selections.drain(..).map(
11553 |mut selection| {
11554 selection.start.row -= row_delta;
11555 selection.end.row -= row_delta;
11556 selection
11557 },
11558 ));
11559
11560 // Move folds up
11561 unfold_ranges.push(range_to_move.clone());
11562 for fold in display_map.folds_in_range(
11563 buffer.anchor_before(range_to_move.start)
11564 ..buffer.anchor_after(range_to_move.end),
11565 ) {
11566 let mut start = fold.range.start.to_point(&buffer);
11567 let mut end = fold.range.end.to_point(&buffer);
11568 start.row -= row_delta;
11569 end.row -= row_delta;
11570 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11571 }
11572 }
11573 }
11574
11575 // If we didn't move line(s), preserve the existing selections
11576 new_selections.append(&mut contiguous_row_selections);
11577 }
11578
11579 self.transact(window, cx, |this, window, cx| {
11580 this.unfold_ranges(&unfold_ranges, true, true, cx);
11581 this.buffer.update(cx, |buffer, cx| {
11582 for (range, text) in edits {
11583 buffer.edit([(range, text)], None, cx);
11584 }
11585 });
11586 this.fold_creases(refold_creases, true, window, cx);
11587 this.change_selections(Default::default(), window, cx, |s| {
11588 s.select(new_selections);
11589 })
11590 });
11591 }
11592
11593 pub fn move_line_down(
11594 &mut self,
11595 _: &MoveLineDown,
11596 window: &mut Window,
11597 cx: &mut Context<Self>,
11598 ) {
11599 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11600 if self.mode.is_single_line() {
11601 cx.propagate();
11602 return;
11603 }
11604
11605 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11606 let buffer = self.buffer.read(cx).snapshot(cx);
11607
11608 let mut edits = Vec::new();
11609 let mut unfold_ranges = Vec::new();
11610 let mut refold_creases = Vec::new();
11611
11612 let selections = self.selections.all::<Point>(cx);
11613 let mut selections = selections.iter().peekable();
11614 let mut contiguous_row_selections = Vec::new();
11615 let mut new_selections = Vec::new();
11616
11617 while let Some(selection) = selections.next() {
11618 // Find all the selections that span a contiguous row range
11619 let (start_row, end_row) = consume_contiguous_rows(
11620 &mut contiguous_row_selections,
11621 selection,
11622 &display_map,
11623 &mut selections,
11624 );
11625
11626 // Move the text spanned by the row range to be after the last line of the row range
11627 if end_row.0 <= buffer.max_point().row {
11628 let range_to_move =
11629 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11630 let insertion_point = display_map
11631 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11632 .0;
11633
11634 // Don't move lines across excerpt boundaries
11635 if buffer
11636 .excerpt_containing(range_to_move.start..insertion_point)
11637 .is_some()
11638 {
11639 let mut text = String::from("\n");
11640 text.extend(buffer.text_for_range(range_to_move.clone()));
11641 text.pop(); // Drop trailing newline
11642 edits.push((
11643 buffer.anchor_after(range_to_move.start)
11644 ..buffer.anchor_before(range_to_move.end),
11645 String::new(),
11646 ));
11647 let insertion_anchor = buffer.anchor_after(insertion_point);
11648 edits.push((insertion_anchor..insertion_anchor, text));
11649
11650 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11651
11652 // Move selections down
11653 new_selections.extend(contiguous_row_selections.drain(..).map(
11654 |mut selection| {
11655 selection.start.row += row_delta;
11656 selection.end.row += row_delta;
11657 selection
11658 },
11659 ));
11660
11661 // Move folds down
11662 unfold_ranges.push(range_to_move.clone());
11663 for fold in display_map.folds_in_range(
11664 buffer.anchor_before(range_to_move.start)
11665 ..buffer.anchor_after(range_to_move.end),
11666 ) {
11667 let mut start = fold.range.start.to_point(&buffer);
11668 let mut end = fold.range.end.to_point(&buffer);
11669 start.row += row_delta;
11670 end.row += row_delta;
11671 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11672 }
11673 }
11674 }
11675
11676 // If we didn't move line(s), preserve the existing selections
11677 new_selections.append(&mut contiguous_row_selections);
11678 }
11679
11680 self.transact(window, cx, |this, window, cx| {
11681 this.unfold_ranges(&unfold_ranges, true, true, cx);
11682 this.buffer.update(cx, |buffer, cx| {
11683 for (range, text) in edits {
11684 buffer.edit([(range, text)], None, cx);
11685 }
11686 });
11687 this.fold_creases(refold_creases, true, window, cx);
11688 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11689 });
11690 }
11691
11692 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11693 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11694 let text_layout_details = &self.text_layout_details(window);
11695 self.transact(window, cx, |this, window, cx| {
11696 let edits = this.change_selections(Default::default(), window, cx, |s| {
11697 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11698 s.move_with(|display_map, selection| {
11699 if !selection.is_empty() {
11700 return;
11701 }
11702
11703 let mut head = selection.head();
11704 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11705 if head.column() == display_map.line_len(head.row()) {
11706 transpose_offset = display_map
11707 .buffer_snapshot
11708 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11709 }
11710
11711 if transpose_offset == 0 {
11712 return;
11713 }
11714
11715 *head.column_mut() += 1;
11716 head = display_map.clip_point(head, Bias::Right);
11717 let goal = SelectionGoal::HorizontalPosition(
11718 display_map
11719 .x_for_display_point(head, text_layout_details)
11720 .into(),
11721 );
11722 selection.collapse_to(head, goal);
11723
11724 let transpose_start = display_map
11725 .buffer_snapshot
11726 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11727 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11728 let transpose_end = display_map
11729 .buffer_snapshot
11730 .clip_offset(transpose_offset + 1, Bias::Right);
11731 if let Some(ch) =
11732 display_map.buffer_snapshot.chars_at(transpose_start).next()
11733 {
11734 edits.push((transpose_start..transpose_offset, String::new()));
11735 edits.push((transpose_end..transpose_end, ch.to_string()));
11736 }
11737 }
11738 });
11739 edits
11740 });
11741 this.buffer
11742 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11743 let selections = this.selections.all::<usize>(cx);
11744 this.change_selections(Default::default(), window, cx, |s| {
11745 s.select(selections);
11746 });
11747 });
11748 }
11749
11750 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11751 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11752 if self.mode.is_single_line() {
11753 cx.propagate();
11754 return;
11755 }
11756
11757 self.rewrap_impl(RewrapOptions::default(), cx)
11758 }
11759
11760 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11761 let buffer = self.buffer.read(cx).snapshot(cx);
11762 let selections = self.selections.all::<Point>(cx);
11763
11764 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11765 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11766 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11767 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11768 .peekable();
11769
11770 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11771 row
11772 } else {
11773 return Vec::new();
11774 };
11775
11776 let language_settings = buffer.language_settings_at(selection.head(), cx);
11777 let language_scope = buffer.language_scope_at(selection.head());
11778
11779 let indent_and_prefix_for_row =
11780 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11781 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11782 let (comment_prefix, rewrap_prefix) =
11783 if let Some(language_scope) = &language_scope {
11784 let indent_end = Point::new(row, indent.len);
11785 let comment_prefix = language_scope
11786 .line_comment_prefixes()
11787 .iter()
11788 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11789 .map(|prefix| prefix.to_string());
11790 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11791 let line_text_after_indent = buffer
11792 .text_for_range(indent_end..line_end)
11793 .collect::<String>();
11794 let rewrap_prefix = language_scope
11795 .rewrap_prefixes()
11796 .iter()
11797 .find_map(|prefix_regex| {
11798 prefix_regex.find(&line_text_after_indent).map(|mat| {
11799 if mat.start() == 0 {
11800 Some(mat.as_str().to_string())
11801 } else {
11802 None
11803 }
11804 })
11805 })
11806 .flatten();
11807 (comment_prefix, rewrap_prefix)
11808 } else {
11809 (None, None)
11810 };
11811 (indent, comment_prefix, rewrap_prefix)
11812 };
11813
11814 let mut ranges = Vec::new();
11815 let from_empty_selection = selection.is_empty();
11816
11817 let mut current_range_start = first_row;
11818 let mut prev_row = first_row;
11819 let (
11820 mut current_range_indent,
11821 mut current_range_comment_prefix,
11822 mut current_range_rewrap_prefix,
11823 ) = indent_and_prefix_for_row(first_row);
11824
11825 for row in non_blank_rows_iter.skip(1) {
11826 let has_paragraph_break = row > prev_row + 1;
11827
11828 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11829 indent_and_prefix_for_row(row);
11830
11831 let has_indent_change = row_indent != current_range_indent;
11832 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11833
11834 let has_boundary_change = has_comment_change
11835 || row_rewrap_prefix.is_some()
11836 || (has_indent_change && current_range_comment_prefix.is_some());
11837
11838 if has_paragraph_break || has_boundary_change {
11839 ranges.push((
11840 language_settings.clone(),
11841 Point::new(current_range_start, 0)
11842 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11843 current_range_indent,
11844 current_range_comment_prefix.clone(),
11845 current_range_rewrap_prefix.clone(),
11846 from_empty_selection,
11847 ));
11848 current_range_start = row;
11849 current_range_indent = row_indent;
11850 current_range_comment_prefix = row_comment_prefix;
11851 current_range_rewrap_prefix = row_rewrap_prefix;
11852 }
11853 prev_row = row;
11854 }
11855
11856 ranges.push((
11857 language_settings.clone(),
11858 Point::new(current_range_start, 0)
11859 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11860 current_range_indent,
11861 current_range_comment_prefix,
11862 current_range_rewrap_prefix,
11863 from_empty_selection,
11864 ));
11865
11866 ranges
11867 });
11868
11869 let mut edits = Vec::new();
11870 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11871
11872 for (
11873 language_settings,
11874 wrap_range,
11875 indent_size,
11876 comment_prefix,
11877 rewrap_prefix,
11878 from_empty_selection,
11879 ) in wrap_ranges
11880 {
11881 let mut start_row = wrap_range.start.row;
11882 let mut end_row = wrap_range.end.row;
11883
11884 // Skip selections that overlap with a range that has already been rewrapped.
11885 let selection_range = start_row..end_row;
11886 if rewrapped_row_ranges
11887 .iter()
11888 .any(|range| range.overlaps(&selection_range))
11889 {
11890 continue;
11891 }
11892
11893 let tab_size = language_settings.tab_size;
11894
11895 let indent_prefix = indent_size.chars().collect::<String>();
11896 let mut line_prefix = indent_prefix.clone();
11897 let mut inside_comment = false;
11898 if let Some(prefix) = &comment_prefix {
11899 line_prefix.push_str(prefix);
11900 inside_comment = true;
11901 }
11902 if let Some(prefix) = &rewrap_prefix {
11903 line_prefix.push_str(prefix);
11904 }
11905
11906 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11907 RewrapBehavior::InComments => inside_comment,
11908 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11909 RewrapBehavior::Anywhere => true,
11910 };
11911
11912 let should_rewrap = options.override_language_settings
11913 || allow_rewrap_based_on_language
11914 || self.hard_wrap.is_some();
11915 if !should_rewrap {
11916 continue;
11917 }
11918
11919 if from_empty_selection {
11920 'expand_upwards: while start_row > 0 {
11921 let prev_row = start_row - 1;
11922 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11923 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11924 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11925 {
11926 start_row = prev_row;
11927 } else {
11928 break 'expand_upwards;
11929 }
11930 }
11931
11932 'expand_downwards: while end_row < buffer.max_point().row {
11933 let next_row = end_row + 1;
11934 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11935 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11936 && !buffer.is_line_blank(MultiBufferRow(next_row))
11937 {
11938 end_row = next_row;
11939 } else {
11940 break 'expand_downwards;
11941 }
11942 }
11943 }
11944
11945 let start = Point::new(start_row, 0);
11946 let start_offset = start.to_offset(&buffer);
11947 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11948 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11949 let Some(lines_without_prefixes) = selection_text
11950 .lines()
11951 .enumerate()
11952 .map(|(ix, line)| {
11953 let line_trimmed = line.trim_start();
11954 if rewrap_prefix.is_some() && ix > 0 {
11955 Ok(line_trimmed)
11956 } else {
11957 line_trimmed
11958 .strip_prefix(&line_prefix.trim_start())
11959 .with_context(|| {
11960 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11961 })
11962 }
11963 })
11964 .collect::<Result<Vec<_>, _>>()
11965 .log_err()
11966 else {
11967 continue;
11968 };
11969
11970 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11971 buffer
11972 .language_settings_at(Point::new(start_row, 0), cx)
11973 .preferred_line_length as usize
11974 });
11975
11976 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11977 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11978 } else {
11979 line_prefix.clone()
11980 };
11981
11982 let wrapped_text = wrap_with_prefix(
11983 line_prefix,
11984 subsequent_lines_prefix,
11985 lines_without_prefixes.join("\n"),
11986 wrap_column,
11987 tab_size,
11988 options.preserve_existing_whitespace,
11989 );
11990
11991 // TODO: should always use char-based diff while still supporting cursor behavior that
11992 // matches vim.
11993 let mut diff_options = DiffOptions::default();
11994 if options.override_language_settings {
11995 diff_options.max_word_diff_len = 0;
11996 diff_options.max_word_diff_line_count = 0;
11997 } else {
11998 diff_options.max_word_diff_len = usize::MAX;
11999 diff_options.max_word_diff_line_count = usize::MAX;
12000 }
12001
12002 for (old_range, new_text) in
12003 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12004 {
12005 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12006 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12007 edits.push((edit_start..edit_end, new_text));
12008 }
12009
12010 rewrapped_row_ranges.push(start_row..=end_row);
12011 }
12012
12013 self.buffer
12014 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12015 }
12016
12017 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
12018 let mut text = String::new();
12019 let buffer = self.buffer.read(cx).snapshot(cx);
12020 let mut selections = self.selections.all::<Point>(cx);
12021 let mut clipboard_selections = Vec::with_capacity(selections.len());
12022 {
12023 let max_point = buffer.max_point();
12024 let mut is_first = true;
12025 for selection in &mut selections {
12026 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12027 if is_entire_line {
12028 selection.start = Point::new(selection.start.row, 0);
12029 if !selection.is_empty() && selection.end.column == 0 {
12030 selection.end = cmp::min(max_point, selection.end);
12031 } else {
12032 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12033 }
12034 selection.goal = SelectionGoal::None;
12035 }
12036 if is_first {
12037 is_first = false;
12038 } else {
12039 text += "\n";
12040 }
12041 let mut len = 0;
12042 for chunk in buffer.text_for_range(selection.start..selection.end) {
12043 text.push_str(chunk);
12044 len += chunk.len();
12045 }
12046 clipboard_selections.push(ClipboardSelection {
12047 len,
12048 is_entire_line,
12049 first_line_indent: buffer
12050 .indent_size_for_line(MultiBufferRow(selection.start.row))
12051 .len,
12052 });
12053 }
12054 }
12055
12056 self.transact(window, cx, |this, window, cx| {
12057 this.change_selections(Default::default(), window, cx, |s| {
12058 s.select(selections);
12059 });
12060 this.insert("", window, cx);
12061 });
12062 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12063 }
12064
12065 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12066 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12067 let item = self.cut_common(window, cx);
12068 cx.write_to_clipboard(item);
12069 }
12070
12071 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12072 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12073 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12074 s.move_with(|snapshot, sel| {
12075 if sel.is_empty() {
12076 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12077 }
12078 });
12079 });
12080 let item = self.cut_common(window, cx);
12081 cx.set_global(KillRing(item))
12082 }
12083
12084 pub fn kill_ring_yank(
12085 &mut self,
12086 _: &KillRingYank,
12087 window: &mut Window,
12088 cx: &mut Context<Self>,
12089 ) {
12090 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12091 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12092 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12093 (kill_ring.text().to_string(), kill_ring.metadata_json())
12094 } else {
12095 return;
12096 }
12097 } else {
12098 return;
12099 };
12100 self.do_paste(&text, metadata, false, window, cx);
12101 }
12102
12103 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12104 self.do_copy(true, cx);
12105 }
12106
12107 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12108 self.do_copy(false, cx);
12109 }
12110
12111 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12112 let selections = self.selections.all::<Point>(cx);
12113 let buffer = self.buffer.read(cx).read(cx);
12114 let mut text = String::new();
12115
12116 let mut clipboard_selections = Vec::with_capacity(selections.len());
12117 {
12118 let max_point = buffer.max_point();
12119 let mut is_first = true;
12120 for selection in &selections {
12121 let mut start = selection.start;
12122 let mut end = selection.end;
12123 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12124 if is_entire_line {
12125 start = Point::new(start.row, 0);
12126 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12127 }
12128
12129 let mut trimmed_selections = Vec::new();
12130 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12131 let row = MultiBufferRow(start.row);
12132 let first_indent = buffer.indent_size_for_line(row);
12133 if first_indent.len == 0 || start.column > first_indent.len {
12134 trimmed_selections.push(start..end);
12135 } else {
12136 trimmed_selections.push(
12137 Point::new(row.0, first_indent.len)
12138 ..Point::new(row.0, buffer.line_len(row)),
12139 );
12140 for row in start.row + 1..=end.row {
12141 let mut line_len = buffer.line_len(MultiBufferRow(row));
12142 if row == end.row {
12143 line_len = end.column;
12144 }
12145 if line_len == 0 {
12146 trimmed_selections
12147 .push(Point::new(row, 0)..Point::new(row, line_len));
12148 continue;
12149 }
12150 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12151 if row_indent_size.len >= first_indent.len {
12152 trimmed_selections.push(
12153 Point::new(row, first_indent.len)..Point::new(row, line_len),
12154 );
12155 } else {
12156 trimmed_selections.clear();
12157 trimmed_selections.push(start..end);
12158 break;
12159 }
12160 }
12161 }
12162 } else {
12163 trimmed_selections.push(start..end);
12164 }
12165
12166 for trimmed_range in trimmed_selections {
12167 if is_first {
12168 is_first = false;
12169 } else {
12170 text += "\n";
12171 }
12172 let mut len = 0;
12173 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12174 text.push_str(chunk);
12175 len += chunk.len();
12176 }
12177 clipboard_selections.push(ClipboardSelection {
12178 len,
12179 is_entire_line,
12180 first_line_indent: buffer
12181 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12182 .len,
12183 });
12184 }
12185 }
12186 }
12187
12188 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12189 text,
12190 clipboard_selections,
12191 ));
12192 }
12193
12194 pub fn do_paste(
12195 &mut self,
12196 text: &String,
12197 clipboard_selections: Option<Vec<ClipboardSelection>>,
12198 handle_entire_lines: bool,
12199 window: &mut Window,
12200 cx: &mut Context<Self>,
12201 ) {
12202 if self.read_only(cx) {
12203 return;
12204 }
12205
12206 let clipboard_text = Cow::Borrowed(text);
12207
12208 self.transact(window, cx, |this, window, cx| {
12209 let had_active_edit_prediction = this.has_active_edit_prediction();
12210
12211 if let Some(mut clipboard_selections) = clipboard_selections {
12212 let old_selections = this.selections.all::<usize>(cx);
12213 let all_selections_were_entire_line =
12214 clipboard_selections.iter().all(|s| s.is_entire_line);
12215 let first_selection_indent_column =
12216 clipboard_selections.first().map(|s| s.first_line_indent);
12217 if clipboard_selections.len() != old_selections.len() {
12218 clipboard_selections.drain(..);
12219 }
12220 let cursor_offset = this.selections.last::<usize>(cx).head();
12221 let mut auto_indent_on_paste = true;
12222
12223 this.buffer.update(cx, |buffer, cx| {
12224 let snapshot = buffer.read(cx);
12225 auto_indent_on_paste = snapshot
12226 .language_settings_at(cursor_offset, cx)
12227 .auto_indent_on_paste;
12228
12229 let mut start_offset = 0;
12230 let mut edits = Vec::new();
12231 let mut original_indent_columns = Vec::new();
12232 for (ix, selection) in old_selections.iter().enumerate() {
12233 let to_insert;
12234 let entire_line;
12235 let original_indent_column;
12236 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12237 let end_offset = start_offset + clipboard_selection.len;
12238 to_insert = &clipboard_text[start_offset..end_offset];
12239 entire_line = clipboard_selection.is_entire_line;
12240 start_offset = end_offset + 1;
12241 original_indent_column = Some(clipboard_selection.first_line_indent);
12242 } else {
12243 to_insert = clipboard_text.as_str();
12244 entire_line = all_selections_were_entire_line;
12245 original_indent_column = first_selection_indent_column
12246 }
12247
12248 // If the corresponding selection was empty when this slice of the
12249 // clipboard text was written, then the entire line containing the
12250 // selection was copied. If this selection is also currently empty,
12251 // then paste the line before the current line of the buffer.
12252 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12253 let column = selection.start.to_point(&snapshot).column as usize;
12254 let line_start = selection.start - column;
12255 line_start..line_start
12256 } else {
12257 selection.range()
12258 };
12259
12260 edits.push((range, to_insert));
12261 original_indent_columns.push(original_indent_column);
12262 }
12263 drop(snapshot);
12264
12265 buffer.edit(
12266 edits,
12267 if auto_indent_on_paste {
12268 Some(AutoindentMode::Block {
12269 original_indent_columns,
12270 })
12271 } else {
12272 None
12273 },
12274 cx,
12275 );
12276 });
12277
12278 let selections = this.selections.all::<usize>(cx);
12279 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12280 } else {
12281 this.insert(&clipboard_text, window, cx);
12282 }
12283
12284 let trigger_in_words =
12285 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12286
12287 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12288 });
12289 }
12290
12291 pub fn diff_clipboard_with_selection(
12292 &mut self,
12293 _: &DiffClipboardWithSelection,
12294 window: &mut Window,
12295 cx: &mut Context<Self>,
12296 ) {
12297 let selections = self.selections.all::<usize>(cx);
12298
12299 if selections.is_empty() {
12300 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12301 return;
12302 };
12303
12304 let clipboard_text = match cx.read_from_clipboard() {
12305 Some(item) => match item.entries().first() {
12306 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12307 _ => None,
12308 },
12309 None => None,
12310 };
12311
12312 let Some(clipboard_text) = clipboard_text else {
12313 log::warn!("Clipboard doesn't contain text.");
12314 return;
12315 };
12316
12317 window.dispatch_action(
12318 Box::new(DiffClipboardWithSelectionData {
12319 clipboard_text,
12320 editor: cx.entity(),
12321 }),
12322 cx,
12323 );
12324 }
12325
12326 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12327 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12328 if let Some(item) = cx.read_from_clipboard() {
12329 let entries = item.entries();
12330
12331 match entries.first() {
12332 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12333 // of all the pasted entries.
12334 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12335 .do_paste(
12336 clipboard_string.text(),
12337 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12338 true,
12339 window,
12340 cx,
12341 ),
12342 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12343 }
12344 }
12345 }
12346
12347 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12348 if self.read_only(cx) {
12349 return;
12350 }
12351
12352 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12353
12354 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12355 if let Some((selections, _)) =
12356 self.selection_history.transaction(transaction_id).cloned()
12357 {
12358 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12359 s.select_anchors(selections.to_vec());
12360 });
12361 } else {
12362 log::error!(
12363 "No entry in selection_history found for undo. \
12364 This may correspond to a bug where undo does not update the selection. \
12365 If this is occurring, please add details to \
12366 https://github.com/zed-industries/zed/issues/22692"
12367 );
12368 }
12369 self.request_autoscroll(Autoscroll::fit(), cx);
12370 self.unmark_text(window, cx);
12371 self.refresh_edit_prediction(true, false, window, cx);
12372 cx.emit(EditorEvent::Edited { transaction_id });
12373 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12374 }
12375 }
12376
12377 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12378 if self.read_only(cx) {
12379 return;
12380 }
12381
12382 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12383
12384 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12385 if let Some((_, Some(selections))) =
12386 self.selection_history.transaction(transaction_id).cloned()
12387 {
12388 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12389 s.select_anchors(selections.to_vec());
12390 });
12391 } else {
12392 log::error!(
12393 "No entry in selection_history found for redo. \
12394 This may correspond to a bug where undo does not update the selection. \
12395 If this is occurring, please add details to \
12396 https://github.com/zed-industries/zed/issues/22692"
12397 );
12398 }
12399 self.request_autoscroll(Autoscroll::fit(), cx);
12400 self.unmark_text(window, cx);
12401 self.refresh_edit_prediction(true, false, window, cx);
12402 cx.emit(EditorEvent::Edited { transaction_id });
12403 }
12404 }
12405
12406 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12407 self.buffer
12408 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12409 }
12410
12411 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12412 self.buffer
12413 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12414 }
12415
12416 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12417 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12418 self.change_selections(Default::default(), window, cx, |s| {
12419 s.move_with(|map, selection| {
12420 let cursor = if selection.is_empty() {
12421 movement::left(map, selection.start)
12422 } else {
12423 selection.start
12424 };
12425 selection.collapse_to(cursor, SelectionGoal::None);
12426 });
12427 })
12428 }
12429
12430 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12431 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12432 self.change_selections(Default::default(), window, cx, |s| {
12433 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12434 })
12435 }
12436
12437 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12439 self.change_selections(Default::default(), window, cx, |s| {
12440 s.move_with(|map, selection| {
12441 let cursor = if selection.is_empty() {
12442 movement::right(map, selection.end)
12443 } else {
12444 selection.end
12445 };
12446 selection.collapse_to(cursor, SelectionGoal::None)
12447 });
12448 })
12449 }
12450
12451 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12453 self.change_selections(Default::default(), window, cx, |s| {
12454 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12455 })
12456 }
12457
12458 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12459 if self.take_rename(true, window, cx).is_some() {
12460 return;
12461 }
12462
12463 if self.mode.is_single_line() {
12464 cx.propagate();
12465 return;
12466 }
12467
12468 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12469
12470 let text_layout_details = &self.text_layout_details(window);
12471 let selection_count = self.selections.count();
12472 let first_selection = self.selections.first_anchor();
12473
12474 self.change_selections(Default::default(), window, cx, |s| {
12475 s.move_with(|map, selection| {
12476 if !selection.is_empty() {
12477 selection.goal = SelectionGoal::None;
12478 }
12479 let (cursor, goal) = movement::up(
12480 map,
12481 selection.start,
12482 selection.goal,
12483 false,
12484 text_layout_details,
12485 );
12486 selection.collapse_to(cursor, goal);
12487 });
12488 });
12489
12490 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12491 {
12492 cx.propagate();
12493 }
12494 }
12495
12496 pub fn move_up_by_lines(
12497 &mut self,
12498 action: &MoveUpByLines,
12499 window: &mut Window,
12500 cx: &mut Context<Self>,
12501 ) {
12502 if self.take_rename(true, window, cx).is_some() {
12503 return;
12504 }
12505
12506 if self.mode.is_single_line() {
12507 cx.propagate();
12508 return;
12509 }
12510
12511 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12512
12513 let text_layout_details = &self.text_layout_details(window);
12514
12515 self.change_selections(Default::default(), window, cx, |s| {
12516 s.move_with(|map, selection| {
12517 if !selection.is_empty() {
12518 selection.goal = SelectionGoal::None;
12519 }
12520 let (cursor, goal) = movement::up_by_rows(
12521 map,
12522 selection.start,
12523 action.lines,
12524 selection.goal,
12525 false,
12526 text_layout_details,
12527 );
12528 selection.collapse_to(cursor, goal);
12529 });
12530 })
12531 }
12532
12533 pub fn move_down_by_lines(
12534 &mut self,
12535 action: &MoveDownByLines,
12536 window: &mut Window,
12537 cx: &mut Context<Self>,
12538 ) {
12539 if self.take_rename(true, window, cx).is_some() {
12540 return;
12541 }
12542
12543 if self.mode.is_single_line() {
12544 cx.propagate();
12545 return;
12546 }
12547
12548 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12549
12550 let text_layout_details = &self.text_layout_details(window);
12551
12552 self.change_selections(Default::default(), window, cx, |s| {
12553 s.move_with(|map, selection| {
12554 if !selection.is_empty() {
12555 selection.goal = SelectionGoal::None;
12556 }
12557 let (cursor, goal) = movement::down_by_rows(
12558 map,
12559 selection.start,
12560 action.lines,
12561 selection.goal,
12562 false,
12563 text_layout_details,
12564 );
12565 selection.collapse_to(cursor, goal);
12566 });
12567 })
12568 }
12569
12570 pub fn select_down_by_lines(
12571 &mut self,
12572 action: &SelectDownByLines,
12573 window: &mut Window,
12574 cx: &mut Context<Self>,
12575 ) {
12576 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12577 let text_layout_details = &self.text_layout_details(window);
12578 self.change_selections(Default::default(), window, cx, |s| {
12579 s.move_heads_with(|map, head, goal| {
12580 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12581 })
12582 })
12583 }
12584
12585 pub fn select_up_by_lines(
12586 &mut self,
12587 action: &SelectUpByLines,
12588 window: &mut Window,
12589 cx: &mut Context<Self>,
12590 ) {
12591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12592 let text_layout_details = &self.text_layout_details(window);
12593 self.change_selections(Default::default(), window, cx, |s| {
12594 s.move_heads_with(|map, head, goal| {
12595 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12596 })
12597 })
12598 }
12599
12600 pub fn select_page_up(
12601 &mut self,
12602 _: &SelectPageUp,
12603 window: &mut Window,
12604 cx: &mut Context<Self>,
12605 ) {
12606 let Some(row_count) = self.visible_row_count() else {
12607 return;
12608 };
12609
12610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12611
12612 let text_layout_details = &self.text_layout_details(window);
12613
12614 self.change_selections(Default::default(), window, cx, |s| {
12615 s.move_heads_with(|map, head, goal| {
12616 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12617 })
12618 })
12619 }
12620
12621 pub fn move_page_up(
12622 &mut self,
12623 action: &MovePageUp,
12624 window: &mut Window,
12625 cx: &mut Context<Self>,
12626 ) {
12627 if self.take_rename(true, window, cx).is_some() {
12628 return;
12629 }
12630
12631 if self
12632 .context_menu
12633 .borrow_mut()
12634 .as_mut()
12635 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12636 .unwrap_or(false)
12637 {
12638 return;
12639 }
12640
12641 if matches!(self.mode, EditorMode::SingleLine) {
12642 cx.propagate();
12643 return;
12644 }
12645
12646 let Some(row_count) = self.visible_row_count() else {
12647 return;
12648 };
12649
12650 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12651
12652 let effects = if action.center_cursor {
12653 SelectionEffects::scroll(Autoscroll::center())
12654 } else {
12655 SelectionEffects::default()
12656 };
12657
12658 let text_layout_details = &self.text_layout_details(window);
12659
12660 self.change_selections(effects, window, cx, |s| {
12661 s.move_with(|map, selection| {
12662 if !selection.is_empty() {
12663 selection.goal = SelectionGoal::None;
12664 }
12665 let (cursor, goal) = movement::up_by_rows(
12666 map,
12667 selection.end,
12668 row_count,
12669 selection.goal,
12670 false,
12671 text_layout_details,
12672 );
12673 selection.collapse_to(cursor, goal);
12674 });
12675 });
12676 }
12677
12678 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12680 let text_layout_details = &self.text_layout_details(window);
12681 self.change_selections(Default::default(), window, cx, |s| {
12682 s.move_heads_with(|map, head, goal| {
12683 movement::up(map, head, goal, false, text_layout_details)
12684 })
12685 })
12686 }
12687
12688 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12689 self.take_rename(true, window, cx);
12690
12691 if self.mode.is_single_line() {
12692 cx.propagate();
12693 return;
12694 }
12695
12696 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12697
12698 let text_layout_details = &self.text_layout_details(window);
12699 let selection_count = self.selections.count();
12700 let first_selection = self.selections.first_anchor();
12701
12702 self.change_selections(Default::default(), window, cx, |s| {
12703 s.move_with(|map, selection| {
12704 if !selection.is_empty() {
12705 selection.goal = SelectionGoal::None;
12706 }
12707 let (cursor, goal) = movement::down(
12708 map,
12709 selection.end,
12710 selection.goal,
12711 false,
12712 text_layout_details,
12713 );
12714 selection.collapse_to(cursor, goal);
12715 });
12716 });
12717
12718 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12719 {
12720 cx.propagate();
12721 }
12722 }
12723
12724 pub fn select_page_down(
12725 &mut self,
12726 _: &SelectPageDown,
12727 window: &mut Window,
12728 cx: &mut Context<Self>,
12729 ) {
12730 let Some(row_count) = self.visible_row_count() else {
12731 return;
12732 };
12733
12734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12735
12736 let text_layout_details = &self.text_layout_details(window);
12737
12738 self.change_selections(Default::default(), window, cx, |s| {
12739 s.move_heads_with(|map, head, goal| {
12740 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12741 })
12742 })
12743 }
12744
12745 pub fn move_page_down(
12746 &mut self,
12747 action: &MovePageDown,
12748 window: &mut Window,
12749 cx: &mut Context<Self>,
12750 ) {
12751 if self.take_rename(true, window, cx).is_some() {
12752 return;
12753 }
12754
12755 if self
12756 .context_menu
12757 .borrow_mut()
12758 .as_mut()
12759 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12760 .unwrap_or(false)
12761 {
12762 return;
12763 }
12764
12765 if matches!(self.mode, EditorMode::SingleLine) {
12766 cx.propagate();
12767 return;
12768 }
12769
12770 let Some(row_count) = self.visible_row_count() else {
12771 return;
12772 };
12773
12774 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12775
12776 let effects = if action.center_cursor {
12777 SelectionEffects::scroll(Autoscroll::center())
12778 } else {
12779 SelectionEffects::default()
12780 };
12781
12782 let text_layout_details = &self.text_layout_details(window);
12783 self.change_selections(effects, window, cx, |s| {
12784 s.move_with(|map, selection| {
12785 if !selection.is_empty() {
12786 selection.goal = SelectionGoal::None;
12787 }
12788 let (cursor, goal) = movement::down_by_rows(
12789 map,
12790 selection.end,
12791 row_count,
12792 selection.goal,
12793 false,
12794 text_layout_details,
12795 );
12796 selection.collapse_to(cursor, goal);
12797 });
12798 });
12799 }
12800
12801 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12802 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12803 let text_layout_details = &self.text_layout_details(window);
12804 self.change_selections(Default::default(), window, cx, |s| {
12805 s.move_heads_with(|map, head, goal| {
12806 movement::down(map, head, goal, false, text_layout_details)
12807 })
12808 });
12809 }
12810
12811 pub fn context_menu_first(
12812 &mut self,
12813 _: &ContextMenuFirst,
12814 window: &mut Window,
12815 cx: &mut Context<Self>,
12816 ) {
12817 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12818 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12819 }
12820 }
12821
12822 pub fn context_menu_prev(
12823 &mut self,
12824 _: &ContextMenuPrevious,
12825 window: &mut Window,
12826 cx: &mut Context<Self>,
12827 ) {
12828 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12829 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12830 }
12831 }
12832
12833 pub fn context_menu_next(
12834 &mut self,
12835 _: &ContextMenuNext,
12836 window: &mut Window,
12837 cx: &mut Context<Self>,
12838 ) {
12839 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12840 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12841 }
12842 }
12843
12844 pub fn context_menu_last(
12845 &mut self,
12846 _: &ContextMenuLast,
12847 window: &mut Window,
12848 cx: &mut Context<Self>,
12849 ) {
12850 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12851 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12852 }
12853 }
12854
12855 pub fn signature_help_prev(
12856 &mut self,
12857 _: &SignatureHelpPrevious,
12858 _: &mut Window,
12859 cx: &mut Context<Self>,
12860 ) {
12861 if let Some(popover) = self.signature_help_state.popover_mut() {
12862 if popover.current_signature == 0 {
12863 popover.current_signature = popover.signatures.len() - 1;
12864 } else {
12865 popover.current_signature -= 1;
12866 }
12867 cx.notify();
12868 }
12869 }
12870
12871 pub fn signature_help_next(
12872 &mut self,
12873 _: &SignatureHelpNext,
12874 _: &mut Window,
12875 cx: &mut Context<Self>,
12876 ) {
12877 if let Some(popover) = self.signature_help_state.popover_mut() {
12878 if popover.current_signature + 1 == popover.signatures.len() {
12879 popover.current_signature = 0;
12880 } else {
12881 popover.current_signature += 1;
12882 }
12883 cx.notify();
12884 }
12885 }
12886
12887 pub fn move_to_previous_word_start(
12888 &mut self,
12889 _: &MoveToPreviousWordStart,
12890 window: &mut Window,
12891 cx: &mut Context<Self>,
12892 ) {
12893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12894 self.change_selections(Default::default(), window, cx, |s| {
12895 s.move_cursors_with(|map, head, _| {
12896 (
12897 movement::previous_word_start(map, head),
12898 SelectionGoal::None,
12899 )
12900 });
12901 })
12902 }
12903
12904 pub fn move_to_previous_subword_start(
12905 &mut self,
12906 _: &MoveToPreviousSubwordStart,
12907 window: &mut Window,
12908 cx: &mut Context<Self>,
12909 ) {
12910 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12911 self.change_selections(Default::default(), window, cx, |s| {
12912 s.move_cursors_with(|map, head, _| {
12913 (
12914 movement::previous_subword_start(map, head),
12915 SelectionGoal::None,
12916 )
12917 });
12918 })
12919 }
12920
12921 pub fn select_to_previous_word_start(
12922 &mut self,
12923 _: &SelectToPreviousWordStart,
12924 window: &mut Window,
12925 cx: &mut Context<Self>,
12926 ) {
12927 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12928 self.change_selections(Default::default(), window, cx, |s| {
12929 s.move_heads_with(|map, head, _| {
12930 (
12931 movement::previous_word_start(map, head),
12932 SelectionGoal::None,
12933 )
12934 });
12935 })
12936 }
12937
12938 pub fn select_to_previous_subword_start(
12939 &mut self,
12940 _: &SelectToPreviousSubwordStart,
12941 window: &mut Window,
12942 cx: &mut Context<Self>,
12943 ) {
12944 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12945 self.change_selections(Default::default(), window, cx, |s| {
12946 s.move_heads_with(|map, head, _| {
12947 (
12948 movement::previous_subword_start(map, head),
12949 SelectionGoal::None,
12950 )
12951 });
12952 })
12953 }
12954
12955 pub fn delete_to_previous_word_start(
12956 &mut self,
12957 action: &DeleteToPreviousWordStart,
12958 window: &mut Window,
12959 cx: &mut Context<Self>,
12960 ) {
12961 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12962 self.transact(window, cx, |this, window, cx| {
12963 this.select_autoclose_pair(window, cx);
12964 this.change_selections(Default::default(), window, cx, |s| {
12965 s.move_with(|map, selection| {
12966 if selection.is_empty() {
12967 let cursor = if action.ignore_newlines {
12968 movement::previous_word_start(map, selection.head())
12969 } else {
12970 movement::previous_word_start_or_newline(map, selection.head())
12971 };
12972 selection.set_head(cursor, SelectionGoal::None);
12973 }
12974 });
12975 });
12976 this.insert("", window, cx);
12977 });
12978 }
12979
12980 pub fn delete_to_previous_subword_start(
12981 &mut self,
12982 _: &DeleteToPreviousSubwordStart,
12983 window: &mut Window,
12984 cx: &mut Context<Self>,
12985 ) {
12986 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12987 self.transact(window, cx, |this, window, cx| {
12988 this.select_autoclose_pair(window, cx);
12989 this.change_selections(Default::default(), window, cx, |s| {
12990 s.move_with(|map, selection| {
12991 if selection.is_empty() {
12992 let cursor = movement::previous_subword_start(map, selection.head());
12993 selection.set_head(cursor, SelectionGoal::None);
12994 }
12995 });
12996 });
12997 this.insert("", window, cx);
12998 });
12999 }
13000
13001 pub fn move_to_next_word_end(
13002 &mut self,
13003 _: &MoveToNextWordEnd,
13004 window: &mut Window,
13005 cx: &mut Context<Self>,
13006 ) {
13007 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13008 self.change_selections(Default::default(), window, cx, |s| {
13009 s.move_cursors_with(|map, head, _| {
13010 (movement::next_word_end(map, head), SelectionGoal::None)
13011 });
13012 })
13013 }
13014
13015 pub fn move_to_next_subword_end(
13016 &mut self,
13017 _: &MoveToNextSubwordEnd,
13018 window: &mut Window,
13019 cx: &mut Context<Self>,
13020 ) {
13021 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13022 self.change_selections(Default::default(), window, cx, |s| {
13023 s.move_cursors_with(|map, head, _| {
13024 (movement::next_subword_end(map, head), SelectionGoal::None)
13025 });
13026 })
13027 }
13028
13029 pub fn select_to_next_word_end(
13030 &mut self,
13031 _: &SelectToNextWordEnd,
13032 window: &mut Window,
13033 cx: &mut Context<Self>,
13034 ) {
13035 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13036 self.change_selections(Default::default(), window, cx, |s| {
13037 s.move_heads_with(|map, head, _| {
13038 (movement::next_word_end(map, head), SelectionGoal::None)
13039 });
13040 })
13041 }
13042
13043 pub fn select_to_next_subword_end(
13044 &mut self,
13045 _: &SelectToNextSubwordEnd,
13046 window: &mut Window,
13047 cx: &mut Context<Self>,
13048 ) {
13049 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13050 self.change_selections(Default::default(), window, cx, |s| {
13051 s.move_heads_with(|map, head, _| {
13052 (movement::next_subword_end(map, head), SelectionGoal::None)
13053 });
13054 })
13055 }
13056
13057 pub fn delete_to_next_word_end(
13058 &mut self,
13059 action: &DeleteToNextWordEnd,
13060 window: &mut Window,
13061 cx: &mut Context<Self>,
13062 ) {
13063 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13064 self.transact(window, cx, |this, window, cx| {
13065 this.change_selections(Default::default(), window, cx, |s| {
13066 s.move_with(|map, selection| {
13067 if selection.is_empty() {
13068 let cursor = if action.ignore_newlines {
13069 movement::next_word_end(map, selection.head())
13070 } else {
13071 movement::next_word_end_or_newline(map, selection.head())
13072 };
13073 selection.set_head(cursor, SelectionGoal::None);
13074 }
13075 });
13076 });
13077 this.insert("", window, cx);
13078 });
13079 }
13080
13081 pub fn delete_to_next_subword_end(
13082 &mut self,
13083 _: &DeleteToNextSubwordEnd,
13084 window: &mut Window,
13085 cx: &mut Context<Self>,
13086 ) {
13087 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13088 self.transact(window, cx, |this, window, cx| {
13089 this.change_selections(Default::default(), window, cx, |s| {
13090 s.move_with(|map, selection| {
13091 if selection.is_empty() {
13092 let cursor = movement::next_subword_end(map, selection.head());
13093 selection.set_head(cursor, SelectionGoal::None);
13094 }
13095 });
13096 });
13097 this.insert("", window, cx);
13098 });
13099 }
13100
13101 pub fn move_to_beginning_of_line(
13102 &mut self,
13103 action: &MoveToBeginningOfLine,
13104 window: &mut Window,
13105 cx: &mut Context<Self>,
13106 ) {
13107 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13108 self.change_selections(Default::default(), window, cx, |s| {
13109 s.move_cursors_with(|map, head, _| {
13110 (
13111 movement::indented_line_beginning(
13112 map,
13113 head,
13114 action.stop_at_soft_wraps,
13115 action.stop_at_indent,
13116 ),
13117 SelectionGoal::None,
13118 )
13119 });
13120 })
13121 }
13122
13123 pub fn select_to_beginning_of_line(
13124 &mut self,
13125 action: &SelectToBeginningOfLine,
13126 window: &mut Window,
13127 cx: &mut Context<Self>,
13128 ) {
13129 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13130 self.change_selections(Default::default(), window, cx, |s| {
13131 s.move_heads_with(|map, head, _| {
13132 (
13133 movement::indented_line_beginning(
13134 map,
13135 head,
13136 action.stop_at_soft_wraps,
13137 action.stop_at_indent,
13138 ),
13139 SelectionGoal::None,
13140 )
13141 });
13142 });
13143 }
13144
13145 pub fn delete_to_beginning_of_line(
13146 &mut self,
13147 action: &DeleteToBeginningOfLine,
13148 window: &mut Window,
13149 cx: &mut Context<Self>,
13150 ) {
13151 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13152 self.transact(window, cx, |this, window, cx| {
13153 this.change_selections(Default::default(), window, cx, |s| {
13154 s.move_with(|_, selection| {
13155 selection.reversed = true;
13156 });
13157 });
13158
13159 this.select_to_beginning_of_line(
13160 &SelectToBeginningOfLine {
13161 stop_at_soft_wraps: false,
13162 stop_at_indent: action.stop_at_indent,
13163 },
13164 window,
13165 cx,
13166 );
13167 this.backspace(&Backspace, window, cx);
13168 });
13169 }
13170
13171 pub fn move_to_end_of_line(
13172 &mut self,
13173 action: &MoveToEndOfLine,
13174 window: &mut Window,
13175 cx: &mut Context<Self>,
13176 ) {
13177 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13178 self.change_selections(Default::default(), window, cx, |s| {
13179 s.move_cursors_with(|map, head, _| {
13180 (
13181 movement::line_end(map, head, action.stop_at_soft_wraps),
13182 SelectionGoal::None,
13183 )
13184 });
13185 })
13186 }
13187
13188 pub fn select_to_end_of_line(
13189 &mut self,
13190 action: &SelectToEndOfLine,
13191 window: &mut Window,
13192 cx: &mut Context<Self>,
13193 ) {
13194 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13195 self.change_selections(Default::default(), window, cx, |s| {
13196 s.move_heads_with(|map, head, _| {
13197 (
13198 movement::line_end(map, head, action.stop_at_soft_wraps),
13199 SelectionGoal::None,
13200 )
13201 });
13202 })
13203 }
13204
13205 pub fn delete_to_end_of_line(
13206 &mut self,
13207 _: &DeleteToEndOfLine,
13208 window: &mut Window,
13209 cx: &mut Context<Self>,
13210 ) {
13211 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13212 self.transact(window, cx, |this, window, cx| {
13213 this.select_to_end_of_line(
13214 &SelectToEndOfLine {
13215 stop_at_soft_wraps: false,
13216 },
13217 window,
13218 cx,
13219 );
13220 this.delete(&Delete, window, cx);
13221 });
13222 }
13223
13224 pub fn cut_to_end_of_line(
13225 &mut self,
13226 _: &CutToEndOfLine,
13227 window: &mut Window,
13228 cx: &mut Context<Self>,
13229 ) {
13230 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13231 self.transact(window, cx, |this, window, cx| {
13232 this.select_to_end_of_line(
13233 &SelectToEndOfLine {
13234 stop_at_soft_wraps: false,
13235 },
13236 window,
13237 cx,
13238 );
13239 this.cut(&Cut, window, cx);
13240 });
13241 }
13242
13243 pub fn move_to_start_of_paragraph(
13244 &mut self,
13245 _: &MoveToStartOfParagraph,
13246 window: &mut Window,
13247 cx: &mut Context<Self>,
13248 ) {
13249 if matches!(self.mode, EditorMode::SingleLine) {
13250 cx.propagate();
13251 return;
13252 }
13253 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13254 self.change_selections(Default::default(), window, cx, |s| {
13255 s.move_with(|map, selection| {
13256 selection.collapse_to(
13257 movement::start_of_paragraph(map, selection.head(), 1),
13258 SelectionGoal::None,
13259 )
13260 });
13261 })
13262 }
13263
13264 pub fn move_to_end_of_paragraph(
13265 &mut self,
13266 _: &MoveToEndOfParagraph,
13267 window: &mut Window,
13268 cx: &mut Context<Self>,
13269 ) {
13270 if matches!(self.mode, EditorMode::SingleLine) {
13271 cx.propagate();
13272 return;
13273 }
13274 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13275 self.change_selections(Default::default(), window, cx, |s| {
13276 s.move_with(|map, selection| {
13277 selection.collapse_to(
13278 movement::end_of_paragraph(map, selection.head(), 1),
13279 SelectionGoal::None,
13280 )
13281 });
13282 })
13283 }
13284
13285 pub fn select_to_start_of_paragraph(
13286 &mut self,
13287 _: &SelectToStartOfParagraph,
13288 window: &mut Window,
13289 cx: &mut Context<Self>,
13290 ) {
13291 if matches!(self.mode, EditorMode::SingleLine) {
13292 cx.propagate();
13293 return;
13294 }
13295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13296 self.change_selections(Default::default(), window, cx, |s| {
13297 s.move_heads_with(|map, head, _| {
13298 (
13299 movement::start_of_paragraph(map, head, 1),
13300 SelectionGoal::None,
13301 )
13302 });
13303 })
13304 }
13305
13306 pub fn select_to_end_of_paragraph(
13307 &mut self,
13308 _: &SelectToEndOfParagraph,
13309 window: &mut Window,
13310 cx: &mut Context<Self>,
13311 ) {
13312 if matches!(self.mode, EditorMode::SingleLine) {
13313 cx.propagate();
13314 return;
13315 }
13316 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13317 self.change_selections(Default::default(), window, cx, |s| {
13318 s.move_heads_with(|map, head, _| {
13319 (
13320 movement::end_of_paragraph(map, head, 1),
13321 SelectionGoal::None,
13322 )
13323 });
13324 })
13325 }
13326
13327 pub fn move_to_start_of_excerpt(
13328 &mut self,
13329 _: &MoveToStartOfExcerpt,
13330 window: &mut Window,
13331 cx: &mut Context<Self>,
13332 ) {
13333 if matches!(self.mode, EditorMode::SingleLine) {
13334 cx.propagate();
13335 return;
13336 }
13337 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13338 self.change_selections(Default::default(), window, cx, |s| {
13339 s.move_with(|map, selection| {
13340 selection.collapse_to(
13341 movement::start_of_excerpt(
13342 map,
13343 selection.head(),
13344 workspace::searchable::Direction::Prev,
13345 ),
13346 SelectionGoal::None,
13347 )
13348 });
13349 })
13350 }
13351
13352 pub fn move_to_start_of_next_excerpt(
13353 &mut self,
13354 _: &MoveToStartOfNextExcerpt,
13355 window: &mut Window,
13356 cx: &mut Context<Self>,
13357 ) {
13358 if matches!(self.mode, EditorMode::SingleLine) {
13359 cx.propagate();
13360 return;
13361 }
13362
13363 self.change_selections(Default::default(), window, cx, |s| {
13364 s.move_with(|map, selection| {
13365 selection.collapse_to(
13366 movement::start_of_excerpt(
13367 map,
13368 selection.head(),
13369 workspace::searchable::Direction::Next,
13370 ),
13371 SelectionGoal::None,
13372 )
13373 });
13374 })
13375 }
13376
13377 pub fn move_to_end_of_excerpt(
13378 &mut self,
13379 _: &MoveToEndOfExcerpt,
13380 window: &mut Window,
13381 cx: &mut Context<Self>,
13382 ) {
13383 if matches!(self.mode, EditorMode::SingleLine) {
13384 cx.propagate();
13385 return;
13386 }
13387 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13388 self.change_selections(Default::default(), window, cx, |s| {
13389 s.move_with(|map, selection| {
13390 selection.collapse_to(
13391 movement::end_of_excerpt(
13392 map,
13393 selection.head(),
13394 workspace::searchable::Direction::Next,
13395 ),
13396 SelectionGoal::None,
13397 )
13398 });
13399 })
13400 }
13401
13402 pub fn move_to_end_of_previous_excerpt(
13403 &mut self,
13404 _: &MoveToEndOfPreviousExcerpt,
13405 window: &mut Window,
13406 cx: &mut Context<Self>,
13407 ) {
13408 if matches!(self.mode, EditorMode::SingleLine) {
13409 cx.propagate();
13410 return;
13411 }
13412 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13413 self.change_selections(Default::default(), window, cx, |s| {
13414 s.move_with(|map, selection| {
13415 selection.collapse_to(
13416 movement::end_of_excerpt(
13417 map,
13418 selection.head(),
13419 workspace::searchable::Direction::Prev,
13420 ),
13421 SelectionGoal::None,
13422 )
13423 });
13424 })
13425 }
13426
13427 pub fn select_to_start_of_excerpt(
13428 &mut self,
13429 _: &SelectToStartOfExcerpt,
13430 window: &mut Window,
13431 cx: &mut Context<Self>,
13432 ) {
13433 if matches!(self.mode, EditorMode::SingleLine) {
13434 cx.propagate();
13435 return;
13436 }
13437 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13438 self.change_selections(Default::default(), window, cx, |s| {
13439 s.move_heads_with(|map, head, _| {
13440 (
13441 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13442 SelectionGoal::None,
13443 )
13444 });
13445 })
13446 }
13447
13448 pub fn select_to_start_of_next_excerpt(
13449 &mut self,
13450 _: &SelectToStartOfNextExcerpt,
13451 window: &mut Window,
13452 cx: &mut Context<Self>,
13453 ) {
13454 if matches!(self.mode, EditorMode::SingleLine) {
13455 cx.propagate();
13456 return;
13457 }
13458 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13459 self.change_selections(Default::default(), window, cx, |s| {
13460 s.move_heads_with(|map, head, _| {
13461 (
13462 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13463 SelectionGoal::None,
13464 )
13465 });
13466 })
13467 }
13468
13469 pub fn select_to_end_of_excerpt(
13470 &mut self,
13471 _: &SelectToEndOfExcerpt,
13472 window: &mut Window,
13473 cx: &mut Context<Self>,
13474 ) {
13475 if matches!(self.mode, EditorMode::SingleLine) {
13476 cx.propagate();
13477 return;
13478 }
13479 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13480 self.change_selections(Default::default(), window, cx, |s| {
13481 s.move_heads_with(|map, head, _| {
13482 (
13483 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13484 SelectionGoal::None,
13485 )
13486 });
13487 })
13488 }
13489
13490 pub fn select_to_end_of_previous_excerpt(
13491 &mut self,
13492 _: &SelectToEndOfPreviousExcerpt,
13493 window: &mut Window,
13494 cx: &mut Context<Self>,
13495 ) {
13496 if matches!(self.mode, EditorMode::SingleLine) {
13497 cx.propagate();
13498 return;
13499 }
13500 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13501 self.change_selections(Default::default(), window, cx, |s| {
13502 s.move_heads_with(|map, head, _| {
13503 (
13504 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13505 SelectionGoal::None,
13506 )
13507 });
13508 })
13509 }
13510
13511 pub fn move_to_beginning(
13512 &mut self,
13513 _: &MoveToBeginning,
13514 window: &mut Window,
13515 cx: &mut Context<Self>,
13516 ) {
13517 if matches!(self.mode, EditorMode::SingleLine) {
13518 cx.propagate();
13519 return;
13520 }
13521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13522 self.change_selections(Default::default(), window, cx, |s| {
13523 s.select_ranges(vec![0..0]);
13524 });
13525 }
13526
13527 pub fn select_to_beginning(
13528 &mut self,
13529 _: &SelectToBeginning,
13530 window: &mut Window,
13531 cx: &mut Context<Self>,
13532 ) {
13533 let mut selection = self.selections.last::<Point>(cx);
13534 selection.set_head(Point::zero(), SelectionGoal::None);
13535 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13536 self.change_selections(Default::default(), window, cx, |s| {
13537 s.select(vec![selection]);
13538 });
13539 }
13540
13541 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13542 if matches!(self.mode, EditorMode::SingleLine) {
13543 cx.propagate();
13544 return;
13545 }
13546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13547 let cursor = self.buffer.read(cx).read(cx).len();
13548 self.change_selections(Default::default(), window, cx, |s| {
13549 s.select_ranges(vec![cursor..cursor])
13550 });
13551 }
13552
13553 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13554 self.nav_history = nav_history;
13555 }
13556
13557 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13558 self.nav_history.as_ref()
13559 }
13560
13561 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13562 self.push_to_nav_history(
13563 self.selections.newest_anchor().head(),
13564 None,
13565 false,
13566 true,
13567 cx,
13568 );
13569 }
13570
13571 fn push_to_nav_history(
13572 &mut self,
13573 cursor_anchor: Anchor,
13574 new_position: Option<Point>,
13575 is_deactivate: bool,
13576 always: bool,
13577 cx: &mut Context<Self>,
13578 ) {
13579 if let Some(nav_history) = self.nav_history.as_mut() {
13580 let buffer = self.buffer.read(cx).read(cx);
13581 let cursor_position = cursor_anchor.to_point(&buffer);
13582 let scroll_state = self.scroll_manager.anchor();
13583 let scroll_top_row = scroll_state.top_row(&buffer);
13584 drop(buffer);
13585
13586 if let Some(new_position) = new_position {
13587 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13588 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13589 return;
13590 }
13591 }
13592
13593 nav_history.push(
13594 Some(NavigationData {
13595 cursor_anchor,
13596 cursor_position,
13597 scroll_anchor: scroll_state,
13598 scroll_top_row,
13599 }),
13600 cx,
13601 );
13602 cx.emit(EditorEvent::PushedToNavHistory {
13603 anchor: cursor_anchor,
13604 is_deactivate,
13605 })
13606 }
13607 }
13608
13609 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13611 let buffer = self.buffer.read(cx).snapshot(cx);
13612 let mut selection = self.selections.first::<usize>(cx);
13613 selection.set_head(buffer.len(), SelectionGoal::None);
13614 self.change_selections(Default::default(), window, cx, |s| {
13615 s.select(vec![selection]);
13616 });
13617 }
13618
13619 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13621 let end = self.buffer.read(cx).read(cx).len();
13622 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13623 s.select_ranges(vec![0..end]);
13624 });
13625 }
13626
13627 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13628 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13629 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13630 let mut selections = self.selections.all::<Point>(cx);
13631 let max_point = display_map.buffer_snapshot.max_point();
13632 for selection in &mut selections {
13633 let rows = selection.spanned_rows(true, &display_map);
13634 selection.start = Point::new(rows.start.0, 0);
13635 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13636 selection.reversed = false;
13637 }
13638 self.change_selections(Default::default(), window, cx, |s| {
13639 s.select(selections);
13640 });
13641 }
13642
13643 pub fn split_selection_into_lines(
13644 &mut self,
13645 action: &SplitSelectionIntoLines,
13646 window: &mut Window,
13647 cx: &mut Context<Self>,
13648 ) {
13649 let selections = self
13650 .selections
13651 .all::<Point>(cx)
13652 .into_iter()
13653 .map(|selection| selection.start..selection.end)
13654 .collect::<Vec<_>>();
13655 self.unfold_ranges(&selections, true, true, cx);
13656
13657 let mut new_selection_ranges = Vec::new();
13658 {
13659 let buffer = self.buffer.read(cx).read(cx);
13660 for selection in selections {
13661 for row in selection.start.row..selection.end.row {
13662 let line_start = Point::new(row, 0);
13663 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13664
13665 if action.keep_selections {
13666 // Keep the selection range for each line
13667 let selection_start = if row == selection.start.row {
13668 selection.start
13669 } else {
13670 line_start
13671 };
13672 new_selection_ranges.push(selection_start..line_end);
13673 } else {
13674 // Collapse to cursor at end of line
13675 new_selection_ranges.push(line_end..line_end);
13676 }
13677 }
13678
13679 let is_multiline_selection = selection.start.row != selection.end.row;
13680 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13681 // so this action feels more ergonomic when paired with other selection operations
13682 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13683 if !should_skip_last {
13684 if action.keep_selections {
13685 if is_multiline_selection {
13686 let line_start = Point::new(selection.end.row, 0);
13687 new_selection_ranges.push(line_start..selection.end);
13688 } else {
13689 new_selection_ranges.push(selection.start..selection.end);
13690 }
13691 } else {
13692 new_selection_ranges.push(selection.end..selection.end);
13693 }
13694 }
13695 }
13696 }
13697 self.change_selections(Default::default(), window, cx, |s| {
13698 s.select_ranges(new_selection_ranges);
13699 });
13700 }
13701
13702 pub fn add_selection_above(
13703 &mut self,
13704 _: &AddSelectionAbove,
13705 window: &mut Window,
13706 cx: &mut Context<Self>,
13707 ) {
13708 self.add_selection(true, window, cx);
13709 }
13710
13711 pub fn add_selection_below(
13712 &mut self,
13713 _: &AddSelectionBelow,
13714 window: &mut Window,
13715 cx: &mut Context<Self>,
13716 ) {
13717 self.add_selection(false, window, cx);
13718 }
13719
13720 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13722
13723 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13724 let all_selections = self.selections.all::<Point>(cx);
13725 let text_layout_details = self.text_layout_details(window);
13726
13727 let (mut columnar_selections, new_selections_to_columnarize) = {
13728 if let Some(state) = self.add_selections_state.as_ref() {
13729 let columnar_selection_ids: HashSet<_> = state
13730 .groups
13731 .iter()
13732 .flat_map(|group| group.stack.iter())
13733 .copied()
13734 .collect();
13735
13736 all_selections
13737 .into_iter()
13738 .partition(|s| columnar_selection_ids.contains(&s.id))
13739 } else {
13740 (Vec::new(), all_selections)
13741 }
13742 };
13743
13744 let mut state = self
13745 .add_selections_state
13746 .take()
13747 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13748
13749 for selection in new_selections_to_columnarize {
13750 let range = selection.display_range(&display_map).sorted();
13751 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13752 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13753 let positions = start_x.min(end_x)..start_x.max(end_x);
13754 let mut stack = Vec::new();
13755 for row in range.start.row().0..=range.end.row().0 {
13756 if let Some(selection) = self.selections.build_columnar_selection(
13757 &display_map,
13758 DisplayRow(row),
13759 &positions,
13760 selection.reversed,
13761 &text_layout_details,
13762 ) {
13763 stack.push(selection.id);
13764 columnar_selections.push(selection);
13765 }
13766 }
13767 if !stack.is_empty() {
13768 if above {
13769 stack.reverse();
13770 }
13771 state.groups.push(AddSelectionsGroup { above, stack });
13772 }
13773 }
13774
13775 let mut final_selections = Vec::new();
13776 let end_row = if above {
13777 DisplayRow(0)
13778 } else {
13779 display_map.max_point().row()
13780 };
13781
13782 let mut last_added_item_per_group = HashMap::default();
13783 for group in state.groups.iter_mut() {
13784 if let Some(last_id) = group.stack.last() {
13785 last_added_item_per_group.insert(*last_id, group);
13786 }
13787 }
13788
13789 for selection in columnar_selections {
13790 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13791 if above == group.above {
13792 let range = selection.display_range(&display_map).sorted();
13793 debug_assert_eq!(range.start.row(), range.end.row());
13794 let mut row = range.start.row();
13795 let positions =
13796 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13797 px(start)..px(end)
13798 } else {
13799 let start_x =
13800 display_map.x_for_display_point(range.start, &text_layout_details);
13801 let end_x =
13802 display_map.x_for_display_point(range.end, &text_layout_details);
13803 start_x.min(end_x)..start_x.max(end_x)
13804 };
13805
13806 let mut maybe_new_selection = None;
13807 while row != end_row {
13808 if above {
13809 row.0 -= 1;
13810 } else {
13811 row.0 += 1;
13812 }
13813 if let Some(new_selection) = self.selections.build_columnar_selection(
13814 &display_map,
13815 row,
13816 &positions,
13817 selection.reversed,
13818 &text_layout_details,
13819 ) {
13820 maybe_new_selection = Some(new_selection);
13821 break;
13822 }
13823 }
13824
13825 if let Some(new_selection) = maybe_new_selection {
13826 group.stack.push(new_selection.id);
13827 if above {
13828 final_selections.push(new_selection);
13829 final_selections.push(selection);
13830 } else {
13831 final_selections.push(selection);
13832 final_selections.push(new_selection);
13833 }
13834 } else {
13835 final_selections.push(selection);
13836 }
13837 } else {
13838 group.stack.pop();
13839 }
13840 } else {
13841 final_selections.push(selection);
13842 }
13843 }
13844
13845 self.change_selections(Default::default(), window, cx, |s| {
13846 s.select(final_selections);
13847 });
13848
13849 let final_selection_ids: HashSet<_> = self
13850 .selections
13851 .all::<Point>(cx)
13852 .iter()
13853 .map(|s| s.id)
13854 .collect();
13855 state.groups.retain_mut(|group| {
13856 // selections might get merged above so we remove invalid items from stacks
13857 group.stack.retain(|id| final_selection_ids.contains(id));
13858
13859 // single selection in stack can be treated as initial state
13860 group.stack.len() > 1
13861 });
13862
13863 if !state.groups.is_empty() {
13864 self.add_selections_state = Some(state);
13865 }
13866 }
13867
13868 fn select_match_ranges(
13869 &mut self,
13870 range: Range<usize>,
13871 reversed: bool,
13872 replace_newest: bool,
13873 auto_scroll: Option<Autoscroll>,
13874 window: &mut Window,
13875 cx: &mut Context<Editor>,
13876 ) {
13877 self.unfold_ranges(
13878 std::slice::from_ref(&range),
13879 false,
13880 auto_scroll.is_some(),
13881 cx,
13882 );
13883 let effects = if let Some(scroll) = auto_scroll {
13884 SelectionEffects::scroll(scroll)
13885 } else {
13886 SelectionEffects::no_scroll()
13887 };
13888 self.change_selections(effects, window, cx, |s| {
13889 if replace_newest {
13890 s.delete(s.newest_anchor().id);
13891 }
13892 if reversed {
13893 s.insert_range(range.end..range.start);
13894 } else {
13895 s.insert_range(range);
13896 }
13897 });
13898 }
13899
13900 pub fn select_next_match_internal(
13901 &mut self,
13902 display_map: &DisplaySnapshot,
13903 replace_newest: bool,
13904 autoscroll: Option<Autoscroll>,
13905 window: &mut Window,
13906 cx: &mut Context<Self>,
13907 ) -> Result<()> {
13908 let buffer = &display_map.buffer_snapshot;
13909 let mut selections = self.selections.all::<usize>(cx);
13910 if let Some(mut select_next_state) = self.select_next_state.take() {
13911 let query = &select_next_state.query;
13912 if !select_next_state.done {
13913 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13914 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13915 let mut next_selected_range = None;
13916
13917 let bytes_after_last_selection =
13918 buffer.bytes_in_range(last_selection.end..buffer.len());
13919 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13920 let query_matches = query
13921 .stream_find_iter(bytes_after_last_selection)
13922 .map(|result| (last_selection.end, result))
13923 .chain(
13924 query
13925 .stream_find_iter(bytes_before_first_selection)
13926 .map(|result| (0, result)),
13927 );
13928
13929 for (start_offset, query_match) in query_matches {
13930 let query_match = query_match.unwrap(); // can only fail due to I/O
13931 let offset_range =
13932 start_offset + query_match.start()..start_offset + query_match.end();
13933
13934 if !select_next_state.wordwise
13935 || (!buffer.is_inside_word(offset_range.start, false)
13936 && !buffer.is_inside_word(offset_range.end, false))
13937 {
13938 // TODO: This is n^2, because we might check all the selections
13939 if !selections
13940 .iter()
13941 .any(|selection| selection.range().overlaps(&offset_range))
13942 {
13943 next_selected_range = Some(offset_range);
13944 break;
13945 }
13946 }
13947 }
13948
13949 if let Some(next_selected_range) = next_selected_range {
13950 self.select_match_ranges(
13951 next_selected_range,
13952 last_selection.reversed,
13953 replace_newest,
13954 autoscroll,
13955 window,
13956 cx,
13957 );
13958 } else {
13959 select_next_state.done = true;
13960 }
13961 }
13962
13963 self.select_next_state = Some(select_next_state);
13964 } else {
13965 let mut only_carets = true;
13966 let mut same_text_selected = true;
13967 let mut selected_text = None;
13968
13969 let mut selections_iter = selections.iter().peekable();
13970 while let Some(selection) = selections_iter.next() {
13971 if selection.start != selection.end {
13972 only_carets = false;
13973 }
13974
13975 if same_text_selected {
13976 if selected_text.is_none() {
13977 selected_text =
13978 Some(buffer.text_for_range(selection.range()).collect::<String>());
13979 }
13980
13981 if let Some(next_selection) = selections_iter.peek() {
13982 if next_selection.range().len() == selection.range().len() {
13983 let next_selected_text = buffer
13984 .text_for_range(next_selection.range())
13985 .collect::<String>();
13986 if Some(next_selected_text) != selected_text {
13987 same_text_selected = false;
13988 selected_text = None;
13989 }
13990 } else {
13991 same_text_selected = false;
13992 selected_text = None;
13993 }
13994 }
13995 }
13996 }
13997
13998 if only_carets {
13999 for selection in &mut selections {
14000 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14001 selection.start = word_range.start;
14002 selection.end = word_range.end;
14003 selection.goal = SelectionGoal::None;
14004 selection.reversed = false;
14005 self.select_match_ranges(
14006 selection.start..selection.end,
14007 selection.reversed,
14008 replace_newest,
14009 autoscroll,
14010 window,
14011 cx,
14012 );
14013 }
14014
14015 if selections.len() == 1 {
14016 let selection = selections
14017 .last()
14018 .expect("ensured that there's only one selection");
14019 let query = buffer
14020 .text_for_range(selection.start..selection.end)
14021 .collect::<String>();
14022 let is_empty = query.is_empty();
14023 let select_state = SelectNextState {
14024 query: AhoCorasick::new(&[query])?,
14025 wordwise: true,
14026 done: is_empty,
14027 };
14028 self.select_next_state = Some(select_state);
14029 } else {
14030 self.select_next_state = None;
14031 }
14032 } else if let Some(selected_text) = selected_text {
14033 self.select_next_state = Some(SelectNextState {
14034 query: AhoCorasick::new(&[selected_text])?,
14035 wordwise: false,
14036 done: false,
14037 });
14038 self.select_next_match_internal(
14039 display_map,
14040 replace_newest,
14041 autoscroll,
14042 window,
14043 cx,
14044 )?;
14045 }
14046 }
14047 Ok(())
14048 }
14049
14050 pub fn select_all_matches(
14051 &mut self,
14052 _action: &SelectAllMatches,
14053 window: &mut Window,
14054 cx: &mut Context<Self>,
14055 ) -> Result<()> {
14056 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14057
14058 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14059
14060 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14061 let Some(select_next_state) = self.select_next_state.as_mut() else {
14062 return Ok(());
14063 };
14064 if select_next_state.done {
14065 return Ok(());
14066 }
14067
14068 let mut new_selections = Vec::new();
14069
14070 let reversed = self.selections.oldest::<usize>(cx).reversed;
14071 let buffer = &display_map.buffer_snapshot;
14072 let query_matches = select_next_state
14073 .query
14074 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14075
14076 for query_match in query_matches.into_iter() {
14077 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14078 let offset_range = if reversed {
14079 query_match.end()..query_match.start()
14080 } else {
14081 query_match.start()..query_match.end()
14082 };
14083
14084 if !select_next_state.wordwise
14085 || (!buffer.is_inside_word(offset_range.start, false)
14086 && !buffer.is_inside_word(offset_range.end, false))
14087 {
14088 new_selections.push(offset_range.start..offset_range.end);
14089 }
14090 }
14091
14092 select_next_state.done = true;
14093
14094 if new_selections.is_empty() {
14095 log::error!("bug: new_selections is empty in select_all_matches");
14096 return Ok(());
14097 }
14098
14099 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14100 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14101 selections.select_ranges(new_selections)
14102 });
14103
14104 Ok(())
14105 }
14106
14107 pub fn select_next(
14108 &mut self,
14109 action: &SelectNext,
14110 window: &mut Window,
14111 cx: &mut Context<Self>,
14112 ) -> Result<()> {
14113 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14114 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14115 self.select_next_match_internal(
14116 &display_map,
14117 action.replace_newest,
14118 Some(Autoscroll::newest()),
14119 window,
14120 cx,
14121 )?;
14122 Ok(())
14123 }
14124
14125 pub fn select_previous(
14126 &mut self,
14127 action: &SelectPrevious,
14128 window: &mut Window,
14129 cx: &mut Context<Self>,
14130 ) -> Result<()> {
14131 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14132 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14133 let buffer = &display_map.buffer_snapshot;
14134 let mut selections = self.selections.all::<usize>(cx);
14135 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14136 let query = &select_prev_state.query;
14137 if !select_prev_state.done {
14138 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14139 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14140 let mut next_selected_range = None;
14141 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14142 let bytes_before_last_selection =
14143 buffer.reversed_bytes_in_range(0..last_selection.start);
14144 let bytes_after_first_selection =
14145 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14146 let query_matches = query
14147 .stream_find_iter(bytes_before_last_selection)
14148 .map(|result| (last_selection.start, result))
14149 .chain(
14150 query
14151 .stream_find_iter(bytes_after_first_selection)
14152 .map(|result| (buffer.len(), result)),
14153 );
14154 for (end_offset, query_match) in query_matches {
14155 let query_match = query_match.unwrap(); // can only fail due to I/O
14156 let offset_range =
14157 end_offset - query_match.end()..end_offset - query_match.start();
14158
14159 if !select_prev_state.wordwise
14160 || (!buffer.is_inside_word(offset_range.start, false)
14161 && !buffer.is_inside_word(offset_range.end, false))
14162 {
14163 next_selected_range = Some(offset_range);
14164 break;
14165 }
14166 }
14167
14168 if let Some(next_selected_range) = next_selected_range {
14169 self.select_match_ranges(
14170 next_selected_range,
14171 last_selection.reversed,
14172 action.replace_newest,
14173 Some(Autoscroll::newest()),
14174 window,
14175 cx,
14176 );
14177 } else {
14178 select_prev_state.done = true;
14179 }
14180 }
14181
14182 self.select_prev_state = Some(select_prev_state);
14183 } else {
14184 let mut only_carets = true;
14185 let mut same_text_selected = true;
14186 let mut selected_text = None;
14187
14188 let mut selections_iter = selections.iter().peekable();
14189 while let Some(selection) = selections_iter.next() {
14190 if selection.start != selection.end {
14191 only_carets = false;
14192 }
14193
14194 if same_text_selected {
14195 if selected_text.is_none() {
14196 selected_text =
14197 Some(buffer.text_for_range(selection.range()).collect::<String>());
14198 }
14199
14200 if let Some(next_selection) = selections_iter.peek() {
14201 if next_selection.range().len() == selection.range().len() {
14202 let next_selected_text = buffer
14203 .text_for_range(next_selection.range())
14204 .collect::<String>();
14205 if Some(next_selected_text) != selected_text {
14206 same_text_selected = false;
14207 selected_text = None;
14208 }
14209 } else {
14210 same_text_selected = false;
14211 selected_text = None;
14212 }
14213 }
14214 }
14215 }
14216
14217 if only_carets {
14218 for selection in &mut selections {
14219 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14220 selection.start = word_range.start;
14221 selection.end = word_range.end;
14222 selection.goal = SelectionGoal::None;
14223 selection.reversed = false;
14224 self.select_match_ranges(
14225 selection.start..selection.end,
14226 selection.reversed,
14227 action.replace_newest,
14228 Some(Autoscroll::newest()),
14229 window,
14230 cx,
14231 );
14232 }
14233 if selections.len() == 1 {
14234 let selection = selections
14235 .last()
14236 .expect("ensured that there's only one selection");
14237 let query = buffer
14238 .text_for_range(selection.start..selection.end)
14239 .collect::<String>();
14240 let is_empty = query.is_empty();
14241 let select_state = SelectNextState {
14242 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14243 wordwise: true,
14244 done: is_empty,
14245 };
14246 self.select_prev_state = Some(select_state);
14247 } else {
14248 self.select_prev_state = None;
14249 }
14250 } else if let Some(selected_text) = selected_text {
14251 self.select_prev_state = Some(SelectNextState {
14252 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14253 wordwise: false,
14254 done: false,
14255 });
14256 self.select_previous(action, window, cx)?;
14257 }
14258 }
14259 Ok(())
14260 }
14261
14262 pub fn find_next_match(
14263 &mut self,
14264 _: &FindNextMatch,
14265 window: &mut Window,
14266 cx: &mut Context<Self>,
14267 ) -> Result<()> {
14268 let selections = self.selections.disjoint_anchors();
14269 match selections.first() {
14270 Some(first) if selections.len() >= 2 => {
14271 self.change_selections(Default::default(), window, cx, |s| {
14272 s.select_ranges([first.range()]);
14273 });
14274 }
14275 _ => self.select_next(
14276 &SelectNext {
14277 replace_newest: true,
14278 },
14279 window,
14280 cx,
14281 )?,
14282 }
14283 Ok(())
14284 }
14285
14286 pub fn find_previous_match(
14287 &mut self,
14288 _: &FindPreviousMatch,
14289 window: &mut Window,
14290 cx: &mut Context<Self>,
14291 ) -> Result<()> {
14292 let selections = self.selections.disjoint_anchors();
14293 match selections.last() {
14294 Some(last) if selections.len() >= 2 => {
14295 self.change_selections(Default::default(), window, cx, |s| {
14296 s.select_ranges([last.range()]);
14297 });
14298 }
14299 _ => self.select_previous(
14300 &SelectPrevious {
14301 replace_newest: true,
14302 },
14303 window,
14304 cx,
14305 )?,
14306 }
14307 Ok(())
14308 }
14309
14310 pub fn toggle_comments(
14311 &mut self,
14312 action: &ToggleComments,
14313 window: &mut Window,
14314 cx: &mut Context<Self>,
14315 ) {
14316 if self.read_only(cx) {
14317 return;
14318 }
14319 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14320 let text_layout_details = &self.text_layout_details(window);
14321 self.transact(window, cx, |this, window, cx| {
14322 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14323 let mut edits = Vec::new();
14324 let mut selection_edit_ranges = Vec::new();
14325 let mut last_toggled_row = None;
14326 let snapshot = this.buffer.read(cx).read(cx);
14327 let empty_str: Arc<str> = Arc::default();
14328 let mut suffixes_inserted = Vec::new();
14329 let ignore_indent = action.ignore_indent;
14330
14331 fn comment_prefix_range(
14332 snapshot: &MultiBufferSnapshot,
14333 row: MultiBufferRow,
14334 comment_prefix: &str,
14335 comment_prefix_whitespace: &str,
14336 ignore_indent: bool,
14337 ) -> Range<Point> {
14338 let indent_size = if ignore_indent {
14339 0
14340 } else {
14341 snapshot.indent_size_for_line(row).len
14342 };
14343
14344 let start = Point::new(row.0, indent_size);
14345
14346 let mut line_bytes = snapshot
14347 .bytes_in_range(start..snapshot.max_point())
14348 .flatten()
14349 .copied();
14350
14351 // If this line currently begins with the line comment prefix, then record
14352 // the range containing the prefix.
14353 if line_bytes
14354 .by_ref()
14355 .take(comment_prefix.len())
14356 .eq(comment_prefix.bytes())
14357 {
14358 // Include any whitespace that matches the comment prefix.
14359 let matching_whitespace_len = line_bytes
14360 .zip(comment_prefix_whitespace.bytes())
14361 .take_while(|(a, b)| a == b)
14362 .count() as u32;
14363 let end = Point::new(
14364 start.row,
14365 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14366 );
14367 start..end
14368 } else {
14369 start..start
14370 }
14371 }
14372
14373 fn comment_suffix_range(
14374 snapshot: &MultiBufferSnapshot,
14375 row: MultiBufferRow,
14376 comment_suffix: &str,
14377 comment_suffix_has_leading_space: bool,
14378 ) -> Range<Point> {
14379 let end = Point::new(row.0, snapshot.line_len(row));
14380 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14381
14382 let mut line_end_bytes = snapshot
14383 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14384 .flatten()
14385 .copied();
14386
14387 let leading_space_len = if suffix_start_column > 0
14388 && line_end_bytes.next() == Some(b' ')
14389 && comment_suffix_has_leading_space
14390 {
14391 1
14392 } else {
14393 0
14394 };
14395
14396 // If this line currently begins with the line comment prefix, then record
14397 // the range containing the prefix.
14398 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14399 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14400 start..end
14401 } else {
14402 end..end
14403 }
14404 }
14405
14406 // TODO: Handle selections that cross excerpts
14407 for selection in &mut selections {
14408 let start_column = snapshot
14409 .indent_size_for_line(MultiBufferRow(selection.start.row))
14410 .len;
14411 let language = if let Some(language) =
14412 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14413 {
14414 language
14415 } else {
14416 continue;
14417 };
14418
14419 selection_edit_ranges.clear();
14420
14421 // If multiple selections contain a given row, avoid processing that
14422 // row more than once.
14423 let mut start_row = MultiBufferRow(selection.start.row);
14424 if last_toggled_row == Some(start_row) {
14425 start_row = start_row.next_row();
14426 }
14427 let end_row =
14428 if selection.end.row > selection.start.row && selection.end.column == 0 {
14429 MultiBufferRow(selection.end.row - 1)
14430 } else {
14431 MultiBufferRow(selection.end.row)
14432 };
14433 last_toggled_row = Some(end_row);
14434
14435 if start_row > end_row {
14436 continue;
14437 }
14438
14439 // If the language has line comments, toggle those.
14440 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14441
14442 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14443 if ignore_indent {
14444 full_comment_prefixes = full_comment_prefixes
14445 .into_iter()
14446 .map(|s| Arc::from(s.trim_end()))
14447 .collect();
14448 }
14449
14450 if !full_comment_prefixes.is_empty() {
14451 let first_prefix = full_comment_prefixes
14452 .first()
14453 .expect("prefixes is non-empty");
14454 let prefix_trimmed_lengths = full_comment_prefixes
14455 .iter()
14456 .map(|p| p.trim_end_matches(' ').len())
14457 .collect::<SmallVec<[usize; 4]>>();
14458
14459 let mut all_selection_lines_are_comments = true;
14460
14461 for row in start_row.0..=end_row.0 {
14462 let row = MultiBufferRow(row);
14463 if start_row < end_row && snapshot.is_line_blank(row) {
14464 continue;
14465 }
14466
14467 let prefix_range = full_comment_prefixes
14468 .iter()
14469 .zip(prefix_trimmed_lengths.iter().copied())
14470 .map(|(prefix, trimmed_prefix_len)| {
14471 comment_prefix_range(
14472 snapshot.deref(),
14473 row,
14474 &prefix[..trimmed_prefix_len],
14475 &prefix[trimmed_prefix_len..],
14476 ignore_indent,
14477 )
14478 })
14479 .max_by_key(|range| range.end.column - range.start.column)
14480 .expect("prefixes is non-empty");
14481
14482 if prefix_range.is_empty() {
14483 all_selection_lines_are_comments = false;
14484 }
14485
14486 selection_edit_ranges.push(prefix_range);
14487 }
14488
14489 if all_selection_lines_are_comments {
14490 edits.extend(
14491 selection_edit_ranges
14492 .iter()
14493 .cloned()
14494 .map(|range| (range, empty_str.clone())),
14495 );
14496 } else {
14497 let min_column = selection_edit_ranges
14498 .iter()
14499 .map(|range| range.start.column)
14500 .min()
14501 .unwrap_or(0);
14502 edits.extend(selection_edit_ranges.iter().map(|range| {
14503 let position = Point::new(range.start.row, min_column);
14504 (position..position, first_prefix.clone())
14505 }));
14506 }
14507 } else if let Some(BlockCommentConfig {
14508 start: full_comment_prefix,
14509 end: comment_suffix,
14510 ..
14511 }) = language.block_comment()
14512 {
14513 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14514 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14515 let prefix_range = comment_prefix_range(
14516 snapshot.deref(),
14517 start_row,
14518 comment_prefix,
14519 comment_prefix_whitespace,
14520 ignore_indent,
14521 );
14522 let suffix_range = comment_suffix_range(
14523 snapshot.deref(),
14524 end_row,
14525 comment_suffix.trim_start_matches(' '),
14526 comment_suffix.starts_with(' '),
14527 );
14528
14529 if prefix_range.is_empty() || suffix_range.is_empty() {
14530 edits.push((
14531 prefix_range.start..prefix_range.start,
14532 full_comment_prefix.clone(),
14533 ));
14534 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14535 suffixes_inserted.push((end_row, comment_suffix.len()));
14536 } else {
14537 edits.push((prefix_range, empty_str.clone()));
14538 edits.push((suffix_range, empty_str.clone()));
14539 }
14540 } else {
14541 continue;
14542 }
14543 }
14544
14545 drop(snapshot);
14546 this.buffer.update(cx, |buffer, cx| {
14547 buffer.edit(edits, None, cx);
14548 });
14549
14550 // Adjust selections so that they end before any comment suffixes that
14551 // were inserted.
14552 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14553 let mut selections = this.selections.all::<Point>(cx);
14554 let snapshot = this.buffer.read(cx).read(cx);
14555 for selection in &mut selections {
14556 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14557 match row.cmp(&MultiBufferRow(selection.end.row)) {
14558 Ordering::Less => {
14559 suffixes_inserted.next();
14560 continue;
14561 }
14562 Ordering::Greater => break,
14563 Ordering::Equal => {
14564 if selection.end.column == snapshot.line_len(row) {
14565 if selection.is_empty() {
14566 selection.start.column -= suffix_len as u32;
14567 }
14568 selection.end.column -= suffix_len as u32;
14569 }
14570 break;
14571 }
14572 }
14573 }
14574 }
14575
14576 drop(snapshot);
14577 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14578
14579 let selections = this.selections.all::<Point>(cx);
14580 let selections_on_single_row = selections.windows(2).all(|selections| {
14581 selections[0].start.row == selections[1].start.row
14582 && selections[0].end.row == selections[1].end.row
14583 && selections[0].start.row == selections[0].end.row
14584 });
14585 let selections_selecting = selections
14586 .iter()
14587 .any(|selection| selection.start != selection.end);
14588 let advance_downwards = action.advance_downwards
14589 && selections_on_single_row
14590 && !selections_selecting
14591 && !matches!(this.mode, EditorMode::SingleLine);
14592
14593 if advance_downwards {
14594 let snapshot = this.buffer.read(cx).snapshot(cx);
14595
14596 this.change_selections(Default::default(), window, cx, |s| {
14597 s.move_cursors_with(|display_snapshot, display_point, _| {
14598 let mut point = display_point.to_point(display_snapshot);
14599 point.row += 1;
14600 point = snapshot.clip_point(point, Bias::Left);
14601 let display_point = point.to_display_point(display_snapshot);
14602 let goal = SelectionGoal::HorizontalPosition(
14603 display_snapshot
14604 .x_for_display_point(display_point, text_layout_details)
14605 .into(),
14606 );
14607 (display_point, goal)
14608 })
14609 });
14610 }
14611 });
14612 }
14613
14614 pub fn select_enclosing_symbol(
14615 &mut self,
14616 _: &SelectEnclosingSymbol,
14617 window: &mut Window,
14618 cx: &mut Context<Self>,
14619 ) {
14620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14621
14622 let buffer = self.buffer.read(cx).snapshot(cx);
14623 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14624
14625 fn update_selection(
14626 selection: &Selection<usize>,
14627 buffer_snap: &MultiBufferSnapshot,
14628 ) -> Option<Selection<usize>> {
14629 let cursor = selection.head();
14630 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14631 for symbol in symbols.iter().rev() {
14632 let start = symbol.range.start.to_offset(buffer_snap);
14633 let end = symbol.range.end.to_offset(buffer_snap);
14634 let new_range = start..end;
14635 if start < selection.start || end > selection.end {
14636 return Some(Selection {
14637 id: selection.id,
14638 start: new_range.start,
14639 end: new_range.end,
14640 goal: SelectionGoal::None,
14641 reversed: selection.reversed,
14642 });
14643 }
14644 }
14645 None
14646 }
14647
14648 let mut selected_larger_symbol = false;
14649 let new_selections = old_selections
14650 .iter()
14651 .map(|selection| match update_selection(selection, &buffer) {
14652 Some(new_selection) => {
14653 if new_selection.range() != selection.range() {
14654 selected_larger_symbol = true;
14655 }
14656 new_selection
14657 }
14658 None => selection.clone(),
14659 })
14660 .collect::<Vec<_>>();
14661
14662 if selected_larger_symbol {
14663 self.change_selections(Default::default(), window, cx, |s| {
14664 s.select(new_selections);
14665 });
14666 }
14667 }
14668
14669 pub fn select_larger_syntax_node(
14670 &mut self,
14671 _: &SelectLargerSyntaxNode,
14672 window: &mut Window,
14673 cx: &mut Context<Self>,
14674 ) {
14675 let Some(visible_row_count) = self.visible_row_count() else {
14676 return;
14677 };
14678 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14679 if old_selections.is_empty() {
14680 return;
14681 }
14682
14683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14684
14685 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14686 let buffer = self.buffer.read(cx).snapshot(cx);
14687
14688 let mut selected_larger_node = false;
14689 let mut new_selections = old_selections
14690 .iter()
14691 .map(|selection| {
14692 let old_range = selection.start..selection.end;
14693
14694 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14695 // manually select word at selection
14696 if ["string_content", "inline"].contains(&node.kind()) {
14697 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14698 // ignore if word is already selected
14699 if !word_range.is_empty() && old_range != word_range {
14700 let (last_word_range, _) =
14701 buffer.surrounding_word(old_range.end, false);
14702 // only select word if start and end point belongs to same word
14703 if word_range == last_word_range {
14704 selected_larger_node = true;
14705 return Selection {
14706 id: selection.id,
14707 start: word_range.start,
14708 end: word_range.end,
14709 goal: SelectionGoal::None,
14710 reversed: selection.reversed,
14711 };
14712 }
14713 }
14714 }
14715 }
14716
14717 let mut new_range = old_range.clone();
14718 while let Some((_node, containing_range)) =
14719 buffer.syntax_ancestor(new_range.clone())
14720 {
14721 new_range = match containing_range {
14722 MultiOrSingleBufferOffsetRange::Single(_) => break,
14723 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14724 };
14725 if !display_map.intersects_fold(new_range.start)
14726 && !display_map.intersects_fold(new_range.end)
14727 {
14728 break;
14729 }
14730 }
14731
14732 selected_larger_node |= new_range != old_range;
14733 Selection {
14734 id: selection.id,
14735 start: new_range.start,
14736 end: new_range.end,
14737 goal: SelectionGoal::None,
14738 reversed: selection.reversed,
14739 }
14740 })
14741 .collect::<Vec<_>>();
14742
14743 if !selected_larger_node {
14744 return; // don't put this call in the history
14745 }
14746
14747 // scroll based on transformation done to the last selection created by the user
14748 let (last_old, last_new) = old_selections
14749 .last()
14750 .zip(new_selections.last().cloned())
14751 .expect("old_selections isn't empty");
14752
14753 // revert selection
14754 let is_selection_reversed = {
14755 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14756 new_selections.last_mut().expect("checked above").reversed =
14757 should_newest_selection_be_reversed;
14758 should_newest_selection_be_reversed
14759 };
14760
14761 if selected_larger_node {
14762 self.select_syntax_node_history.disable_clearing = true;
14763 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14764 s.select(new_selections.clone());
14765 });
14766 self.select_syntax_node_history.disable_clearing = false;
14767 }
14768
14769 let start_row = last_new.start.to_display_point(&display_map).row().0;
14770 let end_row = last_new.end.to_display_point(&display_map).row().0;
14771 let selection_height = end_row - start_row + 1;
14772 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14773
14774 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14775 let scroll_behavior = if fits_on_the_screen {
14776 self.request_autoscroll(Autoscroll::fit(), cx);
14777 SelectSyntaxNodeScrollBehavior::FitSelection
14778 } else if is_selection_reversed {
14779 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14780 SelectSyntaxNodeScrollBehavior::CursorTop
14781 } else {
14782 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14783 SelectSyntaxNodeScrollBehavior::CursorBottom
14784 };
14785
14786 self.select_syntax_node_history.push((
14787 old_selections,
14788 scroll_behavior,
14789 is_selection_reversed,
14790 ));
14791 }
14792
14793 pub fn select_smaller_syntax_node(
14794 &mut self,
14795 _: &SelectSmallerSyntaxNode,
14796 window: &mut Window,
14797 cx: &mut Context<Self>,
14798 ) {
14799 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14800
14801 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14802 self.select_syntax_node_history.pop()
14803 {
14804 if let Some(selection) = selections.last_mut() {
14805 selection.reversed = is_selection_reversed;
14806 }
14807
14808 self.select_syntax_node_history.disable_clearing = true;
14809 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14810 s.select(selections.to_vec());
14811 });
14812 self.select_syntax_node_history.disable_clearing = false;
14813
14814 match scroll_behavior {
14815 SelectSyntaxNodeScrollBehavior::CursorTop => {
14816 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14817 }
14818 SelectSyntaxNodeScrollBehavior::FitSelection => {
14819 self.request_autoscroll(Autoscroll::fit(), cx);
14820 }
14821 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14822 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14823 }
14824 }
14825 }
14826 }
14827
14828 pub fn unwrap_syntax_node(
14829 &mut self,
14830 _: &UnwrapSyntaxNode,
14831 window: &mut Window,
14832 cx: &mut Context<Self>,
14833 ) {
14834 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14835
14836 let buffer = self.buffer.read(cx).snapshot(cx);
14837 let selections = self
14838 .selections
14839 .all::<usize>(cx)
14840 .into_iter()
14841 // subtracting the offset requires sorting
14842 .sorted_by_key(|i| i.start);
14843
14844 let full_edits = selections
14845 .into_iter()
14846 .filter_map(|selection| {
14847 // Only requires two branches once if-let-chains stabilize (#53667)
14848 let child = if !selection.is_empty() {
14849 selection.range()
14850 } else if let Some((_, ancestor_range)) =
14851 buffer.syntax_ancestor(selection.start..selection.end)
14852 {
14853 match ancestor_range {
14854 MultiOrSingleBufferOffsetRange::Single(range) => range,
14855 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14856 }
14857 } else {
14858 selection.range()
14859 };
14860
14861 let mut parent = child.clone();
14862 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
14863 parent = match ancestor_range {
14864 MultiOrSingleBufferOffsetRange::Single(range) => range,
14865 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14866 };
14867 if parent.start < child.start || parent.end > child.end {
14868 break;
14869 }
14870 }
14871
14872 if parent == child {
14873 return None;
14874 }
14875 let text = buffer.text_for_range(child).collect::<String>();
14876 Some((selection.id, parent, text))
14877 })
14878 .collect::<Vec<_>>();
14879
14880 self.transact(window, cx, |this, window, cx| {
14881 this.buffer.update(cx, |buffer, cx| {
14882 buffer.edit(
14883 full_edits
14884 .iter()
14885 .map(|(_, p, t)| (p.clone(), t.clone()))
14886 .collect::<Vec<_>>(),
14887 None,
14888 cx,
14889 );
14890 });
14891 this.change_selections(Default::default(), window, cx, |s| {
14892 let mut offset = 0;
14893 let mut selections = vec![];
14894 for (id, parent, text) in full_edits {
14895 let start = parent.start - offset;
14896 offset += parent.len() - text.len();
14897 selections.push(Selection {
14898 id,
14899 start,
14900 end: start + text.len(),
14901 reversed: false,
14902 goal: Default::default(),
14903 });
14904 }
14905 s.select(selections);
14906 });
14907 });
14908 }
14909
14910 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14911 if !EditorSettings::get_global(cx).gutter.runnables {
14912 self.clear_tasks();
14913 return Task::ready(());
14914 }
14915 let project = self.project().map(Entity::downgrade);
14916 let task_sources = self.lsp_task_sources(cx);
14917 let multi_buffer = self.buffer.downgrade();
14918 cx.spawn_in(window, async move |editor, cx| {
14919 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14920 let Some(project) = project.and_then(|p| p.upgrade()) else {
14921 return;
14922 };
14923 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14924 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14925 }) else {
14926 return;
14927 };
14928
14929 let hide_runnables = project
14930 .update(cx, |project, _| project.is_via_collab())
14931 .unwrap_or(true);
14932 if hide_runnables {
14933 return;
14934 }
14935 let new_rows =
14936 cx.background_spawn({
14937 let snapshot = display_snapshot.clone();
14938 async move {
14939 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14940 }
14941 })
14942 .await;
14943 let Ok(lsp_tasks) =
14944 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14945 else {
14946 return;
14947 };
14948 let lsp_tasks = lsp_tasks.await;
14949
14950 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14951 lsp_tasks
14952 .into_iter()
14953 .flat_map(|(kind, tasks)| {
14954 tasks.into_iter().filter_map(move |(location, task)| {
14955 Some((kind.clone(), location?, task))
14956 })
14957 })
14958 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14959 let buffer = location.target.buffer;
14960 let buffer_snapshot = buffer.read(cx).snapshot();
14961 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14962 |(excerpt_id, snapshot, _)| {
14963 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14964 display_snapshot
14965 .buffer_snapshot
14966 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14967 } else {
14968 None
14969 }
14970 },
14971 );
14972 if let Some(offset) = offset {
14973 let task_buffer_range =
14974 location.target.range.to_point(&buffer_snapshot);
14975 let context_buffer_range =
14976 task_buffer_range.to_offset(&buffer_snapshot);
14977 let context_range = BufferOffset(context_buffer_range.start)
14978 ..BufferOffset(context_buffer_range.end);
14979
14980 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14981 .or_insert_with(|| RunnableTasks {
14982 templates: Vec::new(),
14983 offset,
14984 column: task_buffer_range.start.column,
14985 extra_variables: HashMap::default(),
14986 context_range,
14987 })
14988 .templates
14989 .push((kind, task.original_task().clone()));
14990 }
14991
14992 acc
14993 })
14994 }) else {
14995 return;
14996 };
14997
14998 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14999 buffer.language_settings(cx).tasks.prefer_lsp
15000 }) else {
15001 return;
15002 };
15003
15004 let rows = Self::runnable_rows(
15005 project,
15006 display_snapshot,
15007 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15008 new_rows,
15009 cx.clone(),
15010 )
15011 .await;
15012 editor
15013 .update(cx, |editor, _| {
15014 editor.clear_tasks();
15015 for (key, mut value) in rows {
15016 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15017 value.templates.extend(lsp_tasks.templates);
15018 }
15019
15020 editor.insert_tasks(key, value);
15021 }
15022 for (key, value) in lsp_tasks_by_rows {
15023 editor.insert_tasks(key, value);
15024 }
15025 })
15026 .ok();
15027 })
15028 }
15029 fn fetch_runnable_ranges(
15030 snapshot: &DisplaySnapshot,
15031 range: Range<Anchor>,
15032 ) -> Vec<language::RunnableRange> {
15033 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15034 }
15035
15036 fn runnable_rows(
15037 project: Entity<Project>,
15038 snapshot: DisplaySnapshot,
15039 prefer_lsp: bool,
15040 runnable_ranges: Vec<RunnableRange>,
15041 cx: AsyncWindowContext,
15042 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15043 cx.spawn(async move |cx| {
15044 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15045 for mut runnable in runnable_ranges {
15046 let Some(tasks) = cx
15047 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15048 .ok()
15049 else {
15050 continue;
15051 };
15052 let mut tasks = tasks.await;
15053
15054 if prefer_lsp {
15055 tasks.retain(|(task_kind, _)| {
15056 !matches!(task_kind, TaskSourceKind::Language { .. })
15057 });
15058 }
15059 if tasks.is_empty() {
15060 continue;
15061 }
15062
15063 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15064 let Some(row) = snapshot
15065 .buffer_snapshot
15066 .buffer_line_for_row(MultiBufferRow(point.row))
15067 .map(|(_, range)| range.start.row)
15068 else {
15069 continue;
15070 };
15071
15072 let context_range =
15073 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15074 runnable_rows.push((
15075 (runnable.buffer_id, row),
15076 RunnableTasks {
15077 templates: tasks,
15078 offset: snapshot
15079 .buffer_snapshot
15080 .anchor_before(runnable.run_range.start),
15081 context_range,
15082 column: point.column,
15083 extra_variables: runnable.extra_captures,
15084 },
15085 ));
15086 }
15087 runnable_rows
15088 })
15089 }
15090
15091 fn templates_with_tags(
15092 project: &Entity<Project>,
15093 runnable: &mut Runnable,
15094 cx: &mut App,
15095 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15096 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15097 let (worktree_id, file) = project
15098 .buffer_for_id(runnable.buffer, cx)
15099 .and_then(|buffer| buffer.read(cx).file())
15100 .map(|file| (file.worktree_id(cx), file.clone()))
15101 .unzip();
15102
15103 (
15104 project.task_store().read(cx).task_inventory().cloned(),
15105 worktree_id,
15106 file,
15107 )
15108 });
15109
15110 let tags = mem::take(&mut runnable.tags);
15111 let language = runnable.language.clone();
15112 cx.spawn(async move |cx| {
15113 let mut templates_with_tags = Vec::new();
15114 if let Some(inventory) = inventory {
15115 for RunnableTag(tag) in tags {
15116 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15117 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15118 }) else {
15119 return templates_with_tags;
15120 };
15121 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15122 move |(_, template)| {
15123 template.tags.iter().any(|source_tag| source_tag == &tag)
15124 },
15125 ));
15126 }
15127 }
15128 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15129
15130 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15131 // Strongest source wins; if we have worktree tag binding, prefer that to
15132 // global and language bindings;
15133 // if we have a global binding, prefer that to language binding.
15134 let first_mismatch = templates_with_tags
15135 .iter()
15136 .position(|(tag_source, _)| tag_source != leading_tag_source);
15137 if let Some(index) = first_mismatch {
15138 templates_with_tags.truncate(index);
15139 }
15140 }
15141
15142 templates_with_tags
15143 })
15144 }
15145
15146 pub fn move_to_enclosing_bracket(
15147 &mut self,
15148 _: &MoveToEnclosingBracket,
15149 window: &mut Window,
15150 cx: &mut Context<Self>,
15151 ) {
15152 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15153 self.change_selections(Default::default(), window, cx, |s| {
15154 s.move_offsets_with(|snapshot, selection| {
15155 let Some(enclosing_bracket_ranges) =
15156 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15157 else {
15158 return;
15159 };
15160
15161 let mut best_length = usize::MAX;
15162 let mut best_inside = false;
15163 let mut best_in_bracket_range = false;
15164 let mut best_destination = None;
15165 for (open, close) in enclosing_bracket_ranges {
15166 let close = close.to_inclusive();
15167 let length = close.end() - open.start;
15168 let inside = selection.start >= open.end && selection.end <= *close.start();
15169 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15170 || close.contains(&selection.head());
15171
15172 // If best is next to a bracket and current isn't, skip
15173 if !in_bracket_range && best_in_bracket_range {
15174 continue;
15175 }
15176
15177 // Prefer smaller lengths unless best is inside and current isn't
15178 if length > best_length && (best_inside || !inside) {
15179 continue;
15180 }
15181
15182 best_length = length;
15183 best_inside = inside;
15184 best_in_bracket_range = in_bracket_range;
15185 best_destination = Some(
15186 if close.contains(&selection.start) && close.contains(&selection.end) {
15187 if inside { open.end } else { open.start }
15188 } else if inside {
15189 *close.start()
15190 } else {
15191 *close.end()
15192 },
15193 );
15194 }
15195
15196 if let Some(destination) = best_destination {
15197 selection.collapse_to(destination, SelectionGoal::None);
15198 }
15199 })
15200 });
15201 }
15202
15203 pub fn undo_selection(
15204 &mut self,
15205 _: &UndoSelection,
15206 window: &mut Window,
15207 cx: &mut Context<Self>,
15208 ) {
15209 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15210 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15211 self.selection_history.mode = SelectionHistoryMode::Undoing;
15212 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15213 this.end_selection(window, cx);
15214 this.change_selections(
15215 SelectionEffects::scroll(Autoscroll::newest()),
15216 window,
15217 cx,
15218 |s| s.select_anchors(entry.selections.to_vec()),
15219 );
15220 });
15221 self.selection_history.mode = SelectionHistoryMode::Normal;
15222
15223 self.select_next_state = entry.select_next_state;
15224 self.select_prev_state = entry.select_prev_state;
15225 self.add_selections_state = entry.add_selections_state;
15226 }
15227 }
15228
15229 pub fn redo_selection(
15230 &mut self,
15231 _: &RedoSelection,
15232 window: &mut Window,
15233 cx: &mut Context<Self>,
15234 ) {
15235 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15236 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15237 self.selection_history.mode = SelectionHistoryMode::Redoing;
15238 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15239 this.end_selection(window, cx);
15240 this.change_selections(
15241 SelectionEffects::scroll(Autoscroll::newest()),
15242 window,
15243 cx,
15244 |s| s.select_anchors(entry.selections.to_vec()),
15245 );
15246 });
15247 self.selection_history.mode = SelectionHistoryMode::Normal;
15248
15249 self.select_next_state = entry.select_next_state;
15250 self.select_prev_state = entry.select_prev_state;
15251 self.add_selections_state = entry.add_selections_state;
15252 }
15253 }
15254
15255 pub fn expand_excerpts(
15256 &mut self,
15257 action: &ExpandExcerpts,
15258 _: &mut Window,
15259 cx: &mut Context<Self>,
15260 ) {
15261 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15262 }
15263
15264 pub fn expand_excerpts_down(
15265 &mut self,
15266 action: &ExpandExcerptsDown,
15267 _: &mut Window,
15268 cx: &mut Context<Self>,
15269 ) {
15270 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15271 }
15272
15273 pub fn expand_excerpts_up(
15274 &mut self,
15275 action: &ExpandExcerptsUp,
15276 _: &mut Window,
15277 cx: &mut Context<Self>,
15278 ) {
15279 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15280 }
15281
15282 pub fn expand_excerpts_for_direction(
15283 &mut self,
15284 lines: u32,
15285 direction: ExpandExcerptDirection,
15286
15287 cx: &mut Context<Self>,
15288 ) {
15289 let selections = self.selections.disjoint_anchors();
15290
15291 let lines = if lines == 0 {
15292 EditorSettings::get_global(cx).expand_excerpt_lines
15293 } else {
15294 lines
15295 };
15296
15297 self.buffer.update(cx, |buffer, cx| {
15298 let snapshot = buffer.snapshot(cx);
15299 let mut excerpt_ids = selections
15300 .iter()
15301 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15302 .collect::<Vec<_>>();
15303 excerpt_ids.sort();
15304 excerpt_ids.dedup();
15305 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15306 })
15307 }
15308
15309 pub fn expand_excerpt(
15310 &mut self,
15311 excerpt: ExcerptId,
15312 direction: ExpandExcerptDirection,
15313 window: &mut Window,
15314 cx: &mut Context<Self>,
15315 ) {
15316 let current_scroll_position = self.scroll_position(cx);
15317 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15318 let mut should_scroll_up = false;
15319
15320 if direction == ExpandExcerptDirection::Down {
15321 let multi_buffer = self.buffer.read(cx);
15322 let snapshot = multi_buffer.snapshot(cx);
15323 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15324 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15325 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15326 {
15327 let buffer_snapshot = buffer.read(cx).snapshot();
15328 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15329 let last_row = buffer_snapshot.max_point().row;
15330 let lines_below = last_row.saturating_sub(excerpt_end_row);
15331 should_scroll_up = lines_below >= lines_to_expand;
15332 }
15333 }
15334
15335 self.buffer.update(cx, |buffer, cx| {
15336 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15337 });
15338
15339 if should_scroll_up {
15340 let new_scroll_position =
15341 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15342 self.set_scroll_position(new_scroll_position, window, cx);
15343 }
15344 }
15345
15346 pub fn go_to_singleton_buffer_point(
15347 &mut self,
15348 point: Point,
15349 window: &mut Window,
15350 cx: &mut Context<Self>,
15351 ) {
15352 self.go_to_singleton_buffer_range(point..point, window, cx);
15353 }
15354
15355 pub fn go_to_singleton_buffer_range(
15356 &mut self,
15357 range: Range<Point>,
15358 window: &mut Window,
15359 cx: &mut Context<Self>,
15360 ) {
15361 let multibuffer = self.buffer().read(cx);
15362 let Some(buffer) = multibuffer.as_singleton() else {
15363 return;
15364 };
15365 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15366 return;
15367 };
15368 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15369 return;
15370 };
15371 self.change_selections(
15372 SelectionEffects::default().nav_history(true),
15373 window,
15374 cx,
15375 |s| s.select_anchor_ranges([start..end]),
15376 );
15377 }
15378
15379 pub fn go_to_diagnostic(
15380 &mut self,
15381 action: &GoToDiagnostic,
15382 window: &mut Window,
15383 cx: &mut Context<Self>,
15384 ) {
15385 if !self.diagnostics_enabled() {
15386 return;
15387 }
15388 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15389 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15390 }
15391
15392 pub fn go_to_prev_diagnostic(
15393 &mut self,
15394 action: &GoToPreviousDiagnostic,
15395 window: &mut Window,
15396 cx: &mut Context<Self>,
15397 ) {
15398 if !self.diagnostics_enabled() {
15399 return;
15400 }
15401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15402 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15403 }
15404
15405 pub fn go_to_diagnostic_impl(
15406 &mut self,
15407 direction: Direction,
15408 severity: GoToDiagnosticSeverityFilter,
15409 window: &mut Window,
15410 cx: &mut Context<Self>,
15411 ) {
15412 let buffer = self.buffer.read(cx).snapshot(cx);
15413 let selection = self.selections.newest::<usize>(cx);
15414
15415 let mut active_group_id = None;
15416 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15417 && active_group.active_range.start.to_offset(&buffer) == selection.start
15418 {
15419 active_group_id = Some(active_group.group_id);
15420 }
15421
15422 fn filtered(
15423 snapshot: EditorSnapshot,
15424 severity: GoToDiagnosticSeverityFilter,
15425 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15426 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15427 diagnostics
15428 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15429 .filter(|entry| entry.range.start != entry.range.end)
15430 .filter(|entry| !entry.diagnostic.is_unnecessary)
15431 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15432 }
15433
15434 let snapshot = self.snapshot(window, cx);
15435 let before = filtered(
15436 snapshot.clone(),
15437 severity,
15438 buffer
15439 .diagnostics_in_range(0..selection.start)
15440 .filter(|entry| entry.range.start <= selection.start),
15441 );
15442 let after = filtered(
15443 snapshot,
15444 severity,
15445 buffer
15446 .diagnostics_in_range(selection.start..buffer.len())
15447 .filter(|entry| entry.range.start >= selection.start),
15448 );
15449
15450 let mut found: Option<DiagnosticEntry<usize>> = None;
15451 if direction == Direction::Prev {
15452 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15453 {
15454 for diagnostic in prev_diagnostics.into_iter().rev() {
15455 if diagnostic.range.start != selection.start
15456 || active_group_id
15457 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15458 {
15459 found = Some(diagnostic);
15460 break 'outer;
15461 }
15462 }
15463 }
15464 } else {
15465 for diagnostic in after.chain(before) {
15466 if diagnostic.range.start != selection.start
15467 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15468 {
15469 found = Some(diagnostic);
15470 break;
15471 }
15472 }
15473 }
15474 let Some(next_diagnostic) = found else {
15475 return;
15476 };
15477
15478 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15479 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15480 return;
15481 };
15482 self.change_selections(Default::default(), window, cx, |s| {
15483 s.select_ranges(vec![
15484 next_diagnostic.range.start..next_diagnostic.range.start,
15485 ])
15486 });
15487 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15488 self.refresh_edit_prediction(false, true, window, cx);
15489 }
15490
15491 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15492 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15493 let snapshot = self.snapshot(window, cx);
15494 let selection = self.selections.newest::<Point>(cx);
15495 self.go_to_hunk_before_or_after_position(
15496 &snapshot,
15497 selection.head(),
15498 Direction::Next,
15499 window,
15500 cx,
15501 );
15502 }
15503
15504 pub fn go_to_hunk_before_or_after_position(
15505 &mut self,
15506 snapshot: &EditorSnapshot,
15507 position: Point,
15508 direction: Direction,
15509 window: &mut Window,
15510 cx: &mut Context<Editor>,
15511 ) {
15512 let row = if direction == Direction::Next {
15513 self.hunk_after_position(snapshot, position)
15514 .map(|hunk| hunk.row_range.start)
15515 } else {
15516 self.hunk_before_position(snapshot, position)
15517 };
15518
15519 if let Some(row) = row {
15520 let destination = Point::new(row.0, 0);
15521 let autoscroll = Autoscroll::center();
15522
15523 self.unfold_ranges(&[destination..destination], false, false, cx);
15524 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15525 s.select_ranges([destination..destination]);
15526 });
15527 }
15528 }
15529
15530 fn hunk_after_position(
15531 &mut self,
15532 snapshot: &EditorSnapshot,
15533 position: Point,
15534 ) -> Option<MultiBufferDiffHunk> {
15535 snapshot
15536 .buffer_snapshot
15537 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15538 .find(|hunk| hunk.row_range.start.0 > position.row)
15539 .or_else(|| {
15540 snapshot
15541 .buffer_snapshot
15542 .diff_hunks_in_range(Point::zero()..position)
15543 .find(|hunk| hunk.row_range.end.0 < position.row)
15544 })
15545 }
15546
15547 fn go_to_prev_hunk(
15548 &mut self,
15549 _: &GoToPreviousHunk,
15550 window: &mut Window,
15551 cx: &mut Context<Self>,
15552 ) {
15553 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15554 let snapshot = self.snapshot(window, cx);
15555 let selection = self.selections.newest::<Point>(cx);
15556 self.go_to_hunk_before_or_after_position(
15557 &snapshot,
15558 selection.head(),
15559 Direction::Prev,
15560 window,
15561 cx,
15562 );
15563 }
15564
15565 fn hunk_before_position(
15566 &mut self,
15567 snapshot: &EditorSnapshot,
15568 position: Point,
15569 ) -> Option<MultiBufferRow> {
15570 snapshot
15571 .buffer_snapshot
15572 .diff_hunk_before(position)
15573 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15574 }
15575
15576 fn go_to_next_change(
15577 &mut self,
15578 _: &GoToNextChange,
15579 window: &mut Window,
15580 cx: &mut Context<Self>,
15581 ) {
15582 if let Some(selections) = self
15583 .change_list
15584 .next_change(1, Direction::Next)
15585 .map(|s| s.to_vec())
15586 {
15587 self.change_selections(Default::default(), window, cx, |s| {
15588 let map = s.display_map();
15589 s.select_display_ranges(selections.iter().map(|a| {
15590 let point = a.to_display_point(&map);
15591 point..point
15592 }))
15593 })
15594 }
15595 }
15596
15597 fn go_to_previous_change(
15598 &mut self,
15599 _: &GoToPreviousChange,
15600 window: &mut Window,
15601 cx: &mut Context<Self>,
15602 ) {
15603 if let Some(selections) = self
15604 .change_list
15605 .next_change(1, Direction::Prev)
15606 .map(|s| s.to_vec())
15607 {
15608 self.change_selections(Default::default(), window, cx, |s| {
15609 let map = s.display_map();
15610 s.select_display_ranges(selections.iter().map(|a| {
15611 let point = a.to_display_point(&map);
15612 point..point
15613 }))
15614 })
15615 }
15616 }
15617
15618 fn go_to_line<T: 'static>(
15619 &mut self,
15620 position: Anchor,
15621 highlight_color: Option<Hsla>,
15622 window: &mut Window,
15623 cx: &mut Context<Self>,
15624 ) {
15625 let snapshot = self.snapshot(window, cx).display_snapshot;
15626 let position = position.to_point(&snapshot.buffer_snapshot);
15627 let start = snapshot
15628 .buffer_snapshot
15629 .clip_point(Point::new(position.row, 0), Bias::Left);
15630 let end = start + Point::new(1, 0);
15631 let start = snapshot.buffer_snapshot.anchor_before(start);
15632 let end = snapshot.buffer_snapshot.anchor_before(end);
15633
15634 self.highlight_rows::<T>(
15635 start..end,
15636 highlight_color
15637 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15638 Default::default(),
15639 cx,
15640 );
15641
15642 if self.buffer.read(cx).is_singleton() {
15643 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15644 }
15645 }
15646
15647 pub fn go_to_definition(
15648 &mut self,
15649 _: &GoToDefinition,
15650 window: &mut Window,
15651 cx: &mut Context<Self>,
15652 ) -> Task<Result<Navigated>> {
15653 let definition =
15654 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15655 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15656 cx.spawn_in(window, async move |editor, cx| {
15657 if definition.await? == Navigated::Yes {
15658 return Ok(Navigated::Yes);
15659 }
15660 match fallback_strategy {
15661 GoToDefinitionFallback::None => Ok(Navigated::No),
15662 GoToDefinitionFallback::FindAllReferences => {
15663 match editor.update_in(cx, |editor, window, cx| {
15664 editor.find_all_references(&FindAllReferences, window, cx)
15665 })? {
15666 Some(references) => references.await,
15667 None => Ok(Navigated::No),
15668 }
15669 }
15670 }
15671 })
15672 }
15673
15674 pub fn go_to_declaration(
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, false, window, cx)
15681 }
15682
15683 pub fn go_to_declaration_split(
15684 &mut self,
15685 _: &GoToDeclaration,
15686 window: &mut Window,
15687 cx: &mut Context<Self>,
15688 ) -> Task<Result<Navigated>> {
15689 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15690 }
15691
15692 pub fn go_to_implementation(
15693 &mut self,
15694 _: &GoToImplementation,
15695 window: &mut Window,
15696 cx: &mut Context<Self>,
15697 ) -> Task<Result<Navigated>> {
15698 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15699 }
15700
15701 pub fn go_to_implementation_split(
15702 &mut self,
15703 _: &GoToImplementationSplit,
15704 window: &mut Window,
15705 cx: &mut Context<Self>,
15706 ) -> Task<Result<Navigated>> {
15707 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15708 }
15709
15710 pub fn go_to_type_definition(
15711 &mut self,
15712 _: &GoToTypeDefinition,
15713 window: &mut Window,
15714 cx: &mut Context<Self>,
15715 ) -> Task<Result<Navigated>> {
15716 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15717 }
15718
15719 pub fn go_to_definition_split(
15720 &mut self,
15721 _: &GoToDefinitionSplit,
15722 window: &mut Window,
15723 cx: &mut Context<Self>,
15724 ) -> Task<Result<Navigated>> {
15725 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15726 }
15727
15728 pub fn go_to_type_definition_split(
15729 &mut self,
15730 _: &GoToTypeDefinitionSplit,
15731 window: &mut Window,
15732 cx: &mut Context<Self>,
15733 ) -> Task<Result<Navigated>> {
15734 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15735 }
15736
15737 fn go_to_definition_of_kind(
15738 &mut self,
15739 kind: GotoDefinitionKind,
15740 split: bool,
15741 window: &mut Window,
15742 cx: &mut Context<Self>,
15743 ) -> Task<Result<Navigated>> {
15744 let Some(provider) = self.semantics_provider.clone() else {
15745 return Task::ready(Ok(Navigated::No));
15746 };
15747 let head = self.selections.newest::<usize>(cx).head();
15748 let buffer = self.buffer.read(cx);
15749 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15750 return Task::ready(Ok(Navigated::No));
15751 };
15752 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15753 return Task::ready(Ok(Navigated::No));
15754 };
15755
15756 cx.spawn_in(window, async move |editor, cx| {
15757 let Some(definitions) = definitions.await? else {
15758 return Ok(Navigated::No);
15759 };
15760 let navigated = editor
15761 .update_in(cx, |editor, window, cx| {
15762 editor.navigate_to_hover_links(
15763 Some(kind),
15764 definitions
15765 .into_iter()
15766 .filter(|location| {
15767 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15768 })
15769 .map(HoverLink::Text)
15770 .collect::<Vec<_>>(),
15771 split,
15772 window,
15773 cx,
15774 )
15775 })?
15776 .await?;
15777 anyhow::Ok(navigated)
15778 })
15779 }
15780
15781 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15782 let selection = self.selections.newest_anchor();
15783 let head = selection.head();
15784 let tail = selection.tail();
15785
15786 let Some((buffer, start_position)) =
15787 self.buffer.read(cx).text_anchor_for_position(head, cx)
15788 else {
15789 return;
15790 };
15791
15792 let end_position = if head != tail {
15793 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15794 return;
15795 };
15796 Some(pos)
15797 } else {
15798 None
15799 };
15800
15801 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15802 let url = if let Some(end_pos) = end_position {
15803 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15804 } else {
15805 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15806 };
15807
15808 if let Some(url) = url {
15809 editor.update(cx, |_, cx| {
15810 cx.open_url(&url);
15811 })
15812 } else {
15813 Ok(())
15814 }
15815 });
15816
15817 url_finder.detach();
15818 }
15819
15820 pub fn open_selected_filename(
15821 &mut self,
15822 _: &OpenSelectedFilename,
15823 window: &mut Window,
15824 cx: &mut Context<Self>,
15825 ) {
15826 let Some(workspace) = self.workspace() else {
15827 return;
15828 };
15829
15830 let position = self.selections.newest_anchor().head();
15831
15832 let Some((buffer, buffer_position)) =
15833 self.buffer.read(cx).text_anchor_for_position(position, cx)
15834 else {
15835 return;
15836 };
15837
15838 let project = self.project.clone();
15839
15840 cx.spawn_in(window, async move |_, cx| {
15841 let result = find_file(&buffer, project, buffer_position, cx).await;
15842
15843 if let Some((_, path)) = result {
15844 workspace
15845 .update_in(cx, |workspace, window, cx| {
15846 workspace.open_resolved_path(path, window, cx)
15847 })?
15848 .await?;
15849 }
15850 anyhow::Ok(())
15851 })
15852 .detach();
15853 }
15854
15855 pub(crate) fn navigate_to_hover_links(
15856 &mut self,
15857 kind: Option<GotoDefinitionKind>,
15858 definitions: Vec<HoverLink>,
15859 split: bool,
15860 window: &mut Window,
15861 cx: &mut Context<Editor>,
15862 ) -> Task<Result<Navigated>> {
15863 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15864 let mut first_url_or_file = None;
15865 let definitions: Vec<_> = definitions
15866 .into_iter()
15867 .filter_map(|def| match def {
15868 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15869 HoverLink::InlayHint(lsp_location, server_id) => {
15870 let computation =
15871 self.compute_target_location(lsp_location, server_id, window, cx);
15872 Some(cx.background_spawn(computation))
15873 }
15874 HoverLink::Url(url) => {
15875 first_url_or_file = Some(Either::Left(url));
15876 None
15877 }
15878 HoverLink::File(path) => {
15879 first_url_or_file = Some(Either::Right(path));
15880 None
15881 }
15882 })
15883 .collect();
15884
15885 let workspace = self.workspace();
15886
15887 cx.spawn_in(window, async move |editor, acx| {
15888 let mut locations: Vec<Location> = future::join_all(definitions)
15889 .await
15890 .into_iter()
15891 .filter_map(|location| location.transpose())
15892 .collect::<Result<_>>()
15893 .context("location tasks")?;
15894
15895 if locations.len() > 1 {
15896 let Some(workspace) = workspace else {
15897 return Ok(Navigated::No);
15898 };
15899
15900 let tab_kind = match kind {
15901 Some(GotoDefinitionKind::Implementation) => "Implementations",
15902 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15903 Some(GotoDefinitionKind::Declaration) => "Declarations",
15904 Some(GotoDefinitionKind::Type) => "Types",
15905 };
15906 let title = editor
15907 .update_in(acx, |_, _, cx| {
15908 let target = locations
15909 .iter()
15910 .map(|location| {
15911 location
15912 .buffer
15913 .read(cx)
15914 .text_for_range(location.range.clone())
15915 .collect::<String>()
15916 })
15917 .filter(|text| !text.contains('\n'))
15918 .unique()
15919 .take(3)
15920 .join(", ");
15921 if target.is_empty() {
15922 tab_kind.to_owned()
15923 } else {
15924 format!("{tab_kind} for {target}")
15925 }
15926 })
15927 .context("buffer title")?;
15928
15929 let opened = workspace
15930 .update_in(acx, |workspace, window, cx| {
15931 Self::open_locations_in_multibuffer(
15932 workspace,
15933 locations,
15934 title,
15935 split,
15936 MultibufferSelectionMode::First,
15937 window,
15938 cx,
15939 )
15940 })
15941 .is_ok();
15942
15943 anyhow::Ok(Navigated::from_bool(opened))
15944 } else if locations.is_empty() {
15945 // If there is one definition, just open it directly
15946 match first_url_or_file {
15947 Some(Either::Left(url)) => {
15948 acx.update(|_, cx| cx.open_url(&url))?;
15949 Ok(Navigated::Yes)
15950 }
15951 Some(Either::Right(path)) => {
15952 let Some(workspace) = workspace else {
15953 return Ok(Navigated::No);
15954 };
15955
15956 workspace
15957 .update_in(acx, |workspace, window, cx| {
15958 workspace.open_resolved_path(path, window, cx)
15959 })?
15960 .await?;
15961 Ok(Navigated::Yes)
15962 }
15963 None => Ok(Navigated::No),
15964 }
15965 } else {
15966 let Some(workspace) = workspace else {
15967 return Ok(Navigated::No);
15968 };
15969
15970 let target = locations.pop().unwrap();
15971 editor.update_in(acx, |editor, window, cx| {
15972 let pane = workspace.read(cx).active_pane().clone();
15973
15974 let range = target.range.to_point(target.buffer.read(cx));
15975 let range = editor.range_for_match(&range);
15976 let range = collapse_multiline_range(range);
15977
15978 if !split
15979 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15980 {
15981 editor.go_to_singleton_buffer_range(range, window, cx);
15982 } else {
15983 window.defer(cx, move |window, cx| {
15984 let target_editor: Entity<Self> =
15985 workspace.update(cx, |workspace, cx| {
15986 let pane = if split {
15987 workspace.adjacent_pane(window, cx)
15988 } else {
15989 workspace.active_pane().clone()
15990 };
15991
15992 workspace.open_project_item(
15993 pane,
15994 target.buffer.clone(),
15995 true,
15996 true,
15997 window,
15998 cx,
15999 )
16000 });
16001 target_editor.update(cx, |target_editor, cx| {
16002 // When selecting a definition in a different buffer, disable the nav history
16003 // to avoid creating a history entry at the previous cursor location.
16004 pane.update(cx, |pane, _| pane.disable_history());
16005 target_editor.go_to_singleton_buffer_range(range, window, cx);
16006 pane.update(cx, |pane, _| pane.enable_history());
16007 });
16008 });
16009 }
16010 Navigated::Yes
16011 })
16012 }
16013 })
16014 }
16015
16016 fn compute_target_location(
16017 &self,
16018 lsp_location: lsp::Location,
16019 server_id: LanguageServerId,
16020 window: &mut Window,
16021 cx: &mut Context<Self>,
16022 ) -> Task<anyhow::Result<Option<Location>>> {
16023 let Some(project) = self.project.clone() else {
16024 return Task::ready(Ok(None));
16025 };
16026
16027 cx.spawn_in(window, async move |editor, cx| {
16028 let location_task = editor.update(cx, |_, cx| {
16029 project.update(cx, |project, cx| {
16030 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16031 })
16032 })?;
16033 let location = Some({
16034 let target_buffer_handle = location_task.await.context("open local buffer")?;
16035 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16036 let target_start = target_buffer
16037 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16038 let target_end = target_buffer
16039 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16040 target_buffer.anchor_after(target_start)
16041 ..target_buffer.anchor_before(target_end)
16042 })?;
16043 Location {
16044 buffer: target_buffer_handle,
16045 range,
16046 }
16047 });
16048 Ok(location)
16049 })
16050 }
16051
16052 pub fn find_all_references(
16053 &mut self,
16054 _: &FindAllReferences,
16055 window: &mut Window,
16056 cx: &mut Context<Self>,
16057 ) -> Option<Task<Result<Navigated>>> {
16058 let selection = self.selections.newest::<usize>(cx);
16059 let multi_buffer = self.buffer.read(cx);
16060 let head = selection.head();
16061
16062 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16063 let head_anchor = multi_buffer_snapshot.anchor_at(
16064 head,
16065 if head < selection.tail() {
16066 Bias::Right
16067 } else {
16068 Bias::Left
16069 },
16070 );
16071
16072 match self
16073 .find_all_references_task_sources
16074 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16075 {
16076 Ok(_) => {
16077 log::info!(
16078 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16079 );
16080 return None;
16081 }
16082 Err(i) => {
16083 self.find_all_references_task_sources.insert(i, head_anchor);
16084 }
16085 }
16086
16087 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16088 let workspace = self.workspace()?;
16089 let project = workspace.read(cx).project().clone();
16090 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16091 Some(cx.spawn_in(window, async move |editor, cx| {
16092 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16093 if let Ok(i) = editor
16094 .find_all_references_task_sources
16095 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16096 {
16097 editor.find_all_references_task_sources.remove(i);
16098 }
16099 });
16100
16101 let Some(locations) = references.await? else {
16102 return anyhow::Ok(Navigated::No);
16103 };
16104 if locations.is_empty() {
16105 return anyhow::Ok(Navigated::No);
16106 }
16107
16108 workspace.update_in(cx, |workspace, window, cx| {
16109 let target = locations
16110 .iter()
16111 .map(|location| {
16112 location
16113 .buffer
16114 .read(cx)
16115 .text_for_range(location.range.clone())
16116 .collect::<String>()
16117 })
16118 .filter(|text| !text.contains('\n'))
16119 .unique()
16120 .take(3)
16121 .join(", ");
16122 let title = if target.is_empty() {
16123 "References".to_owned()
16124 } else {
16125 format!("References to {target}")
16126 };
16127 Self::open_locations_in_multibuffer(
16128 workspace,
16129 locations,
16130 title,
16131 false,
16132 MultibufferSelectionMode::First,
16133 window,
16134 cx,
16135 );
16136 Navigated::Yes
16137 })
16138 }))
16139 }
16140
16141 /// Opens a multibuffer with the given project locations in it
16142 pub fn open_locations_in_multibuffer(
16143 workspace: &mut Workspace,
16144 mut locations: Vec<Location>,
16145 title: String,
16146 split: bool,
16147 multibuffer_selection_mode: MultibufferSelectionMode,
16148 window: &mut Window,
16149 cx: &mut Context<Workspace>,
16150 ) {
16151 if locations.is_empty() {
16152 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16153 return;
16154 }
16155
16156 // If there are multiple definitions, open them in a multibuffer
16157 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16158 let mut locations = locations.into_iter().peekable();
16159 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16160 let capability = workspace.project().read(cx).capability();
16161
16162 let excerpt_buffer = cx.new(|cx| {
16163 let mut multibuffer = MultiBuffer::new(capability);
16164 while let Some(location) = locations.next() {
16165 let buffer = location.buffer.read(cx);
16166 let mut ranges_for_buffer = Vec::new();
16167 let range = location.range.to_point(buffer);
16168 ranges_for_buffer.push(range.clone());
16169
16170 while let Some(next_location) = locations.peek() {
16171 if next_location.buffer == location.buffer {
16172 ranges_for_buffer.push(next_location.range.to_point(buffer));
16173 locations.next();
16174 } else {
16175 break;
16176 }
16177 }
16178
16179 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16180 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16181 PathKey::for_buffer(&location.buffer, cx),
16182 location.buffer.clone(),
16183 ranges_for_buffer,
16184 DEFAULT_MULTIBUFFER_CONTEXT,
16185 cx,
16186 );
16187 ranges.extend(new_ranges)
16188 }
16189
16190 multibuffer.with_title(title)
16191 });
16192
16193 let editor = cx.new(|cx| {
16194 Editor::for_multibuffer(
16195 excerpt_buffer,
16196 Some(workspace.project().clone()),
16197 window,
16198 cx,
16199 )
16200 });
16201 editor.update(cx, |editor, cx| {
16202 match multibuffer_selection_mode {
16203 MultibufferSelectionMode::First => {
16204 if let Some(first_range) = ranges.first() {
16205 editor.change_selections(
16206 SelectionEffects::no_scroll(),
16207 window,
16208 cx,
16209 |selections| {
16210 selections.clear_disjoint();
16211 selections
16212 .select_anchor_ranges(std::iter::once(first_range.clone()));
16213 },
16214 );
16215 }
16216 editor.highlight_background::<Self>(
16217 &ranges,
16218 |theme| theme.colors().editor_highlighted_line_background,
16219 cx,
16220 );
16221 }
16222 MultibufferSelectionMode::All => {
16223 editor.change_selections(
16224 SelectionEffects::no_scroll(),
16225 window,
16226 cx,
16227 |selections| {
16228 selections.clear_disjoint();
16229 selections.select_anchor_ranges(ranges);
16230 },
16231 );
16232 }
16233 }
16234 editor.register_buffers_with_language_servers(cx);
16235 });
16236
16237 let item = Box::new(editor);
16238 let item_id = item.item_id();
16239
16240 if split {
16241 workspace.split_item(SplitDirection::Right, item, window, cx);
16242 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16243 let (preview_item_id, preview_item_idx) =
16244 workspace.active_pane().read_with(cx, |pane, _| {
16245 (pane.preview_item_id(), pane.preview_item_idx())
16246 });
16247
16248 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16249
16250 if let Some(preview_item_id) = preview_item_id {
16251 workspace.active_pane().update(cx, |pane, cx| {
16252 pane.remove_item(preview_item_id, false, false, window, cx);
16253 });
16254 }
16255 } else {
16256 workspace.add_item_to_active_pane(item, None, true, window, cx);
16257 }
16258 workspace.active_pane().update(cx, |pane, cx| {
16259 pane.set_preview_item_id(Some(item_id), cx);
16260 });
16261 }
16262
16263 pub fn rename(
16264 &mut self,
16265 _: &Rename,
16266 window: &mut Window,
16267 cx: &mut Context<Self>,
16268 ) -> Option<Task<Result<()>>> {
16269 use language::ToOffset as _;
16270
16271 let provider = self.semantics_provider.clone()?;
16272 let selection = self.selections.newest_anchor().clone();
16273 let (cursor_buffer, cursor_buffer_position) = self
16274 .buffer
16275 .read(cx)
16276 .text_anchor_for_position(selection.head(), cx)?;
16277 let (tail_buffer, cursor_buffer_position_end) = self
16278 .buffer
16279 .read(cx)
16280 .text_anchor_for_position(selection.tail(), cx)?;
16281 if tail_buffer != cursor_buffer {
16282 return None;
16283 }
16284
16285 let snapshot = cursor_buffer.read(cx).snapshot();
16286 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16287 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16288 let prepare_rename = provider
16289 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16290 .unwrap_or_else(|| Task::ready(Ok(None)));
16291 drop(snapshot);
16292
16293 Some(cx.spawn_in(window, async move |this, cx| {
16294 let rename_range = if let Some(range) = prepare_rename.await? {
16295 Some(range)
16296 } else {
16297 this.update(cx, |this, cx| {
16298 let buffer = this.buffer.read(cx).snapshot(cx);
16299 let mut buffer_highlights = this
16300 .document_highlights_for_position(selection.head(), &buffer)
16301 .filter(|highlight| {
16302 highlight.start.excerpt_id == selection.head().excerpt_id
16303 && highlight.end.excerpt_id == selection.head().excerpt_id
16304 });
16305 buffer_highlights
16306 .next()
16307 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16308 })?
16309 };
16310 if let Some(rename_range) = rename_range {
16311 this.update_in(cx, |this, window, cx| {
16312 let snapshot = cursor_buffer.read(cx).snapshot();
16313 let rename_buffer_range = rename_range.to_offset(&snapshot);
16314 let cursor_offset_in_rename_range =
16315 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16316 let cursor_offset_in_rename_range_end =
16317 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16318
16319 this.take_rename(false, window, cx);
16320 let buffer = this.buffer.read(cx).read(cx);
16321 let cursor_offset = selection.head().to_offset(&buffer);
16322 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16323 let rename_end = rename_start + rename_buffer_range.len();
16324 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16325 let mut old_highlight_id = None;
16326 let old_name: Arc<str> = buffer
16327 .chunks(rename_start..rename_end, true)
16328 .map(|chunk| {
16329 if old_highlight_id.is_none() {
16330 old_highlight_id = chunk.syntax_highlight_id;
16331 }
16332 chunk.text
16333 })
16334 .collect::<String>()
16335 .into();
16336
16337 drop(buffer);
16338
16339 // Position the selection in the rename editor so that it matches the current selection.
16340 this.show_local_selections = false;
16341 let rename_editor = cx.new(|cx| {
16342 let mut editor = Editor::single_line(window, cx);
16343 editor.buffer.update(cx, |buffer, cx| {
16344 buffer.edit([(0..0, old_name.clone())], None, cx)
16345 });
16346 let rename_selection_range = match cursor_offset_in_rename_range
16347 .cmp(&cursor_offset_in_rename_range_end)
16348 {
16349 Ordering::Equal => {
16350 editor.select_all(&SelectAll, window, cx);
16351 return editor;
16352 }
16353 Ordering::Less => {
16354 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16355 }
16356 Ordering::Greater => {
16357 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16358 }
16359 };
16360 if rename_selection_range.end > old_name.len() {
16361 editor.select_all(&SelectAll, window, cx);
16362 } else {
16363 editor.change_selections(Default::default(), window, cx, |s| {
16364 s.select_ranges([rename_selection_range]);
16365 });
16366 }
16367 editor
16368 });
16369 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16370 if e == &EditorEvent::Focused {
16371 cx.emit(EditorEvent::FocusedIn)
16372 }
16373 })
16374 .detach();
16375
16376 let write_highlights =
16377 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16378 let read_highlights =
16379 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16380 let ranges = write_highlights
16381 .iter()
16382 .flat_map(|(_, ranges)| ranges.iter())
16383 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16384 .cloned()
16385 .collect();
16386
16387 this.highlight_text::<Rename>(
16388 ranges,
16389 HighlightStyle {
16390 fade_out: Some(0.6),
16391 ..Default::default()
16392 },
16393 cx,
16394 );
16395 let rename_focus_handle = rename_editor.focus_handle(cx);
16396 window.focus(&rename_focus_handle);
16397 let block_id = this.insert_blocks(
16398 [BlockProperties {
16399 style: BlockStyle::Flex,
16400 placement: BlockPlacement::Below(range.start),
16401 height: Some(1),
16402 render: Arc::new({
16403 let rename_editor = rename_editor.clone();
16404 move |cx: &mut BlockContext| {
16405 let mut text_style = cx.editor_style.text.clone();
16406 if let Some(highlight_style) = old_highlight_id
16407 .and_then(|h| h.style(&cx.editor_style.syntax))
16408 {
16409 text_style = text_style.highlight(highlight_style);
16410 }
16411 div()
16412 .block_mouse_except_scroll()
16413 .pl(cx.anchor_x)
16414 .child(EditorElement::new(
16415 &rename_editor,
16416 EditorStyle {
16417 background: cx.theme().system().transparent,
16418 local_player: cx.editor_style.local_player,
16419 text: text_style,
16420 scrollbar_width: cx.editor_style.scrollbar_width,
16421 syntax: cx.editor_style.syntax.clone(),
16422 status: cx.editor_style.status.clone(),
16423 inlay_hints_style: HighlightStyle {
16424 font_weight: Some(FontWeight::BOLD),
16425 ..make_inlay_hints_style(cx.app)
16426 },
16427 edit_prediction_styles: make_suggestion_styles(
16428 cx.app,
16429 ),
16430 ..EditorStyle::default()
16431 },
16432 ))
16433 .into_any_element()
16434 }
16435 }),
16436 priority: 0,
16437 }],
16438 Some(Autoscroll::fit()),
16439 cx,
16440 )[0];
16441 this.pending_rename = Some(RenameState {
16442 range,
16443 old_name,
16444 editor: rename_editor,
16445 block_id,
16446 });
16447 })?;
16448 }
16449
16450 Ok(())
16451 }))
16452 }
16453
16454 pub fn confirm_rename(
16455 &mut self,
16456 _: &ConfirmRename,
16457 window: &mut Window,
16458 cx: &mut Context<Self>,
16459 ) -> Option<Task<Result<()>>> {
16460 let rename = self.take_rename(false, window, cx)?;
16461 let workspace = self.workspace()?.downgrade();
16462 let (buffer, start) = self
16463 .buffer
16464 .read(cx)
16465 .text_anchor_for_position(rename.range.start, cx)?;
16466 let (end_buffer, _) = self
16467 .buffer
16468 .read(cx)
16469 .text_anchor_for_position(rename.range.end, cx)?;
16470 if buffer != end_buffer {
16471 return None;
16472 }
16473
16474 let old_name = rename.old_name;
16475 let new_name = rename.editor.read(cx).text(cx);
16476
16477 let rename = self.semantics_provider.as_ref()?.perform_rename(
16478 &buffer,
16479 start,
16480 new_name.clone(),
16481 cx,
16482 )?;
16483
16484 Some(cx.spawn_in(window, async move |editor, cx| {
16485 let project_transaction = rename.await?;
16486 Self::open_project_transaction(
16487 &editor,
16488 workspace,
16489 project_transaction,
16490 format!("Rename: {} → {}", old_name, new_name),
16491 cx,
16492 )
16493 .await?;
16494
16495 editor.update(cx, |editor, cx| {
16496 editor.refresh_document_highlights(cx);
16497 })?;
16498 Ok(())
16499 }))
16500 }
16501
16502 fn take_rename(
16503 &mut self,
16504 moving_cursor: bool,
16505 window: &mut Window,
16506 cx: &mut Context<Self>,
16507 ) -> Option<RenameState> {
16508 let rename = self.pending_rename.take()?;
16509 if rename.editor.focus_handle(cx).is_focused(window) {
16510 window.focus(&self.focus_handle);
16511 }
16512
16513 self.remove_blocks(
16514 [rename.block_id].into_iter().collect(),
16515 Some(Autoscroll::fit()),
16516 cx,
16517 );
16518 self.clear_highlights::<Rename>(cx);
16519 self.show_local_selections = true;
16520
16521 if moving_cursor {
16522 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16523 editor.selections.newest::<usize>(cx).head()
16524 });
16525
16526 // Update the selection to match the position of the selection inside
16527 // the rename editor.
16528 let snapshot = self.buffer.read(cx).read(cx);
16529 let rename_range = rename.range.to_offset(&snapshot);
16530 let cursor_in_editor = snapshot
16531 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16532 .min(rename_range.end);
16533 drop(snapshot);
16534
16535 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16536 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16537 });
16538 } else {
16539 self.refresh_document_highlights(cx);
16540 }
16541
16542 Some(rename)
16543 }
16544
16545 pub fn pending_rename(&self) -> Option<&RenameState> {
16546 self.pending_rename.as_ref()
16547 }
16548
16549 fn format(
16550 &mut self,
16551 _: &Format,
16552 window: &mut Window,
16553 cx: &mut Context<Self>,
16554 ) -> Option<Task<Result<()>>> {
16555 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16556
16557 let project = match &self.project {
16558 Some(project) => project.clone(),
16559 None => return None,
16560 };
16561
16562 Some(self.perform_format(
16563 project,
16564 FormatTrigger::Manual,
16565 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16566 window,
16567 cx,
16568 ))
16569 }
16570
16571 fn format_selections(
16572 &mut self,
16573 _: &FormatSelections,
16574 window: &mut Window,
16575 cx: &mut Context<Self>,
16576 ) -> Option<Task<Result<()>>> {
16577 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16578
16579 let project = match &self.project {
16580 Some(project) => project.clone(),
16581 None => return None,
16582 };
16583
16584 let ranges = self
16585 .selections
16586 .all_adjusted(cx)
16587 .into_iter()
16588 .map(|selection| selection.range())
16589 .collect_vec();
16590
16591 Some(self.perform_format(
16592 project,
16593 FormatTrigger::Manual,
16594 FormatTarget::Ranges(ranges),
16595 window,
16596 cx,
16597 ))
16598 }
16599
16600 fn perform_format(
16601 &mut self,
16602 project: Entity<Project>,
16603 trigger: FormatTrigger,
16604 target: FormatTarget,
16605 window: &mut Window,
16606 cx: &mut Context<Self>,
16607 ) -> Task<Result<()>> {
16608 let buffer = self.buffer.clone();
16609 let (buffers, target) = match target {
16610 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16611 FormatTarget::Ranges(selection_ranges) => {
16612 let multi_buffer = buffer.read(cx);
16613 let snapshot = multi_buffer.read(cx);
16614 let mut buffers = HashSet::default();
16615 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16616 BTreeMap::new();
16617 for selection_range in selection_ranges {
16618 for (buffer, buffer_range, _) in
16619 snapshot.range_to_buffer_ranges(selection_range)
16620 {
16621 let buffer_id = buffer.remote_id();
16622 let start = buffer.anchor_before(buffer_range.start);
16623 let end = buffer.anchor_after(buffer_range.end);
16624 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16625 buffer_id_to_ranges
16626 .entry(buffer_id)
16627 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16628 .or_insert_with(|| vec![start..end]);
16629 }
16630 }
16631 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16632 }
16633 };
16634
16635 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16636 let selections_prev = transaction_id_prev
16637 .and_then(|transaction_id_prev| {
16638 // default to selections as they were after the last edit, if we have them,
16639 // instead of how they are now.
16640 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16641 // will take you back to where you made the last edit, instead of staying where you scrolled
16642 self.selection_history
16643 .transaction(transaction_id_prev)
16644 .map(|t| t.0.clone())
16645 })
16646 .unwrap_or_else(|| self.selections.disjoint_anchors());
16647
16648 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16649 let format = project.update(cx, |project, cx| {
16650 project.format(buffers, target, true, trigger, cx)
16651 });
16652
16653 cx.spawn_in(window, async move |editor, cx| {
16654 let transaction = futures::select_biased! {
16655 transaction = format.log_err().fuse() => transaction,
16656 () = timeout => {
16657 log::warn!("timed out waiting for formatting");
16658 None
16659 }
16660 };
16661
16662 buffer
16663 .update(cx, |buffer, cx| {
16664 if let Some(transaction) = transaction
16665 && !buffer.is_singleton()
16666 {
16667 buffer.push_transaction(&transaction.0, cx);
16668 }
16669 cx.notify();
16670 })
16671 .ok();
16672
16673 if let Some(transaction_id_now) =
16674 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16675 {
16676 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16677 if has_new_transaction {
16678 _ = editor.update(cx, |editor, _| {
16679 editor
16680 .selection_history
16681 .insert_transaction(transaction_id_now, selections_prev);
16682 });
16683 }
16684 }
16685
16686 Ok(())
16687 })
16688 }
16689
16690 fn organize_imports(
16691 &mut self,
16692 _: &OrganizeImports,
16693 window: &mut Window,
16694 cx: &mut Context<Self>,
16695 ) -> Option<Task<Result<()>>> {
16696 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16697 let project = match &self.project {
16698 Some(project) => project.clone(),
16699 None => return None,
16700 };
16701 Some(self.perform_code_action_kind(
16702 project,
16703 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16704 window,
16705 cx,
16706 ))
16707 }
16708
16709 fn perform_code_action_kind(
16710 &mut self,
16711 project: Entity<Project>,
16712 kind: CodeActionKind,
16713 window: &mut Window,
16714 cx: &mut Context<Self>,
16715 ) -> Task<Result<()>> {
16716 let buffer = self.buffer.clone();
16717 let buffers = buffer.read(cx).all_buffers();
16718 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16719 let apply_action = project.update(cx, |project, cx| {
16720 project.apply_code_action_kind(buffers, kind, true, cx)
16721 });
16722 cx.spawn_in(window, async move |_, cx| {
16723 let transaction = futures::select_biased! {
16724 () = timeout => {
16725 log::warn!("timed out waiting for executing code action");
16726 None
16727 }
16728 transaction = apply_action.log_err().fuse() => transaction,
16729 };
16730 buffer
16731 .update(cx, |buffer, cx| {
16732 // check if we need this
16733 if let Some(transaction) = transaction
16734 && !buffer.is_singleton()
16735 {
16736 buffer.push_transaction(&transaction.0, cx);
16737 }
16738 cx.notify();
16739 })
16740 .ok();
16741 Ok(())
16742 })
16743 }
16744
16745 pub fn restart_language_server(
16746 &mut self,
16747 _: &RestartLanguageServer,
16748 _: &mut Window,
16749 cx: &mut Context<Self>,
16750 ) {
16751 if let Some(project) = self.project.clone() {
16752 self.buffer.update(cx, |multi_buffer, cx| {
16753 project.update(cx, |project, cx| {
16754 project.restart_language_servers_for_buffers(
16755 multi_buffer.all_buffers().into_iter().collect(),
16756 HashSet::default(),
16757 cx,
16758 );
16759 });
16760 })
16761 }
16762 }
16763
16764 pub fn stop_language_server(
16765 &mut self,
16766 _: &StopLanguageServer,
16767 _: &mut Window,
16768 cx: &mut Context<Self>,
16769 ) {
16770 if let Some(project) = self.project.clone() {
16771 self.buffer.update(cx, |multi_buffer, cx| {
16772 project.update(cx, |project, cx| {
16773 project.stop_language_servers_for_buffers(
16774 multi_buffer.all_buffers().into_iter().collect(),
16775 HashSet::default(),
16776 cx,
16777 );
16778 cx.emit(project::Event::RefreshInlayHints);
16779 });
16780 });
16781 }
16782 }
16783
16784 fn cancel_language_server_work(
16785 workspace: &mut Workspace,
16786 _: &actions::CancelLanguageServerWork,
16787 _: &mut Window,
16788 cx: &mut Context<Workspace>,
16789 ) {
16790 let project = workspace.project();
16791 let buffers = workspace
16792 .active_item(cx)
16793 .and_then(|item| item.act_as::<Editor>(cx))
16794 .map_or(HashSet::default(), |editor| {
16795 editor.read(cx).buffer.read(cx).all_buffers()
16796 });
16797 project.update(cx, |project, cx| {
16798 project.cancel_language_server_work_for_buffers(buffers, cx);
16799 });
16800 }
16801
16802 fn show_character_palette(
16803 &mut self,
16804 _: &ShowCharacterPalette,
16805 window: &mut Window,
16806 _: &mut Context<Self>,
16807 ) {
16808 window.show_character_palette();
16809 }
16810
16811 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16812 if !self.diagnostics_enabled() {
16813 return;
16814 }
16815
16816 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16817 let buffer = self.buffer.read(cx).snapshot(cx);
16818 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16819 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16820 let is_valid = buffer
16821 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16822 .any(|entry| {
16823 entry.diagnostic.is_primary
16824 && !entry.range.is_empty()
16825 && entry.range.start == primary_range_start
16826 && entry.diagnostic.message == active_diagnostics.active_message
16827 });
16828
16829 if !is_valid {
16830 self.dismiss_diagnostics(cx);
16831 }
16832 }
16833 }
16834
16835 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16836 match &self.active_diagnostics {
16837 ActiveDiagnostic::Group(group) => Some(group),
16838 _ => None,
16839 }
16840 }
16841
16842 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16843 if !self.diagnostics_enabled() {
16844 return;
16845 }
16846 self.dismiss_diagnostics(cx);
16847 self.active_diagnostics = ActiveDiagnostic::All;
16848 }
16849
16850 fn activate_diagnostics(
16851 &mut self,
16852 buffer_id: BufferId,
16853 diagnostic: DiagnosticEntry<usize>,
16854 window: &mut Window,
16855 cx: &mut Context<Self>,
16856 ) {
16857 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16858 return;
16859 }
16860 self.dismiss_diagnostics(cx);
16861 let snapshot = self.snapshot(window, cx);
16862 let buffer = self.buffer.read(cx).snapshot(cx);
16863 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16864 return;
16865 };
16866
16867 let diagnostic_group = buffer
16868 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16869 .collect::<Vec<_>>();
16870
16871 let blocks =
16872 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16873
16874 let blocks = self.display_map.update(cx, |display_map, cx| {
16875 display_map.insert_blocks(blocks, cx).into_iter().collect()
16876 });
16877 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16878 active_range: buffer.anchor_before(diagnostic.range.start)
16879 ..buffer.anchor_after(diagnostic.range.end),
16880 active_message: diagnostic.diagnostic.message.clone(),
16881 group_id: diagnostic.diagnostic.group_id,
16882 blocks,
16883 });
16884 cx.notify();
16885 }
16886
16887 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16888 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16889 return;
16890 };
16891
16892 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16893 if let ActiveDiagnostic::Group(group) = prev {
16894 self.display_map.update(cx, |display_map, cx| {
16895 display_map.remove_blocks(group.blocks, cx);
16896 });
16897 cx.notify();
16898 }
16899 }
16900
16901 /// Disable inline diagnostics rendering for this editor.
16902 pub fn disable_inline_diagnostics(&mut self) {
16903 self.inline_diagnostics_enabled = false;
16904 self.inline_diagnostics_update = Task::ready(());
16905 self.inline_diagnostics.clear();
16906 }
16907
16908 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16909 self.diagnostics_enabled = false;
16910 self.dismiss_diagnostics(cx);
16911 self.inline_diagnostics_update = Task::ready(());
16912 self.inline_diagnostics.clear();
16913 }
16914
16915 pub fn diagnostics_enabled(&self) -> bool {
16916 self.diagnostics_enabled && self.mode.is_full()
16917 }
16918
16919 pub fn inline_diagnostics_enabled(&self) -> bool {
16920 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16921 }
16922
16923 pub fn show_inline_diagnostics(&self) -> bool {
16924 self.show_inline_diagnostics
16925 }
16926
16927 pub fn toggle_inline_diagnostics(
16928 &mut self,
16929 _: &ToggleInlineDiagnostics,
16930 window: &mut Window,
16931 cx: &mut Context<Editor>,
16932 ) {
16933 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16934 self.refresh_inline_diagnostics(false, window, cx);
16935 }
16936
16937 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16938 self.diagnostics_max_severity = severity;
16939 self.display_map.update(cx, |display_map, _| {
16940 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16941 });
16942 }
16943
16944 pub fn toggle_diagnostics(
16945 &mut self,
16946 _: &ToggleDiagnostics,
16947 window: &mut Window,
16948 cx: &mut Context<Editor>,
16949 ) {
16950 if !self.diagnostics_enabled() {
16951 return;
16952 }
16953
16954 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16955 EditorSettings::get_global(cx)
16956 .diagnostics_max_severity
16957 .filter(|severity| severity != &DiagnosticSeverity::Off)
16958 .unwrap_or(DiagnosticSeverity::Hint)
16959 } else {
16960 DiagnosticSeverity::Off
16961 };
16962 self.set_max_diagnostics_severity(new_severity, cx);
16963 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16964 self.active_diagnostics = ActiveDiagnostic::None;
16965 self.inline_diagnostics_update = Task::ready(());
16966 self.inline_diagnostics.clear();
16967 } else {
16968 self.refresh_inline_diagnostics(false, window, cx);
16969 }
16970
16971 cx.notify();
16972 }
16973
16974 pub fn toggle_minimap(
16975 &mut self,
16976 _: &ToggleMinimap,
16977 window: &mut Window,
16978 cx: &mut Context<Editor>,
16979 ) {
16980 if self.supports_minimap(cx) {
16981 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16982 }
16983 }
16984
16985 fn refresh_inline_diagnostics(
16986 &mut self,
16987 debounce: bool,
16988 window: &mut Window,
16989 cx: &mut Context<Self>,
16990 ) {
16991 let max_severity = ProjectSettings::get_global(cx)
16992 .diagnostics
16993 .inline
16994 .max_severity
16995 .unwrap_or(self.diagnostics_max_severity);
16996
16997 if !self.inline_diagnostics_enabled()
16998 || !self.show_inline_diagnostics
16999 || max_severity == DiagnosticSeverity::Off
17000 {
17001 self.inline_diagnostics_update = Task::ready(());
17002 self.inline_diagnostics.clear();
17003 return;
17004 }
17005
17006 let debounce_ms = ProjectSettings::get_global(cx)
17007 .diagnostics
17008 .inline
17009 .update_debounce_ms;
17010 let debounce = if debounce && debounce_ms > 0 {
17011 Some(Duration::from_millis(debounce_ms))
17012 } else {
17013 None
17014 };
17015 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17016 if let Some(debounce) = debounce {
17017 cx.background_executor().timer(debounce).await;
17018 }
17019 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17020 editor
17021 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17022 .ok()
17023 }) else {
17024 return;
17025 };
17026
17027 let new_inline_diagnostics = cx
17028 .background_spawn(async move {
17029 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17030 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17031 let message = diagnostic_entry
17032 .diagnostic
17033 .message
17034 .split_once('\n')
17035 .map(|(line, _)| line)
17036 .map(SharedString::new)
17037 .unwrap_or_else(|| {
17038 SharedString::from(diagnostic_entry.diagnostic.message)
17039 });
17040 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17041 let (Ok(i) | Err(i)) = inline_diagnostics
17042 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17043 inline_diagnostics.insert(
17044 i,
17045 (
17046 start_anchor,
17047 InlineDiagnostic {
17048 message,
17049 group_id: diagnostic_entry.diagnostic.group_id,
17050 start: diagnostic_entry.range.start.to_point(&snapshot),
17051 is_primary: diagnostic_entry.diagnostic.is_primary,
17052 severity: diagnostic_entry.diagnostic.severity,
17053 },
17054 ),
17055 );
17056 }
17057 inline_diagnostics
17058 })
17059 .await;
17060
17061 editor
17062 .update(cx, |editor, cx| {
17063 editor.inline_diagnostics = new_inline_diagnostics;
17064 cx.notify();
17065 })
17066 .ok();
17067 });
17068 }
17069
17070 fn pull_diagnostics(
17071 &mut self,
17072 buffer_id: Option<BufferId>,
17073 window: &Window,
17074 cx: &mut Context<Self>,
17075 ) -> Option<()> {
17076 if !self.mode().is_full() {
17077 return None;
17078 }
17079 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17080 .diagnostics
17081 .lsp_pull_diagnostics;
17082 if !pull_diagnostics_settings.enabled {
17083 return None;
17084 }
17085 let project = self.project()?.downgrade();
17086 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17087 let mut buffers = self.buffer.read(cx).all_buffers();
17088 if let Some(buffer_id) = buffer_id {
17089 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17090 }
17091
17092 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17093 cx.background_executor().timer(debounce).await;
17094
17095 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17096 buffers
17097 .into_iter()
17098 .filter_map(|buffer| {
17099 project
17100 .update(cx, |project, cx| {
17101 project.lsp_store().update(cx, |lsp_store, cx| {
17102 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17103 })
17104 })
17105 .ok()
17106 })
17107 .collect::<FuturesUnordered<_>>()
17108 }) else {
17109 return;
17110 };
17111
17112 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17113 match pull_task {
17114 Ok(()) => {
17115 if editor
17116 .update_in(cx, |editor, window, cx| {
17117 editor.update_diagnostics_state(window, cx);
17118 })
17119 .is_err()
17120 {
17121 return;
17122 }
17123 }
17124 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17125 }
17126 }
17127 });
17128
17129 Some(())
17130 }
17131
17132 pub fn set_selections_from_remote(
17133 &mut self,
17134 selections: Vec<Selection<Anchor>>,
17135 pending_selection: Option<Selection<Anchor>>,
17136 window: &mut Window,
17137 cx: &mut Context<Self>,
17138 ) {
17139 let old_cursor_position = self.selections.newest_anchor().head();
17140 self.selections.change_with(cx, |s| {
17141 s.select_anchors(selections);
17142 if let Some(pending_selection) = pending_selection {
17143 s.set_pending(pending_selection, SelectMode::Character);
17144 } else {
17145 s.clear_pending();
17146 }
17147 });
17148 self.selections_did_change(
17149 false,
17150 &old_cursor_position,
17151 SelectionEffects::default(),
17152 window,
17153 cx,
17154 );
17155 }
17156
17157 pub fn transact(
17158 &mut self,
17159 window: &mut Window,
17160 cx: &mut Context<Self>,
17161 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17162 ) -> Option<TransactionId> {
17163 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17164 this.start_transaction_at(Instant::now(), window, cx);
17165 update(this, window, cx);
17166 this.end_transaction_at(Instant::now(), cx)
17167 })
17168 }
17169
17170 pub fn start_transaction_at(
17171 &mut self,
17172 now: Instant,
17173 window: &mut Window,
17174 cx: &mut Context<Self>,
17175 ) -> Option<TransactionId> {
17176 self.end_selection(window, cx);
17177 if let Some(tx_id) = self
17178 .buffer
17179 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17180 {
17181 self.selection_history
17182 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17183 cx.emit(EditorEvent::TransactionBegun {
17184 transaction_id: tx_id,
17185 });
17186 Some(tx_id)
17187 } else {
17188 None
17189 }
17190 }
17191
17192 pub fn end_transaction_at(
17193 &mut self,
17194 now: Instant,
17195 cx: &mut Context<Self>,
17196 ) -> Option<TransactionId> {
17197 if let Some(transaction_id) = self
17198 .buffer
17199 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17200 {
17201 if let Some((_, end_selections)) =
17202 self.selection_history.transaction_mut(transaction_id)
17203 {
17204 *end_selections = Some(self.selections.disjoint_anchors());
17205 } else {
17206 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17207 }
17208
17209 cx.emit(EditorEvent::Edited { transaction_id });
17210 Some(transaction_id)
17211 } else {
17212 None
17213 }
17214 }
17215
17216 pub fn modify_transaction_selection_history(
17217 &mut self,
17218 transaction_id: TransactionId,
17219 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17220 ) -> bool {
17221 self.selection_history
17222 .transaction_mut(transaction_id)
17223 .map(modify)
17224 .is_some()
17225 }
17226
17227 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17228 if self.selection_mark_mode {
17229 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17230 s.move_with(|_, sel| {
17231 sel.collapse_to(sel.head(), SelectionGoal::None);
17232 });
17233 })
17234 }
17235 self.selection_mark_mode = true;
17236 cx.notify();
17237 }
17238
17239 pub fn swap_selection_ends(
17240 &mut self,
17241 _: &actions::SwapSelectionEnds,
17242 window: &mut Window,
17243 cx: &mut Context<Self>,
17244 ) {
17245 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17246 s.move_with(|_, sel| {
17247 if sel.start != sel.end {
17248 sel.reversed = !sel.reversed
17249 }
17250 });
17251 });
17252 self.request_autoscroll(Autoscroll::newest(), cx);
17253 cx.notify();
17254 }
17255
17256 pub fn toggle_focus(
17257 workspace: &mut Workspace,
17258 _: &actions::ToggleFocus,
17259 window: &mut Window,
17260 cx: &mut Context<Workspace>,
17261 ) {
17262 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17263 return;
17264 };
17265 workspace.activate_item(&item, true, true, window, cx);
17266 }
17267
17268 pub fn toggle_fold(
17269 &mut self,
17270 _: &actions::ToggleFold,
17271 window: &mut Window,
17272 cx: &mut Context<Self>,
17273 ) {
17274 if self.is_singleton(cx) {
17275 let selection = self.selections.newest::<Point>(cx);
17276
17277 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17278 let range = if selection.is_empty() {
17279 let point = selection.head().to_display_point(&display_map);
17280 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17281 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17282 .to_point(&display_map);
17283 start..end
17284 } else {
17285 selection.range()
17286 };
17287 if display_map.folds_in_range(range).next().is_some() {
17288 self.unfold_lines(&Default::default(), window, cx)
17289 } else {
17290 self.fold(&Default::default(), window, cx)
17291 }
17292 } else {
17293 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17294 let buffer_ids: HashSet<_> = self
17295 .selections
17296 .disjoint_anchor_ranges()
17297 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17298 .collect();
17299
17300 let should_unfold = buffer_ids
17301 .iter()
17302 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17303
17304 for buffer_id in buffer_ids {
17305 if should_unfold {
17306 self.unfold_buffer(buffer_id, cx);
17307 } else {
17308 self.fold_buffer(buffer_id, cx);
17309 }
17310 }
17311 }
17312 }
17313
17314 pub fn toggle_fold_recursive(
17315 &mut self,
17316 _: &actions::ToggleFoldRecursive,
17317 window: &mut Window,
17318 cx: &mut Context<Self>,
17319 ) {
17320 let selection = self.selections.newest::<Point>(cx);
17321
17322 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17323 let range = if selection.is_empty() {
17324 let point = selection.head().to_display_point(&display_map);
17325 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17326 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17327 .to_point(&display_map);
17328 start..end
17329 } else {
17330 selection.range()
17331 };
17332 if display_map.folds_in_range(range).next().is_some() {
17333 self.unfold_recursive(&Default::default(), window, cx)
17334 } else {
17335 self.fold_recursive(&Default::default(), window, cx)
17336 }
17337 }
17338
17339 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17340 if self.is_singleton(cx) {
17341 let mut to_fold = Vec::new();
17342 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17343 let selections = self.selections.all_adjusted(cx);
17344
17345 for selection in selections {
17346 let range = selection.range().sorted();
17347 let buffer_start_row = range.start.row;
17348
17349 if range.start.row != range.end.row {
17350 let mut found = false;
17351 let mut row = range.start.row;
17352 while row <= range.end.row {
17353 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17354 {
17355 found = true;
17356 row = crease.range().end.row + 1;
17357 to_fold.push(crease);
17358 } else {
17359 row += 1
17360 }
17361 }
17362 if found {
17363 continue;
17364 }
17365 }
17366
17367 for row in (0..=range.start.row).rev() {
17368 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17369 && crease.range().end.row >= buffer_start_row
17370 {
17371 to_fold.push(crease);
17372 if row <= range.start.row {
17373 break;
17374 }
17375 }
17376 }
17377 }
17378
17379 self.fold_creases(to_fold, true, window, cx);
17380 } else {
17381 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17382 let buffer_ids = self
17383 .selections
17384 .disjoint_anchor_ranges()
17385 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17386 .collect::<HashSet<_>>();
17387 for buffer_id in buffer_ids {
17388 self.fold_buffer(buffer_id, cx);
17389 }
17390 }
17391 }
17392
17393 pub fn toggle_fold_all(
17394 &mut self,
17395 _: &actions::ToggleFoldAll,
17396 window: &mut Window,
17397 cx: &mut Context<Self>,
17398 ) {
17399 if self.buffer.read(cx).is_singleton() {
17400 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17401 let has_folds = display_map
17402 .folds_in_range(0..display_map.buffer_snapshot.len())
17403 .next()
17404 .is_some();
17405
17406 if has_folds {
17407 self.unfold_all(&actions::UnfoldAll, window, cx);
17408 } else {
17409 self.fold_all(&actions::FoldAll, window, cx);
17410 }
17411 } else {
17412 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17413 let should_unfold = buffer_ids
17414 .iter()
17415 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17416
17417 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17418 editor
17419 .update_in(cx, |editor, _, cx| {
17420 for buffer_id in buffer_ids {
17421 if should_unfold {
17422 editor.unfold_buffer(buffer_id, cx);
17423 } else {
17424 editor.fold_buffer(buffer_id, cx);
17425 }
17426 }
17427 })
17428 .ok();
17429 });
17430 }
17431 }
17432
17433 fn fold_at_level(
17434 &mut self,
17435 fold_at: &FoldAtLevel,
17436 window: &mut Window,
17437 cx: &mut Context<Self>,
17438 ) {
17439 if !self.buffer.read(cx).is_singleton() {
17440 return;
17441 }
17442
17443 let fold_at_level = fold_at.0;
17444 let snapshot = self.buffer.read(cx).snapshot(cx);
17445 let mut to_fold = Vec::new();
17446 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17447
17448 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17449 while start_row < end_row {
17450 match self
17451 .snapshot(window, cx)
17452 .crease_for_buffer_row(MultiBufferRow(start_row))
17453 {
17454 Some(crease) => {
17455 let nested_start_row = crease.range().start.row + 1;
17456 let nested_end_row = crease.range().end.row;
17457
17458 if current_level < fold_at_level {
17459 stack.push((nested_start_row, nested_end_row, current_level + 1));
17460 } else if current_level == fold_at_level {
17461 to_fold.push(crease);
17462 }
17463
17464 start_row = nested_end_row + 1;
17465 }
17466 None => start_row += 1,
17467 }
17468 }
17469 }
17470
17471 self.fold_creases(to_fold, true, window, cx);
17472 }
17473
17474 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17475 if self.buffer.read(cx).is_singleton() {
17476 let mut fold_ranges = Vec::new();
17477 let snapshot = self.buffer.read(cx).snapshot(cx);
17478
17479 for row in 0..snapshot.max_row().0 {
17480 if let Some(foldable_range) = self
17481 .snapshot(window, cx)
17482 .crease_for_buffer_row(MultiBufferRow(row))
17483 {
17484 fold_ranges.push(foldable_range);
17485 }
17486 }
17487
17488 self.fold_creases(fold_ranges, true, window, cx);
17489 } else {
17490 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17491 editor
17492 .update_in(cx, |editor, _, cx| {
17493 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17494 editor.fold_buffer(buffer_id, cx);
17495 }
17496 })
17497 .ok();
17498 });
17499 }
17500 }
17501
17502 pub fn fold_function_bodies(
17503 &mut self,
17504 _: &actions::FoldFunctionBodies,
17505 window: &mut Window,
17506 cx: &mut Context<Self>,
17507 ) {
17508 let snapshot = self.buffer.read(cx).snapshot(cx);
17509
17510 let ranges = snapshot
17511 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17512 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17513 .collect::<Vec<_>>();
17514
17515 let creases = ranges
17516 .into_iter()
17517 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17518 .collect();
17519
17520 self.fold_creases(creases, true, window, cx);
17521 }
17522
17523 pub fn fold_recursive(
17524 &mut self,
17525 _: &actions::FoldRecursive,
17526 window: &mut Window,
17527 cx: &mut Context<Self>,
17528 ) {
17529 let mut to_fold = Vec::new();
17530 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17531 let selections = self.selections.all_adjusted(cx);
17532
17533 for selection in selections {
17534 let range = selection.range().sorted();
17535 let buffer_start_row = range.start.row;
17536
17537 if range.start.row != range.end.row {
17538 let mut found = false;
17539 for row in range.start.row..=range.end.row {
17540 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17541 found = true;
17542 to_fold.push(crease);
17543 }
17544 }
17545 if found {
17546 continue;
17547 }
17548 }
17549
17550 for row in (0..=range.start.row).rev() {
17551 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17552 if crease.range().end.row >= buffer_start_row {
17553 to_fold.push(crease);
17554 } else {
17555 break;
17556 }
17557 }
17558 }
17559 }
17560
17561 self.fold_creases(to_fold, true, window, cx);
17562 }
17563
17564 pub fn fold_at(
17565 &mut self,
17566 buffer_row: MultiBufferRow,
17567 window: &mut Window,
17568 cx: &mut Context<Self>,
17569 ) {
17570 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17571
17572 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17573 let autoscroll = self
17574 .selections
17575 .all::<Point>(cx)
17576 .iter()
17577 .any(|selection| crease.range().overlaps(&selection.range()));
17578
17579 self.fold_creases(vec![crease], autoscroll, window, cx);
17580 }
17581 }
17582
17583 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17584 if self.is_singleton(cx) {
17585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17586 let buffer = &display_map.buffer_snapshot;
17587 let selections = self.selections.all::<Point>(cx);
17588 let ranges = selections
17589 .iter()
17590 .map(|s| {
17591 let range = s.display_range(&display_map).sorted();
17592 let mut start = range.start.to_point(&display_map);
17593 let mut end = range.end.to_point(&display_map);
17594 start.column = 0;
17595 end.column = buffer.line_len(MultiBufferRow(end.row));
17596 start..end
17597 })
17598 .collect::<Vec<_>>();
17599
17600 self.unfold_ranges(&ranges, true, true, cx);
17601 } else {
17602 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17603 let buffer_ids = self
17604 .selections
17605 .disjoint_anchor_ranges()
17606 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17607 .collect::<HashSet<_>>();
17608 for buffer_id in buffer_ids {
17609 self.unfold_buffer(buffer_id, cx);
17610 }
17611 }
17612 }
17613
17614 pub fn unfold_recursive(
17615 &mut self,
17616 _: &UnfoldRecursive,
17617 _window: &mut Window,
17618 cx: &mut Context<Self>,
17619 ) {
17620 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17621 let selections = self.selections.all::<Point>(cx);
17622 let ranges = selections
17623 .iter()
17624 .map(|s| {
17625 let mut range = s.display_range(&display_map).sorted();
17626 *range.start.column_mut() = 0;
17627 *range.end.column_mut() = display_map.line_len(range.end.row());
17628 let start = range.start.to_point(&display_map);
17629 let end = range.end.to_point(&display_map);
17630 start..end
17631 })
17632 .collect::<Vec<_>>();
17633
17634 self.unfold_ranges(&ranges, true, true, cx);
17635 }
17636
17637 pub fn unfold_at(
17638 &mut self,
17639 buffer_row: MultiBufferRow,
17640 _window: &mut Window,
17641 cx: &mut Context<Self>,
17642 ) {
17643 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17644
17645 let intersection_range = Point::new(buffer_row.0, 0)
17646 ..Point::new(
17647 buffer_row.0,
17648 display_map.buffer_snapshot.line_len(buffer_row),
17649 );
17650
17651 let autoscroll = self
17652 .selections
17653 .all::<Point>(cx)
17654 .iter()
17655 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17656
17657 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17658 }
17659
17660 pub fn unfold_all(
17661 &mut self,
17662 _: &actions::UnfoldAll,
17663 _window: &mut Window,
17664 cx: &mut Context<Self>,
17665 ) {
17666 if self.buffer.read(cx).is_singleton() {
17667 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17668 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17669 } else {
17670 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17671 editor
17672 .update(cx, |editor, cx| {
17673 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17674 editor.unfold_buffer(buffer_id, cx);
17675 }
17676 })
17677 .ok();
17678 });
17679 }
17680 }
17681
17682 pub fn fold_selected_ranges(
17683 &mut self,
17684 _: &FoldSelectedRanges,
17685 window: &mut Window,
17686 cx: &mut Context<Self>,
17687 ) {
17688 let selections = self.selections.all_adjusted(cx);
17689 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17690 let ranges = selections
17691 .into_iter()
17692 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17693 .collect::<Vec<_>>();
17694 self.fold_creases(ranges, true, window, cx);
17695 }
17696
17697 pub fn fold_ranges<T: ToOffset + Clone>(
17698 &mut self,
17699 ranges: Vec<Range<T>>,
17700 auto_scroll: bool,
17701 window: &mut Window,
17702 cx: &mut Context<Self>,
17703 ) {
17704 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17705 let ranges = ranges
17706 .into_iter()
17707 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17708 .collect::<Vec<_>>();
17709 self.fold_creases(ranges, auto_scroll, window, cx);
17710 }
17711
17712 pub fn fold_creases<T: ToOffset + Clone>(
17713 &mut self,
17714 creases: Vec<Crease<T>>,
17715 auto_scroll: bool,
17716 _window: &mut Window,
17717 cx: &mut Context<Self>,
17718 ) {
17719 if creases.is_empty() {
17720 return;
17721 }
17722
17723 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17724
17725 if auto_scroll {
17726 self.request_autoscroll(Autoscroll::fit(), cx);
17727 }
17728
17729 cx.notify();
17730
17731 self.scrollbar_marker_state.dirty = true;
17732 self.folds_did_change(cx);
17733 }
17734
17735 /// Removes any folds whose ranges intersect any of the given ranges.
17736 pub fn unfold_ranges<T: ToOffset + Clone>(
17737 &mut self,
17738 ranges: &[Range<T>],
17739 inclusive: bool,
17740 auto_scroll: bool,
17741 cx: &mut Context<Self>,
17742 ) {
17743 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17744 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17745 });
17746 self.folds_did_change(cx);
17747 }
17748
17749 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17750 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17751 return;
17752 }
17753 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17754 self.display_map.update(cx, |display_map, cx| {
17755 display_map.fold_buffers([buffer_id], cx)
17756 });
17757 cx.emit(EditorEvent::BufferFoldToggled {
17758 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17759 folded: true,
17760 });
17761 cx.notify();
17762 }
17763
17764 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17765 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17766 return;
17767 }
17768 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17769 self.display_map.update(cx, |display_map, cx| {
17770 display_map.unfold_buffers([buffer_id], cx);
17771 });
17772 cx.emit(EditorEvent::BufferFoldToggled {
17773 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17774 folded: false,
17775 });
17776 cx.notify();
17777 }
17778
17779 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17780 self.display_map.read(cx).is_buffer_folded(buffer)
17781 }
17782
17783 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17784 self.display_map.read(cx).folded_buffers()
17785 }
17786
17787 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17788 self.display_map.update(cx, |display_map, cx| {
17789 display_map.disable_header_for_buffer(buffer_id, cx);
17790 });
17791 cx.notify();
17792 }
17793
17794 /// Removes any folds with the given ranges.
17795 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17796 &mut self,
17797 ranges: &[Range<T>],
17798 type_id: TypeId,
17799 auto_scroll: bool,
17800 cx: &mut Context<Self>,
17801 ) {
17802 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17803 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17804 });
17805 self.folds_did_change(cx);
17806 }
17807
17808 fn remove_folds_with<T: ToOffset + Clone>(
17809 &mut self,
17810 ranges: &[Range<T>],
17811 auto_scroll: bool,
17812 cx: &mut Context<Self>,
17813 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17814 ) {
17815 if ranges.is_empty() {
17816 return;
17817 }
17818
17819 let mut buffers_affected = HashSet::default();
17820 let multi_buffer = self.buffer().read(cx);
17821 for range in ranges {
17822 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17823 buffers_affected.insert(buffer.read(cx).remote_id());
17824 };
17825 }
17826
17827 self.display_map.update(cx, update);
17828
17829 if auto_scroll {
17830 self.request_autoscroll(Autoscroll::fit(), cx);
17831 }
17832
17833 cx.notify();
17834 self.scrollbar_marker_state.dirty = true;
17835 self.active_indent_guides_state.dirty = true;
17836 }
17837
17838 pub fn update_renderer_widths(
17839 &mut self,
17840 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17841 cx: &mut Context<Self>,
17842 ) -> bool {
17843 self.display_map
17844 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17845 }
17846
17847 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17848 self.display_map.read(cx).fold_placeholder.clone()
17849 }
17850
17851 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17852 self.buffer.update(cx, |buffer, cx| {
17853 buffer.set_all_diff_hunks_expanded(cx);
17854 });
17855 }
17856
17857 pub fn expand_all_diff_hunks(
17858 &mut self,
17859 _: &ExpandAllDiffHunks,
17860 _window: &mut Window,
17861 cx: &mut Context<Self>,
17862 ) {
17863 self.buffer.update(cx, |buffer, cx| {
17864 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17865 });
17866 }
17867
17868 pub fn toggle_selected_diff_hunks(
17869 &mut self,
17870 _: &ToggleSelectedDiffHunks,
17871 _window: &mut Window,
17872 cx: &mut Context<Self>,
17873 ) {
17874 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17875 self.toggle_diff_hunks_in_ranges(ranges, cx);
17876 }
17877
17878 pub fn diff_hunks_in_ranges<'a>(
17879 &'a self,
17880 ranges: &'a [Range<Anchor>],
17881 buffer: &'a MultiBufferSnapshot,
17882 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17883 ranges.iter().flat_map(move |range| {
17884 let end_excerpt_id = range.end.excerpt_id;
17885 let range = range.to_point(buffer);
17886 let mut peek_end = range.end;
17887 if range.end.row < buffer.max_row().0 {
17888 peek_end = Point::new(range.end.row + 1, 0);
17889 }
17890 buffer
17891 .diff_hunks_in_range(range.start..peek_end)
17892 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17893 })
17894 }
17895
17896 pub fn has_stageable_diff_hunks_in_ranges(
17897 &self,
17898 ranges: &[Range<Anchor>],
17899 snapshot: &MultiBufferSnapshot,
17900 ) -> bool {
17901 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
17902 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17903 }
17904
17905 pub fn toggle_staged_selected_diff_hunks(
17906 &mut self,
17907 _: &::git::ToggleStaged,
17908 _: &mut Window,
17909 cx: &mut Context<Self>,
17910 ) {
17911 let snapshot = self.buffer.read(cx).snapshot(cx);
17912 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17913 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17914 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17915 }
17916
17917 pub fn set_render_diff_hunk_controls(
17918 &mut self,
17919 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17920 cx: &mut Context<Self>,
17921 ) {
17922 self.render_diff_hunk_controls = render_diff_hunk_controls;
17923 cx.notify();
17924 }
17925
17926 pub fn stage_and_next(
17927 &mut self,
17928 _: &::git::StageAndNext,
17929 window: &mut Window,
17930 cx: &mut Context<Self>,
17931 ) {
17932 self.do_stage_or_unstage_and_next(true, window, cx);
17933 }
17934
17935 pub fn unstage_and_next(
17936 &mut self,
17937 _: &::git::UnstageAndNext,
17938 window: &mut Window,
17939 cx: &mut Context<Self>,
17940 ) {
17941 self.do_stage_or_unstage_and_next(false, window, cx);
17942 }
17943
17944 pub fn stage_or_unstage_diff_hunks(
17945 &mut self,
17946 stage: bool,
17947 ranges: Vec<Range<Anchor>>,
17948 cx: &mut Context<Self>,
17949 ) {
17950 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17951 cx.spawn(async move |this, cx| {
17952 task.await?;
17953 this.update(cx, |this, cx| {
17954 let snapshot = this.buffer.read(cx).snapshot(cx);
17955 let chunk_by = this
17956 .diff_hunks_in_ranges(&ranges, &snapshot)
17957 .chunk_by(|hunk| hunk.buffer_id);
17958 for (buffer_id, hunks) in &chunk_by {
17959 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17960 }
17961 })
17962 })
17963 .detach_and_log_err(cx);
17964 }
17965
17966 fn save_buffers_for_ranges_if_needed(
17967 &mut self,
17968 ranges: &[Range<Anchor>],
17969 cx: &mut Context<Editor>,
17970 ) -> Task<Result<()>> {
17971 let multibuffer = self.buffer.read(cx);
17972 let snapshot = multibuffer.read(cx);
17973 let buffer_ids: HashSet<_> = ranges
17974 .iter()
17975 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17976 .collect();
17977 drop(snapshot);
17978
17979 let mut buffers = HashSet::default();
17980 for buffer_id in buffer_ids {
17981 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17982 let buffer = buffer_entity.read(cx);
17983 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17984 {
17985 buffers.insert(buffer_entity);
17986 }
17987 }
17988 }
17989
17990 if let Some(project) = &self.project {
17991 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17992 } else {
17993 Task::ready(Ok(()))
17994 }
17995 }
17996
17997 fn do_stage_or_unstage_and_next(
17998 &mut self,
17999 stage: bool,
18000 window: &mut Window,
18001 cx: &mut Context<Self>,
18002 ) {
18003 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18004
18005 if ranges.iter().any(|range| range.start != range.end) {
18006 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18007 return;
18008 }
18009
18010 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18011 let snapshot = self.snapshot(window, cx);
18012 let position = self.selections.newest::<Point>(cx).head();
18013 let mut row = snapshot
18014 .buffer_snapshot
18015 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18016 .find(|hunk| hunk.row_range.start.0 > position.row)
18017 .map(|hunk| hunk.row_range.start);
18018
18019 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18020 // Outside of the project diff editor, wrap around to the beginning.
18021 if !all_diff_hunks_expanded {
18022 row = row.or_else(|| {
18023 snapshot
18024 .buffer_snapshot
18025 .diff_hunks_in_range(Point::zero()..position)
18026 .find(|hunk| hunk.row_range.end.0 < position.row)
18027 .map(|hunk| hunk.row_range.start)
18028 });
18029 }
18030
18031 if let Some(row) = row {
18032 let destination = Point::new(row.0, 0);
18033 let autoscroll = Autoscroll::center();
18034
18035 self.unfold_ranges(&[destination..destination], false, false, cx);
18036 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18037 s.select_ranges([destination..destination]);
18038 });
18039 }
18040 }
18041
18042 fn do_stage_or_unstage(
18043 &self,
18044 stage: bool,
18045 buffer_id: BufferId,
18046 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18047 cx: &mut App,
18048 ) -> Option<()> {
18049 let project = self.project()?;
18050 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18051 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18052 let buffer_snapshot = buffer.read(cx).snapshot();
18053 let file_exists = buffer_snapshot
18054 .file()
18055 .is_some_and(|file| file.disk_state().exists());
18056 diff.update(cx, |diff, cx| {
18057 diff.stage_or_unstage_hunks(
18058 stage,
18059 &hunks
18060 .map(|hunk| buffer_diff::DiffHunk {
18061 buffer_range: hunk.buffer_range,
18062 diff_base_byte_range: hunk.diff_base_byte_range,
18063 secondary_status: hunk.secondary_status,
18064 range: Point::zero()..Point::zero(), // unused
18065 })
18066 .collect::<Vec<_>>(),
18067 &buffer_snapshot,
18068 file_exists,
18069 cx,
18070 )
18071 });
18072 None
18073 }
18074
18075 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18076 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18077 self.buffer
18078 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18079 }
18080
18081 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18082 self.buffer.update(cx, |buffer, cx| {
18083 let ranges = vec![Anchor::min()..Anchor::max()];
18084 if !buffer.all_diff_hunks_expanded()
18085 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18086 {
18087 buffer.collapse_diff_hunks(ranges, cx);
18088 true
18089 } else {
18090 false
18091 }
18092 })
18093 }
18094
18095 fn toggle_diff_hunks_in_ranges(
18096 &mut self,
18097 ranges: Vec<Range<Anchor>>,
18098 cx: &mut Context<Editor>,
18099 ) {
18100 self.buffer.update(cx, |buffer, cx| {
18101 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18102 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18103 })
18104 }
18105
18106 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18107 self.buffer.update(cx, |buffer, cx| {
18108 let snapshot = buffer.snapshot(cx);
18109 let excerpt_id = range.end.excerpt_id;
18110 let point_range = range.to_point(&snapshot);
18111 let expand = !buffer.single_hunk_is_expanded(range, cx);
18112 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18113 })
18114 }
18115
18116 pub(crate) fn apply_all_diff_hunks(
18117 &mut self,
18118 _: &ApplyAllDiffHunks,
18119 window: &mut Window,
18120 cx: &mut Context<Self>,
18121 ) {
18122 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18123
18124 let buffers = self.buffer.read(cx).all_buffers();
18125 for branch_buffer in buffers {
18126 branch_buffer.update(cx, |branch_buffer, cx| {
18127 branch_buffer.merge_into_base(Vec::new(), cx);
18128 });
18129 }
18130
18131 if let Some(project) = self.project.clone() {
18132 self.save(
18133 SaveOptions {
18134 format: true,
18135 autosave: false,
18136 },
18137 project,
18138 window,
18139 cx,
18140 )
18141 .detach_and_log_err(cx);
18142 }
18143 }
18144
18145 pub(crate) fn apply_selected_diff_hunks(
18146 &mut self,
18147 _: &ApplyDiffHunk,
18148 window: &mut Window,
18149 cx: &mut Context<Self>,
18150 ) {
18151 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18152 let snapshot = self.snapshot(window, cx);
18153 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18154 let mut ranges_by_buffer = HashMap::default();
18155 self.transact(window, cx, |editor, _window, cx| {
18156 for hunk in hunks {
18157 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18158 ranges_by_buffer
18159 .entry(buffer.clone())
18160 .or_insert_with(Vec::new)
18161 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18162 }
18163 }
18164
18165 for (buffer, ranges) in ranges_by_buffer {
18166 buffer.update(cx, |buffer, cx| {
18167 buffer.merge_into_base(ranges, cx);
18168 });
18169 }
18170 });
18171
18172 if let Some(project) = self.project.clone() {
18173 self.save(
18174 SaveOptions {
18175 format: true,
18176 autosave: false,
18177 },
18178 project,
18179 window,
18180 cx,
18181 )
18182 .detach_and_log_err(cx);
18183 }
18184 }
18185
18186 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18187 if hovered != self.gutter_hovered {
18188 self.gutter_hovered = hovered;
18189 cx.notify();
18190 }
18191 }
18192
18193 pub fn insert_blocks(
18194 &mut self,
18195 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18196 autoscroll: Option<Autoscroll>,
18197 cx: &mut Context<Self>,
18198 ) -> Vec<CustomBlockId> {
18199 let blocks = self
18200 .display_map
18201 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18202 if let Some(autoscroll) = autoscroll {
18203 self.request_autoscroll(autoscroll, cx);
18204 }
18205 cx.notify();
18206 blocks
18207 }
18208
18209 pub fn resize_blocks(
18210 &mut self,
18211 heights: HashMap<CustomBlockId, u32>,
18212 autoscroll: Option<Autoscroll>,
18213 cx: &mut Context<Self>,
18214 ) {
18215 self.display_map
18216 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18217 if let Some(autoscroll) = autoscroll {
18218 self.request_autoscroll(autoscroll, cx);
18219 }
18220 cx.notify();
18221 }
18222
18223 pub fn replace_blocks(
18224 &mut self,
18225 renderers: HashMap<CustomBlockId, RenderBlock>,
18226 autoscroll: Option<Autoscroll>,
18227 cx: &mut Context<Self>,
18228 ) {
18229 self.display_map
18230 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18231 if let Some(autoscroll) = autoscroll {
18232 self.request_autoscroll(autoscroll, cx);
18233 }
18234 cx.notify();
18235 }
18236
18237 pub fn remove_blocks(
18238 &mut self,
18239 block_ids: HashSet<CustomBlockId>,
18240 autoscroll: Option<Autoscroll>,
18241 cx: &mut Context<Self>,
18242 ) {
18243 self.display_map.update(cx, |display_map, cx| {
18244 display_map.remove_blocks(block_ids, cx)
18245 });
18246 if let Some(autoscroll) = autoscroll {
18247 self.request_autoscroll(autoscroll, cx);
18248 }
18249 cx.notify();
18250 }
18251
18252 pub fn row_for_block(
18253 &self,
18254 block_id: CustomBlockId,
18255 cx: &mut Context<Self>,
18256 ) -> Option<DisplayRow> {
18257 self.display_map
18258 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18259 }
18260
18261 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18262 self.focused_block = Some(focused_block);
18263 }
18264
18265 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18266 self.focused_block.take()
18267 }
18268
18269 pub fn insert_creases(
18270 &mut self,
18271 creases: impl IntoIterator<Item = Crease<Anchor>>,
18272 cx: &mut Context<Self>,
18273 ) -> Vec<CreaseId> {
18274 self.display_map
18275 .update(cx, |map, cx| map.insert_creases(creases, cx))
18276 }
18277
18278 pub fn remove_creases(
18279 &mut self,
18280 ids: impl IntoIterator<Item = CreaseId>,
18281 cx: &mut Context<Self>,
18282 ) -> Vec<(CreaseId, Range<Anchor>)> {
18283 self.display_map
18284 .update(cx, |map, cx| map.remove_creases(ids, cx))
18285 }
18286
18287 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18288 self.display_map
18289 .update(cx, |map, cx| map.snapshot(cx))
18290 .longest_row()
18291 }
18292
18293 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18294 self.display_map
18295 .update(cx, |map, cx| map.snapshot(cx))
18296 .max_point()
18297 }
18298
18299 pub fn text(&self, cx: &App) -> String {
18300 self.buffer.read(cx).read(cx).text()
18301 }
18302
18303 pub fn is_empty(&self, cx: &App) -> bool {
18304 self.buffer.read(cx).read(cx).is_empty()
18305 }
18306
18307 pub fn text_option(&self, cx: &App) -> Option<String> {
18308 let text = self.text(cx);
18309 let text = text.trim();
18310
18311 if text.is_empty() {
18312 return None;
18313 }
18314
18315 Some(text.to_string())
18316 }
18317
18318 pub fn set_text(
18319 &mut self,
18320 text: impl Into<Arc<str>>,
18321 window: &mut Window,
18322 cx: &mut Context<Self>,
18323 ) {
18324 self.transact(window, cx, |this, _, cx| {
18325 this.buffer
18326 .read(cx)
18327 .as_singleton()
18328 .expect("you can only call set_text on editors for singleton buffers")
18329 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18330 });
18331 }
18332
18333 pub fn display_text(&self, cx: &mut App) -> String {
18334 self.display_map
18335 .update(cx, |map, cx| map.snapshot(cx))
18336 .text()
18337 }
18338
18339 fn create_minimap(
18340 &self,
18341 minimap_settings: MinimapSettings,
18342 window: &mut Window,
18343 cx: &mut Context<Self>,
18344 ) -> Option<Entity<Self>> {
18345 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18346 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18347 }
18348
18349 fn initialize_new_minimap(
18350 &self,
18351 minimap_settings: MinimapSettings,
18352 window: &mut Window,
18353 cx: &mut Context<Self>,
18354 ) -> Entity<Self> {
18355 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18356
18357 let mut minimap = Editor::new_internal(
18358 EditorMode::Minimap {
18359 parent: cx.weak_entity(),
18360 },
18361 self.buffer.clone(),
18362 None,
18363 Some(self.display_map.clone()),
18364 window,
18365 cx,
18366 );
18367 minimap.scroll_manager.clone_state(&self.scroll_manager);
18368 minimap.set_text_style_refinement(TextStyleRefinement {
18369 font_size: Some(MINIMAP_FONT_SIZE),
18370 font_weight: Some(MINIMAP_FONT_WEIGHT),
18371 ..Default::default()
18372 });
18373 minimap.update_minimap_configuration(minimap_settings, cx);
18374 cx.new(|_| minimap)
18375 }
18376
18377 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18378 let current_line_highlight = minimap_settings
18379 .current_line_highlight
18380 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18381 self.set_current_line_highlight(Some(current_line_highlight));
18382 }
18383
18384 pub fn minimap(&self) -> Option<&Entity<Self>> {
18385 self.minimap
18386 .as_ref()
18387 .filter(|_| self.minimap_visibility.visible())
18388 }
18389
18390 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18391 let mut wrap_guides = smallvec![];
18392
18393 if self.show_wrap_guides == Some(false) {
18394 return wrap_guides;
18395 }
18396
18397 let settings = self.buffer.read(cx).language_settings(cx);
18398 if settings.show_wrap_guides {
18399 match self.soft_wrap_mode(cx) {
18400 SoftWrap::Column(soft_wrap) => {
18401 wrap_guides.push((soft_wrap as usize, true));
18402 }
18403 SoftWrap::Bounded(soft_wrap) => {
18404 wrap_guides.push((soft_wrap as usize, true));
18405 }
18406 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18407 }
18408 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18409 }
18410
18411 wrap_guides
18412 }
18413
18414 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18415 let settings = self.buffer.read(cx).language_settings(cx);
18416 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18417 match mode {
18418 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18419 SoftWrap::None
18420 }
18421 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18422 language_settings::SoftWrap::PreferredLineLength => {
18423 SoftWrap::Column(settings.preferred_line_length)
18424 }
18425 language_settings::SoftWrap::Bounded => {
18426 SoftWrap::Bounded(settings.preferred_line_length)
18427 }
18428 }
18429 }
18430
18431 pub fn set_soft_wrap_mode(
18432 &mut self,
18433 mode: language_settings::SoftWrap,
18434
18435 cx: &mut Context<Self>,
18436 ) {
18437 self.soft_wrap_mode_override = Some(mode);
18438 cx.notify();
18439 }
18440
18441 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18442 self.hard_wrap = hard_wrap;
18443 cx.notify();
18444 }
18445
18446 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18447 self.text_style_refinement = Some(style);
18448 }
18449
18450 /// called by the Element so we know what style we were most recently rendered with.
18451 pub(crate) fn set_style(
18452 &mut self,
18453 style: EditorStyle,
18454 window: &mut Window,
18455 cx: &mut Context<Self>,
18456 ) {
18457 // We intentionally do not inform the display map about the minimap style
18458 // so that wrapping is not recalculated and stays consistent for the editor
18459 // and its linked minimap.
18460 if !self.mode.is_minimap() {
18461 let rem_size = window.rem_size();
18462 self.display_map.update(cx, |map, cx| {
18463 map.set_font(
18464 style.text.font(),
18465 style.text.font_size.to_pixels(rem_size),
18466 cx,
18467 )
18468 });
18469 }
18470 self.style = Some(style);
18471 }
18472
18473 pub fn style(&self) -> Option<&EditorStyle> {
18474 self.style.as_ref()
18475 }
18476
18477 // Called by the element. This method is not designed to be called outside of the editor
18478 // element's layout code because it does not notify when rewrapping is computed synchronously.
18479 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18480 self.display_map
18481 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18482 }
18483
18484 pub fn set_soft_wrap(&mut self) {
18485 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18486 }
18487
18488 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18489 if self.soft_wrap_mode_override.is_some() {
18490 self.soft_wrap_mode_override.take();
18491 } else {
18492 let soft_wrap = match self.soft_wrap_mode(cx) {
18493 SoftWrap::GitDiff => return,
18494 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18495 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18496 language_settings::SoftWrap::None
18497 }
18498 };
18499 self.soft_wrap_mode_override = Some(soft_wrap);
18500 }
18501 cx.notify();
18502 }
18503
18504 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18505 let Some(workspace) = self.workspace() else {
18506 return;
18507 };
18508 let fs = workspace.read(cx).app_state().fs.clone();
18509 let current_show = TabBarSettings::get_global(cx).show;
18510 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18511 setting.show = Some(!current_show);
18512 });
18513 }
18514
18515 pub fn toggle_indent_guides(
18516 &mut self,
18517 _: &ToggleIndentGuides,
18518 _: &mut Window,
18519 cx: &mut Context<Self>,
18520 ) {
18521 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18522 self.buffer
18523 .read(cx)
18524 .language_settings(cx)
18525 .indent_guides
18526 .enabled
18527 });
18528 self.show_indent_guides = Some(!currently_enabled);
18529 cx.notify();
18530 }
18531
18532 fn should_show_indent_guides(&self) -> Option<bool> {
18533 self.show_indent_guides
18534 }
18535
18536 pub fn toggle_line_numbers(
18537 &mut self,
18538 _: &ToggleLineNumbers,
18539 _: &mut Window,
18540 cx: &mut Context<Self>,
18541 ) {
18542 let mut editor_settings = EditorSettings::get_global(cx).clone();
18543 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18544 EditorSettings::override_global(editor_settings, cx);
18545 }
18546
18547 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18548 if let Some(show_line_numbers) = self.show_line_numbers {
18549 return show_line_numbers;
18550 }
18551 EditorSettings::get_global(cx).gutter.line_numbers
18552 }
18553
18554 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18555 self.use_relative_line_numbers
18556 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18557 }
18558
18559 pub fn toggle_relative_line_numbers(
18560 &mut self,
18561 _: &ToggleRelativeLineNumbers,
18562 _: &mut Window,
18563 cx: &mut Context<Self>,
18564 ) {
18565 let is_relative = self.should_use_relative_line_numbers(cx);
18566 self.set_relative_line_number(Some(!is_relative), cx)
18567 }
18568
18569 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18570 self.use_relative_line_numbers = is_relative;
18571 cx.notify();
18572 }
18573
18574 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18575 self.show_gutter = show_gutter;
18576 cx.notify();
18577 }
18578
18579 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18580 self.show_scrollbars = ScrollbarAxes {
18581 horizontal: show,
18582 vertical: show,
18583 };
18584 cx.notify();
18585 }
18586
18587 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18588 self.show_scrollbars.vertical = show;
18589 cx.notify();
18590 }
18591
18592 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18593 self.show_scrollbars.horizontal = show;
18594 cx.notify();
18595 }
18596
18597 pub fn set_minimap_visibility(
18598 &mut self,
18599 minimap_visibility: MinimapVisibility,
18600 window: &mut Window,
18601 cx: &mut Context<Self>,
18602 ) {
18603 if self.minimap_visibility != minimap_visibility {
18604 if minimap_visibility.visible() && self.minimap.is_none() {
18605 let minimap_settings = EditorSettings::get_global(cx).minimap;
18606 self.minimap =
18607 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18608 }
18609 self.minimap_visibility = minimap_visibility;
18610 cx.notify();
18611 }
18612 }
18613
18614 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18615 self.set_show_scrollbars(false, cx);
18616 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18617 }
18618
18619 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18620 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18621 }
18622
18623 /// Normally the text in full mode and auto height editors is padded on the
18624 /// left side by roughly half a character width for improved hit testing.
18625 ///
18626 /// Use this method to disable this for cases where this is not wanted (e.g.
18627 /// if you want to align the editor text with some other text above or below)
18628 /// or if you want to add this padding to single-line editors.
18629 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18630 self.offset_content = offset_content;
18631 cx.notify();
18632 }
18633
18634 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18635 self.show_line_numbers = Some(show_line_numbers);
18636 cx.notify();
18637 }
18638
18639 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18640 self.disable_expand_excerpt_buttons = true;
18641 cx.notify();
18642 }
18643
18644 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18645 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18646 cx.notify();
18647 }
18648
18649 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18650 self.show_code_actions = Some(show_code_actions);
18651 cx.notify();
18652 }
18653
18654 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18655 self.show_runnables = Some(show_runnables);
18656 cx.notify();
18657 }
18658
18659 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18660 self.show_breakpoints = Some(show_breakpoints);
18661 cx.notify();
18662 }
18663
18664 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18665 if self.display_map.read(cx).masked != masked {
18666 self.display_map.update(cx, |map, _| map.masked = masked);
18667 }
18668 cx.notify()
18669 }
18670
18671 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18672 self.show_wrap_guides = Some(show_wrap_guides);
18673 cx.notify();
18674 }
18675
18676 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18677 self.show_indent_guides = Some(show_indent_guides);
18678 cx.notify();
18679 }
18680
18681 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18682 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18683 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18684 && let Some(dir) = file.abs_path(cx).parent()
18685 {
18686 return Some(dir.to_owned());
18687 }
18688
18689 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18690 return Some(project_path.path.to_path_buf());
18691 }
18692 }
18693
18694 None
18695 }
18696
18697 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18698 self.active_excerpt(cx)?
18699 .1
18700 .read(cx)
18701 .file()
18702 .and_then(|f| f.as_local())
18703 }
18704
18705 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18706 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18707 let buffer = buffer.read(cx);
18708 if let Some(project_path) = buffer.project_path(cx) {
18709 let project = self.project()?.read(cx);
18710 project.absolute_path(&project_path, cx)
18711 } else {
18712 buffer
18713 .file()
18714 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18715 }
18716 })
18717 }
18718
18719 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18720 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18721 let project_path = buffer.read(cx).project_path(cx)?;
18722 let project = self.project()?.read(cx);
18723 let entry = project.entry_for_path(&project_path, cx)?;
18724 let path = entry.path.to_path_buf();
18725 Some(path)
18726 })
18727 }
18728
18729 pub fn reveal_in_finder(
18730 &mut self,
18731 _: &RevealInFileManager,
18732 _window: &mut Window,
18733 cx: &mut Context<Self>,
18734 ) {
18735 if let Some(target) = self.target_file(cx) {
18736 cx.reveal_path(&target.abs_path(cx));
18737 }
18738 }
18739
18740 pub fn copy_path(
18741 &mut self,
18742 _: &zed_actions::workspace::CopyPath,
18743 _window: &mut Window,
18744 cx: &mut Context<Self>,
18745 ) {
18746 if let Some(path) = self.target_file_abs_path(cx)
18747 && let Some(path) = path.to_str()
18748 {
18749 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18750 }
18751 }
18752
18753 pub fn copy_relative_path(
18754 &mut self,
18755 _: &zed_actions::workspace::CopyRelativePath,
18756 _window: &mut Window,
18757 cx: &mut Context<Self>,
18758 ) {
18759 if let Some(path) = self.target_file_path(cx)
18760 && let Some(path) = path.to_str()
18761 {
18762 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18763 }
18764 }
18765
18766 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18767 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18768 buffer.read(cx).project_path(cx)
18769 } else {
18770 None
18771 }
18772 }
18773
18774 // Returns true if the editor handled a go-to-line request
18775 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18776 maybe!({
18777 let breakpoint_store = self.breakpoint_store.as_ref()?;
18778
18779 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18780 else {
18781 self.clear_row_highlights::<ActiveDebugLine>();
18782 return None;
18783 };
18784
18785 let position = active_stack_frame.position;
18786 let buffer_id = position.buffer_id?;
18787 let snapshot = self
18788 .project
18789 .as_ref()?
18790 .read(cx)
18791 .buffer_for_id(buffer_id, cx)?
18792 .read(cx)
18793 .snapshot();
18794
18795 let mut handled = false;
18796 for (id, ExcerptRange { context, .. }) in
18797 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18798 {
18799 if context.start.cmp(&position, &snapshot).is_ge()
18800 || context.end.cmp(&position, &snapshot).is_lt()
18801 {
18802 continue;
18803 }
18804 let snapshot = self.buffer.read(cx).snapshot(cx);
18805 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18806
18807 handled = true;
18808 self.clear_row_highlights::<ActiveDebugLine>();
18809
18810 self.go_to_line::<ActiveDebugLine>(
18811 multibuffer_anchor,
18812 Some(cx.theme().colors().editor_debugger_active_line_background),
18813 window,
18814 cx,
18815 );
18816
18817 cx.notify();
18818 }
18819
18820 handled.then_some(())
18821 })
18822 .is_some()
18823 }
18824
18825 pub fn copy_file_name_without_extension(
18826 &mut self,
18827 _: &CopyFileNameWithoutExtension,
18828 _: &mut Window,
18829 cx: &mut Context<Self>,
18830 ) {
18831 if let Some(file) = self.target_file(cx)
18832 && let Some(file_stem) = file.path().file_stem()
18833 && let Some(name) = file_stem.to_str()
18834 {
18835 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18836 }
18837 }
18838
18839 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18840 if let Some(file) = self.target_file(cx)
18841 && let Some(file_name) = file.path().file_name()
18842 && let Some(name) = file_name.to_str()
18843 {
18844 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18845 }
18846 }
18847
18848 pub fn toggle_git_blame(
18849 &mut self,
18850 _: &::git::Blame,
18851 window: &mut Window,
18852 cx: &mut Context<Self>,
18853 ) {
18854 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18855
18856 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18857 self.start_git_blame(true, window, cx);
18858 }
18859
18860 cx.notify();
18861 }
18862
18863 pub fn toggle_git_blame_inline(
18864 &mut self,
18865 _: &ToggleGitBlameInline,
18866 window: &mut Window,
18867 cx: &mut Context<Self>,
18868 ) {
18869 self.toggle_git_blame_inline_internal(true, window, cx);
18870 cx.notify();
18871 }
18872
18873 pub fn open_git_blame_commit(
18874 &mut self,
18875 _: &OpenGitBlameCommit,
18876 window: &mut Window,
18877 cx: &mut Context<Self>,
18878 ) {
18879 self.open_git_blame_commit_internal(window, cx);
18880 }
18881
18882 fn open_git_blame_commit_internal(
18883 &mut self,
18884 window: &mut Window,
18885 cx: &mut Context<Self>,
18886 ) -> Option<()> {
18887 let blame = self.blame.as_ref()?;
18888 let snapshot = self.snapshot(window, cx);
18889 let cursor = self.selections.newest::<Point>(cx).head();
18890 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18891 let blame_entry = blame
18892 .update(cx, |blame, cx| {
18893 blame
18894 .blame_for_rows(
18895 &[RowInfo {
18896 buffer_id: Some(buffer.remote_id()),
18897 buffer_row: Some(point.row),
18898 ..Default::default()
18899 }],
18900 cx,
18901 )
18902 .next()
18903 })
18904 .flatten()?;
18905 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18906 let repo = blame.read(cx).repository(cx)?;
18907 let workspace = self.workspace()?.downgrade();
18908 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18909 None
18910 }
18911
18912 pub fn git_blame_inline_enabled(&self) -> bool {
18913 self.git_blame_inline_enabled
18914 }
18915
18916 pub fn toggle_selection_menu(
18917 &mut self,
18918 _: &ToggleSelectionMenu,
18919 _: &mut Window,
18920 cx: &mut Context<Self>,
18921 ) {
18922 self.show_selection_menu = self
18923 .show_selection_menu
18924 .map(|show_selections_menu| !show_selections_menu)
18925 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18926
18927 cx.notify();
18928 }
18929
18930 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18931 self.show_selection_menu
18932 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18933 }
18934
18935 fn start_git_blame(
18936 &mut self,
18937 user_triggered: bool,
18938 window: &mut Window,
18939 cx: &mut Context<Self>,
18940 ) {
18941 if let Some(project) = self.project() {
18942 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18943 return;
18944 };
18945
18946 if buffer.read(cx).file().is_none() {
18947 return;
18948 }
18949
18950 let focused = self.focus_handle(cx).contains_focused(window, cx);
18951
18952 let project = project.clone();
18953 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18954 self.blame_subscription =
18955 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18956 self.blame = Some(blame);
18957 }
18958 }
18959
18960 fn toggle_git_blame_inline_internal(
18961 &mut self,
18962 user_triggered: bool,
18963 window: &mut Window,
18964 cx: &mut Context<Self>,
18965 ) {
18966 if self.git_blame_inline_enabled {
18967 self.git_blame_inline_enabled = false;
18968 self.show_git_blame_inline = false;
18969 self.show_git_blame_inline_delay_task.take();
18970 } else {
18971 self.git_blame_inline_enabled = true;
18972 self.start_git_blame_inline(user_triggered, window, cx);
18973 }
18974
18975 cx.notify();
18976 }
18977
18978 fn start_git_blame_inline(
18979 &mut self,
18980 user_triggered: bool,
18981 window: &mut Window,
18982 cx: &mut Context<Self>,
18983 ) {
18984 self.start_git_blame(user_triggered, window, cx);
18985
18986 if ProjectSettings::get_global(cx)
18987 .git
18988 .inline_blame_delay()
18989 .is_some()
18990 {
18991 self.start_inline_blame_timer(window, cx);
18992 } else {
18993 self.show_git_blame_inline = true
18994 }
18995 }
18996
18997 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18998 self.blame.as_ref()
18999 }
19000
19001 pub fn show_git_blame_gutter(&self) -> bool {
19002 self.show_git_blame_gutter
19003 }
19004
19005 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19006 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19007 }
19008
19009 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19010 self.show_git_blame_inline
19011 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19012 && !self.newest_selection_head_on_empty_line(cx)
19013 && self.has_blame_entries(cx)
19014 }
19015
19016 fn has_blame_entries(&self, cx: &App) -> bool {
19017 self.blame()
19018 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19019 }
19020
19021 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19022 let cursor_anchor = self.selections.newest_anchor().head();
19023
19024 let snapshot = self.buffer.read(cx).snapshot(cx);
19025 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19026
19027 snapshot.line_len(buffer_row) == 0
19028 }
19029
19030 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19031 let buffer_and_selection = maybe!({
19032 let selection = self.selections.newest::<Point>(cx);
19033 let selection_range = selection.range();
19034
19035 let multi_buffer = self.buffer().read(cx);
19036 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19037 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19038
19039 let (buffer, range, _) = if selection.reversed {
19040 buffer_ranges.first()
19041 } else {
19042 buffer_ranges.last()
19043 }?;
19044
19045 let selection = text::ToPoint::to_point(&range.start, buffer).row
19046 ..text::ToPoint::to_point(&range.end, buffer).row;
19047 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19048 });
19049
19050 let Some((buffer, selection)) = buffer_and_selection else {
19051 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19052 };
19053
19054 let Some(project) = self.project() else {
19055 return Task::ready(Err(anyhow!("editor does not have project")));
19056 };
19057
19058 project.update(cx, |project, cx| {
19059 project.get_permalink_to_line(&buffer, selection, cx)
19060 })
19061 }
19062
19063 pub fn copy_permalink_to_line(
19064 &mut self,
19065 _: &CopyPermalinkToLine,
19066 window: &mut Window,
19067 cx: &mut Context<Self>,
19068 ) {
19069 let permalink_task = self.get_permalink_to_line(cx);
19070 let workspace = self.workspace();
19071
19072 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19073 Ok(permalink) => {
19074 cx.update(|_, cx| {
19075 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19076 })
19077 .ok();
19078 }
19079 Err(err) => {
19080 let message = format!("Failed to copy permalink: {err}");
19081
19082 anyhow::Result::<()>::Err(err).log_err();
19083
19084 if let Some(workspace) = workspace {
19085 workspace
19086 .update_in(cx, |workspace, _, cx| {
19087 struct CopyPermalinkToLine;
19088
19089 workspace.show_toast(
19090 Toast::new(
19091 NotificationId::unique::<CopyPermalinkToLine>(),
19092 message,
19093 ),
19094 cx,
19095 )
19096 })
19097 .ok();
19098 }
19099 }
19100 })
19101 .detach();
19102 }
19103
19104 pub fn copy_file_location(
19105 &mut self,
19106 _: &CopyFileLocation,
19107 _: &mut Window,
19108 cx: &mut Context<Self>,
19109 ) {
19110 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19111 if let Some(file) = self.target_file(cx)
19112 && let Some(path) = file.path().to_str()
19113 {
19114 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19115 }
19116 }
19117
19118 pub fn open_permalink_to_line(
19119 &mut self,
19120 _: &OpenPermalinkToLine,
19121 window: &mut Window,
19122 cx: &mut Context<Self>,
19123 ) {
19124 let permalink_task = self.get_permalink_to_line(cx);
19125 let workspace = self.workspace();
19126
19127 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19128 Ok(permalink) => {
19129 cx.update(|_, cx| {
19130 cx.open_url(permalink.as_ref());
19131 })
19132 .ok();
19133 }
19134 Err(err) => {
19135 let message = format!("Failed to open permalink: {err}");
19136
19137 anyhow::Result::<()>::Err(err).log_err();
19138
19139 if let Some(workspace) = workspace {
19140 workspace
19141 .update(cx, |workspace, cx| {
19142 struct OpenPermalinkToLine;
19143
19144 workspace.show_toast(
19145 Toast::new(
19146 NotificationId::unique::<OpenPermalinkToLine>(),
19147 message,
19148 ),
19149 cx,
19150 )
19151 })
19152 .ok();
19153 }
19154 }
19155 })
19156 .detach();
19157 }
19158
19159 pub fn insert_uuid_v4(
19160 &mut self,
19161 _: &InsertUuidV4,
19162 window: &mut Window,
19163 cx: &mut Context<Self>,
19164 ) {
19165 self.insert_uuid(UuidVersion::V4, window, cx);
19166 }
19167
19168 pub fn insert_uuid_v7(
19169 &mut self,
19170 _: &InsertUuidV7,
19171 window: &mut Window,
19172 cx: &mut Context<Self>,
19173 ) {
19174 self.insert_uuid(UuidVersion::V7, window, cx);
19175 }
19176
19177 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19178 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19179 self.transact(window, cx, |this, window, cx| {
19180 let edits = this
19181 .selections
19182 .all::<Point>(cx)
19183 .into_iter()
19184 .map(|selection| {
19185 let uuid = match version {
19186 UuidVersion::V4 => uuid::Uuid::new_v4(),
19187 UuidVersion::V7 => uuid::Uuid::now_v7(),
19188 };
19189
19190 (selection.range(), uuid.to_string())
19191 });
19192 this.edit(edits, cx);
19193 this.refresh_edit_prediction(true, false, window, cx);
19194 });
19195 }
19196
19197 pub fn open_selections_in_multibuffer(
19198 &mut self,
19199 _: &OpenSelectionsInMultibuffer,
19200 window: &mut Window,
19201 cx: &mut Context<Self>,
19202 ) {
19203 let multibuffer = self.buffer.read(cx);
19204
19205 let Some(buffer) = multibuffer.as_singleton() else {
19206 return;
19207 };
19208
19209 let Some(workspace) = self.workspace() else {
19210 return;
19211 };
19212
19213 let title = multibuffer.title(cx).to_string();
19214
19215 let locations = self
19216 .selections
19217 .all_anchors(cx)
19218 .iter()
19219 .map(|selection| Location {
19220 buffer: buffer.clone(),
19221 range: selection.start.text_anchor..selection.end.text_anchor,
19222 })
19223 .collect::<Vec<_>>();
19224
19225 cx.spawn_in(window, async move |_, cx| {
19226 workspace.update_in(cx, |workspace, window, cx| {
19227 Self::open_locations_in_multibuffer(
19228 workspace,
19229 locations,
19230 format!("Selections for '{title}'"),
19231 false,
19232 MultibufferSelectionMode::All,
19233 window,
19234 cx,
19235 );
19236 })
19237 })
19238 .detach();
19239 }
19240
19241 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19242 /// last highlight added will be used.
19243 ///
19244 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19245 pub fn highlight_rows<T: 'static>(
19246 &mut self,
19247 range: Range<Anchor>,
19248 color: Hsla,
19249 options: RowHighlightOptions,
19250 cx: &mut Context<Self>,
19251 ) {
19252 let snapshot = self.buffer().read(cx).snapshot(cx);
19253 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19254 let ix = row_highlights.binary_search_by(|highlight| {
19255 Ordering::Equal
19256 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19257 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19258 });
19259
19260 if let Err(mut ix) = ix {
19261 let index = post_inc(&mut self.highlight_order);
19262
19263 // If this range intersects with the preceding highlight, then merge it with
19264 // the preceding highlight. Otherwise insert a new highlight.
19265 let mut merged = false;
19266 if ix > 0 {
19267 let prev_highlight = &mut row_highlights[ix - 1];
19268 if prev_highlight
19269 .range
19270 .end
19271 .cmp(&range.start, &snapshot)
19272 .is_ge()
19273 {
19274 ix -= 1;
19275 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19276 prev_highlight.range.end = range.end;
19277 }
19278 merged = true;
19279 prev_highlight.index = index;
19280 prev_highlight.color = color;
19281 prev_highlight.options = options;
19282 }
19283 }
19284
19285 if !merged {
19286 row_highlights.insert(
19287 ix,
19288 RowHighlight {
19289 range,
19290 index,
19291 color,
19292 options,
19293 type_id: TypeId::of::<T>(),
19294 },
19295 );
19296 }
19297
19298 // If any of the following highlights intersect with this one, merge them.
19299 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19300 let highlight = &row_highlights[ix];
19301 if next_highlight
19302 .range
19303 .start
19304 .cmp(&highlight.range.end, &snapshot)
19305 .is_le()
19306 {
19307 if next_highlight
19308 .range
19309 .end
19310 .cmp(&highlight.range.end, &snapshot)
19311 .is_gt()
19312 {
19313 row_highlights[ix].range.end = next_highlight.range.end;
19314 }
19315 row_highlights.remove(ix + 1);
19316 } else {
19317 break;
19318 }
19319 }
19320 }
19321 }
19322
19323 /// Remove any highlighted row ranges of the given type that intersect the
19324 /// given ranges.
19325 pub fn remove_highlighted_rows<T: 'static>(
19326 &mut self,
19327 ranges_to_remove: Vec<Range<Anchor>>,
19328 cx: &mut Context<Self>,
19329 ) {
19330 let snapshot = self.buffer().read(cx).snapshot(cx);
19331 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19332 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19333 row_highlights.retain(|highlight| {
19334 while let Some(range_to_remove) = ranges_to_remove.peek() {
19335 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19336 Ordering::Less | Ordering::Equal => {
19337 ranges_to_remove.next();
19338 }
19339 Ordering::Greater => {
19340 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19341 Ordering::Less | Ordering::Equal => {
19342 return false;
19343 }
19344 Ordering::Greater => break,
19345 }
19346 }
19347 }
19348 }
19349
19350 true
19351 })
19352 }
19353
19354 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19355 pub fn clear_row_highlights<T: 'static>(&mut self) {
19356 self.highlighted_rows.remove(&TypeId::of::<T>());
19357 }
19358
19359 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19360 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19361 self.highlighted_rows
19362 .get(&TypeId::of::<T>())
19363 .map_or(&[] as &[_], |vec| vec.as_slice())
19364 .iter()
19365 .map(|highlight| (highlight.range.clone(), highlight.color))
19366 }
19367
19368 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19369 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19370 /// Allows to ignore certain kinds of highlights.
19371 pub fn highlighted_display_rows(
19372 &self,
19373 window: &mut Window,
19374 cx: &mut App,
19375 ) -> BTreeMap<DisplayRow, LineHighlight> {
19376 let snapshot = self.snapshot(window, cx);
19377 let mut used_highlight_orders = HashMap::default();
19378 self.highlighted_rows
19379 .iter()
19380 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19381 .fold(
19382 BTreeMap::<DisplayRow, LineHighlight>::new(),
19383 |mut unique_rows, highlight| {
19384 let start = highlight.range.start.to_display_point(&snapshot);
19385 let end = highlight.range.end.to_display_point(&snapshot);
19386 let start_row = start.row().0;
19387 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19388 && end.column() == 0
19389 {
19390 end.row().0.saturating_sub(1)
19391 } else {
19392 end.row().0
19393 };
19394 for row in start_row..=end_row {
19395 let used_index =
19396 used_highlight_orders.entry(row).or_insert(highlight.index);
19397 if highlight.index >= *used_index {
19398 *used_index = highlight.index;
19399 unique_rows.insert(
19400 DisplayRow(row),
19401 LineHighlight {
19402 include_gutter: highlight.options.include_gutter,
19403 border: None,
19404 background: highlight.color.into(),
19405 type_id: Some(highlight.type_id),
19406 },
19407 );
19408 }
19409 }
19410 unique_rows
19411 },
19412 )
19413 }
19414
19415 pub fn highlighted_display_row_for_autoscroll(
19416 &self,
19417 snapshot: &DisplaySnapshot,
19418 ) -> Option<DisplayRow> {
19419 self.highlighted_rows
19420 .values()
19421 .flat_map(|highlighted_rows| highlighted_rows.iter())
19422 .filter_map(|highlight| {
19423 if highlight.options.autoscroll {
19424 Some(highlight.range.start.to_display_point(snapshot).row())
19425 } else {
19426 None
19427 }
19428 })
19429 .min()
19430 }
19431
19432 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19433 self.highlight_background::<SearchWithinRange>(
19434 ranges,
19435 |colors| colors.colors().editor_document_highlight_read_background,
19436 cx,
19437 )
19438 }
19439
19440 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19441 self.breadcrumb_header = Some(new_header);
19442 }
19443
19444 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19445 self.clear_background_highlights::<SearchWithinRange>(cx);
19446 }
19447
19448 pub fn highlight_background<T: 'static>(
19449 &mut self,
19450 ranges: &[Range<Anchor>],
19451 color_fetcher: fn(&Theme) -> Hsla,
19452 cx: &mut Context<Self>,
19453 ) {
19454 self.background_highlights.insert(
19455 HighlightKey::Type(TypeId::of::<T>()),
19456 (color_fetcher, Arc::from(ranges)),
19457 );
19458 self.scrollbar_marker_state.dirty = true;
19459 cx.notify();
19460 }
19461
19462 pub fn highlight_background_key<T: 'static>(
19463 &mut self,
19464 key: usize,
19465 ranges: &[Range<Anchor>],
19466 color_fetcher: fn(&Theme) -> Hsla,
19467 cx: &mut Context<Self>,
19468 ) {
19469 self.background_highlights.insert(
19470 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19471 (color_fetcher, Arc::from(ranges)),
19472 );
19473 self.scrollbar_marker_state.dirty = true;
19474 cx.notify();
19475 }
19476
19477 pub fn clear_background_highlights<T: 'static>(
19478 &mut self,
19479 cx: &mut Context<Self>,
19480 ) -> Option<BackgroundHighlight> {
19481 let text_highlights = self
19482 .background_highlights
19483 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19484 if !text_highlights.1.is_empty() {
19485 self.scrollbar_marker_state.dirty = true;
19486 cx.notify();
19487 }
19488 Some(text_highlights)
19489 }
19490
19491 pub fn highlight_gutter<T: 'static>(
19492 &mut self,
19493 ranges: impl Into<Vec<Range<Anchor>>>,
19494 color_fetcher: fn(&App) -> Hsla,
19495 cx: &mut Context<Self>,
19496 ) {
19497 self.gutter_highlights
19498 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19499 cx.notify();
19500 }
19501
19502 pub fn clear_gutter_highlights<T: 'static>(
19503 &mut self,
19504 cx: &mut Context<Self>,
19505 ) -> Option<GutterHighlight> {
19506 cx.notify();
19507 self.gutter_highlights.remove(&TypeId::of::<T>())
19508 }
19509
19510 pub fn insert_gutter_highlight<T: 'static>(
19511 &mut self,
19512 range: Range<Anchor>,
19513 color_fetcher: fn(&App) -> Hsla,
19514 cx: &mut Context<Self>,
19515 ) {
19516 let snapshot = self.buffer().read(cx).snapshot(cx);
19517 let mut highlights = self
19518 .gutter_highlights
19519 .remove(&TypeId::of::<T>())
19520 .map(|(_, highlights)| highlights)
19521 .unwrap_or_default();
19522 let ix = highlights.binary_search_by(|highlight| {
19523 Ordering::Equal
19524 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19525 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19526 });
19527 if let Err(ix) = ix {
19528 highlights.insert(ix, range);
19529 }
19530 self.gutter_highlights
19531 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19532 }
19533
19534 pub fn remove_gutter_highlights<T: 'static>(
19535 &mut self,
19536 ranges_to_remove: Vec<Range<Anchor>>,
19537 cx: &mut Context<Self>,
19538 ) {
19539 let snapshot = self.buffer().read(cx).snapshot(cx);
19540 let Some((color_fetcher, mut gutter_highlights)) =
19541 self.gutter_highlights.remove(&TypeId::of::<T>())
19542 else {
19543 return;
19544 };
19545 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19546 gutter_highlights.retain(|highlight| {
19547 while let Some(range_to_remove) = ranges_to_remove.peek() {
19548 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19549 Ordering::Less | Ordering::Equal => {
19550 ranges_to_remove.next();
19551 }
19552 Ordering::Greater => {
19553 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19554 Ordering::Less | Ordering::Equal => {
19555 return false;
19556 }
19557 Ordering::Greater => break,
19558 }
19559 }
19560 }
19561 }
19562
19563 true
19564 });
19565 self.gutter_highlights
19566 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19567 }
19568
19569 #[cfg(feature = "test-support")]
19570 pub fn all_text_highlights(
19571 &self,
19572 window: &mut Window,
19573 cx: &mut Context<Self>,
19574 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19575 let snapshot = self.snapshot(window, cx);
19576 self.display_map.update(cx, |display_map, _| {
19577 display_map
19578 .all_text_highlights()
19579 .map(|highlight| {
19580 let (style, ranges) = highlight.as_ref();
19581 (
19582 *style,
19583 ranges
19584 .iter()
19585 .map(|range| range.clone().to_display_points(&snapshot))
19586 .collect(),
19587 )
19588 })
19589 .collect()
19590 })
19591 }
19592
19593 #[cfg(feature = "test-support")]
19594 pub fn all_text_background_highlights(
19595 &self,
19596 window: &mut Window,
19597 cx: &mut Context<Self>,
19598 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19599 let snapshot = self.snapshot(window, cx);
19600 let buffer = &snapshot.buffer_snapshot;
19601 let start = buffer.anchor_before(0);
19602 let end = buffer.anchor_after(buffer.len());
19603 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19604 }
19605
19606 #[cfg(feature = "test-support")]
19607 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19608 let snapshot = self.buffer().read(cx).snapshot(cx);
19609
19610 let highlights = self
19611 .background_highlights
19612 .get(&HighlightKey::Type(TypeId::of::<
19613 items::BufferSearchHighlights,
19614 >()));
19615
19616 if let Some((_color, ranges)) = highlights {
19617 ranges
19618 .iter()
19619 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19620 .collect_vec()
19621 } else {
19622 vec![]
19623 }
19624 }
19625
19626 fn document_highlights_for_position<'a>(
19627 &'a self,
19628 position: Anchor,
19629 buffer: &'a MultiBufferSnapshot,
19630 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19631 let read_highlights = self
19632 .background_highlights
19633 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19634 .map(|h| &h.1);
19635 let write_highlights = self
19636 .background_highlights
19637 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19638 .map(|h| &h.1);
19639 let left_position = position.bias_left(buffer);
19640 let right_position = position.bias_right(buffer);
19641 read_highlights
19642 .into_iter()
19643 .chain(write_highlights)
19644 .flat_map(move |ranges| {
19645 let start_ix = match ranges.binary_search_by(|probe| {
19646 let cmp = probe.end.cmp(&left_position, buffer);
19647 if cmp.is_ge() {
19648 Ordering::Greater
19649 } else {
19650 Ordering::Less
19651 }
19652 }) {
19653 Ok(i) | Err(i) => i,
19654 };
19655
19656 ranges[start_ix..]
19657 .iter()
19658 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19659 })
19660 }
19661
19662 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19663 self.background_highlights
19664 .get(&HighlightKey::Type(TypeId::of::<T>()))
19665 .is_some_and(|(_, highlights)| !highlights.is_empty())
19666 }
19667
19668 pub fn background_highlights_in_range(
19669 &self,
19670 search_range: Range<Anchor>,
19671 display_snapshot: &DisplaySnapshot,
19672 theme: &Theme,
19673 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19674 let mut results = Vec::new();
19675 for (color_fetcher, ranges) in self.background_highlights.values() {
19676 let color = color_fetcher(theme);
19677 let start_ix = match ranges.binary_search_by(|probe| {
19678 let cmp = probe
19679 .end
19680 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19681 if cmp.is_gt() {
19682 Ordering::Greater
19683 } else {
19684 Ordering::Less
19685 }
19686 }) {
19687 Ok(i) | Err(i) => i,
19688 };
19689 for range in &ranges[start_ix..] {
19690 if range
19691 .start
19692 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19693 .is_ge()
19694 {
19695 break;
19696 }
19697
19698 let start = range.start.to_display_point(display_snapshot);
19699 let end = range.end.to_display_point(display_snapshot);
19700 results.push((start..end, color))
19701 }
19702 }
19703 results
19704 }
19705
19706 pub fn background_highlight_row_ranges<T: 'static>(
19707 &self,
19708 search_range: Range<Anchor>,
19709 display_snapshot: &DisplaySnapshot,
19710 count: usize,
19711 ) -> Vec<RangeInclusive<DisplayPoint>> {
19712 let mut results = Vec::new();
19713 let Some((_, ranges)) = self
19714 .background_highlights
19715 .get(&HighlightKey::Type(TypeId::of::<T>()))
19716 else {
19717 return vec![];
19718 };
19719
19720 let start_ix = match ranges.binary_search_by(|probe| {
19721 let cmp = probe
19722 .end
19723 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19724 if cmp.is_gt() {
19725 Ordering::Greater
19726 } else {
19727 Ordering::Less
19728 }
19729 }) {
19730 Ok(i) | Err(i) => i,
19731 };
19732 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19733 if let (Some(start_display), Some(end_display)) = (start, end) {
19734 results.push(
19735 start_display.to_display_point(display_snapshot)
19736 ..=end_display.to_display_point(display_snapshot),
19737 );
19738 }
19739 };
19740 let mut start_row: Option<Point> = None;
19741 let mut end_row: Option<Point> = None;
19742 if ranges.len() > count {
19743 return Vec::new();
19744 }
19745 for range in &ranges[start_ix..] {
19746 if range
19747 .start
19748 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19749 .is_ge()
19750 {
19751 break;
19752 }
19753 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19754 if let Some(current_row) = &end_row
19755 && end.row == current_row.row
19756 {
19757 continue;
19758 }
19759 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19760 if start_row.is_none() {
19761 assert_eq!(end_row, None);
19762 start_row = Some(start);
19763 end_row = Some(end);
19764 continue;
19765 }
19766 if let Some(current_end) = end_row.as_mut() {
19767 if start.row > current_end.row + 1 {
19768 push_region(start_row, end_row);
19769 start_row = Some(start);
19770 end_row = Some(end);
19771 } else {
19772 // Merge two hunks.
19773 *current_end = end;
19774 }
19775 } else {
19776 unreachable!();
19777 }
19778 }
19779 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19780 push_region(start_row, end_row);
19781 results
19782 }
19783
19784 pub fn gutter_highlights_in_range(
19785 &self,
19786 search_range: Range<Anchor>,
19787 display_snapshot: &DisplaySnapshot,
19788 cx: &App,
19789 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19790 let mut results = Vec::new();
19791 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19792 let color = color_fetcher(cx);
19793 let start_ix = match ranges.binary_search_by(|probe| {
19794 let cmp = probe
19795 .end
19796 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19797 if cmp.is_gt() {
19798 Ordering::Greater
19799 } else {
19800 Ordering::Less
19801 }
19802 }) {
19803 Ok(i) | Err(i) => i,
19804 };
19805 for range in &ranges[start_ix..] {
19806 if range
19807 .start
19808 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19809 .is_ge()
19810 {
19811 break;
19812 }
19813
19814 let start = range.start.to_display_point(display_snapshot);
19815 let end = range.end.to_display_point(display_snapshot);
19816 results.push((start..end, color))
19817 }
19818 }
19819 results
19820 }
19821
19822 /// Get the text ranges corresponding to the redaction query
19823 pub fn redacted_ranges(
19824 &self,
19825 search_range: Range<Anchor>,
19826 display_snapshot: &DisplaySnapshot,
19827 cx: &App,
19828 ) -> Vec<Range<DisplayPoint>> {
19829 display_snapshot
19830 .buffer_snapshot
19831 .redacted_ranges(search_range, |file| {
19832 if let Some(file) = file {
19833 file.is_private()
19834 && EditorSettings::get(
19835 Some(SettingsLocation {
19836 worktree_id: file.worktree_id(cx),
19837 path: file.path().as_ref(),
19838 }),
19839 cx,
19840 )
19841 .redact_private_values
19842 } else {
19843 false
19844 }
19845 })
19846 .map(|range| {
19847 range.start.to_display_point(display_snapshot)
19848 ..range.end.to_display_point(display_snapshot)
19849 })
19850 .collect()
19851 }
19852
19853 pub fn highlight_text_key<T: 'static>(
19854 &mut self,
19855 key: usize,
19856 ranges: Vec<Range<Anchor>>,
19857 style: HighlightStyle,
19858 cx: &mut Context<Self>,
19859 ) {
19860 self.display_map.update(cx, |map, _| {
19861 map.highlight_text(
19862 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19863 ranges,
19864 style,
19865 );
19866 });
19867 cx.notify();
19868 }
19869
19870 pub fn highlight_text<T: 'static>(
19871 &mut self,
19872 ranges: Vec<Range<Anchor>>,
19873 style: HighlightStyle,
19874 cx: &mut Context<Self>,
19875 ) {
19876 self.display_map.update(cx, |map, _| {
19877 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19878 });
19879 cx.notify();
19880 }
19881
19882 pub(crate) fn highlight_inlays<T: 'static>(
19883 &mut self,
19884 highlights: Vec<InlayHighlight>,
19885 style: HighlightStyle,
19886 cx: &mut Context<Self>,
19887 ) {
19888 self.display_map.update(cx, |map, _| {
19889 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19890 });
19891 cx.notify();
19892 }
19893
19894 pub fn text_highlights<'a, T: 'static>(
19895 &'a self,
19896 cx: &'a App,
19897 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19898 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19899 }
19900
19901 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19902 let cleared = self
19903 .display_map
19904 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19905 if cleared {
19906 cx.notify();
19907 }
19908 }
19909
19910 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19911 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19912 && self.focus_handle.is_focused(window)
19913 }
19914
19915 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19916 self.show_cursor_when_unfocused = is_enabled;
19917 cx.notify();
19918 }
19919
19920 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19921 cx.notify();
19922 }
19923
19924 fn on_debug_session_event(
19925 &mut self,
19926 _session: Entity<Session>,
19927 event: &SessionEvent,
19928 cx: &mut Context<Self>,
19929 ) {
19930 if let SessionEvent::InvalidateInlineValue = event {
19931 self.refresh_inline_values(cx);
19932 }
19933 }
19934
19935 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19936 let Some(project) = self.project.clone() else {
19937 return;
19938 };
19939
19940 if !self.inline_value_cache.enabled {
19941 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19942 self.splice_inlays(&inlays, Vec::new(), cx);
19943 return;
19944 }
19945
19946 let current_execution_position = self
19947 .highlighted_rows
19948 .get(&TypeId::of::<ActiveDebugLine>())
19949 .and_then(|lines| lines.last().map(|line| line.range.end));
19950
19951 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19952 let inline_values = editor
19953 .update(cx, |editor, cx| {
19954 let Some(current_execution_position) = current_execution_position else {
19955 return Some(Task::ready(Ok(Vec::new())));
19956 };
19957
19958 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19959 let snapshot = buffer.snapshot(cx);
19960
19961 let excerpt = snapshot.excerpt_containing(
19962 current_execution_position..current_execution_position,
19963 )?;
19964
19965 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19966 })?;
19967
19968 let range =
19969 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19970
19971 project.inline_values(buffer, range, cx)
19972 })
19973 .ok()
19974 .flatten()?
19975 .await
19976 .context("refreshing debugger inlays")
19977 .log_err()?;
19978
19979 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19980
19981 for (buffer_id, inline_value) in inline_values
19982 .into_iter()
19983 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19984 {
19985 buffer_inline_values
19986 .entry(buffer_id)
19987 .or_default()
19988 .push(inline_value);
19989 }
19990
19991 editor
19992 .update(cx, |editor, cx| {
19993 let snapshot = editor.buffer.read(cx).snapshot(cx);
19994 let mut new_inlays = Vec::default();
19995
19996 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19997 let buffer_id = buffer_snapshot.remote_id();
19998 buffer_inline_values
19999 .get(&buffer_id)
20000 .into_iter()
20001 .flatten()
20002 .for_each(|hint| {
20003 let inlay = Inlay::debugger(
20004 post_inc(&mut editor.next_inlay_id),
20005 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20006 hint.text(),
20007 );
20008 if !inlay.text.chars().contains(&'\n') {
20009 new_inlays.push(inlay);
20010 }
20011 });
20012 }
20013
20014 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20015 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20016
20017 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20018 })
20019 .ok()?;
20020 Some(())
20021 });
20022 }
20023
20024 fn on_buffer_event(
20025 &mut self,
20026 multibuffer: &Entity<MultiBuffer>,
20027 event: &multi_buffer::Event,
20028 window: &mut Window,
20029 cx: &mut Context<Self>,
20030 ) {
20031 match event {
20032 multi_buffer::Event::Edited {
20033 singleton_buffer_edited,
20034 edited_buffer,
20035 } => {
20036 self.scrollbar_marker_state.dirty = true;
20037 self.active_indent_guides_state.dirty = true;
20038 self.refresh_active_diagnostics(cx);
20039 self.refresh_code_actions(window, cx);
20040 self.refresh_selected_text_highlights(true, window, cx);
20041 self.refresh_single_line_folds(window, cx);
20042 refresh_matching_bracket_highlights(self, window, cx);
20043 if self.has_active_edit_prediction() {
20044 self.update_visible_edit_prediction(window, cx);
20045 }
20046 if let Some(project) = self.project.as_ref()
20047 && let Some(edited_buffer) = edited_buffer
20048 {
20049 project.update(cx, |project, cx| {
20050 self.registered_buffers
20051 .entry(edited_buffer.read(cx).remote_id())
20052 .or_insert_with(|| {
20053 project.register_buffer_with_language_servers(edited_buffer, cx)
20054 });
20055 });
20056 }
20057 cx.emit(EditorEvent::BufferEdited);
20058 cx.emit(SearchEvent::MatchesInvalidated);
20059
20060 if let Some(buffer) = edited_buffer {
20061 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20062 }
20063
20064 if *singleton_buffer_edited {
20065 if let Some(buffer) = edited_buffer
20066 && buffer.read(cx).file().is_none()
20067 {
20068 cx.emit(EditorEvent::TitleChanged);
20069 }
20070 if let Some(project) = &self.project {
20071 #[allow(clippy::mutable_key_type)]
20072 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20073 multibuffer
20074 .all_buffers()
20075 .into_iter()
20076 .filter_map(|buffer| {
20077 buffer.update(cx, |buffer, cx| {
20078 let language = buffer.language()?;
20079 let should_discard = project.update(cx, |project, cx| {
20080 project.is_local()
20081 && !project.has_language_servers_for(buffer, cx)
20082 });
20083 should_discard.not().then_some(language.clone())
20084 })
20085 })
20086 .collect::<HashSet<_>>()
20087 });
20088 if !languages_affected.is_empty() {
20089 self.refresh_inlay_hints(
20090 InlayHintRefreshReason::BufferEdited(languages_affected),
20091 cx,
20092 );
20093 }
20094 }
20095 }
20096
20097 let Some(project) = &self.project else { return };
20098 let (telemetry, is_via_ssh) = {
20099 let project = project.read(cx);
20100 let telemetry = project.client().telemetry().clone();
20101 let is_via_ssh = project.is_via_ssh();
20102 (telemetry, is_via_ssh)
20103 };
20104 refresh_linked_ranges(self, window, cx);
20105 telemetry.log_edit_event("editor", is_via_ssh);
20106 }
20107 multi_buffer::Event::ExcerptsAdded {
20108 buffer,
20109 predecessor,
20110 excerpts,
20111 } => {
20112 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20113 let buffer_id = buffer.read(cx).remote_id();
20114 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20115 && let Some(project) = &self.project
20116 {
20117 update_uncommitted_diff_for_buffer(
20118 cx.entity(),
20119 project,
20120 [buffer.clone()],
20121 self.buffer.clone(),
20122 cx,
20123 )
20124 .detach();
20125 }
20126 self.update_lsp_data(false, Some(buffer_id), window, cx);
20127 cx.emit(EditorEvent::ExcerptsAdded {
20128 buffer: buffer.clone(),
20129 predecessor: *predecessor,
20130 excerpts: excerpts.clone(),
20131 });
20132 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20133 }
20134 multi_buffer::Event::ExcerptsRemoved {
20135 ids,
20136 removed_buffer_ids,
20137 } => {
20138 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20139 let buffer = self.buffer.read(cx);
20140 self.registered_buffers
20141 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20142 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20143 cx.emit(EditorEvent::ExcerptsRemoved {
20144 ids: ids.clone(),
20145 removed_buffer_ids: removed_buffer_ids.clone(),
20146 });
20147 }
20148 multi_buffer::Event::ExcerptsEdited {
20149 excerpt_ids,
20150 buffer_ids,
20151 } => {
20152 self.display_map.update(cx, |map, cx| {
20153 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20154 });
20155 cx.emit(EditorEvent::ExcerptsEdited {
20156 ids: excerpt_ids.clone(),
20157 });
20158 }
20159 multi_buffer::Event::ExcerptsExpanded { ids } => {
20160 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20161 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20162 }
20163 multi_buffer::Event::Reparsed(buffer_id) => {
20164 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20165 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20166
20167 cx.emit(EditorEvent::Reparsed(*buffer_id));
20168 }
20169 multi_buffer::Event::DiffHunksToggled => {
20170 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20171 }
20172 multi_buffer::Event::LanguageChanged(buffer_id) => {
20173 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20174 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20175 cx.emit(EditorEvent::Reparsed(*buffer_id));
20176 cx.notify();
20177 }
20178 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20179 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20180 multi_buffer::Event::FileHandleChanged
20181 | multi_buffer::Event::Reloaded
20182 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20183 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20184 multi_buffer::Event::DiagnosticsUpdated => {
20185 self.update_diagnostics_state(window, cx);
20186 }
20187 _ => {}
20188 };
20189 }
20190
20191 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20192 if !self.diagnostics_enabled() {
20193 return;
20194 }
20195 self.refresh_active_diagnostics(cx);
20196 self.refresh_inline_diagnostics(true, window, cx);
20197 self.scrollbar_marker_state.dirty = true;
20198 cx.notify();
20199 }
20200
20201 pub fn start_temporary_diff_override(&mut self) {
20202 self.load_diff_task.take();
20203 self.temporary_diff_override = true;
20204 }
20205
20206 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20207 self.temporary_diff_override = false;
20208 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20209 self.buffer.update(cx, |buffer, cx| {
20210 buffer.set_all_diff_hunks_collapsed(cx);
20211 });
20212
20213 if let Some(project) = self.project.clone() {
20214 self.load_diff_task = Some(
20215 update_uncommitted_diff_for_buffer(
20216 cx.entity(),
20217 &project,
20218 self.buffer.read(cx).all_buffers(),
20219 self.buffer.clone(),
20220 cx,
20221 )
20222 .shared(),
20223 );
20224 }
20225 }
20226
20227 fn on_display_map_changed(
20228 &mut self,
20229 _: Entity<DisplayMap>,
20230 _: &mut Window,
20231 cx: &mut Context<Self>,
20232 ) {
20233 cx.notify();
20234 }
20235
20236 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20237 if self.diagnostics_enabled() {
20238 let new_severity = EditorSettings::get_global(cx)
20239 .diagnostics_max_severity
20240 .unwrap_or(DiagnosticSeverity::Hint);
20241 self.set_max_diagnostics_severity(new_severity, cx);
20242 }
20243 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20244 self.update_edit_prediction_settings(cx);
20245 self.refresh_edit_prediction(true, false, window, cx);
20246 self.refresh_inline_values(cx);
20247 self.refresh_inlay_hints(
20248 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20249 self.selections.newest_anchor().head(),
20250 &self.buffer.read(cx).snapshot(cx),
20251 cx,
20252 )),
20253 cx,
20254 );
20255
20256 let old_cursor_shape = self.cursor_shape;
20257 let old_show_breadcrumbs = self.show_breadcrumbs;
20258
20259 {
20260 let editor_settings = EditorSettings::get_global(cx);
20261 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20262 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20263 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20264 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20265 }
20266
20267 if old_cursor_shape != self.cursor_shape {
20268 cx.emit(EditorEvent::CursorShapeChanged);
20269 }
20270
20271 if old_show_breadcrumbs != self.show_breadcrumbs {
20272 cx.emit(EditorEvent::BreadcrumbsChanged);
20273 }
20274
20275 let project_settings = ProjectSettings::get_global(cx);
20276 self.serialize_dirty_buffers =
20277 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20278
20279 if self.mode.is_full() {
20280 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20281 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20282 if self.show_inline_diagnostics != show_inline_diagnostics {
20283 self.show_inline_diagnostics = show_inline_diagnostics;
20284 self.refresh_inline_diagnostics(false, window, cx);
20285 }
20286
20287 if self.git_blame_inline_enabled != inline_blame_enabled {
20288 self.toggle_git_blame_inline_internal(false, window, cx);
20289 }
20290
20291 let minimap_settings = EditorSettings::get_global(cx).minimap;
20292 if self.minimap_visibility != MinimapVisibility::Disabled {
20293 if self.minimap_visibility.settings_visibility()
20294 != minimap_settings.minimap_enabled()
20295 {
20296 self.set_minimap_visibility(
20297 MinimapVisibility::for_mode(self.mode(), cx),
20298 window,
20299 cx,
20300 );
20301 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20302 minimap_entity.update(cx, |minimap_editor, cx| {
20303 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20304 })
20305 }
20306 }
20307 }
20308
20309 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20310 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20311 }) {
20312 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20313 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20314 }
20315 self.refresh_colors(false, None, window, cx);
20316 }
20317
20318 cx.notify();
20319 }
20320
20321 pub fn set_searchable(&mut self, searchable: bool) {
20322 self.searchable = searchable;
20323 }
20324
20325 pub fn searchable(&self) -> bool {
20326 self.searchable
20327 }
20328
20329 fn open_proposed_changes_editor(
20330 &mut self,
20331 _: &OpenProposedChangesEditor,
20332 window: &mut Window,
20333 cx: &mut Context<Self>,
20334 ) {
20335 let Some(workspace) = self.workspace() else {
20336 cx.propagate();
20337 return;
20338 };
20339
20340 let selections = self.selections.all::<usize>(cx);
20341 let multi_buffer = self.buffer.read(cx);
20342 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20343 let mut new_selections_by_buffer = HashMap::default();
20344 for selection in selections {
20345 for (buffer, range, _) in
20346 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20347 {
20348 let mut range = range.to_point(buffer);
20349 range.start.column = 0;
20350 range.end.column = buffer.line_len(range.end.row);
20351 new_selections_by_buffer
20352 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20353 .or_insert(Vec::new())
20354 .push(range)
20355 }
20356 }
20357
20358 let proposed_changes_buffers = new_selections_by_buffer
20359 .into_iter()
20360 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20361 .collect::<Vec<_>>();
20362 let proposed_changes_editor = cx.new(|cx| {
20363 ProposedChangesEditor::new(
20364 "Proposed changes",
20365 proposed_changes_buffers,
20366 self.project.clone(),
20367 window,
20368 cx,
20369 )
20370 });
20371
20372 window.defer(cx, move |window, cx| {
20373 workspace.update(cx, |workspace, cx| {
20374 workspace.active_pane().update(cx, |pane, cx| {
20375 pane.add_item(
20376 Box::new(proposed_changes_editor),
20377 true,
20378 true,
20379 None,
20380 window,
20381 cx,
20382 );
20383 });
20384 });
20385 });
20386 }
20387
20388 pub fn open_excerpts_in_split(
20389 &mut self,
20390 _: &OpenExcerptsSplit,
20391 window: &mut Window,
20392 cx: &mut Context<Self>,
20393 ) {
20394 self.open_excerpts_common(None, true, window, cx)
20395 }
20396
20397 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20398 self.open_excerpts_common(None, false, window, cx)
20399 }
20400
20401 fn open_excerpts_common(
20402 &mut self,
20403 jump_data: Option<JumpData>,
20404 split: bool,
20405 window: &mut Window,
20406 cx: &mut Context<Self>,
20407 ) {
20408 let Some(workspace) = self.workspace() else {
20409 cx.propagate();
20410 return;
20411 };
20412
20413 if self.buffer.read(cx).is_singleton() {
20414 cx.propagate();
20415 return;
20416 }
20417
20418 let mut new_selections_by_buffer = HashMap::default();
20419 match &jump_data {
20420 Some(JumpData::MultiBufferPoint {
20421 excerpt_id,
20422 position,
20423 anchor,
20424 line_offset_from_top,
20425 }) => {
20426 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20427 if let Some(buffer) = multi_buffer_snapshot
20428 .buffer_id_for_excerpt(*excerpt_id)
20429 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20430 {
20431 let buffer_snapshot = buffer.read(cx).snapshot();
20432 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20433 language::ToPoint::to_point(anchor, &buffer_snapshot)
20434 } else {
20435 buffer_snapshot.clip_point(*position, Bias::Left)
20436 };
20437 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20438 new_selections_by_buffer.insert(
20439 buffer,
20440 (
20441 vec![jump_to_offset..jump_to_offset],
20442 Some(*line_offset_from_top),
20443 ),
20444 );
20445 }
20446 }
20447 Some(JumpData::MultiBufferRow {
20448 row,
20449 line_offset_from_top,
20450 }) => {
20451 let point = MultiBufferPoint::new(row.0, 0);
20452 if let Some((buffer, buffer_point, _)) =
20453 self.buffer.read(cx).point_to_buffer_point(point, cx)
20454 {
20455 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20456 new_selections_by_buffer
20457 .entry(buffer)
20458 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20459 .0
20460 .push(buffer_offset..buffer_offset)
20461 }
20462 }
20463 None => {
20464 let selections = self.selections.all::<usize>(cx);
20465 let multi_buffer = self.buffer.read(cx);
20466 for selection in selections {
20467 for (snapshot, range, _, anchor) in multi_buffer
20468 .snapshot(cx)
20469 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20470 {
20471 if let Some(anchor) = anchor {
20472 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20473 else {
20474 continue;
20475 };
20476 let offset = text::ToOffset::to_offset(
20477 &anchor.text_anchor,
20478 &buffer_handle.read(cx).snapshot(),
20479 );
20480 let range = offset..offset;
20481 new_selections_by_buffer
20482 .entry(buffer_handle)
20483 .or_insert((Vec::new(), None))
20484 .0
20485 .push(range)
20486 } else {
20487 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20488 else {
20489 continue;
20490 };
20491 new_selections_by_buffer
20492 .entry(buffer_handle)
20493 .or_insert((Vec::new(), None))
20494 .0
20495 .push(range)
20496 }
20497 }
20498 }
20499 }
20500 }
20501
20502 new_selections_by_buffer
20503 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20504
20505 if new_selections_by_buffer.is_empty() {
20506 return;
20507 }
20508
20509 // We defer the pane interaction because we ourselves are a workspace item
20510 // and activating a new item causes the pane to call a method on us reentrantly,
20511 // which panics if we're on the stack.
20512 window.defer(cx, move |window, cx| {
20513 workspace.update(cx, |workspace, cx| {
20514 let pane = if split {
20515 workspace.adjacent_pane(window, cx)
20516 } else {
20517 workspace.active_pane().clone()
20518 };
20519
20520 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20521 let editor = buffer
20522 .read(cx)
20523 .file()
20524 .is_none()
20525 .then(|| {
20526 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20527 // so `workspace.open_project_item` will never find them, always opening a new editor.
20528 // Instead, we try to activate the existing editor in the pane first.
20529 let (editor, pane_item_index) =
20530 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20531 let editor = item.downcast::<Editor>()?;
20532 let singleton_buffer =
20533 editor.read(cx).buffer().read(cx).as_singleton()?;
20534 if singleton_buffer == buffer {
20535 Some((editor, i))
20536 } else {
20537 None
20538 }
20539 })?;
20540 pane.update(cx, |pane, cx| {
20541 pane.activate_item(pane_item_index, true, true, window, cx)
20542 });
20543 Some(editor)
20544 })
20545 .flatten()
20546 .unwrap_or_else(|| {
20547 workspace.open_project_item::<Self>(
20548 pane.clone(),
20549 buffer,
20550 true,
20551 true,
20552 window,
20553 cx,
20554 )
20555 });
20556
20557 editor.update(cx, |editor, cx| {
20558 let autoscroll = match scroll_offset {
20559 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20560 None => Autoscroll::newest(),
20561 };
20562 let nav_history = editor.nav_history.take();
20563 editor.change_selections(
20564 SelectionEffects::scroll(autoscroll),
20565 window,
20566 cx,
20567 |s| {
20568 s.select_ranges(ranges);
20569 },
20570 );
20571 editor.nav_history = nav_history;
20572 });
20573 }
20574 })
20575 });
20576 }
20577
20578 // For now, don't allow opening excerpts in buffers that aren't backed by
20579 // regular project files.
20580 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20581 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20582 }
20583
20584 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20585 let snapshot = self.buffer.read(cx).read(cx);
20586 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20587 Some(
20588 ranges
20589 .iter()
20590 .map(move |range| {
20591 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20592 })
20593 .collect(),
20594 )
20595 }
20596
20597 fn selection_replacement_ranges(
20598 &self,
20599 range: Range<OffsetUtf16>,
20600 cx: &mut App,
20601 ) -> Vec<Range<OffsetUtf16>> {
20602 let selections = self.selections.all::<OffsetUtf16>(cx);
20603 let newest_selection = selections
20604 .iter()
20605 .max_by_key(|selection| selection.id)
20606 .unwrap();
20607 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20608 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20609 let snapshot = self.buffer.read(cx).read(cx);
20610 selections
20611 .into_iter()
20612 .map(|mut selection| {
20613 selection.start.0 =
20614 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20615 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20616 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20617 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20618 })
20619 .collect()
20620 }
20621
20622 fn report_editor_event(
20623 &self,
20624 reported_event: ReportEditorEvent,
20625 file_extension: Option<String>,
20626 cx: &App,
20627 ) {
20628 if cfg!(any(test, feature = "test-support")) {
20629 return;
20630 }
20631
20632 let Some(project) = &self.project else { return };
20633
20634 // If None, we are in a file without an extension
20635 let file = self
20636 .buffer
20637 .read(cx)
20638 .as_singleton()
20639 .and_then(|b| b.read(cx).file());
20640 let file_extension = file_extension.or(file
20641 .as_ref()
20642 .and_then(|file| Path::new(file.file_name(cx)).extension())
20643 .and_then(|e| e.to_str())
20644 .map(|a| a.to_string()));
20645
20646 let vim_mode = vim_enabled(cx);
20647
20648 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20649 let copilot_enabled = edit_predictions_provider
20650 == language::language_settings::EditPredictionProvider::Copilot;
20651 let copilot_enabled_for_language = self
20652 .buffer
20653 .read(cx)
20654 .language_settings(cx)
20655 .show_edit_predictions;
20656
20657 let project = project.read(cx);
20658 let event_type = reported_event.event_type();
20659
20660 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20661 telemetry::event!(
20662 event_type,
20663 type = if auto_saved {"autosave"} else {"manual"},
20664 file_extension,
20665 vim_mode,
20666 copilot_enabled,
20667 copilot_enabled_for_language,
20668 edit_predictions_provider,
20669 is_via_ssh = project.is_via_ssh(),
20670 );
20671 } else {
20672 telemetry::event!(
20673 event_type,
20674 file_extension,
20675 vim_mode,
20676 copilot_enabled,
20677 copilot_enabled_for_language,
20678 edit_predictions_provider,
20679 is_via_ssh = project.is_via_ssh(),
20680 );
20681 };
20682 }
20683
20684 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20685 /// with each line being an array of {text, highlight} objects.
20686 fn copy_highlight_json(
20687 &mut self,
20688 _: &CopyHighlightJson,
20689 window: &mut Window,
20690 cx: &mut Context<Self>,
20691 ) {
20692 #[derive(Serialize)]
20693 struct Chunk<'a> {
20694 text: String,
20695 highlight: Option<&'a str>,
20696 }
20697
20698 let snapshot = self.buffer.read(cx).snapshot(cx);
20699 let range = self
20700 .selected_text_range(false, window, cx)
20701 .and_then(|selection| {
20702 if selection.range.is_empty() {
20703 None
20704 } else {
20705 Some(selection.range)
20706 }
20707 })
20708 .unwrap_or_else(|| 0..snapshot.len());
20709
20710 let chunks = snapshot.chunks(range, true);
20711 let mut lines = Vec::new();
20712 let mut line: VecDeque<Chunk> = VecDeque::new();
20713
20714 let Some(style) = self.style.as_ref() else {
20715 return;
20716 };
20717
20718 for chunk in chunks {
20719 let highlight = chunk
20720 .syntax_highlight_id
20721 .and_then(|id| id.name(&style.syntax));
20722 let mut chunk_lines = chunk.text.split('\n').peekable();
20723 while let Some(text) = chunk_lines.next() {
20724 let mut merged_with_last_token = false;
20725 if let Some(last_token) = line.back_mut()
20726 && last_token.highlight == highlight
20727 {
20728 last_token.text.push_str(text);
20729 merged_with_last_token = true;
20730 }
20731
20732 if !merged_with_last_token {
20733 line.push_back(Chunk {
20734 text: text.into(),
20735 highlight,
20736 });
20737 }
20738
20739 if chunk_lines.peek().is_some() {
20740 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20741 line.pop_front();
20742 }
20743 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20744 line.pop_back();
20745 }
20746
20747 lines.push(mem::take(&mut line));
20748 }
20749 }
20750 }
20751
20752 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20753 return;
20754 };
20755 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20756 }
20757
20758 pub fn open_context_menu(
20759 &mut self,
20760 _: &OpenContextMenu,
20761 window: &mut Window,
20762 cx: &mut Context<Self>,
20763 ) {
20764 self.request_autoscroll(Autoscroll::newest(), cx);
20765 let position = self.selections.newest_display(cx).start;
20766 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20767 }
20768
20769 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20770 &self.inlay_hint_cache
20771 }
20772
20773 pub fn replay_insert_event(
20774 &mut self,
20775 text: &str,
20776 relative_utf16_range: Option<Range<isize>>,
20777 window: &mut Window,
20778 cx: &mut Context<Self>,
20779 ) {
20780 if !self.input_enabled {
20781 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20782 return;
20783 }
20784 if let Some(relative_utf16_range) = relative_utf16_range {
20785 let selections = self.selections.all::<OffsetUtf16>(cx);
20786 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20787 let new_ranges = selections.into_iter().map(|range| {
20788 let start = OffsetUtf16(
20789 range
20790 .head()
20791 .0
20792 .saturating_add_signed(relative_utf16_range.start),
20793 );
20794 let end = OffsetUtf16(
20795 range
20796 .head()
20797 .0
20798 .saturating_add_signed(relative_utf16_range.end),
20799 );
20800 start..end
20801 });
20802 s.select_ranges(new_ranges);
20803 });
20804 }
20805
20806 self.handle_input(text, window, cx);
20807 }
20808
20809 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20810 let Some(provider) = self.semantics_provider.as_ref() else {
20811 return false;
20812 };
20813
20814 let mut supports = false;
20815 self.buffer().update(cx, |this, cx| {
20816 this.for_each_buffer(|buffer| {
20817 supports |= provider.supports_inlay_hints(buffer, cx);
20818 });
20819 });
20820
20821 supports
20822 }
20823
20824 pub fn is_focused(&self, window: &Window) -> bool {
20825 self.focus_handle.is_focused(window)
20826 }
20827
20828 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20829 cx.emit(EditorEvent::Focused);
20830
20831 if let Some(descendant) = self
20832 .last_focused_descendant
20833 .take()
20834 .and_then(|descendant| descendant.upgrade())
20835 {
20836 window.focus(&descendant);
20837 } else {
20838 if let Some(blame) = self.blame.as_ref() {
20839 blame.update(cx, GitBlame::focus)
20840 }
20841
20842 self.blink_manager.update(cx, BlinkManager::enable);
20843 self.show_cursor_names(window, cx);
20844 self.buffer.update(cx, |buffer, cx| {
20845 buffer.finalize_last_transaction(cx);
20846 if self.leader_id.is_none() {
20847 buffer.set_active_selections(
20848 &self.selections.disjoint_anchors(),
20849 self.selections.line_mode,
20850 self.cursor_shape,
20851 cx,
20852 );
20853 }
20854 });
20855 }
20856 }
20857
20858 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20859 cx.emit(EditorEvent::FocusedIn)
20860 }
20861
20862 fn handle_focus_out(
20863 &mut self,
20864 event: FocusOutEvent,
20865 _window: &mut Window,
20866 cx: &mut Context<Self>,
20867 ) {
20868 if event.blurred != self.focus_handle {
20869 self.last_focused_descendant = Some(event.blurred);
20870 }
20871 self.selection_drag_state = SelectionDragState::None;
20872 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20873 }
20874
20875 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20876 self.blink_manager.update(cx, BlinkManager::disable);
20877 self.buffer
20878 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20879
20880 if let Some(blame) = self.blame.as_ref() {
20881 blame.update(cx, GitBlame::blur)
20882 }
20883 if !self.hover_state.focused(window, cx) {
20884 hide_hover(self, cx);
20885 }
20886 if !self
20887 .context_menu
20888 .borrow()
20889 .as_ref()
20890 .is_some_and(|context_menu| context_menu.focused(window, cx))
20891 {
20892 self.hide_context_menu(window, cx);
20893 }
20894 self.discard_edit_prediction(false, cx);
20895 cx.emit(EditorEvent::Blurred);
20896 cx.notify();
20897 }
20898
20899 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20900 let mut pending: String = window
20901 .pending_input_keystrokes()
20902 .into_iter()
20903 .flatten()
20904 .filter_map(|keystroke| {
20905 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20906 keystroke.key_char.clone()
20907 } else {
20908 None
20909 }
20910 })
20911 .collect();
20912
20913 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20914 pending = "".to_string();
20915 }
20916
20917 let existing_pending = self
20918 .text_highlights::<PendingInput>(cx)
20919 .map(|(_, ranges)| ranges.to_vec());
20920 if existing_pending.is_none() && pending.is_empty() {
20921 return;
20922 }
20923 let transaction =
20924 self.transact(window, cx, |this, window, cx| {
20925 let selections = this.selections.all::<usize>(cx);
20926 let edits = selections
20927 .iter()
20928 .map(|selection| (selection.end..selection.end, pending.clone()));
20929 this.edit(edits, cx);
20930 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20931 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20932 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20933 }));
20934 });
20935 if let Some(existing_ranges) = existing_pending {
20936 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20937 this.edit(edits, cx);
20938 }
20939 });
20940
20941 let snapshot = self.snapshot(window, cx);
20942 let ranges = self
20943 .selections
20944 .all::<usize>(cx)
20945 .into_iter()
20946 .map(|selection| {
20947 snapshot.buffer_snapshot.anchor_after(selection.end)
20948 ..snapshot
20949 .buffer_snapshot
20950 .anchor_before(selection.end + pending.len())
20951 })
20952 .collect();
20953
20954 if pending.is_empty() {
20955 self.clear_highlights::<PendingInput>(cx);
20956 } else {
20957 self.highlight_text::<PendingInput>(
20958 ranges,
20959 HighlightStyle {
20960 underline: Some(UnderlineStyle {
20961 thickness: px(1.),
20962 color: None,
20963 wavy: false,
20964 }),
20965 ..Default::default()
20966 },
20967 cx,
20968 );
20969 }
20970
20971 self.ime_transaction = self.ime_transaction.or(transaction);
20972 if let Some(transaction) = self.ime_transaction {
20973 self.buffer.update(cx, |buffer, cx| {
20974 buffer.group_until_transaction(transaction, cx);
20975 });
20976 }
20977
20978 if self.text_highlights::<PendingInput>(cx).is_none() {
20979 self.ime_transaction.take();
20980 }
20981 }
20982
20983 pub fn register_action_renderer(
20984 &mut self,
20985 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20986 ) -> Subscription {
20987 let id = self.next_editor_action_id.post_inc();
20988 self.editor_actions
20989 .borrow_mut()
20990 .insert(id, Box::new(listener));
20991
20992 let editor_actions = self.editor_actions.clone();
20993 Subscription::new(move || {
20994 editor_actions.borrow_mut().remove(&id);
20995 })
20996 }
20997
20998 pub fn register_action<A: Action>(
20999 &mut self,
21000 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21001 ) -> Subscription {
21002 let id = self.next_editor_action_id.post_inc();
21003 let listener = Arc::new(listener);
21004 self.editor_actions.borrow_mut().insert(
21005 id,
21006 Box::new(move |_, window, _| {
21007 let listener = listener.clone();
21008 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21009 let action = action.downcast_ref().unwrap();
21010 if phase == DispatchPhase::Bubble {
21011 listener(action, window, cx)
21012 }
21013 })
21014 }),
21015 );
21016
21017 let editor_actions = self.editor_actions.clone();
21018 Subscription::new(move || {
21019 editor_actions.borrow_mut().remove(&id);
21020 })
21021 }
21022
21023 pub fn file_header_size(&self) -> u32 {
21024 FILE_HEADER_HEIGHT
21025 }
21026
21027 pub fn restore(
21028 &mut self,
21029 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21030 window: &mut Window,
21031 cx: &mut Context<Self>,
21032 ) {
21033 let workspace = self.workspace();
21034 let project = self.project();
21035 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21036 let mut tasks = Vec::new();
21037 for (buffer_id, changes) in revert_changes {
21038 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21039 buffer.update(cx, |buffer, cx| {
21040 buffer.edit(
21041 changes
21042 .into_iter()
21043 .map(|(range, text)| (range, text.to_string())),
21044 None,
21045 cx,
21046 );
21047 });
21048
21049 if let Some(project) =
21050 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21051 {
21052 project.update(cx, |project, cx| {
21053 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21054 })
21055 }
21056 }
21057 }
21058 tasks
21059 });
21060 cx.spawn_in(window, async move |_, cx| {
21061 for (buffer, task) in save_tasks {
21062 let result = task.await;
21063 if result.is_err() {
21064 let Some(path) = buffer
21065 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21066 .ok()
21067 else {
21068 continue;
21069 };
21070 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21071 let Some(task) = cx
21072 .update_window_entity(workspace, |workspace, window, cx| {
21073 workspace
21074 .open_path_preview(path, None, false, false, false, window, cx)
21075 })
21076 .ok()
21077 else {
21078 continue;
21079 };
21080 task.await.log_err();
21081 }
21082 }
21083 }
21084 })
21085 .detach();
21086 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21087 selections.refresh()
21088 });
21089 }
21090
21091 pub fn to_pixel_point(
21092 &self,
21093 source: multi_buffer::Anchor,
21094 editor_snapshot: &EditorSnapshot,
21095 window: &mut Window,
21096 ) -> Option<gpui::Point<Pixels>> {
21097 let source_point = source.to_display_point(editor_snapshot);
21098 self.display_to_pixel_point(source_point, editor_snapshot, window)
21099 }
21100
21101 pub fn display_to_pixel_point(
21102 &self,
21103 source: DisplayPoint,
21104 editor_snapshot: &EditorSnapshot,
21105 window: &mut Window,
21106 ) -> Option<gpui::Point<Pixels>> {
21107 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21108 let text_layout_details = self.text_layout_details(window);
21109 let scroll_top = text_layout_details
21110 .scroll_anchor
21111 .scroll_position(editor_snapshot)
21112 .y;
21113
21114 if source.row().as_f32() < scroll_top.floor() {
21115 return None;
21116 }
21117 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21118 let source_y = line_height * (source.row().as_f32() - scroll_top);
21119 Some(gpui::Point::new(source_x, source_y))
21120 }
21121
21122 pub fn has_visible_completions_menu(&self) -> bool {
21123 !self.edit_prediction_preview_is_active()
21124 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21125 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21126 })
21127 }
21128
21129 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21130 if self.mode.is_minimap() {
21131 return;
21132 }
21133 self.addons
21134 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21135 }
21136
21137 pub fn unregister_addon<T: Addon>(&mut self) {
21138 self.addons.remove(&std::any::TypeId::of::<T>());
21139 }
21140
21141 pub fn addon<T: Addon>(&self) -> Option<&T> {
21142 let type_id = std::any::TypeId::of::<T>();
21143 self.addons
21144 .get(&type_id)
21145 .and_then(|item| item.to_any().downcast_ref::<T>())
21146 }
21147
21148 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21149 let type_id = std::any::TypeId::of::<T>();
21150 self.addons
21151 .get_mut(&type_id)
21152 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21153 }
21154
21155 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21156 let text_layout_details = self.text_layout_details(window);
21157 let style = &text_layout_details.editor_style;
21158 let font_id = window.text_system().resolve_font(&style.text.font());
21159 let font_size = style.text.font_size.to_pixels(window.rem_size());
21160 let line_height = style.text.line_height_in_pixels(window.rem_size());
21161 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21162 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21163
21164 CharacterDimensions {
21165 em_width,
21166 em_advance,
21167 line_height,
21168 }
21169 }
21170
21171 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21172 self.load_diff_task.clone()
21173 }
21174
21175 fn read_metadata_from_db(
21176 &mut self,
21177 item_id: u64,
21178 workspace_id: WorkspaceId,
21179 window: &mut Window,
21180 cx: &mut Context<Editor>,
21181 ) {
21182 if self.is_singleton(cx)
21183 && !self.mode.is_minimap()
21184 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21185 {
21186 let buffer_snapshot = OnceCell::new();
21187
21188 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21189 && !folds.is_empty()
21190 {
21191 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21192 self.fold_ranges(
21193 folds
21194 .into_iter()
21195 .map(|(start, end)| {
21196 snapshot.clip_offset(start, Bias::Left)
21197 ..snapshot.clip_offset(end, Bias::Right)
21198 })
21199 .collect(),
21200 false,
21201 window,
21202 cx,
21203 );
21204 }
21205
21206 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21207 && !selections.is_empty()
21208 {
21209 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21210 // skip adding the initial selection to selection history
21211 self.selection_history.mode = SelectionHistoryMode::Skipping;
21212 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21213 s.select_ranges(selections.into_iter().map(|(start, end)| {
21214 snapshot.clip_offset(start, Bias::Left)
21215 ..snapshot.clip_offset(end, Bias::Right)
21216 }));
21217 });
21218 self.selection_history.mode = SelectionHistoryMode::Normal;
21219 };
21220 }
21221
21222 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21223 }
21224
21225 fn update_lsp_data(
21226 &mut self,
21227 ignore_cache: bool,
21228 for_buffer: Option<BufferId>,
21229 window: &mut Window,
21230 cx: &mut Context<'_, Self>,
21231 ) {
21232 self.pull_diagnostics(for_buffer, window, cx);
21233 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21234 }
21235}
21236
21237fn vim_enabled(cx: &App) -> bool {
21238 cx.global::<SettingsStore>()
21239 .raw_user_settings()
21240 .get("vim_mode")
21241 == Some(&serde_json::Value::Bool(true))
21242}
21243
21244fn process_completion_for_edit(
21245 completion: &Completion,
21246 intent: CompletionIntent,
21247 buffer: &Entity<Buffer>,
21248 cursor_position: &text::Anchor,
21249 cx: &mut Context<Editor>,
21250) -> CompletionEdit {
21251 let buffer = buffer.read(cx);
21252 let buffer_snapshot = buffer.snapshot();
21253 let (snippet, new_text) = if completion.is_snippet() {
21254 // Workaround for typescript language server issues so that methods don't expand within
21255 // strings and functions with type expressions. The previous point is used because the query
21256 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21257 let mut snippet_source = completion.new_text.clone();
21258 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21259 previous_point.column = previous_point.column.saturating_sub(1);
21260 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21261 && scope.prefers_label_for_snippet_in_completion()
21262 && let Some(label) = completion.label()
21263 && matches!(
21264 completion.kind(),
21265 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21266 )
21267 {
21268 snippet_source = label;
21269 }
21270 match Snippet::parse(&snippet_source).log_err() {
21271 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21272 None => (None, completion.new_text.clone()),
21273 }
21274 } else {
21275 (None, completion.new_text.clone())
21276 };
21277
21278 let mut range_to_replace = {
21279 let replace_range = &completion.replace_range;
21280 if let CompletionSource::Lsp {
21281 insert_range: Some(insert_range),
21282 ..
21283 } = &completion.source
21284 {
21285 debug_assert_eq!(
21286 insert_range.start, replace_range.start,
21287 "insert_range and replace_range should start at the same position"
21288 );
21289 debug_assert!(
21290 insert_range
21291 .start
21292 .cmp(cursor_position, &buffer_snapshot)
21293 .is_le(),
21294 "insert_range should start before or at cursor position"
21295 );
21296 debug_assert!(
21297 replace_range
21298 .start
21299 .cmp(cursor_position, &buffer_snapshot)
21300 .is_le(),
21301 "replace_range should start before or at cursor position"
21302 );
21303
21304 let should_replace = match intent {
21305 CompletionIntent::CompleteWithInsert => false,
21306 CompletionIntent::CompleteWithReplace => true,
21307 CompletionIntent::Complete | CompletionIntent::Compose => {
21308 let insert_mode =
21309 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21310 .completions
21311 .lsp_insert_mode;
21312 match insert_mode {
21313 LspInsertMode::Insert => false,
21314 LspInsertMode::Replace => true,
21315 LspInsertMode::ReplaceSubsequence => {
21316 let mut text_to_replace = buffer.chars_for_range(
21317 buffer.anchor_before(replace_range.start)
21318 ..buffer.anchor_after(replace_range.end),
21319 );
21320 let mut current_needle = text_to_replace.next();
21321 for haystack_ch in completion.label.text.chars() {
21322 if let Some(needle_ch) = current_needle
21323 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21324 {
21325 current_needle = text_to_replace.next();
21326 }
21327 }
21328 current_needle.is_none()
21329 }
21330 LspInsertMode::ReplaceSuffix => {
21331 if replace_range
21332 .end
21333 .cmp(cursor_position, &buffer_snapshot)
21334 .is_gt()
21335 {
21336 let range_after_cursor = *cursor_position..replace_range.end;
21337 let text_after_cursor = buffer
21338 .text_for_range(
21339 buffer.anchor_before(range_after_cursor.start)
21340 ..buffer.anchor_after(range_after_cursor.end),
21341 )
21342 .collect::<String>()
21343 .to_ascii_lowercase();
21344 completion
21345 .label
21346 .text
21347 .to_ascii_lowercase()
21348 .ends_with(&text_after_cursor)
21349 } else {
21350 true
21351 }
21352 }
21353 }
21354 }
21355 };
21356
21357 if should_replace {
21358 replace_range.clone()
21359 } else {
21360 insert_range.clone()
21361 }
21362 } else {
21363 replace_range.clone()
21364 }
21365 };
21366
21367 if range_to_replace
21368 .end
21369 .cmp(cursor_position, &buffer_snapshot)
21370 .is_lt()
21371 {
21372 range_to_replace.end = *cursor_position;
21373 }
21374
21375 CompletionEdit {
21376 new_text,
21377 replace_range: range_to_replace.to_offset(buffer),
21378 snippet,
21379 }
21380}
21381
21382struct CompletionEdit {
21383 new_text: String,
21384 replace_range: Range<usize>,
21385 snippet: Option<Snippet>,
21386}
21387
21388fn insert_extra_newline_brackets(
21389 buffer: &MultiBufferSnapshot,
21390 range: Range<usize>,
21391 language: &language::LanguageScope,
21392) -> bool {
21393 let leading_whitespace_len = buffer
21394 .reversed_chars_at(range.start)
21395 .take_while(|c| c.is_whitespace() && *c != '\n')
21396 .map(|c| c.len_utf8())
21397 .sum::<usize>();
21398 let trailing_whitespace_len = buffer
21399 .chars_at(range.end)
21400 .take_while(|c| c.is_whitespace() && *c != '\n')
21401 .map(|c| c.len_utf8())
21402 .sum::<usize>();
21403 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21404
21405 language.brackets().any(|(pair, enabled)| {
21406 let pair_start = pair.start.trim_end();
21407 let pair_end = pair.end.trim_start();
21408
21409 enabled
21410 && pair.newline
21411 && buffer.contains_str_at(range.end, pair_end)
21412 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21413 })
21414}
21415
21416fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21417 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21418 [(buffer, range, _)] => (*buffer, range.clone()),
21419 _ => return false,
21420 };
21421 let pair = {
21422 let mut result: Option<BracketMatch> = None;
21423
21424 for pair in buffer
21425 .all_bracket_ranges(range.clone())
21426 .filter(move |pair| {
21427 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21428 })
21429 {
21430 let len = pair.close_range.end - pair.open_range.start;
21431
21432 if let Some(existing) = &result {
21433 let existing_len = existing.close_range.end - existing.open_range.start;
21434 if len > existing_len {
21435 continue;
21436 }
21437 }
21438
21439 result = Some(pair);
21440 }
21441
21442 result
21443 };
21444 let Some(pair) = pair else {
21445 return false;
21446 };
21447 pair.newline_only
21448 && buffer
21449 .chars_for_range(pair.open_range.end..range.start)
21450 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21451 .all(|c| c.is_whitespace() && c != '\n')
21452}
21453
21454fn update_uncommitted_diff_for_buffer(
21455 editor: Entity<Editor>,
21456 project: &Entity<Project>,
21457 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21458 buffer: Entity<MultiBuffer>,
21459 cx: &mut App,
21460) -> Task<()> {
21461 let mut tasks = Vec::new();
21462 project.update(cx, |project, cx| {
21463 for buffer in buffers {
21464 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21465 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21466 }
21467 }
21468 });
21469 cx.spawn(async move |cx| {
21470 let diffs = future::join_all(tasks).await;
21471 if editor
21472 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21473 .unwrap_or(false)
21474 {
21475 return;
21476 }
21477
21478 buffer
21479 .update(cx, |buffer, cx| {
21480 for diff in diffs.into_iter().flatten() {
21481 buffer.add_diff(diff, cx);
21482 }
21483 })
21484 .ok();
21485 })
21486}
21487
21488fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21489 let tab_size = tab_size.get() as usize;
21490 let mut width = offset;
21491
21492 for ch in text.chars() {
21493 width += if ch == '\t' {
21494 tab_size - (width % tab_size)
21495 } else {
21496 1
21497 };
21498 }
21499
21500 width - offset
21501}
21502
21503#[cfg(test)]
21504mod tests {
21505 use super::*;
21506
21507 #[test]
21508 fn test_string_size_with_expanded_tabs() {
21509 let nz = |val| NonZeroU32::new(val).unwrap();
21510 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21511 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21512 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21513 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21514 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21515 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21516 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21517 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21518 }
21519}
21520
21521/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21522struct WordBreakingTokenizer<'a> {
21523 input: &'a str,
21524}
21525
21526impl<'a> WordBreakingTokenizer<'a> {
21527 fn new(input: &'a str) -> Self {
21528 Self { input }
21529 }
21530}
21531
21532fn is_char_ideographic(ch: char) -> bool {
21533 use unicode_script::Script::*;
21534 use unicode_script::UnicodeScript;
21535 matches!(ch.script(), Han | Tangut | Yi)
21536}
21537
21538fn is_grapheme_ideographic(text: &str) -> bool {
21539 text.chars().any(is_char_ideographic)
21540}
21541
21542fn is_grapheme_whitespace(text: &str) -> bool {
21543 text.chars().any(|x| x.is_whitespace())
21544}
21545
21546fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21547 text.chars()
21548 .next()
21549 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21550}
21551
21552#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21553enum WordBreakToken<'a> {
21554 Word { token: &'a str, grapheme_len: usize },
21555 InlineWhitespace { token: &'a str, grapheme_len: usize },
21556 Newline,
21557}
21558
21559impl<'a> Iterator for WordBreakingTokenizer<'a> {
21560 /// Yields a span, the count of graphemes in the token, and whether it was
21561 /// whitespace. Note that it also breaks at word boundaries.
21562 type Item = WordBreakToken<'a>;
21563
21564 fn next(&mut self) -> Option<Self::Item> {
21565 use unicode_segmentation::UnicodeSegmentation;
21566 if self.input.is_empty() {
21567 return None;
21568 }
21569
21570 let mut iter = self.input.graphemes(true).peekable();
21571 let mut offset = 0;
21572 let mut grapheme_len = 0;
21573 if let Some(first_grapheme) = iter.next() {
21574 let is_newline = first_grapheme == "\n";
21575 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21576 offset += first_grapheme.len();
21577 grapheme_len += 1;
21578 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21579 if let Some(grapheme) = iter.peek().copied()
21580 && should_stay_with_preceding_ideograph(grapheme)
21581 {
21582 offset += grapheme.len();
21583 grapheme_len += 1;
21584 }
21585 } else {
21586 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21587 let mut next_word_bound = words.peek().copied();
21588 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21589 next_word_bound = words.next();
21590 }
21591 while let Some(grapheme) = iter.peek().copied() {
21592 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21593 break;
21594 };
21595 if is_grapheme_whitespace(grapheme) != is_whitespace
21596 || (grapheme == "\n") != is_newline
21597 {
21598 break;
21599 };
21600 offset += grapheme.len();
21601 grapheme_len += 1;
21602 iter.next();
21603 }
21604 }
21605 let token = &self.input[..offset];
21606 self.input = &self.input[offset..];
21607 if token == "\n" {
21608 Some(WordBreakToken::Newline)
21609 } else if is_whitespace {
21610 Some(WordBreakToken::InlineWhitespace {
21611 token,
21612 grapheme_len,
21613 })
21614 } else {
21615 Some(WordBreakToken::Word {
21616 token,
21617 grapheme_len,
21618 })
21619 }
21620 } else {
21621 None
21622 }
21623 }
21624}
21625
21626#[test]
21627fn test_word_breaking_tokenizer() {
21628 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21629 ("", &[]),
21630 (" ", &[whitespace(" ", 2)]),
21631 ("Ʒ", &[word("Ʒ", 1)]),
21632 ("Ǽ", &[word("Ǽ", 1)]),
21633 ("⋑", &[word("⋑", 1)]),
21634 ("⋑⋑", &[word("⋑⋑", 2)]),
21635 (
21636 "原理,进而",
21637 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21638 ),
21639 (
21640 "hello world",
21641 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21642 ),
21643 (
21644 "hello, world",
21645 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21646 ),
21647 (
21648 " hello world",
21649 &[
21650 whitespace(" ", 2),
21651 word("hello", 5),
21652 whitespace(" ", 1),
21653 word("world", 5),
21654 ],
21655 ),
21656 (
21657 "这是什么 \n 钢笔",
21658 &[
21659 word("这", 1),
21660 word("是", 1),
21661 word("什", 1),
21662 word("么", 1),
21663 whitespace(" ", 1),
21664 newline(),
21665 whitespace(" ", 1),
21666 word("钢", 1),
21667 word("笔", 1),
21668 ],
21669 ),
21670 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21671 ];
21672
21673 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21674 WordBreakToken::Word {
21675 token,
21676 grapheme_len,
21677 }
21678 }
21679
21680 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21681 WordBreakToken::InlineWhitespace {
21682 token,
21683 grapheme_len,
21684 }
21685 }
21686
21687 fn newline() -> WordBreakToken<'static> {
21688 WordBreakToken::Newline
21689 }
21690
21691 for (input, result) in tests {
21692 assert_eq!(
21693 WordBreakingTokenizer::new(input)
21694 .collect::<Vec<_>>()
21695 .as_slice(),
21696 *result,
21697 );
21698 }
21699}
21700
21701fn wrap_with_prefix(
21702 first_line_prefix: String,
21703 subsequent_lines_prefix: String,
21704 unwrapped_text: String,
21705 wrap_column: usize,
21706 tab_size: NonZeroU32,
21707 preserve_existing_whitespace: bool,
21708) -> String {
21709 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21710 let subsequent_lines_prefix_len =
21711 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21712 let mut wrapped_text = String::new();
21713 let mut current_line = first_line_prefix;
21714 let mut is_first_line = true;
21715
21716 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21717 let mut current_line_len = first_line_prefix_len;
21718 let mut in_whitespace = false;
21719 for token in tokenizer {
21720 let have_preceding_whitespace = in_whitespace;
21721 match token {
21722 WordBreakToken::Word {
21723 token,
21724 grapheme_len,
21725 } => {
21726 in_whitespace = false;
21727 let current_prefix_len = if is_first_line {
21728 first_line_prefix_len
21729 } else {
21730 subsequent_lines_prefix_len
21731 };
21732 if current_line_len + grapheme_len > wrap_column
21733 && current_line_len != current_prefix_len
21734 {
21735 wrapped_text.push_str(current_line.trim_end());
21736 wrapped_text.push('\n');
21737 is_first_line = false;
21738 current_line = subsequent_lines_prefix.clone();
21739 current_line_len = subsequent_lines_prefix_len;
21740 }
21741 current_line.push_str(token);
21742 current_line_len += grapheme_len;
21743 }
21744 WordBreakToken::InlineWhitespace {
21745 mut token,
21746 mut grapheme_len,
21747 } => {
21748 in_whitespace = true;
21749 if have_preceding_whitespace && !preserve_existing_whitespace {
21750 continue;
21751 }
21752 if !preserve_existing_whitespace {
21753 token = " ";
21754 grapheme_len = 1;
21755 }
21756 let current_prefix_len = if is_first_line {
21757 first_line_prefix_len
21758 } else {
21759 subsequent_lines_prefix_len
21760 };
21761 if current_line_len + grapheme_len > wrap_column {
21762 wrapped_text.push_str(current_line.trim_end());
21763 wrapped_text.push('\n');
21764 is_first_line = false;
21765 current_line = subsequent_lines_prefix.clone();
21766 current_line_len = subsequent_lines_prefix_len;
21767 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21768 current_line.push_str(token);
21769 current_line_len += grapheme_len;
21770 }
21771 }
21772 WordBreakToken::Newline => {
21773 in_whitespace = true;
21774 let current_prefix_len = if is_first_line {
21775 first_line_prefix_len
21776 } else {
21777 subsequent_lines_prefix_len
21778 };
21779 if preserve_existing_whitespace {
21780 wrapped_text.push_str(current_line.trim_end());
21781 wrapped_text.push('\n');
21782 is_first_line = false;
21783 current_line = subsequent_lines_prefix.clone();
21784 current_line_len = subsequent_lines_prefix_len;
21785 } else if have_preceding_whitespace {
21786 continue;
21787 } else if current_line_len + 1 > wrap_column
21788 && current_line_len != current_prefix_len
21789 {
21790 wrapped_text.push_str(current_line.trim_end());
21791 wrapped_text.push('\n');
21792 is_first_line = false;
21793 current_line = subsequent_lines_prefix.clone();
21794 current_line_len = subsequent_lines_prefix_len;
21795 } else if current_line_len != current_prefix_len {
21796 current_line.push(' ');
21797 current_line_len += 1;
21798 }
21799 }
21800 }
21801 }
21802
21803 if !current_line.is_empty() {
21804 wrapped_text.push_str(¤t_line);
21805 }
21806 wrapped_text
21807}
21808
21809#[test]
21810fn test_wrap_with_prefix() {
21811 assert_eq!(
21812 wrap_with_prefix(
21813 "# ".to_string(),
21814 "# ".to_string(),
21815 "abcdefg".to_string(),
21816 4,
21817 NonZeroU32::new(4).unwrap(),
21818 false,
21819 ),
21820 "# abcdefg"
21821 );
21822 assert_eq!(
21823 wrap_with_prefix(
21824 "".to_string(),
21825 "".to_string(),
21826 "\thello world".to_string(),
21827 8,
21828 NonZeroU32::new(4).unwrap(),
21829 false,
21830 ),
21831 "hello\nworld"
21832 );
21833 assert_eq!(
21834 wrap_with_prefix(
21835 "// ".to_string(),
21836 "// ".to_string(),
21837 "xx \nyy zz aa bb cc".to_string(),
21838 12,
21839 NonZeroU32::new(4).unwrap(),
21840 false,
21841 ),
21842 "// xx yy zz\n// aa bb cc"
21843 );
21844 assert_eq!(
21845 wrap_with_prefix(
21846 String::new(),
21847 String::new(),
21848 "这是什么 \n 钢笔".to_string(),
21849 3,
21850 NonZeroU32::new(4).unwrap(),
21851 false,
21852 ),
21853 "这是什\n么 钢\n笔"
21854 );
21855}
21856
21857pub trait CollaborationHub {
21858 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21859 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21860 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21861}
21862
21863impl CollaborationHub for Entity<Project> {
21864 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21865 self.read(cx).collaborators()
21866 }
21867
21868 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21869 self.read(cx).user_store().read(cx).participant_indices()
21870 }
21871
21872 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21873 let this = self.read(cx);
21874 let user_ids = this.collaborators().values().map(|c| c.user_id);
21875 this.user_store().read(cx).participant_names(user_ids, cx)
21876 }
21877}
21878
21879pub trait SemanticsProvider {
21880 fn hover(
21881 &self,
21882 buffer: &Entity<Buffer>,
21883 position: text::Anchor,
21884 cx: &mut App,
21885 ) -> Option<Task<Option<Vec<project::Hover>>>>;
21886
21887 fn inline_values(
21888 &self,
21889 buffer_handle: Entity<Buffer>,
21890 range: Range<text::Anchor>,
21891 cx: &mut App,
21892 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21893
21894 fn inlay_hints(
21895 &self,
21896 buffer_handle: Entity<Buffer>,
21897 range: Range<text::Anchor>,
21898 cx: &mut App,
21899 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21900
21901 fn resolve_inlay_hint(
21902 &self,
21903 hint: InlayHint,
21904 buffer_handle: Entity<Buffer>,
21905 server_id: LanguageServerId,
21906 cx: &mut App,
21907 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21908
21909 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21910
21911 fn document_highlights(
21912 &self,
21913 buffer: &Entity<Buffer>,
21914 position: text::Anchor,
21915 cx: &mut App,
21916 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21917
21918 fn definitions(
21919 &self,
21920 buffer: &Entity<Buffer>,
21921 position: text::Anchor,
21922 kind: GotoDefinitionKind,
21923 cx: &mut App,
21924 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
21925
21926 fn range_for_rename(
21927 &self,
21928 buffer: &Entity<Buffer>,
21929 position: text::Anchor,
21930 cx: &mut App,
21931 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21932
21933 fn perform_rename(
21934 &self,
21935 buffer: &Entity<Buffer>,
21936 position: text::Anchor,
21937 new_name: String,
21938 cx: &mut App,
21939 ) -> Option<Task<Result<ProjectTransaction>>>;
21940}
21941
21942pub trait CompletionProvider {
21943 fn completions(
21944 &self,
21945 excerpt_id: ExcerptId,
21946 buffer: &Entity<Buffer>,
21947 buffer_position: text::Anchor,
21948 trigger: CompletionContext,
21949 window: &mut Window,
21950 cx: &mut Context<Editor>,
21951 ) -> Task<Result<Vec<CompletionResponse>>>;
21952
21953 fn resolve_completions(
21954 &self,
21955 _buffer: Entity<Buffer>,
21956 _completion_indices: Vec<usize>,
21957 _completions: Rc<RefCell<Box<[Completion]>>>,
21958 _cx: &mut Context<Editor>,
21959 ) -> Task<Result<bool>> {
21960 Task::ready(Ok(false))
21961 }
21962
21963 fn apply_additional_edits_for_completion(
21964 &self,
21965 _buffer: Entity<Buffer>,
21966 _completions: Rc<RefCell<Box<[Completion]>>>,
21967 _completion_index: usize,
21968 _push_to_history: bool,
21969 _cx: &mut Context<Editor>,
21970 ) -> Task<Result<Option<language::Transaction>>> {
21971 Task::ready(Ok(None))
21972 }
21973
21974 fn is_completion_trigger(
21975 &self,
21976 buffer: &Entity<Buffer>,
21977 position: language::Anchor,
21978 text: &str,
21979 trigger_in_words: bool,
21980 menu_is_open: bool,
21981 cx: &mut Context<Editor>,
21982 ) -> bool;
21983
21984 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21985
21986 fn sort_completions(&self) -> bool {
21987 true
21988 }
21989
21990 fn filter_completions(&self) -> bool {
21991 true
21992 }
21993}
21994
21995pub trait CodeActionProvider {
21996 fn id(&self) -> Arc<str>;
21997
21998 fn code_actions(
21999 &self,
22000 buffer: &Entity<Buffer>,
22001 range: Range<text::Anchor>,
22002 window: &mut Window,
22003 cx: &mut App,
22004 ) -> Task<Result<Vec<CodeAction>>>;
22005
22006 fn apply_code_action(
22007 &self,
22008 buffer_handle: Entity<Buffer>,
22009 action: CodeAction,
22010 excerpt_id: ExcerptId,
22011 push_to_history: bool,
22012 window: &mut Window,
22013 cx: &mut App,
22014 ) -> Task<Result<ProjectTransaction>>;
22015}
22016
22017impl CodeActionProvider for Entity<Project> {
22018 fn id(&self) -> Arc<str> {
22019 "project".into()
22020 }
22021
22022 fn code_actions(
22023 &self,
22024 buffer: &Entity<Buffer>,
22025 range: Range<text::Anchor>,
22026 _window: &mut Window,
22027 cx: &mut App,
22028 ) -> Task<Result<Vec<CodeAction>>> {
22029 self.update(cx, |project, cx| {
22030 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22031 let code_actions = project.code_actions(buffer, range, None, cx);
22032 cx.background_spawn(async move {
22033 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22034 Ok(code_lens_actions
22035 .context("code lens fetch")?
22036 .into_iter()
22037 .flatten()
22038 .chain(
22039 code_actions
22040 .context("code action fetch")?
22041 .into_iter()
22042 .flatten(),
22043 )
22044 .collect())
22045 })
22046 })
22047 }
22048
22049 fn apply_code_action(
22050 &self,
22051 buffer_handle: Entity<Buffer>,
22052 action: CodeAction,
22053 _excerpt_id: ExcerptId,
22054 push_to_history: bool,
22055 _window: &mut Window,
22056 cx: &mut App,
22057 ) -> Task<Result<ProjectTransaction>> {
22058 self.update(cx, |project, cx| {
22059 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22060 })
22061 }
22062}
22063
22064fn snippet_completions(
22065 project: &Project,
22066 buffer: &Entity<Buffer>,
22067 buffer_position: text::Anchor,
22068 cx: &mut App,
22069) -> Task<Result<CompletionResponse>> {
22070 let languages = buffer.read(cx).languages_at(buffer_position);
22071 let snippet_store = project.snippets().read(cx);
22072
22073 let scopes: Vec<_> = languages
22074 .iter()
22075 .filter_map(|language| {
22076 let language_name = language.lsp_id();
22077 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22078
22079 if snippets.is_empty() {
22080 None
22081 } else {
22082 Some((language.default_scope(), snippets))
22083 }
22084 })
22085 .collect();
22086
22087 if scopes.is_empty() {
22088 return Task::ready(Ok(CompletionResponse {
22089 completions: vec![],
22090 is_incomplete: false,
22091 }));
22092 }
22093
22094 let snapshot = buffer.read(cx).text_snapshot();
22095 let chars: String = snapshot
22096 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22097 .collect();
22098 let executor = cx.background_executor().clone();
22099
22100 cx.background_spawn(async move {
22101 let mut is_incomplete = false;
22102 let mut completions: Vec<Completion> = Vec::new();
22103 for (scope, snippets) in scopes.into_iter() {
22104 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22105 let mut last_word = chars
22106 .chars()
22107 .take_while(|c| classifier.is_word(*c))
22108 .collect::<String>();
22109 last_word = last_word.chars().rev().collect();
22110
22111 if last_word.is_empty() {
22112 return Ok(CompletionResponse {
22113 completions: vec![],
22114 is_incomplete: true,
22115 });
22116 }
22117
22118 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22119 let to_lsp = |point: &text::Anchor| {
22120 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22121 point_to_lsp(end)
22122 };
22123 let lsp_end = to_lsp(&buffer_position);
22124
22125 let candidates = snippets
22126 .iter()
22127 .enumerate()
22128 .flat_map(|(ix, snippet)| {
22129 snippet
22130 .prefix
22131 .iter()
22132 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22133 })
22134 .collect::<Vec<StringMatchCandidate>>();
22135
22136 const MAX_RESULTS: usize = 100;
22137 let mut matches = fuzzy::match_strings(
22138 &candidates,
22139 &last_word,
22140 last_word.chars().any(|c| c.is_uppercase()),
22141 true,
22142 MAX_RESULTS,
22143 &Default::default(),
22144 executor.clone(),
22145 )
22146 .await;
22147
22148 if matches.len() >= MAX_RESULTS {
22149 is_incomplete = true;
22150 }
22151
22152 // Remove all candidates where the query's start does not match the start of any word in the candidate
22153 if let Some(query_start) = last_word.chars().next() {
22154 matches.retain(|string_match| {
22155 split_words(&string_match.string).any(|word| {
22156 // Check that the first codepoint of the word as lowercase matches the first
22157 // codepoint of the query as lowercase
22158 word.chars()
22159 .flat_map(|codepoint| codepoint.to_lowercase())
22160 .zip(query_start.to_lowercase())
22161 .all(|(word_cp, query_cp)| word_cp == query_cp)
22162 })
22163 });
22164 }
22165
22166 let matched_strings = matches
22167 .into_iter()
22168 .map(|m| m.string)
22169 .collect::<HashSet<_>>();
22170
22171 completions.extend(snippets.iter().filter_map(|snippet| {
22172 let matching_prefix = snippet
22173 .prefix
22174 .iter()
22175 .find(|prefix| matched_strings.contains(*prefix))?;
22176 let start = as_offset - last_word.len();
22177 let start = snapshot.anchor_before(start);
22178 let range = start..buffer_position;
22179 let lsp_start = to_lsp(&start);
22180 let lsp_range = lsp::Range {
22181 start: lsp_start,
22182 end: lsp_end,
22183 };
22184 Some(Completion {
22185 replace_range: range,
22186 new_text: snippet.body.clone(),
22187 source: CompletionSource::Lsp {
22188 insert_range: None,
22189 server_id: LanguageServerId(usize::MAX),
22190 resolved: true,
22191 lsp_completion: Box::new(lsp::CompletionItem {
22192 label: snippet.prefix.first().unwrap().clone(),
22193 kind: Some(CompletionItemKind::SNIPPET),
22194 label_details: snippet.description.as_ref().map(|description| {
22195 lsp::CompletionItemLabelDetails {
22196 detail: Some(description.clone()),
22197 description: None,
22198 }
22199 }),
22200 insert_text_format: Some(InsertTextFormat::SNIPPET),
22201 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22202 lsp::InsertReplaceEdit {
22203 new_text: snippet.body.clone(),
22204 insert: lsp_range,
22205 replace: lsp_range,
22206 },
22207 )),
22208 filter_text: Some(snippet.body.clone()),
22209 sort_text: Some(char::MAX.to_string()),
22210 ..lsp::CompletionItem::default()
22211 }),
22212 lsp_defaults: None,
22213 },
22214 label: CodeLabel {
22215 text: matching_prefix.clone(),
22216 runs: Vec::new(),
22217 filter_range: 0..matching_prefix.len(),
22218 },
22219 icon_path: None,
22220 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22221 single_line: snippet.name.clone().into(),
22222 plain_text: snippet
22223 .description
22224 .clone()
22225 .map(|description| description.into()),
22226 }),
22227 insert_text_mode: None,
22228 confirm: None,
22229 })
22230 }))
22231 }
22232
22233 Ok(CompletionResponse {
22234 completions,
22235 is_incomplete,
22236 })
22237 })
22238}
22239
22240impl CompletionProvider for Entity<Project> {
22241 fn completions(
22242 &self,
22243 _excerpt_id: ExcerptId,
22244 buffer: &Entity<Buffer>,
22245 buffer_position: text::Anchor,
22246 options: CompletionContext,
22247 _window: &mut Window,
22248 cx: &mut Context<Editor>,
22249 ) -> Task<Result<Vec<CompletionResponse>>> {
22250 self.update(cx, |project, cx| {
22251 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22252 let project_completions = project.completions(buffer, buffer_position, options, cx);
22253 cx.background_spawn(async move {
22254 let mut responses = project_completions.await?;
22255 let snippets = snippets.await?;
22256 if !snippets.completions.is_empty() {
22257 responses.push(snippets);
22258 }
22259 Ok(responses)
22260 })
22261 })
22262 }
22263
22264 fn resolve_completions(
22265 &self,
22266 buffer: Entity<Buffer>,
22267 completion_indices: Vec<usize>,
22268 completions: Rc<RefCell<Box<[Completion]>>>,
22269 cx: &mut Context<Editor>,
22270 ) -> Task<Result<bool>> {
22271 self.update(cx, |project, cx| {
22272 project.lsp_store().update(cx, |lsp_store, cx| {
22273 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22274 })
22275 })
22276 }
22277
22278 fn apply_additional_edits_for_completion(
22279 &self,
22280 buffer: Entity<Buffer>,
22281 completions: Rc<RefCell<Box<[Completion]>>>,
22282 completion_index: usize,
22283 push_to_history: bool,
22284 cx: &mut Context<Editor>,
22285 ) -> Task<Result<Option<language::Transaction>>> {
22286 self.update(cx, |project, cx| {
22287 project.lsp_store().update(cx, |lsp_store, cx| {
22288 lsp_store.apply_additional_edits_for_completion(
22289 buffer,
22290 completions,
22291 completion_index,
22292 push_to_history,
22293 cx,
22294 )
22295 })
22296 })
22297 }
22298
22299 fn is_completion_trigger(
22300 &self,
22301 buffer: &Entity<Buffer>,
22302 position: language::Anchor,
22303 text: &str,
22304 trigger_in_words: bool,
22305 menu_is_open: bool,
22306 cx: &mut Context<Editor>,
22307 ) -> bool {
22308 let mut chars = text.chars();
22309 let char = if let Some(char) = chars.next() {
22310 char
22311 } else {
22312 return false;
22313 };
22314 if chars.next().is_some() {
22315 return false;
22316 }
22317
22318 let buffer = buffer.read(cx);
22319 let snapshot = buffer.snapshot();
22320 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22321 return false;
22322 }
22323 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22324 if trigger_in_words && classifier.is_word(char) {
22325 return true;
22326 }
22327
22328 buffer.completion_triggers().contains(text)
22329 }
22330}
22331
22332impl SemanticsProvider for Entity<Project> {
22333 fn hover(
22334 &self,
22335 buffer: &Entity<Buffer>,
22336 position: text::Anchor,
22337 cx: &mut App,
22338 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22339 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22340 }
22341
22342 fn document_highlights(
22343 &self,
22344 buffer: &Entity<Buffer>,
22345 position: text::Anchor,
22346 cx: &mut App,
22347 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22348 Some(self.update(cx, |project, cx| {
22349 project.document_highlights(buffer, position, cx)
22350 }))
22351 }
22352
22353 fn definitions(
22354 &self,
22355 buffer: &Entity<Buffer>,
22356 position: text::Anchor,
22357 kind: GotoDefinitionKind,
22358 cx: &mut App,
22359 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22360 Some(self.update(cx, |project, cx| match kind {
22361 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22362 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22363 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22364 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22365 }))
22366 }
22367
22368 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22369 self.update(cx, |project, cx| {
22370 if project
22371 .active_debug_session(cx)
22372 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22373 {
22374 return true;
22375 }
22376
22377 buffer.update(cx, |buffer, cx| {
22378 project.any_language_server_supports_inlay_hints(buffer, cx)
22379 })
22380 })
22381 }
22382
22383 fn inline_values(
22384 &self,
22385 buffer_handle: Entity<Buffer>,
22386 range: Range<text::Anchor>,
22387 cx: &mut App,
22388 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22389 self.update(cx, |project, cx| {
22390 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22391
22392 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22393 })
22394 }
22395
22396 fn inlay_hints(
22397 &self,
22398 buffer_handle: Entity<Buffer>,
22399 range: Range<text::Anchor>,
22400 cx: &mut App,
22401 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22402 Some(self.update(cx, |project, cx| {
22403 project.inlay_hints(buffer_handle, range, cx)
22404 }))
22405 }
22406
22407 fn resolve_inlay_hint(
22408 &self,
22409 hint: InlayHint,
22410 buffer_handle: Entity<Buffer>,
22411 server_id: LanguageServerId,
22412 cx: &mut App,
22413 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22414 Some(self.update(cx, |project, cx| {
22415 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22416 }))
22417 }
22418
22419 fn range_for_rename(
22420 &self,
22421 buffer: &Entity<Buffer>,
22422 position: text::Anchor,
22423 cx: &mut App,
22424 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22425 Some(self.update(cx, |project, cx| {
22426 let buffer = buffer.clone();
22427 let task = project.prepare_rename(buffer.clone(), position, cx);
22428 cx.spawn(async move |_, cx| {
22429 Ok(match task.await? {
22430 PrepareRenameResponse::Success(range) => Some(range),
22431 PrepareRenameResponse::InvalidPosition => None,
22432 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22433 // Fallback on using TreeSitter info to determine identifier range
22434 buffer.read_with(cx, |buffer, _| {
22435 let snapshot = buffer.snapshot();
22436 let (range, kind) = snapshot.surrounding_word(position, false);
22437 if kind != Some(CharKind::Word) {
22438 return None;
22439 }
22440 Some(
22441 snapshot.anchor_before(range.start)
22442 ..snapshot.anchor_after(range.end),
22443 )
22444 })?
22445 }
22446 })
22447 })
22448 }))
22449 }
22450
22451 fn perform_rename(
22452 &self,
22453 buffer: &Entity<Buffer>,
22454 position: text::Anchor,
22455 new_name: String,
22456 cx: &mut App,
22457 ) -> Option<Task<Result<ProjectTransaction>>> {
22458 Some(self.update(cx, |project, cx| {
22459 project.perform_rename(buffer.clone(), position, new_name, cx)
22460 }))
22461 }
22462}
22463
22464fn inlay_hint_settings(
22465 location: Anchor,
22466 snapshot: &MultiBufferSnapshot,
22467 cx: &mut Context<Editor>,
22468) -> InlayHintSettings {
22469 let file = snapshot.file_at(location);
22470 let language = snapshot.language_at(location).map(|l| l.name());
22471 language_settings(language, file, cx).inlay_hints
22472}
22473
22474fn consume_contiguous_rows(
22475 contiguous_row_selections: &mut Vec<Selection<Point>>,
22476 selection: &Selection<Point>,
22477 display_map: &DisplaySnapshot,
22478 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22479) -> (MultiBufferRow, MultiBufferRow) {
22480 contiguous_row_selections.push(selection.clone());
22481 let start_row = starting_row(selection, display_map);
22482 let mut end_row = ending_row(selection, display_map);
22483
22484 while let Some(next_selection) = selections.peek() {
22485 if next_selection.start.row <= end_row.0 {
22486 end_row = ending_row(next_selection, display_map);
22487 contiguous_row_selections.push(selections.next().unwrap().clone());
22488 } else {
22489 break;
22490 }
22491 }
22492 (start_row, end_row)
22493}
22494
22495fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22496 if selection.start.column > 0 {
22497 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22498 } else {
22499 MultiBufferRow(selection.start.row)
22500 }
22501}
22502
22503fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22504 if next_selection.end.column > 0 || next_selection.is_empty() {
22505 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22506 } else {
22507 MultiBufferRow(next_selection.end.row)
22508 }
22509}
22510
22511impl EditorSnapshot {
22512 pub fn remote_selections_in_range<'a>(
22513 &'a self,
22514 range: &'a Range<Anchor>,
22515 collaboration_hub: &dyn CollaborationHub,
22516 cx: &'a App,
22517 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22518 let participant_names = collaboration_hub.user_names(cx);
22519 let participant_indices = collaboration_hub.user_participant_indices(cx);
22520 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22521 let collaborators_by_replica_id = collaborators_by_peer_id
22522 .values()
22523 .map(|collaborator| (collaborator.replica_id, collaborator))
22524 .collect::<HashMap<_, _>>();
22525 self.buffer_snapshot
22526 .selections_in_range(range, false)
22527 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22528 if replica_id == AGENT_REPLICA_ID {
22529 Some(RemoteSelection {
22530 replica_id,
22531 selection,
22532 cursor_shape,
22533 line_mode,
22534 collaborator_id: CollaboratorId::Agent,
22535 user_name: Some("Agent".into()),
22536 color: cx.theme().players().agent(),
22537 })
22538 } else {
22539 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22540 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22541 let user_name = participant_names.get(&collaborator.user_id).cloned();
22542 Some(RemoteSelection {
22543 replica_id,
22544 selection,
22545 cursor_shape,
22546 line_mode,
22547 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22548 user_name,
22549 color: if let Some(index) = participant_index {
22550 cx.theme().players().color_for_participant(index.0)
22551 } else {
22552 cx.theme().players().absent()
22553 },
22554 })
22555 }
22556 })
22557 }
22558
22559 pub fn hunks_for_ranges(
22560 &self,
22561 ranges: impl IntoIterator<Item = Range<Point>>,
22562 ) -> Vec<MultiBufferDiffHunk> {
22563 let mut hunks = Vec::new();
22564 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22565 HashMap::default();
22566 for query_range in ranges {
22567 let query_rows =
22568 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22569 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22570 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22571 ) {
22572 // Include deleted hunks that are adjacent to the query range, because
22573 // otherwise they would be missed.
22574 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22575 if hunk.status().is_deleted() {
22576 intersects_range |= hunk.row_range.start == query_rows.end;
22577 intersects_range |= hunk.row_range.end == query_rows.start;
22578 }
22579 if intersects_range {
22580 if !processed_buffer_rows
22581 .entry(hunk.buffer_id)
22582 .or_default()
22583 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22584 {
22585 continue;
22586 }
22587 hunks.push(hunk);
22588 }
22589 }
22590 }
22591
22592 hunks
22593 }
22594
22595 fn display_diff_hunks_for_rows<'a>(
22596 &'a self,
22597 display_rows: Range<DisplayRow>,
22598 folded_buffers: &'a HashSet<BufferId>,
22599 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22600 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22601 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22602
22603 self.buffer_snapshot
22604 .diff_hunks_in_range(buffer_start..buffer_end)
22605 .filter_map(|hunk| {
22606 if folded_buffers.contains(&hunk.buffer_id) {
22607 return None;
22608 }
22609
22610 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22611 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22612
22613 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22614 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22615
22616 let display_hunk = if hunk_display_start.column() != 0 {
22617 DisplayDiffHunk::Folded {
22618 display_row: hunk_display_start.row(),
22619 }
22620 } else {
22621 let mut end_row = hunk_display_end.row();
22622 if hunk_display_end.column() > 0 {
22623 end_row.0 += 1;
22624 }
22625 let is_created_file = hunk.is_created_file();
22626 DisplayDiffHunk::Unfolded {
22627 status: hunk.status(),
22628 diff_base_byte_range: hunk.diff_base_byte_range,
22629 display_row_range: hunk_display_start.row()..end_row,
22630 multi_buffer_range: Anchor::range_in_buffer(
22631 hunk.excerpt_id,
22632 hunk.buffer_id,
22633 hunk.buffer_range,
22634 ),
22635 is_created_file,
22636 }
22637 };
22638
22639 Some(display_hunk)
22640 })
22641 }
22642
22643 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22644 self.display_snapshot.buffer_snapshot.language_at(position)
22645 }
22646
22647 pub fn is_focused(&self) -> bool {
22648 self.is_focused
22649 }
22650
22651 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22652 self.placeholder_text.as_ref()
22653 }
22654
22655 pub fn scroll_position(&self) -> gpui::Point<f32> {
22656 self.scroll_anchor.scroll_position(&self.display_snapshot)
22657 }
22658
22659 fn gutter_dimensions(
22660 &self,
22661 font_id: FontId,
22662 font_size: Pixels,
22663 max_line_number_width: Pixels,
22664 cx: &App,
22665 ) -> Option<GutterDimensions> {
22666 if !self.show_gutter {
22667 return None;
22668 }
22669
22670 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22671 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22672
22673 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22674 matches!(
22675 ProjectSettings::get_global(cx).git.git_gutter,
22676 Some(GitGutterSetting::TrackedFiles)
22677 )
22678 });
22679 let gutter_settings = EditorSettings::get_global(cx).gutter;
22680 let show_line_numbers = self
22681 .show_line_numbers
22682 .unwrap_or(gutter_settings.line_numbers);
22683 let line_gutter_width = if show_line_numbers {
22684 // Avoid flicker-like gutter resizes when the line number gains another digit by
22685 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22686 let min_width_for_number_on_gutter =
22687 ch_advance * gutter_settings.min_line_number_digits as f32;
22688 max_line_number_width.max(min_width_for_number_on_gutter)
22689 } else {
22690 0.0.into()
22691 };
22692
22693 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22694 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22695
22696 let git_blame_entries_width =
22697 self.git_blame_gutter_max_author_length
22698 .map(|max_author_length| {
22699 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22700 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22701
22702 /// The number of characters to dedicate to gaps and margins.
22703 const SPACING_WIDTH: usize = 4;
22704
22705 let max_char_count = max_author_length.min(renderer.max_author_length())
22706 + ::git::SHORT_SHA_LENGTH
22707 + MAX_RELATIVE_TIMESTAMP.len()
22708 + SPACING_WIDTH;
22709
22710 ch_advance * max_char_count
22711 });
22712
22713 let is_singleton = self.buffer_snapshot.is_singleton();
22714
22715 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22716 left_padding += if !is_singleton {
22717 ch_width * 4.0
22718 } else if show_runnables || show_breakpoints {
22719 ch_width * 3.0
22720 } else if show_git_gutter && show_line_numbers {
22721 ch_width * 2.0
22722 } else if show_git_gutter || show_line_numbers {
22723 ch_width
22724 } else {
22725 px(0.)
22726 };
22727
22728 let shows_folds = is_singleton && gutter_settings.folds;
22729
22730 let right_padding = if shows_folds && show_line_numbers {
22731 ch_width * 4.0
22732 } else if shows_folds || (!is_singleton && show_line_numbers) {
22733 ch_width * 3.0
22734 } else if show_line_numbers {
22735 ch_width
22736 } else {
22737 px(0.)
22738 };
22739
22740 Some(GutterDimensions {
22741 left_padding,
22742 right_padding,
22743 width: line_gutter_width + left_padding + right_padding,
22744 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22745 git_blame_entries_width,
22746 })
22747 }
22748
22749 pub fn render_crease_toggle(
22750 &self,
22751 buffer_row: MultiBufferRow,
22752 row_contains_cursor: bool,
22753 editor: Entity<Editor>,
22754 window: &mut Window,
22755 cx: &mut App,
22756 ) -> Option<AnyElement> {
22757 let folded = self.is_line_folded(buffer_row);
22758 let mut is_foldable = false;
22759
22760 if let Some(crease) = self
22761 .crease_snapshot
22762 .query_row(buffer_row, &self.buffer_snapshot)
22763 {
22764 is_foldable = true;
22765 match crease {
22766 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22767 if let Some(render_toggle) = render_toggle {
22768 let toggle_callback =
22769 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22770 if folded {
22771 editor.update(cx, |editor, cx| {
22772 editor.fold_at(buffer_row, window, cx)
22773 });
22774 } else {
22775 editor.update(cx, |editor, cx| {
22776 editor.unfold_at(buffer_row, window, cx)
22777 });
22778 }
22779 });
22780 return Some((render_toggle)(
22781 buffer_row,
22782 folded,
22783 toggle_callback,
22784 window,
22785 cx,
22786 ));
22787 }
22788 }
22789 }
22790 }
22791
22792 is_foldable |= self.starts_indent(buffer_row);
22793
22794 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22795 Some(
22796 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22797 .toggle_state(folded)
22798 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22799 if folded {
22800 this.unfold_at(buffer_row, window, cx);
22801 } else {
22802 this.fold_at(buffer_row, window, cx);
22803 }
22804 }))
22805 .into_any_element(),
22806 )
22807 } else {
22808 None
22809 }
22810 }
22811
22812 pub fn render_crease_trailer(
22813 &self,
22814 buffer_row: MultiBufferRow,
22815 window: &mut Window,
22816 cx: &mut App,
22817 ) -> Option<AnyElement> {
22818 let folded = self.is_line_folded(buffer_row);
22819 if let Crease::Inline { render_trailer, .. } = self
22820 .crease_snapshot
22821 .query_row(buffer_row, &self.buffer_snapshot)?
22822 {
22823 let render_trailer = render_trailer.as_ref()?;
22824 Some(render_trailer(buffer_row, folded, window, cx))
22825 } else {
22826 None
22827 }
22828 }
22829}
22830
22831impl Deref for EditorSnapshot {
22832 type Target = DisplaySnapshot;
22833
22834 fn deref(&self) -> &Self::Target {
22835 &self.display_snapshot
22836 }
22837}
22838
22839#[derive(Clone, Debug, PartialEq, Eq)]
22840pub enum EditorEvent {
22841 InputIgnored {
22842 text: Arc<str>,
22843 },
22844 InputHandled {
22845 utf16_range_to_replace: Option<Range<isize>>,
22846 text: Arc<str>,
22847 },
22848 ExcerptsAdded {
22849 buffer: Entity<Buffer>,
22850 predecessor: ExcerptId,
22851 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22852 },
22853 ExcerptsRemoved {
22854 ids: Vec<ExcerptId>,
22855 removed_buffer_ids: Vec<BufferId>,
22856 },
22857 BufferFoldToggled {
22858 ids: Vec<ExcerptId>,
22859 folded: bool,
22860 },
22861 ExcerptsEdited {
22862 ids: Vec<ExcerptId>,
22863 },
22864 ExcerptsExpanded {
22865 ids: Vec<ExcerptId>,
22866 },
22867 BufferEdited,
22868 Edited {
22869 transaction_id: clock::Lamport,
22870 },
22871 Reparsed(BufferId),
22872 Focused,
22873 FocusedIn,
22874 Blurred,
22875 DirtyChanged,
22876 Saved,
22877 TitleChanged,
22878 DiffBaseChanged,
22879 SelectionsChanged {
22880 local: bool,
22881 },
22882 ScrollPositionChanged {
22883 local: bool,
22884 autoscroll: bool,
22885 },
22886 Closed,
22887 TransactionUndone {
22888 transaction_id: clock::Lamport,
22889 },
22890 TransactionBegun {
22891 transaction_id: clock::Lamport,
22892 },
22893 Reloaded,
22894 CursorShapeChanged,
22895 BreadcrumbsChanged,
22896 PushedToNavHistory {
22897 anchor: Anchor,
22898 is_deactivate: bool,
22899 },
22900}
22901
22902impl EventEmitter<EditorEvent> for Editor {}
22903
22904impl Focusable for Editor {
22905 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22906 self.focus_handle.clone()
22907 }
22908}
22909
22910impl Render for Editor {
22911 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22912 let settings = ThemeSettings::get_global(cx);
22913
22914 let mut text_style = match self.mode {
22915 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
22916 color: cx.theme().colors().editor_foreground,
22917 font_family: settings.ui_font.family.clone(),
22918 font_features: settings.ui_font.features.clone(),
22919 font_fallbacks: settings.ui_font.fallbacks.clone(),
22920 font_size: rems(0.875).into(),
22921 font_weight: settings.ui_font.weight,
22922 line_height: relative(settings.buffer_line_height.value()),
22923 ..Default::default()
22924 },
22925 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22926 color: cx.theme().colors().editor_foreground,
22927 font_family: settings.buffer_font.family.clone(),
22928 font_features: settings.buffer_font.features.clone(),
22929 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22930 font_size: settings.buffer_font_size(cx).into(),
22931 font_weight: settings.buffer_font.weight,
22932 line_height: relative(settings.buffer_line_height.value()),
22933 ..Default::default()
22934 },
22935 };
22936 if let Some(text_style_refinement) = &self.text_style_refinement {
22937 text_style.refine(text_style_refinement)
22938 }
22939
22940 let background = match self.mode {
22941 EditorMode::SingleLine => cx.theme().system().transparent,
22942 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22943 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22944 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22945 };
22946
22947 EditorElement::new(
22948 &cx.entity(),
22949 EditorStyle {
22950 background,
22951 border: cx.theme().colors().border,
22952 local_player: cx.theme().players().local(),
22953 text: text_style,
22954 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22955 syntax: cx.theme().syntax().clone(),
22956 status: cx.theme().status().clone(),
22957 inlay_hints_style: make_inlay_hints_style(cx),
22958 edit_prediction_styles: make_suggestion_styles(cx),
22959 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22960 show_underlines: self.diagnostics_enabled(),
22961 },
22962 )
22963 }
22964}
22965
22966impl EntityInputHandler for Editor {
22967 fn text_for_range(
22968 &mut self,
22969 range_utf16: Range<usize>,
22970 adjusted_range: &mut Option<Range<usize>>,
22971 _: &mut Window,
22972 cx: &mut Context<Self>,
22973 ) -> Option<String> {
22974 let snapshot = self.buffer.read(cx).read(cx);
22975 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22976 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22977 if (start.0..end.0) != range_utf16 {
22978 adjusted_range.replace(start.0..end.0);
22979 }
22980 Some(snapshot.text_for_range(start..end).collect())
22981 }
22982
22983 fn selected_text_range(
22984 &mut self,
22985 ignore_disabled_input: bool,
22986 _: &mut Window,
22987 cx: &mut Context<Self>,
22988 ) -> Option<UTF16Selection> {
22989 // Prevent the IME menu from appearing when holding down an alphabetic key
22990 // while input is disabled.
22991 if !ignore_disabled_input && !self.input_enabled {
22992 return None;
22993 }
22994
22995 let selection = self.selections.newest::<OffsetUtf16>(cx);
22996 let range = selection.range();
22997
22998 Some(UTF16Selection {
22999 range: range.start.0..range.end.0,
23000 reversed: selection.reversed,
23001 })
23002 }
23003
23004 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23005 let snapshot = self.buffer.read(cx).read(cx);
23006 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23007 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23008 }
23009
23010 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23011 self.clear_highlights::<InputComposition>(cx);
23012 self.ime_transaction.take();
23013 }
23014
23015 fn replace_text_in_range(
23016 &mut self,
23017 range_utf16: Option<Range<usize>>,
23018 text: &str,
23019 window: &mut Window,
23020 cx: &mut Context<Self>,
23021 ) {
23022 if !self.input_enabled {
23023 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23024 return;
23025 }
23026
23027 self.transact(window, cx, |this, window, cx| {
23028 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23029 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23030 Some(this.selection_replacement_ranges(range_utf16, cx))
23031 } else {
23032 this.marked_text_ranges(cx)
23033 };
23034
23035 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23036 let newest_selection_id = this.selections.newest_anchor().id;
23037 this.selections
23038 .all::<OffsetUtf16>(cx)
23039 .iter()
23040 .zip(ranges_to_replace.iter())
23041 .find_map(|(selection, range)| {
23042 if selection.id == newest_selection_id {
23043 Some(
23044 (range.start.0 as isize - selection.head().0 as isize)
23045 ..(range.end.0 as isize - selection.head().0 as isize),
23046 )
23047 } else {
23048 None
23049 }
23050 })
23051 });
23052
23053 cx.emit(EditorEvent::InputHandled {
23054 utf16_range_to_replace: range_to_replace,
23055 text: text.into(),
23056 });
23057
23058 if let Some(new_selected_ranges) = new_selected_ranges {
23059 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23060 selections.select_ranges(new_selected_ranges)
23061 });
23062 this.backspace(&Default::default(), window, cx);
23063 }
23064
23065 this.handle_input(text, window, cx);
23066 });
23067
23068 if let Some(transaction) = self.ime_transaction {
23069 self.buffer.update(cx, |buffer, cx| {
23070 buffer.group_until_transaction(transaction, cx);
23071 });
23072 }
23073
23074 self.unmark_text(window, cx);
23075 }
23076
23077 fn replace_and_mark_text_in_range(
23078 &mut self,
23079 range_utf16: Option<Range<usize>>,
23080 text: &str,
23081 new_selected_range_utf16: Option<Range<usize>>,
23082 window: &mut Window,
23083 cx: &mut Context<Self>,
23084 ) {
23085 if !self.input_enabled {
23086 return;
23087 }
23088
23089 let transaction = self.transact(window, cx, |this, window, cx| {
23090 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23091 let snapshot = this.buffer.read(cx).read(cx);
23092 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23093 for marked_range in &mut marked_ranges {
23094 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23095 marked_range.start.0 += relative_range_utf16.start;
23096 marked_range.start =
23097 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23098 marked_range.end =
23099 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23100 }
23101 }
23102 Some(marked_ranges)
23103 } else if let Some(range_utf16) = range_utf16 {
23104 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23105 Some(this.selection_replacement_ranges(range_utf16, cx))
23106 } else {
23107 None
23108 };
23109
23110 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23111 let newest_selection_id = this.selections.newest_anchor().id;
23112 this.selections
23113 .all::<OffsetUtf16>(cx)
23114 .iter()
23115 .zip(ranges_to_replace.iter())
23116 .find_map(|(selection, range)| {
23117 if selection.id == newest_selection_id {
23118 Some(
23119 (range.start.0 as isize - selection.head().0 as isize)
23120 ..(range.end.0 as isize - selection.head().0 as isize),
23121 )
23122 } else {
23123 None
23124 }
23125 })
23126 });
23127
23128 cx.emit(EditorEvent::InputHandled {
23129 utf16_range_to_replace: range_to_replace,
23130 text: text.into(),
23131 });
23132
23133 if let Some(ranges) = ranges_to_replace {
23134 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23135 s.select_ranges(ranges)
23136 });
23137 }
23138
23139 let marked_ranges = {
23140 let snapshot = this.buffer.read(cx).read(cx);
23141 this.selections
23142 .disjoint_anchors()
23143 .iter()
23144 .map(|selection| {
23145 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23146 })
23147 .collect::<Vec<_>>()
23148 };
23149
23150 if text.is_empty() {
23151 this.unmark_text(window, cx);
23152 } else {
23153 this.highlight_text::<InputComposition>(
23154 marked_ranges.clone(),
23155 HighlightStyle {
23156 underline: Some(UnderlineStyle {
23157 thickness: px(1.),
23158 color: None,
23159 wavy: false,
23160 }),
23161 ..Default::default()
23162 },
23163 cx,
23164 );
23165 }
23166
23167 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23168 let use_autoclose = this.use_autoclose;
23169 let use_auto_surround = this.use_auto_surround;
23170 this.set_use_autoclose(false);
23171 this.set_use_auto_surround(false);
23172 this.handle_input(text, window, cx);
23173 this.set_use_autoclose(use_autoclose);
23174 this.set_use_auto_surround(use_auto_surround);
23175
23176 if let Some(new_selected_range) = new_selected_range_utf16 {
23177 let snapshot = this.buffer.read(cx).read(cx);
23178 let new_selected_ranges = marked_ranges
23179 .into_iter()
23180 .map(|marked_range| {
23181 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23182 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23183 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23184 snapshot.clip_offset_utf16(new_start, Bias::Left)
23185 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23186 })
23187 .collect::<Vec<_>>();
23188
23189 drop(snapshot);
23190 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23191 selections.select_ranges(new_selected_ranges)
23192 });
23193 }
23194 });
23195
23196 self.ime_transaction = self.ime_transaction.or(transaction);
23197 if let Some(transaction) = self.ime_transaction {
23198 self.buffer.update(cx, |buffer, cx| {
23199 buffer.group_until_transaction(transaction, cx);
23200 });
23201 }
23202
23203 if self.text_highlights::<InputComposition>(cx).is_none() {
23204 self.ime_transaction.take();
23205 }
23206 }
23207
23208 fn bounds_for_range(
23209 &mut self,
23210 range_utf16: Range<usize>,
23211 element_bounds: gpui::Bounds<Pixels>,
23212 window: &mut Window,
23213 cx: &mut Context<Self>,
23214 ) -> Option<gpui::Bounds<Pixels>> {
23215 let text_layout_details = self.text_layout_details(window);
23216 let CharacterDimensions {
23217 em_width,
23218 em_advance,
23219 line_height,
23220 } = self.character_dimensions(window);
23221
23222 let snapshot = self.snapshot(window, cx);
23223 let scroll_position = snapshot.scroll_position();
23224 let scroll_left = scroll_position.x * em_advance;
23225
23226 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23227 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23228 + self.gutter_dimensions.full_width();
23229 let y = line_height * (start.row().as_f32() - scroll_position.y);
23230
23231 Some(Bounds {
23232 origin: element_bounds.origin + point(x, y),
23233 size: size(em_width, line_height),
23234 })
23235 }
23236
23237 fn character_index_for_point(
23238 &mut self,
23239 point: gpui::Point<Pixels>,
23240 _window: &mut Window,
23241 _cx: &mut Context<Self>,
23242 ) -> Option<usize> {
23243 let position_map = self.last_position_map.as_ref()?;
23244 if !position_map.text_hitbox.contains(&point) {
23245 return None;
23246 }
23247 let display_point = position_map.point_for_position(point).previous_valid;
23248 let anchor = position_map
23249 .snapshot
23250 .display_point_to_anchor(display_point, Bias::Left);
23251 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23252 Some(utf16_offset.0)
23253 }
23254}
23255
23256trait SelectionExt {
23257 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23258 fn spanned_rows(
23259 &self,
23260 include_end_if_at_line_start: bool,
23261 map: &DisplaySnapshot,
23262 ) -> Range<MultiBufferRow>;
23263}
23264
23265impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23266 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23267 let start = self
23268 .start
23269 .to_point(&map.buffer_snapshot)
23270 .to_display_point(map);
23271 let end = self
23272 .end
23273 .to_point(&map.buffer_snapshot)
23274 .to_display_point(map);
23275 if self.reversed {
23276 end..start
23277 } else {
23278 start..end
23279 }
23280 }
23281
23282 fn spanned_rows(
23283 &self,
23284 include_end_if_at_line_start: bool,
23285 map: &DisplaySnapshot,
23286 ) -> Range<MultiBufferRow> {
23287 let start = self.start.to_point(&map.buffer_snapshot);
23288 let mut end = self.end.to_point(&map.buffer_snapshot);
23289 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23290 end.row -= 1;
23291 }
23292
23293 let buffer_start = map.prev_line_boundary(start).0;
23294 let buffer_end = map.next_line_boundary(end).0;
23295 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23296 }
23297}
23298
23299impl<T: InvalidationRegion> InvalidationStack<T> {
23300 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23301 where
23302 S: Clone + ToOffset,
23303 {
23304 while let Some(region) = self.last() {
23305 let all_selections_inside_invalidation_ranges =
23306 if selections.len() == region.ranges().len() {
23307 selections
23308 .iter()
23309 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23310 .all(|(selection, invalidation_range)| {
23311 let head = selection.head().to_offset(buffer);
23312 invalidation_range.start <= head && invalidation_range.end >= head
23313 })
23314 } else {
23315 false
23316 };
23317
23318 if all_selections_inside_invalidation_ranges {
23319 break;
23320 } else {
23321 self.pop();
23322 }
23323 }
23324 }
23325}
23326
23327impl<T> Default for InvalidationStack<T> {
23328 fn default() -> Self {
23329 Self(Default::default())
23330 }
23331}
23332
23333impl<T> Deref for InvalidationStack<T> {
23334 type Target = Vec<T>;
23335
23336 fn deref(&self) -> &Self::Target {
23337 &self.0
23338 }
23339}
23340
23341impl<T> DerefMut for InvalidationStack<T> {
23342 fn deref_mut(&mut self) -> &mut Self::Target {
23343 &mut self.0
23344 }
23345}
23346
23347impl InvalidationRegion for SnippetState {
23348 fn ranges(&self) -> &[Range<Anchor>] {
23349 &self.ranges[self.active_index]
23350 }
23351}
23352
23353fn edit_prediction_edit_text(
23354 current_snapshot: &BufferSnapshot,
23355 edits: &[(Range<Anchor>, String)],
23356 edit_preview: &EditPreview,
23357 include_deletions: bool,
23358 cx: &App,
23359) -> HighlightedText {
23360 let edits = edits
23361 .iter()
23362 .map(|(anchor, text)| {
23363 (
23364 anchor.start.text_anchor..anchor.end.text_anchor,
23365 text.clone(),
23366 )
23367 })
23368 .collect::<Vec<_>>();
23369
23370 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23371}
23372
23373fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23374 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23375 // Just show the raw edit text with basic styling
23376 let mut text = String::new();
23377 let mut highlights = Vec::new();
23378
23379 let insertion_highlight_style = HighlightStyle {
23380 color: Some(cx.theme().colors().text),
23381 ..Default::default()
23382 };
23383
23384 for (_, edit_text) in edits {
23385 let start_offset = text.len();
23386 text.push_str(edit_text);
23387 let end_offset = text.len();
23388
23389 if start_offset < end_offset {
23390 highlights.push((start_offset..end_offset, insertion_highlight_style));
23391 }
23392 }
23393
23394 HighlightedText {
23395 text: text.into(),
23396 highlights,
23397 }
23398}
23399
23400pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23401 match severity {
23402 lsp::DiagnosticSeverity::ERROR => colors.error,
23403 lsp::DiagnosticSeverity::WARNING => colors.warning,
23404 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23405 lsp::DiagnosticSeverity::HINT => colors.info,
23406 _ => colors.ignored,
23407 }
23408}
23409
23410pub fn styled_runs_for_code_label<'a>(
23411 label: &'a CodeLabel,
23412 syntax_theme: &'a theme::SyntaxTheme,
23413) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23414 let fade_out = HighlightStyle {
23415 fade_out: Some(0.35),
23416 ..Default::default()
23417 };
23418
23419 let mut prev_end = label.filter_range.end;
23420 label
23421 .runs
23422 .iter()
23423 .enumerate()
23424 .flat_map(move |(ix, (range, highlight_id))| {
23425 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23426 style
23427 } else {
23428 return Default::default();
23429 };
23430 let mut muted_style = style;
23431 muted_style.highlight(fade_out);
23432
23433 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23434 if range.start >= label.filter_range.end {
23435 if range.start > prev_end {
23436 runs.push((prev_end..range.start, fade_out));
23437 }
23438 runs.push((range.clone(), muted_style));
23439 } else if range.end <= label.filter_range.end {
23440 runs.push((range.clone(), style));
23441 } else {
23442 runs.push((range.start..label.filter_range.end, style));
23443 runs.push((label.filter_range.end..range.end, muted_style));
23444 }
23445 prev_end = cmp::max(prev_end, range.end);
23446
23447 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23448 runs.push((prev_end..label.text.len(), fade_out));
23449 }
23450
23451 runs
23452 })
23453}
23454
23455pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23456 let mut prev_index = 0;
23457 let mut prev_codepoint: Option<char> = None;
23458 text.char_indices()
23459 .chain([(text.len(), '\0')])
23460 .filter_map(move |(index, codepoint)| {
23461 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23462 let is_boundary = index == text.len()
23463 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23464 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23465 if is_boundary {
23466 let chunk = &text[prev_index..index];
23467 prev_index = index;
23468 Some(chunk)
23469 } else {
23470 None
23471 }
23472 })
23473}
23474
23475pub trait RangeToAnchorExt: Sized {
23476 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23477
23478 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23479 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23480 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23481 }
23482}
23483
23484impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23485 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23486 let start_offset = self.start.to_offset(snapshot);
23487 let end_offset = self.end.to_offset(snapshot);
23488 if start_offset == end_offset {
23489 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23490 } else {
23491 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23492 }
23493 }
23494}
23495
23496pub trait RowExt {
23497 fn as_f32(&self) -> f32;
23498
23499 fn next_row(&self) -> Self;
23500
23501 fn previous_row(&self) -> Self;
23502
23503 fn minus(&self, other: Self) -> u32;
23504}
23505
23506impl RowExt for DisplayRow {
23507 fn as_f32(&self) -> f32 {
23508 self.0 as f32
23509 }
23510
23511 fn next_row(&self) -> Self {
23512 Self(self.0 + 1)
23513 }
23514
23515 fn previous_row(&self) -> Self {
23516 Self(self.0.saturating_sub(1))
23517 }
23518
23519 fn minus(&self, other: Self) -> u32 {
23520 self.0 - other.0
23521 }
23522}
23523
23524impl RowExt for MultiBufferRow {
23525 fn as_f32(&self) -> f32 {
23526 self.0 as f32
23527 }
23528
23529 fn next_row(&self) -> Self {
23530 Self(self.0 + 1)
23531 }
23532
23533 fn previous_row(&self) -> Self {
23534 Self(self.0.saturating_sub(1))
23535 }
23536
23537 fn minus(&self, other: Self) -> u32 {
23538 self.0 - other.0
23539 }
23540}
23541
23542trait RowRangeExt {
23543 type Row;
23544
23545 fn len(&self) -> usize;
23546
23547 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23548}
23549
23550impl RowRangeExt for Range<MultiBufferRow> {
23551 type Row = MultiBufferRow;
23552
23553 fn len(&self) -> usize {
23554 (self.end.0 - self.start.0) as usize
23555 }
23556
23557 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23558 (self.start.0..self.end.0).map(MultiBufferRow)
23559 }
23560}
23561
23562impl RowRangeExt for Range<DisplayRow> {
23563 type Row = DisplayRow;
23564
23565 fn len(&self) -> usize {
23566 (self.end.0 - self.start.0) as usize
23567 }
23568
23569 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23570 (self.start.0..self.end.0).map(DisplayRow)
23571 }
23572}
23573
23574/// If select range has more than one line, we
23575/// just point the cursor to range.start.
23576fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23577 if range.start.row == range.end.row {
23578 range
23579 } else {
23580 range.start..range.start
23581 }
23582}
23583pub struct KillRing(ClipboardItem);
23584impl Global for KillRing {}
23585
23586const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23587
23588enum BreakpointPromptEditAction {
23589 Log,
23590 Condition,
23591 HitCondition,
23592}
23593
23594struct BreakpointPromptEditor {
23595 pub(crate) prompt: Entity<Editor>,
23596 editor: WeakEntity<Editor>,
23597 breakpoint_anchor: Anchor,
23598 breakpoint: Breakpoint,
23599 edit_action: BreakpointPromptEditAction,
23600 block_ids: HashSet<CustomBlockId>,
23601 editor_margins: Arc<Mutex<EditorMargins>>,
23602 _subscriptions: Vec<Subscription>,
23603}
23604
23605impl BreakpointPromptEditor {
23606 const MAX_LINES: u8 = 4;
23607
23608 fn new(
23609 editor: WeakEntity<Editor>,
23610 breakpoint_anchor: Anchor,
23611 breakpoint: Breakpoint,
23612 edit_action: BreakpointPromptEditAction,
23613 window: &mut Window,
23614 cx: &mut Context<Self>,
23615 ) -> Self {
23616 let base_text = match edit_action {
23617 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23618 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23619 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23620 }
23621 .map(|msg| msg.to_string())
23622 .unwrap_or_default();
23623
23624 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23625 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23626
23627 let prompt = cx.new(|cx| {
23628 let mut prompt = Editor::new(
23629 EditorMode::AutoHeight {
23630 min_lines: 1,
23631 max_lines: Some(Self::MAX_LINES as usize),
23632 },
23633 buffer,
23634 None,
23635 window,
23636 cx,
23637 );
23638 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23639 prompt.set_show_cursor_when_unfocused(false, cx);
23640 prompt.set_placeholder_text(
23641 match edit_action {
23642 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23643 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23644 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23645 },
23646 cx,
23647 );
23648
23649 prompt
23650 });
23651
23652 Self {
23653 prompt,
23654 editor,
23655 breakpoint_anchor,
23656 breakpoint,
23657 edit_action,
23658 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23659 block_ids: Default::default(),
23660 _subscriptions: vec![],
23661 }
23662 }
23663
23664 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23665 self.block_ids.extend(block_ids)
23666 }
23667
23668 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23669 if let Some(editor) = self.editor.upgrade() {
23670 let message = self
23671 .prompt
23672 .read(cx)
23673 .buffer
23674 .read(cx)
23675 .as_singleton()
23676 .expect("A multi buffer in breakpoint prompt isn't possible")
23677 .read(cx)
23678 .as_rope()
23679 .to_string();
23680
23681 editor.update(cx, |editor, cx| {
23682 editor.edit_breakpoint_at_anchor(
23683 self.breakpoint_anchor,
23684 self.breakpoint.clone(),
23685 match self.edit_action {
23686 BreakpointPromptEditAction::Log => {
23687 BreakpointEditAction::EditLogMessage(message.into())
23688 }
23689 BreakpointPromptEditAction::Condition => {
23690 BreakpointEditAction::EditCondition(message.into())
23691 }
23692 BreakpointPromptEditAction::HitCondition => {
23693 BreakpointEditAction::EditHitCondition(message.into())
23694 }
23695 },
23696 cx,
23697 );
23698
23699 editor.remove_blocks(self.block_ids.clone(), None, cx);
23700 cx.focus_self(window);
23701 });
23702 }
23703 }
23704
23705 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23706 self.editor
23707 .update(cx, |editor, cx| {
23708 editor.remove_blocks(self.block_ids.clone(), None, cx);
23709 window.focus(&editor.focus_handle);
23710 })
23711 .log_err();
23712 }
23713
23714 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23715 let settings = ThemeSettings::get_global(cx);
23716 let text_style = TextStyle {
23717 color: if self.prompt.read(cx).read_only(cx) {
23718 cx.theme().colors().text_disabled
23719 } else {
23720 cx.theme().colors().text
23721 },
23722 font_family: settings.buffer_font.family.clone(),
23723 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23724 font_size: settings.buffer_font_size(cx).into(),
23725 font_weight: settings.buffer_font.weight,
23726 line_height: relative(settings.buffer_line_height.value()),
23727 ..Default::default()
23728 };
23729 EditorElement::new(
23730 &self.prompt,
23731 EditorStyle {
23732 background: cx.theme().colors().editor_background,
23733 local_player: cx.theme().players().local(),
23734 text: text_style,
23735 ..Default::default()
23736 },
23737 )
23738 }
23739}
23740
23741impl Render for BreakpointPromptEditor {
23742 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23743 let editor_margins = *self.editor_margins.lock();
23744 let gutter_dimensions = editor_margins.gutter;
23745 h_flex()
23746 .key_context("Editor")
23747 .bg(cx.theme().colors().editor_background)
23748 .border_y_1()
23749 .border_color(cx.theme().status().info_border)
23750 .size_full()
23751 .py(window.line_height() / 2.5)
23752 .on_action(cx.listener(Self::confirm))
23753 .on_action(cx.listener(Self::cancel))
23754 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23755 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23756 }
23757}
23758
23759impl Focusable for BreakpointPromptEditor {
23760 fn focus_handle(&self, cx: &App) -> FocusHandle {
23761 self.prompt.focus_handle(cx)
23762 }
23763}
23764
23765fn all_edits_insertions_or_deletions(
23766 edits: &Vec<(Range<Anchor>, String)>,
23767 snapshot: &MultiBufferSnapshot,
23768) -> bool {
23769 let mut all_insertions = true;
23770 let mut all_deletions = true;
23771
23772 for (range, new_text) in edits.iter() {
23773 let range_is_empty = range.to_offset(snapshot).is_empty();
23774 let text_is_empty = new_text.is_empty();
23775
23776 if range_is_empty != text_is_empty {
23777 if range_is_empty {
23778 all_deletions = false;
23779 } else {
23780 all_insertions = false;
23781 }
23782 } else {
23783 return false;
23784 }
23785
23786 if !all_insertions && !all_deletions {
23787 return false;
23788 }
23789 }
23790 all_insertions || all_deletions
23791}
23792
23793struct MissingEditPredictionKeybindingTooltip;
23794
23795impl Render for MissingEditPredictionKeybindingTooltip {
23796 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23797 ui::tooltip_container(window, cx, |container, _, cx| {
23798 container
23799 .flex_shrink_0()
23800 .max_w_80()
23801 .min_h(rems_from_px(124.))
23802 .justify_between()
23803 .child(
23804 v_flex()
23805 .flex_1()
23806 .text_ui_sm(cx)
23807 .child(Label::new("Conflict with Accept Keybinding"))
23808 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23809 )
23810 .child(
23811 h_flex()
23812 .pb_1()
23813 .gap_1()
23814 .items_end()
23815 .w_full()
23816 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23817 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23818 }))
23819 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23820 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23821 })),
23822 )
23823 })
23824 }
23825}
23826
23827#[derive(Debug, Clone, Copy, PartialEq)]
23828pub struct LineHighlight {
23829 pub background: Background,
23830 pub border: Option<gpui::Hsla>,
23831 pub include_gutter: bool,
23832 pub type_id: Option<TypeId>,
23833}
23834
23835struct LineManipulationResult {
23836 pub new_text: String,
23837 pub line_count_before: usize,
23838 pub line_count_after: usize,
23839}
23840
23841fn render_diff_hunk_controls(
23842 row: u32,
23843 status: &DiffHunkStatus,
23844 hunk_range: Range<Anchor>,
23845 is_created_file: bool,
23846 line_height: Pixels,
23847 editor: &Entity<Editor>,
23848 _window: &mut Window,
23849 cx: &mut App,
23850) -> AnyElement {
23851 h_flex()
23852 .h(line_height)
23853 .mr_1()
23854 .gap_1()
23855 .px_0p5()
23856 .pb_1()
23857 .border_x_1()
23858 .border_b_1()
23859 .border_color(cx.theme().colors().border_variant)
23860 .rounded_b_lg()
23861 .bg(cx.theme().colors().editor_background)
23862 .gap_1()
23863 .block_mouse_except_scroll()
23864 .shadow_md()
23865 .child(if status.has_secondary_hunk() {
23866 Button::new(("stage", row as u64), "Stage")
23867 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23868 .tooltip({
23869 let focus_handle = editor.focus_handle(cx);
23870 move |window, cx| {
23871 Tooltip::for_action_in(
23872 "Stage Hunk",
23873 &::git::ToggleStaged,
23874 &focus_handle,
23875 window,
23876 cx,
23877 )
23878 }
23879 })
23880 .on_click({
23881 let editor = editor.clone();
23882 move |_event, _window, cx| {
23883 editor.update(cx, |editor, cx| {
23884 editor.stage_or_unstage_diff_hunks(
23885 true,
23886 vec![hunk_range.start..hunk_range.start],
23887 cx,
23888 );
23889 });
23890 }
23891 })
23892 } else {
23893 Button::new(("unstage", row as u64), "Unstage")
23894 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23895 .tooltip({
23896 let focus_handle = editor.focus_handle(cx);
23897 move |window, cx| {
23898 Tooltip::for_action_in(
23899 "Unstage Hunk",
23900 &::git::ToggleStaged,
23901 &focus_handle,
23902 window,
23903 cx,
23904 )
23905 }
23906 })
23907 .on_click({
23908 let editor = editor.clone();
23909 move |_event, _window, cx| {
23910 editor.update(cx, |editor, cx| {
23911 editor.stage_or_unstage_diff_hunks(
23912 false,
23913 vec![hunk_range.start..hunk_range.start],
23914 cx,
23915 );
23916 });
23917 }
23918 })
23919 })
23920 .child(
23921 Button::new(("restore", row as u64), "Restore")
23922 .tooltip({
23923 let focus_handle = editor.focus_handle(cx);
23924 move |window, cx| {
23925 Tooltip::for_action_in(
23926 "Restore Hunk",
23927 &::git::Restore,
23928 &focus_handle,
23929 window,
23930 cx,
23931 )
23932 }
23933 })
23934 .on_click({
23935 let editor = editor.clone();
23936 move |_event, window, cx| {
23937 editor.update(cx, |editor, cx| {
23938 let snapshot = editor.snapshot(window, cx);
23939 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23940 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23941 });
23942 }
23943 })
23944 .disabled(is_created_file),
23945 )
23946 .when(
23947 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23948 |el| {
23949 el.child(
23950 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23951 .shape(IconButtonShape::Square)
23952 .icon_size(IconSize::Small)
23953 // .disabled(!has_multiple_hunks)
23954 .tooltip({
23955 let focus_handle = editor.focus_handle(cx);
23956 move |window, cx| {
23957 Tooltip::for_action_in(
23958 "Next Hunk",
23959 &GoToHunk,
23960 &focus_handle,
23961 window,
23962 cx,
23963 )
23964 }
23965 })
23966 .on_click({
23967 let editor = editor.clone();
23968 move |_event, window, cx| {
23969 editor.update(cx, |editor, cx| {
23970 let snapshot = editor.snapshot(window, cx);
23971 let position =
23972 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23973 editor.go_to_hunk_before_or_after_position(
23974 &snapshot,
23975 position,
23976 Direction::Next,
23977 window,
23978 cx,
23979 );
23980 editor.expand_selected_diff_hunks(cx);
23981 });
23982 }
23983 }),
23984 )
23985 .child(
23986 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23987 .shape(IconButtonShape::Square)
23988 .icon_size(IconSize::Small)
23989 // .disabled(!has_multiple_hunks)
23990 .tooltip({
23991 let focus_handle = editor.focus_handle(cx);
23992 move |window, cx| {
23993 Tooltip::for_action_in(
23994 "Previous Hunk",
23995 &GoToPreviousHunk,
23996 &focus_handle,
23997 window,
23998 cx,
23999 )
24000 }
24001 })
24002 .on_click({
24003 let editor = editor.clone();
24004 move |_event, window, cx| {
24005 editor.update(cx, |editor, cx| {
24006 let snapshot = editor.snapshot(window, cx);
24007 let point =
24008 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24009 editor.go_to_hunk_before_or_after_position(
24010 &snapshot,
24011 point,
24012 Direction::Prev,
24013 window,
24014 cx,
24015 );
24016 editor.expand_selected_diff_hunks(cx);
24017 });
24018 }
24019 }),
24020 )
24021 },
24022 )
24023 .into_any_element()
24024}