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, RefCell},
180 cmp::{self, Ordering, Reverse},
181 iter::Peekable,
182 mem,
183 num::NonZeroU32,
184 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
185 path::{Path, PathBuf},
186 rc::Rc,
187 sync::{Arc, LazyLock},
188 time::{Duration, Instant},
189};
190use sum_tree::TreeMap;
191use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
192use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
193use theme::{
194 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
195 observe_buffer_font_size_adjustment,
196};
197use ui::{
198 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
199 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
200};
201use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
202use workspace::{
203 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
204 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
205 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
206 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
207 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
208 searchable::SearchEvent,
209};
210
211use crate::{
212 code_context_menus::CompletionsMenuSource,
213 editor_settings::MultiCursorModifier,
214 hover_links::{find_url, find_url_from_range},
215 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
216};
217
218pub const FILE_HEADER_HEIGHT: u32 = 2;
219pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
220pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
221const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
222const MAX_LINE_LEN: usize = 1024;
223const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
224const MAX_SELECTION_HISTORY_LEN: usize = 1024;
225pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
226#[doc(hidden)]
227pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
228const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
229
230pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
233
234pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
235pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
236pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
237
238#[derive(Clone, Debug, Eq, PartialEq)]
239pub struct LastCursorPosition {
240 pub path: PathBuf,
241 pub worktree_path: Arc<Path>,
242 pub point: Point,
243}
244
245pub static LAST_CURSOR_POSITION_WATCH: LazyLock<(
246 Mutex<postage::watch::Sender<Option<LastCursorPosition>>>,
247 postage::watch::Receiver<Option<LastCursorPosition>>,
248)> = LazyLock::new(|| {
249 let (sender, receiver) = postage::watch::channel();
250 (Mutex::new(sender), receiver)
251});
252
253pub type RenderDiffHunkControlsFn = Arc<
254 dyn Fn(
255 u32,
256 &DiffHunkStatus,
257 Range<Anchor>,
258 bool,
259 Pixels,
260 &Entity<Editor>,
261 &mut Window,
262 &mut App,
263 ) -> AnyElement,
264>;
265
266enum ReportEditorEvent {
267 Saved { auto_saved: bool },
268 EditorOpened,
269 ZetaTosClicked,
270 Closed,
271}
272
273impl ReportEditorEvent {
274 pub fn event_type(&self) -> &'static str {
275 match self {
276 Self::Saved { .. } => "Editor Saved",
277 Self::EditorOpened => "Editor Opened",
278 Self::ZetaTosClicked => "Edit Prediction Provider ToS Clicked",
279 Self::Closed => "Editor Closed",
280 }
281 }
282}
283
284struct InlineValueCache {
285 enabled: bool,
286 inlays: Vec<InlayId>,
287 refresh_task: Task<Option<()>>,
288}
289
290impl InlineValueCache {
291 fn new(enabled: bool) -> Self {
292 Self {
293 enabled,
294 inlays: Vec::new(),
295 refresh_task: Task::ready(None),
296 }
297 }
298}
299
300#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
301pub enum InlayId {
302 EditPrediction(usize),
303 DebuggerValue(usize),
304 // LSP
305 Hint(usize),
306 Color(usize),
307}
308
309impl InlayId {
310 fn id(&self) -> usize {
311 match self {
312 Self::EditPrediction(id) => *id,
313 Self::DebuggerValue(id) => *id,
314 Self::Hint(id) => *id,
315 Self::Color(id) => *id,
316 }
317 }
318}
319
320pub enum ActiveDebugLine {}
321pub enum DebugStackFrameLine {}
322enum DocumentHighlightRead {}
323enum DocumentHighlightWrite {}
324enum InputComposition {}
325pub enum PendingInput {}
326enum SelectedTextHighlight {}
327
328pub enum ConflictsOuter {}
329pub enum ConflictsOurs {}
330pub enum ConflictsTheirs {}
331pub enum ConflictsOursMarker {}
332pub enum ConflictsTheirsMarker {}
333
334#[derive(Debug, Copy, Clone, PartialEq, Eq)]
335pub enum Navigated {
336 Yes,
337 No,
338}
339
340impl Navigated {
341 pub fn from_bool(yes: bool) -> Navigated {
342 if yes { Navigated::Yes } else { Navigated::No }
343 }
344}
345
346#[derive(Debug, Clone, PartialEq, Eq)]
347enum DisplayDiffHunk {
348 Folded {
349 display_row: DisplayRow,
350 },
351 Unfolded {
352 is_created_file: bool,
353 diff_base_byte_range: Range<usize>,
354 display_row_range: Range<DisplayRow>,
355 multi_buffer_range: Range<Anchor>,
356 status: DiffHunkStatus,
357 },
358}
359
360pub enum HideMouseCursorOrigin {
361 TypingAction,
362 MovementAction,
363}
364
365pub fn init_settings(cx: &mut App) {
366 EditorSettings::register(cx);
367}
368
369pub fn init(cx: &mut App) {
370 init_settings(cx);
371
372 cx.set_global(GlobalBlameRenderer(Arc::new(())));
373
374 workspace::register_project_item::<Editor>(cx);
375 workspace::FollowableViewRegistry::register::<Editor>(cx);
376 workspace::register_serializable_item::<Editor>(cx);
377
378 cx.observe_new(
379 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
380 workspace.register_action(Editor::new_file);
381 workspace.register_action(Editor::new_file_vertical);
382 workspace.register_action(Editor::new_file_horizontal);
383 workspace.register_action(Editor::cancel_language_server_work);
384 workspace.register_action(Editor::toggle_focus);
385 },
386 )
387 .detach();
388
389 cx.on_action(move |_: &workspace::NewFile, cx| {
390 let app_state = workspace::AppState::global(cx);
391 if let Some(app_state) = app_state.upgrade() {
392 workspace::open_new(
393 Default::default(),
394 app_state,
395 cx,
396 |workspace, window, cx| {
397 Editor::new_file(workspace, &Default::default(), window, cx)
398 },
399 )
400 .detach();
401 }
402 });
403 cx.on_action(move |_: &workspace::NewWindow, cx| {
404 let app_state = workspace::AppState::global(cx);
405 if let Some(app_state) = app_state.upgrade() {
406 workspace::open_new(
407 Default::default(),
408 app_state,
409 cx,
410 |workspace, window, cx| {
411 cx.activate(true);
412 Editor::new_file(workspace, &Default::default(), window, cx)
413 },
414 )
415 .detach();
416 }
417 });
418}
419
420pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
421 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
422}
423
424pub trait DiagnosticRenderer {
425 fn render_group(
426 &self,
427 diagnostic_group: Vec<DiagnosticEntry<Point>>,
428 buffer_id: BufferId,
429 snapshot: EditorSnapshot,
430 editor: WeakEntity<Editor>,
431 cx: &mut App,
432 ) -> Vec<BlockProperties<Anchor>>;
433
434 fn render_hover(
435 &self,
436 diagnostic_group: Vec<DiagnosticEntry<Point>>,
437 range: Range<Point>,
438 buffer_id: BufferId,
439 cx: &mut App,
440 ) -> Option<Entity<markdown::Markdown>>;
441
442 fn open_link(
443 &self,
444 editor: &mut Editor,
445 link: SharedString,
446 window: &mut Window,
447 cx: &mut Context<Editor>,
448 );
449}
450
451pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
452
453impl GlobalDiagnosticRenderer {
454 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
455 cx.try_global::<Self>().map(|g| g.0.clone())
456 }
457}
458
459impl gpui::Global for GlobalDiagnosticRenderer {}
460pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
461 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
462}
463
464pub struct SearchWithinRange;
465
466trait InvalidationRegion {
467 fn ranges(&self) -> &[Range<Anchor>];
468}
469
470#[derive(Clone, Debug, PartialEq)]
471pub enum SelectPhase {
472 Begin {
473 position: DisplayPoint,
474 add: bool,
475 click_count: usize,
476 },
477 BeginColumnar {
478 position: DisplayPoint,
479 reset: bool,
480 mode: ColumnarMode,
481 goal_column: u32,
482 },
483 Extend {
484 position: DisplayPoint,
485 click_count: usize,
486 },
487 Update {
488 position: DisplayPoint,
489 goal_column: u32,
490 scroll_delta: gpui::Point<f32>,
491 },
492 End,
493}
494
495#[derive(Clone, Debug, PartialEq)]
496pub enum ColumnarMode {
497 FromMouse,
498 FromSelection,
499}
500
501#[derive(Clone, Debug)]
502pub enum SelectMode {
503 Character,
504 Word(Range<Anchor>),
505 Line(Range<Anchor>),
506 All,
507}
508
509#[derive(Clone, PartialEq, Eq, Debug)]
510pub enum EditorMode {
511 SingleLine,
512 AutoHeight {
513 min_lines: usize,
514 max_lines: Option<usize>,
515 },
516 Full {
517 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
518 scale_ui_elements_with_buffer_font_size: bool,
519 /// When set to `true`, the editor will render a background for the active line.
520 show_active_line_background: bool,
521 /// When set to `true`, the editor's height will be determined by its content.
522 sized_by_content: bool,
523 },
524 Minimap {
525 parent: WeakEntity<Editor>,
526 },
527}
528
529impl EditorMode {
530 pub fn full() -> Self {
531 Self::Full {
532 scale_ui_elements_with_buffer_font_size: true,
533 show_active_line_background: true,
534 sized_by_content: false,
535 }
536 }
537
538 #[inline]
539 pub fn is_full(&self) -> bool {
540 matches!(self, Self::Full { .. })
541 }
542
543 #[inline]
544 pub fn is_single_line(&self) -> bool {
545 matches!(self, Self::SingleLine { .. })
546 }
547
548 #[inline]
549 fn is_minimap(&self) -> bool {
550 matches!(self, Self::Minimap { .. })
551 }
552}
553
554#[derive(Copy, Clone, Debug)]
555pub enum SoftWrap {
556 /// Prefer not to wrap at all.
557 ///
558 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
559 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
560 GitDiff,
561 /// Prefer a single line generally, unless an overly long line is encountered.
562 None,
563 /// Soft wrap lines that exceed the editor width.
564 EditorWidth,
565 /// Soft wrap lines at the preferred line length.
566 Column(u32),
567 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
568 Bounded(u32),
569}
570
571#[derive(Clone)]
572pub struct EditorStyle {
573 pub background: Hsla,
574 pub border: Hsla,
575 pub local_player: PlayerColor,
576 pub text: TextStyle,
577 pub scrollbar_width: Pixels,
578 pub syntax: Arc<SyntaxTheme>,
579 pub status: StatusColors,
580 pub inlay_hints_style: HighlightStyle,
581 pub edit_prediction_styles: EditPredictionStyles,
582 pub unnecessary_code_fade: f32,
583 pub show_underlines: bool,
584}
585
586impl Default for EditorStyle {
587 fn default() -> Self {
588 Self {
589 background: Hsla::default(),
590 border: Hsla::default(),
591 local_player: PlayerColor::default(),
592 text: TextStyle::default(),
593 scrollbar_width: Pixels::default(),
594 syntax: Default::default(),
595 // HACK: Status colors don't have a real default.
596 // We should look into removing the status colors from the editor
597 // style and retrieve them directly from the theme.
598 status: StatusColors::dark(),
599 inlay_hints_style: HighlightStyle::default(),
600 edit_prediction_styles: EditPredictionStyles {
601 insertion: HighlightStyle::default(),
602 whitespace: HighlightStyle::default(),
603 },
604 unnecessary_code_fade: Default::default(),
605 show_underlines: true,
606 }
607 }
608}
609
610pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
611 let show_background = language_settings::language_settings(None, None, cx)
612 .inlay_hints
613 .show_background;
614
615 HighlightStyle {
616 color: Some(cx.theme().status().hint),
617 background_color: show_background.then(|| cx.theme().status().hint_background),
618 ..HighlightStyle::default()
619 }
620}
621
622pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
623 EditPredictionStyles {
624 insertion: HighlightStyle {
625 color: Some(cx.theme().status().predictive),
626 ..HighlightStyle::default()
627 },
628 whitespace: HighlightStyle {
629 background_color: Some(cx.theme().status().created_background),
630 ..HighlightStyle::default()
631 },
632 }
633}
634
635type CompletionId = usize;
636
637pub(crate) enum EditDisplayMode {
638 TabAccept,
639 DiffPopover,
640 Inline,
641}
642
643enum EditPrediction {
644 Edit {
645 edits: Vec<(Range<Anchor>, String)>,
646 edit_preview: Option<EditPreview>,
647 display_mode: EditDisplayMode,
648 snapshot: BufferSnapshot,
649 },
650 Move {
651 target: Anchor,
652 snapshot: BufferSnapshot,
653 },
654}
655
656struct EditPredictionState {
657 inlay_ids: Vec<InlayId>,
658 completion: EditPrediction,
659 completion_id: Option<SharedString>,
660 invalidation_range: Range<Anchor>,
661}
662
663enum EditPredictionSettings {
664 Disabled,
665 Enabled {
666 show_in_menu: bool,
667 preview_requires_modifier: bool,
668 },
669}
670
671enum EditPredictionHighlight {}
672
673#[derive(Debug, Clone)]
674struct InlineDiagnostic {
675 message: SharedString,
676 group_id: usize,
677 is_primary: bool,
678 start: Point,
679 severity: lsp::DiagnosticSeverity,
680}
681
682pub enum MenuEditPredictionsPolicy {
683 Never,
684 ByProvider,
685}
686
687pub enum EditPredictionPreview {
688 /// Modifier is not pressed
689 Inactive { released_too_fast: bool },
690 /// Modifier pressed
691 Active {
692 since: Instant,
693 previous_scroll_position: Option<ScrollAnchor>,
694 },
695}
696
697impl EditPredictionPreview {
698 pub fn released_too_fast(&self) -> bool {
699 match self {
700 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
701 EditPredictionPreview::Active { .. } => false,
702 }
703 }
704
705 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
706 if let EditPredictionPreview::Active {
707 previous_scroll_position,
708 ..
709 } = self
710 {
711 *previous_scroll_position = scroll_position;
712 }
713 }
714}
715
716pub struct ContextMenuOptions {
717 pub min_entries_visible: usize,
718 pub max_entries_visible: usize,
719 pub placement: Option<ContextMenuPlacement>,
720}
721
722#[derive(Debug, Clone, PartialEq, Eq)]
723pub enum ContextMenuPlacement {
724 Above,
725 Below,
726}
727
728#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
729struct EditorActionId(usize);
730
731impl EditorActionId {
732 pub fn post_inc(&mut self) -> Self {
733 let answer = self.0;
734
735 *self = Self(answer + 1);
736
737 Self(answer)
738 }
739}
740
741// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
742// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
743
744type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
745type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
746
747#[derive(Default)]
748struct ScrollbarMarkerState {
749 scrollbar_size: Size<Pixels>,
750 dirty: bool,
751 markers: Arc<[PaintQuad]>,
752 pending_refresh: Option<Task<Result<()>>>,
753}
754
755impl ScrollbarMarkerState {
756 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
757 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
758 }
759}
760
761#[derive(Clone, Copy, PartialEq, Eq)]
762pub enum MinimapVisibility {
763 Disabled,
764 Enabled {
765 /// The configuration currently present in the users settings.
766 setting_configuration: bool,
767 /// Whether to override the currently set visibility from the users setting.
768 toggle_override: bool,
769 },
770}
771
772impl MinimapVisibility {
773 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
774 if mode.is_full() {
775 Self::Enabled {
776 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
777 toggle_override: false,
778 }
779 } else {
780 Self::Disabled
781 }
782 }
783
784 fn hidden(&self) -> Self {
785 match *self {
786 Self::Enabled {
787 setting_configuration,
788 ..
789 } => Self::Enabled {
790 setting_configuration,
791 toggle_override: setting_configuration,
792 },
793 Self::Disabled => Self::Disabled,
794 }
795 }
796
797 fn disabled(&self) -> bool {
798 match *self {
799 Self::Disabled => true,
800 _ => false,
801 }
802 }
803
804 fn settings_visibility(&self) -> bool {
805 match *self {
806 Self::Enabled {
807 setting_configuration,
808 ..
809 } => setting_configuration,
810 _ => false,
811 }
812 }
813
814 fn visible(&self) -> bool {
815 match *self {
816 Self::Enabled {
817 setting_configuration,
818 toggle_override,
819 } => setting_configuration ^ toggle_override,
820 _ => false,
821 }
822 }
823
824 fn toggle_visibility(&self) -> Self {
825 match *self {
826 Self::Enabled {
827 toggle_override,
828 setting_configuration,
829 } => Self::Enabled {
830 setting_configuration,
831 toggle_override: !toggle_override,
832 },
833 Self::Disabled => Self::Disabled,
834 }
835 }
836}
837
838#[derive(Clone, Debug)]
839struct RunnableTasks {
840 templates: Vec<(TaskSourceKind, TaskTemplate)>,
841 offset: multi_buffer::Anchor,
842 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
843 column: u32,
844 // Values of all named captures, including those starting with '_'
845 extra_variables: HashMap<String, String>,
846 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
847 context_range: Range<BufferOffset>,
848}
849
850impl RunnableTasks {
851 fn resolve<'a>(
852 &'a self,
853 cx: &'a task::TaskContext,
854 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
855 self.templates.iter().filter_map(|(kind, template)| {
856 template
857 .resolve_task(&kind.to_id_base(), cx)
858 .map(|task| (kind.clone(), task))
859 })
860 }
861}
862
863#[derive(Clone)]
864pub struct ResolvedTasks {
865 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
866 position: Anchor,
867}
868
869#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
870struct BufferOffset(usize);
871
872// Addons allow storing per-editor state in other crates (e.g. Vim)
873pub trait Addon: 'static {
874 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
875
876 fn render_buffer_header_controls(
877 &self,
878 _: &ExcerptInfo,
879 _: &Window,
880 _: &App,
881 ) -> Option<AnyElement> {
882 None
883 }
884
885 fn to_any(&self) -> &dyn std::any::Any;
886
887 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
888 None
889 }
890}
891
892struct ChangeLocation {
893 current: Option<Vec<Anchor>>,
894 original: Vec<Anchor>,
895}
896impl ChangeLocation {
897 fn locations(&self) -> &[Anchor] {
898 self.current.as_ref().unwrap_or(&self.original)
899 }
900}
901
902/// A set of caret positions, registered when the editor was edited.
903pub struct ChangeList {
904 changes: Vec<ChangeLocation>,
905 /// Currently "selected" change.
906 position: Option<usize>,
907}
908
909impl ChangeList {
910 pub fn new() -> Self {
911 Self {
912 changes: Vec::new(),
913 position: None,
914 }
915 }
916
917 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
918 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
919 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
920 if self.changes.is_empty() {
921 return None;
922 }
923
924 let prev = self.position.unwrap_or(self.changes.len());
925 let next = if direction == Direction::Prev {
926 prev.saturating_sub(count)
927 } else {
928 (prev + count).min(self.changes.len() - 1)
929 };
930 self.position = Some(next);
931 self.changes.get(next).map(|change| change.locations())
932 }
933
934 /// Adds a new change to the list, resetting the change list position.
935 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
936 self.position.take();
937 if let Some(last) = self.changes.last_mut()
938 && group
939 {
940 last.current = Some(new_positions)
941 } else {
942 self.changes.push(ChangeLocation {
943 original: new_positions,
944 current: None,
945 });
946 }
947 }
948
949 pub fn last(&self) -> Option<&[Anchor]> {
950 self.changes.last().map(|change| change.locations())
951 }
952
953 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
954 self.changes.last().map(|change| change.original.as_slice())
955 }
956
957 pub fn invert_last_group(&mut self) {
958 if let Some(last) = self.changes.last_mut()
959 && let Some(current) = last.current.as_mut()
960 {
961 mem::swap(&mut last.original, current);
962 }
963 }
964}
965
966#[derive(Clone)]
967struct InlineBlamePopoverState {
968 scroll_handle: ScrollHandle,
969 commit_message: Option<ParsedCommitMessage>,
970 markdown: Entity<Markdown>,
971}
972
973struct InlineBlamePopover {
974 position: gpui::Point<Pixels>,
975 hide_task: Option<Task<()>>,
976 popover_bounds: Option<Bounds<Pixels>>,
977 popover_state: InlineBlamePopoverState,
978 keyboard_grace: bool,
979}
980
981enum SelectionDragState {
982 /// State when no drag related activity is detected.
983 None,
984 /// State when the mouse is down on a selection that is about to be dragged.
985 ReadyToDrag {
986 selection: Selection<Anchor>,
987 click_position: gpui::Point<Pixels>,
988 mouse_down_time: Instant,
989 },
990 /// State when the mouse is dragging the selection in the editor.
991 Dragging {
992 selection: Selection<Anchor>,
993 drop_cursor: Selection<Anchor>,
994 hide_drop_cursor: bool,
995 },
996}
997
998enum ColumnarSelectionState {
999 FromMouse {
1000 selection_tail: Anchor,
1001 display_point: Option<DisplayPoint>,
1002 },
1003 FromSelection {
1004 selection_tail: Anchor,
1005 },
1006}
1007
1008/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1009/// a breakpoint on them.
1010#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1011struct PhantomBreakpointIndicator {
1012 display_row: DisplayRow,
1013 /// There's a small debounce between hovering over the line and showing the indicator.
1014 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1015 is_active: bool,
1016 collides_with_existing_breakpoint: bool,
1017}
1018
1019/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1020///
1021/// See the [module level documentation](self) for more information.
1022pub struct Editor {
1023 focus_handle: FocusHandle,
1024 last_focused_descendant: Option<WeakFocusHandle>,
1025 /// The text buffer being edited
1026 buffer: Entity<MultiBuffer>,
1027 /// Map of how text in the buffer should be displayed.
1028 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1029 pub display_map: Entity<DisplayMap>,
1030 pub selections: SelectionsCollection,
1031 pub scroll_manager: ScrollManager,
1032 /// When inline assist editors are linked, they all render cursors because
1033 /// typing enters text into each of them, even the ones that aren't focused.
1034 pub(crate) show_cursor_when_unfocused: bool,
1035 columnar_selection_state: Option<ColumnarSelectionState>,
1036 add_selections_state: Option<AddSelectionsState>,
1037 select_next_state: Option<SelectNextState>,
1038 select_prev_state: Option<SelectNextState>,
1039 selection_history: SelectionHistory,
1040 defer_selection_effects: bool,
1041 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1042 autoclose_regions: Vec<AutocloseRegion>,
1043 snippet_stack: InvalidationStack<SnippetState>,
1044 select_syntax_node_history: SelectSyntaxNodeHistory,
1045 ime_transaction: Option<TransactionId>,
1046 pub diagnostics_max_severity: DiagnosticSeverity,
1047 active_diagnostics: ActiveDiagnostic,
1048 show_inline_diagnostics: bool,
1049 inline_diagnostics_update: Task<()>,
1050 inline_diagnostics_enabled: bool,
1051 diagnostics_enabled: bool,
1052 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1053 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1054 hard_wrap: Option<usize>,
1055 project: Option<Entity<Project>>,
1056 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1057 completion_provider: Option<Rc<dyn CompletionProvider>>,
1058 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1059 blink_manager: Entity<BlinkManager>,
1060 show_cursor_names: bool,
1061 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1062 pub show_local_selections: bool,
1063 mode: EditorMode,
1064 show_breadcrumbs: bool,
1065 show_gutter: bool,
1066 show_scrollbars: ScrollbarAxes,
1067 minimap_visibility: MinimapVisibility,
1068 offset_content: bool,
1069 disable_expand_excerpt_buttons: bool,
1070 show_line_numbers: Option<bool>,
1071 use_relative_line_numbers: Option<bool>,
1072 show_git_diff_gutter: Option<bool>,
1073 show_code_actions: Option<bool>,
1074 show_runnables: Option<bool>,
1075 show_breakpoints: Option<bool>,
1076 show_wrap_guides: Option<bool>,
1077 show_indent_guides: Option<bool>,
1078 placeholder_text: Option<Arc<str>>,
1079 highlight_order: usize,
1080 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1081 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1082 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1083 scrollbar_marker_state: ScrollbarMarkerState,
1084 active_indent_guides_state: ActiveIndentGuidesState,
1085 nav_history: Option<ItemNavHistory>,
1086 context_menu: RefCell<Option<CodeContextMenu>>,
1087 context_menu_options: Option<ContextMenuOptions>,
1088 mouse_context_menu: Option<MouseContextMenu>,
1089 completion_tasks: Vec<(CompletionId, Task<()>)>,
1090 inline_blame_popover: Option<InlineBlamePopover>,
1091 inline_blame_popover_show_task: Option<Task<()>>,
1092 signature_help_state: SignatureHelpState,
1093 auto_signature_help: Option<bool>,
1094 find_all_references_task_sources: Vec<Anchor>,
1095 next_completion_id: CompletionId,
1096 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1097 code_actions_task: Option<Task<Result<()>>>,
1098 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1099 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1100 document_highlights_task: Option<Task<()>>,
1101 linked_editing_range_task: Option<Task<Option<()>>>,
1102 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1103 pending_rename: Option<RenameState>,
1104 searchable: bool,
1105 cursor_shape: CursorShape,
1106 current_line_highlight: Option<CurrentLineHighlight>,
1107 collapse_matches: bool,
1108 autoindent_mode: Option<AutoindentMode>,
1109 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1110 input_enabled: bool,
1111 use_modal_editing: bool,
1112 read_only: bool,
1113 leader_id: Option<CollaboratorId>,
1114 remote_id: Option<ViewId>,
1115 pub hover_state: HoverState,
1116 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1117 gutter_hovered: bool,
1118 hovered_link_state: Option<HoveredLinkState>,
1119 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1120 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1121 active_edit_prediction: Option<EditPredictionState>,
1122 /// Used to prevent flickering as the user types while the menu is open
1123 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1124 edit_prediction_settings: EditPredictionSettings,
1125 edit_predictions_hidden_for_vim_mode: bool,
1126 show_edit_predictions_override: Option<bool>,
1127 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1128 edit_prediction_preview: EditPredictionPreview,
1129 edit_prediction_indent_conflict: bool,
1130 edit_prediction_requires_modifier_in_indent_conflict: bool,
1131 inlay_hint_cache: InlayHintCache,
1132 next_inlay_id: usize,
1133 _subscriptions: Vec<Subscription>,
1134 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1135 gutter_dimensions: GutterDimensions,
1136 style: Option<EditorStyle>,
1137 text_style_refinement: Option<TextStyleRefinement>,
1138 next_editor_action_id: EditorActionId,
1139 editor_actions: Rc<
1140 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1141 >,
1142 use_autoclose: bool,
1143 use_auto_surround: bool,
1144 auto_replace_emoji_shortcode: bool,
1145 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1146 show_git_blame_gutter: bool,
1147 show_git_blame_inline: bool,
1148 show_git_blame_inline_delay_task: Option<Task<()>>,
1149 git_blame_inline_enabled: bool,
1150 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1151 serialize_dirty_buffers: bool,
1152 show_selection_menu: Option<bool>,
1153 blame: Option<Entity<GitBlame>>,
1154 blame_subscription: Option<Subscription>,
1155 custom_context_menu: Option<
1156 Box<
1157 dyn 'static
1158 + Fn(
1159 &mut Self,
1160 DisplayPoint,
1161 &mut Window,
1162 &mut Context<Self>,
1163 ) -> Option<Entity<ui::ContextMenu>>,
1164 >,
1165 >,
1166 last_bounds: Option<Bounds<Pixels>>,
1167 last_position_map: Option<Rc<PositionMap>>,
1168 expect_bounds_change: Option<Bounds<Pixels>>,
1169 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1170 tasks_update_task: Option<Task<()>>,
1171 breakpoint_store: Option<Entity<BreakpointStore>>,
1172 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1173 hovered_diff_hunk_row: Option<DisplayRow>,
1174 pull_diagnostics_task: Task<()>,
1175 in_project_search: bool,
1176 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1177 breadcrumb_header: Option<String>,
1178 focused_block: Option<FocusedBlock>,
1179 next_scroll_position: NextScrollCursorCenterTopBottom,
1180 addons: HashMap<TypeId, Box<dyn Addon>>,
1181 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1182 load_diff_task: Option<Shared<Task<()>>>,
1183 /// Whether we are temporarily displaying a diff other than git's
1184 temporary_diff_override: bool,
1185 selection_mark_mode: bool,
1186 toggle_fold_multiple_buffers: Task<()>,
1187 _scroll_cursor_center_top_bottom_task: Task<()>,
1188 serialize_selections: Task<()>,
1189 serialize_folds: Task<()>,
1190 mouse_cursor_hidden: bool,
1191 minimap: Option<Entity<Self>>,
1192 hide_mouse_mode: HideMouseMode,
1193 pub change_list: ChangeList,
1194 inline_value_cache: InlineValueCache,
1195 selection_drag_state: SelectionDragState,
1196 next_color_inlay_id: usize,
1197 colors: Option<LspColorData>,
1198 folding_newlines: Task<()>,
1199}
1200
1201#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1202enum NextScrollCursorCenterTopBottom {
1203 #[default]
1204 Center,
1205 Top,
1206 Bottom,
1207}
1208
1209impl NextScrollCursorCenterTopBottom {
1210 fn next(&self) -> Self {
1211 match self {
1212 Self::Center => Self::Top,
1213 Self::Top => Self::Bottom,
1214 Self::Bottom => Self::Center,
1215 }
1216 }
1217}
1218
1219#[derive(Clone)]
1220pub struct EditorSnapshot {
1221 pub mode: EditorMode,
1222 show_gutter: bool,
1223 show_line_numbers: Option<bool>,
1224 show_git_diff_gutter: Option<bool>,
1225 show_code_actions: Option<bool>,
1226 show_runnables: Option<bool>,
1227 show_breakpoints: Option<bool>,
1228 git_blame_gutter_max_author_length: Option<usize>,
1229 pub display_snapshot: DisplaySnapshot,
1230 pub placeholder_text: Option<Arc<str>>,
1231 is_focused: bool,
1232 scroll_anchor: ScrollAnchor,
1233 ongoing_scroll: OngoingScroll,
1234 current_line_highlight: CurrentLineHighlight,
1235 gutter_hovered: bool,
1236}
1237
1238#[derive(Default, Debug, Clone, Copy)]
1239pub struct GutterDimensions {
1240 pub left_padding: Pixels,
1241 pub right_padding: Pixels,
1242 pub width: Pixels,
1243 pub margin: Pixels,
1244 pub git_blame_entries_width: Option<Pixels>,
1245}
1246
1247impl GutterDimensions {
1248 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1249 Self {
1250 margin: Self::default_gutter_margin(font_id, font_size, cx),
1251 ..Default::default()
1252 }
1253 }
1254
1255 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1256 -cx.text_system().descent(font_id, font_size)
1257 }
1258 /// The full width of the space taken up by the gutter.
1259 pub fn full_width(&self) -> Pixels {
1260 self.margin + self.width
1261 }
1262
1263 /// The width of the space reserved for the fold indicators,
1264 /// use alongside 'justify_end' and `gutter_width` to
1265 /// right align content with the line numbers
1266 pub fn fold_area_width(&self) -> Pixels {
1267 self.margin + self.right_padding
1268 }
1269}
1270
1271struct CharacterDimensions {
1272 em_width: Pixels,
1273 em_advance: Pixels,
1274 line_height: Pixels,
1275}
1276
1277#[derive(Debug)]
1278pub struct RemoteSelection {
1279 pub replica_id: ReplicaId,
1280 pub selection: Selection<Anchor>,
1281 pub cursor_shape: CursorShape,
1282 pub collaborator_id: CollaboratorId,
1283 pub line_mode: bool,
1284 pub user_name: Option<SharedString>,
1285 pub color: PlayerColor,
1286}
1287
1288#[derive(Clone, Debug)]
1289struct SelectionHistoryEntry {
1290 selections: Arc<[Selection<Anchor>]>,
1291 select_next_state: Option<SelectNextState>,
1292 select_prev_state: Option<SelectNextState>,
1293 add_selections_state: Option<AddSelectionsState>,
1294}
1295
1296#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1297enum SelectionHistoryMode {
1298 Normal,
1299 Undoing,
1300 Redoing,
1301 Skipping,
1302}
1303
1304#[derive(Clone, PartialEq, Eq, Hash)]
1305struct HoveredCursor {
1306 replica_id: u16,
1307 selection_id: usize,
1308}
1309
1310impl Default for SelectionHistoryMode {
1311 fn default() -> Self {
1312 Self::Normal
1313 }
1314}
1315
1316#[derive(Debug)]
1317/// SelectionEffects controls the side-effects of updating the selection.
1318///
1319/// The default behaviour does "what you mostly want":
1320/// - it pushes to the nav history if the cursor moved by >10 lines
1321/// - it re-triggers completion requests
1322/// - it scrolls to fit
1323///
1324/// You might want to modify these behaviours. For example when doing a "jump"
1325/// like go to definition, we always want to add to nav history; but when scrolling
1326/// in vim mode we never do.
1327///
1328/// Similarly, you might want to disable scrolling if you don't want the viewport to
1329/// move.
1330#[derive(Clone)]
1331pub struct SelectionEffects {
1332 nav_history: Option<bool>,
1333 completions: bool,
1334 scroll: Option<Autoscroll>,
1335}
1336
1337impl Default for SelectionEffects {
1338 fn default() -> Self {
1339 Self {
1340 nav_history: None,
1341 completions: true,
1342 scroll: Some(Autoscroll::fit()),
1343 }
1344 }
1345}
1346impl SelectionEffects {
1347 pub fn scroll(scroll: Autoscroll) -> Self {
1348 Self {
1349 scroll: Some(scroll),
1350 ..Default::default()
1351 }
1352 }
1353
1354 pub fn no_scroll() -> Self {
1355 Self {
1356 scroll: None,
1357 ..Default::default()
1358 }
1359 }
1360
1361 pub fn completions(self, completions: bool) -> Self {
1362 Self {
1363 completions,
1364 ..self
1365 }
1366 }
1367
1368 pub fn nav_history(self, nav_history: bool) -> Self {
1369 Self {
1370 nav_history: Some(nav_history),
1371 ..self
1372 }
1373 }
1374}
1375
1376struct DeferredSelectionEffectsState {
1377 changed: bool,
1378 effects: SelectionEffects,
1379 old_cursor_position: Anchor,
1380 history_entry: SelectionHistoryEntry,
1381}
1382
1383#[derive(Default)]
1384struct SelectionHistory {
1385 #[allow(clippy::type_complexity)]
1386 selections_by_transaction:
1387 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1388 mode: SelectionHistoryMode,
1389 undo_stack: VecDeque<SelectionHistoryEntry>,
1390 redo_stack: VecDeque<SelectionHistoryEntry>,
1391}
1392
1393impl SelectionHistory {
1394 #[track_caller]
1395 fn insert_transaction(
1396 &mut self,
1397 transaction_id: TransactionId,
1398 selections: Arc<[Selection<Anchor>]>,
1399 ) {
1400 if selections.is_empty() {
1401 log::error!(
1402 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1403 std::panic::Location::caller()
1404 );
1405 return;
1406 }
1407 self.selections_by_transaction
1408 .insert(transaction_id, (selections, None));
1409 }
1410
1411 #[allow(clippy::type_complexity)]
1412 fn transaction(
1413 &self,
1414 transaction_id: TransactionId,
1415 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1416 self.selections_by_transaction.get(&transaction_id)
1417 }
1418
1419 #[allow(clippy::type_complexity)]
1420 fn transaction_mut(
1421 &mut self,
1422 transaction_id: TransactionId,
1423 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1424 self.selections_by_transaction.get_mut(&transaction_id)
1425 }
1426
1427 fn push(&mut self, entry: SelectionHistoryEntry) {
1428 if !entry.selections.is_empty() {
1429 match self.mode {
1430 SelectionHistoryMode::Normal => {
1431 self.push_undo(entry);
1432 self.redo_stack.clear();
1433 }
1434 SelectionHistoryMode::Undoing => self.push_redo(entry),
1435 SelectionHistoryMode::Redoing => self.push_undo(entry),
1436 SelectionHistoryMode::Skipping => {}
1437 }
1438 }
1439 }
1440
1441 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1442 if self
1443 .undo_stack
1444 .back()
1445 .is_none_or(|e| e.selections != entry.selections)
1446 {
1447 self.undo_stack.push_back(entry);
1448 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1449 self.undo_stack.pop_front();
1450 }
1451 }
1452 }
1453
1454 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1455 if self
1456 .redo_stack
1457 .back()
1458 .is_none_or(|e| e.selections != entry.selections)
1459 {
1460 self.redo_stack.push_back(entry);
1461 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1462 self.redo_stack.pop_front();
1463 }
1464 }
1465 }
1466}
1467
1468#[derive(Clone, Copy)]
1469pub struct RowHighlightOptions {
1470 pub autoscroll: bool,
1471 pub include_gutter: bool,
1472}
1473
1474impl Default for RowHighlightOptions {
1475 fn default() -> Self {
1476 Self {
1477 autoscroll: Default::default(),
1478 include_gutter: true,
1479 }
1480 }
1481}
1482
1483struct RowHighlight {
1484 index: usize,
1485 range: Range<Anchor>,
1486 color: Hsla,
1487 options: RowHighlightOptions,
1488 type_id: TypeId,
1489}
1490
1491#[derive(Clone, Debug)]
1492struct AddSelectionsState {
1493 groups: Vec<AddSelectionsGroup>,
1494}
1495
1496#[derive(Clone, Debug)]
1497struct AddSelectionsGroup {
1498 above: bool,
1499 stack: Vec<usize>,
1500}
1501
1502#[derive(Clone)]
1503struct SelectNextState {
1504 query: AhoCorasick,
1505 wordwise: bool,
1506 done: bool,
1507}
1508
1509impl std::fmt::Debug for SelectNextState {
1510 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1511 f.debug_struct(std::any::type_name::<Self>())
1512 .field("wordwise", &self.wordwise)
1513 .field("done", &self.done)
1514 .finish()
1515 }
1516}
1517
1518#[derive(Debug)]
1519struct AutocloseRegion {
1520 selection_id: usize,
1521 range: Range<Anchor>,
1522 pair: BracketPair,
1523}
1524
1525#[derive(Debug)]
1526struct SnippetState {
1527 ranges: Vec<Vec<Range<Anchor>>>,
1528 active_index: usize,
1529 choices: Vec<Option<Vec<String>>>,
1530}
1531
1532#[doc(hidden)]
1533pub struct RenameState {
1534 pub range: Range<Anchor>,
1535 pub old_name: Arc<str>,
1536 pub editor: Entity<Editor>,
1537 block_id: CustomBlockId,
1538}
1539
1540struct InvalidationStack<T>(Vec<T>);
1541
1542struct RegisteredEditPredictionProvider {
1543 provider: Arc<dyn EditPredictionProviderHandle>,
1544 _subscription: Subscription,
1545}
1546
1547#[derive(Debug, PartialEq, Eq)]
1548pub struct ActiveDiagnosticGroup {
1549 pub active_range: Range<Anchor>,
1550 pub active_message: String,
1551 pub group_id: usize,
1552 pub blocks: HashSet<CustomBlockId>,
1553}
1554
1555#[derive(Debug, PartialEq, Eq)]
1556
1557pub(crate) enum ActiveDiagnostic {
1558 None,
1559 All,
1560 Group(ActiveDiagnosticGroup),
1561}
1562
1563#[derive(Serialize, Deserialize, Clone, Debug)]
1564pub struct ClipboardSelection {
1565 /// The number of bytes in this selection.
1566 pub len: usize,
1567 /// Whether this was a full-line selection.
1568 pub is_entire_line: bool,
1569 /// The indentation of the first line when this content was originally copied.
1570 pub first_line_indent: u32,
1571}
1572
1573// selections, scroll behavior, was newest selection reversed
1574type SelectSyntaxNodeHistoryState = (
1575 Box<[Selection<usize>]>,
1576 SelectSyntaxNodeScrollBehavior,
1577 bool,
1578);
1579
1580#[derive(Default)]
1581struct SelectSyntaxNodeHistory {
1582 stack: Vec<SelectSyntaxNodeHistoryState>,
1583 // disable temporarily to allow changing selections without losing the stack
1584 pub disable_clearing: bool,
1585}
1586
1587impl SelectSyntaxNodeHistory {
1588 pub fn try_clear(&mut self) {
1589 if !self.disable_clearing {
1590 self.stack.clear();
1591 }
1592 }
1593
1594 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1595 self.stack.push(selection);
1596 }
1597
1598 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1599 self.stack.pop()
1600 }
1601}
1602
1603enum SelectSyntaxNodeScrollBehavior {
1604 CursorTop,
1605 FitSelection,
1606 CursorBottom,
1607}
1608
1609#[derive(Debug)]
1610pub(crate) struct NavigationData {
1611 cursor_anchor: Anchor,
1612 cursor_position: Point,
1613 scroll_anchor: ScrollAnchor,
1614 scroll_top_row: u32,
1615}
1616
1617#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1618pub enum GotoDefinitionKind {
1619 Symbol,
1620 Declaration,
1621 Type,
1622 Implementation,
1623}
1624
1625#[derive(Debug, Clone)]
1626enum InlayHintRefreshReason {
1627 ModifiersChanged(bool),
1628 Toggle(bool),
1629 SettingsChange(InlayHintSettings),
1630 NewLinesShown,
1631 BufferEdited(HashSet<Arc<Language>>),
1632 RefreshRequested,
1633 ExcerptsRemoved(Vec<ExcerptId>),
1634}
1635
1636impl InlayHintRefreshReason {
1637 fn description(&self) -> &'static str {
1638 match self {
1639 Self::ModifiersChanged(_) => "modifiers changed",
1640 Self::Toggle(_) => "toggle",
1641 Self::SettingsChange(_) => "settings change",
1642 Self::NewLinesShown => "new lines shown",
1643 Self::BufferEdited(_) => "buffer edited",
1644 Self::RefreshRequested => "refresh requested",
1645 Self::ExcerptsRemoved(_) => "excerpts removed",
1646 }
1647 }
1648}
1649
1650pub enum FormatTarget {
1651 Buffers(HashSet<Entity<Buffer>>),
1652 Ranges(Vec<Range<MultiBufferPoint>>),
1653}
1654
1655pub(crate) struct FocusedBlock {
1656 id: BlockId,
1657 focus_handle: WeakFocusHandle,
1658}
1659
1660#[derive(Clone)]
1661enum JumpData {
1662 MultiBufferRow {
1663 row: MultiBufferRow,
1664 line_offset_from_top: u32,
1665 },
1666 MultiBufferPoint {
1667 excerpt_id: ExcerptId,
1668 position: Point,
1669 anchor: text::Anchor,
1670 line_offset_from_top: u32,
1671 },
1672}
1673
1674pub enum MultibufferSelectionMode {
1675 First,
1676 All,
1677}
1678
1679#[derive(Clone, Copy, Debug, Default)]
1680pub struct RewrapOptions {
1681 pub override_language_settings: bool,
1682 pub preserve_existing_whitespace: bool,
1683}
1684
1685impl Editor {
1686 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1687 let buffer = cx.new(|cx| Buffer::local("", cx));
1688 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1689 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1690 }
1691
1692 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1693 let buffer = cx.new(|cx| Buffer::local("", cx));
1694 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1695 Self::new(EditorMode::full(), buffer, None, window, cx)
1696 }
1697
1698 pub fn auto_height(
1699 min_lines: usize,
1700 max_lines: usize,
1701 window: &mut Window,
1702 cx: &mut Context<Self>,
1703 ) -> Self {
1704 let buffer = cx.new(|cx| Buffer::local("", cx));
1705 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1706 Self::new(
1707 EditorMode::AutoHeight {
1708 min_lines,
1709 max_lines: Some(max_lines),
1710 },
1711 buffer,
1712 None,
1713 window,
1714 cx,
1715 )
1716 }
1717
1718 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1719 /// The editor grows as tall as needed to fit its content.
1720 pub fn auto_height_unbounded(
1721 min_lines: usize,
1722 window: &mut Window,
1723 cx: &mut Context<Self>,
1724 ) -> Self {
1725 let buffer = cx.new(|cx| Buffer::local("", cx));
1726 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1727 Self::new(
1728 EditorMode::AutoHeight {
1729 min_lines,
1730 max_lines: None,
1731 },
1732 buffer,
1733 None,
1734 window,
1735 cx,
1736 )
1737 }
1738
1739 pub fn for_buffer(
1740 buffer: Entity<Buffer>,
1741 project: Option<Entity<Project>>,
1742 window: &mut Window,
1743 cx: &mut Context<Self>,
1744 ) -> Self {
1745 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1746 Self::new(EditorMode::full(), buffer, project, window, cx)
1747 }
1748
1749 pub fn for_multibuffer(
1750 buffer: Entity<MultiBuffer>,
1751 project: Option<Entity<Project>>,
1752 window: &mut Window,
1753 cx: &mut Context<Self>,
1754 ) -> Self {
1755 Self::new(EditorMode::full(), buffer, project, window, cx)
1756 }
1757
1758 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1759 let mut clone = Self::new(
1760 self.mode.clone(),
1761 self.buffer.clone(),
1762 self.project.clone(),
1763 window,
1764 cx,
1765 );
1766 self.display_map.update(cx, |display_map, cx| {
1767 let snapshot = display_map.snapshot(cx);
1768 clone.display_map.update(cx, |display_map, cx| {
1769 display_map.set_state(&snapshot, cx);
1770 });
1771 });
1772 clone.folds_did_change(cx);
1773 clone.selections.clone_state(&self.selections);
1774 clone.scroll_manager.clone_state(&self.scroll_manager);
1775 clone.searchable = self.searchable;
1776 clone.read_only = self.read_only;
1777 clone
1778 }
1779
1780 pub fn new(
1781 mode: EditorMode,
1782 buffer: Entity<MultiBuffer>,
1783 project: Option<Entity<Project>>,
1784 window: &mut Window,
1785 cx: &mut Context<Self>,
1786 ) -> Self {
1787 Editor::new_internal(mode, buffer, project, None, window, cx)
1788 }
1789
1790 fn new_internal(
1791 mode: EditorMode,
1792 buffer: Entity<MultiBuffer>,
1793 project: Option<Entity<Project>>,
1794 display_map: Option<Entity<DisplayMap>>,
1795 window: &mut Window,
1796 cx: &mut Context<Self>,
1797 ) -> Self {
1798 debug_assert!(
1799 display_map.is_none() || mode.is_minimap(),
1800 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1801 );
1802
1803 let full_mode = mode.is_full();
1804 let is_minimap = mode.is_minimap();
1805 let diagnostics_max_severity = if full_mode {
1806 EditorSettings::get_global(cx)
1807 .diagnostics_max_severity
1808 .unwrap_or(DiagnosticSeverity::Hint)
1809 } else {
1810 DiagnosticSeverity::Off
1811 };
1812 let style = window.text_style();
1813 let font_size = style.font_size.to_pixels(window.rem_size());
1814 let editor = cx.entity().downgrade();
1815 let fold_placeholder = FoldPlaceholder {
1816 constrain_width: true,
1817 render: Arc::new(move |fold_id, fold_range, cx| {
1818 let editor = editor.clone();
1819 div()
1820 .id(fold_id)
1821 .bg(cx.theme().colors().ghost_element_background)
1822 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1823 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1824 .rounded_xs()
1825 .size_full()
1826 .cursor_pointer()
1827 .child("⋯")
1828 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1829 .on_click(move |_, _window, cx| {
1830 editor
1831 .update(cx, |editor, cx| {
1832 editor.unfold_ranges(
1833 &[fold_range.start..fold_range.end],
1834 true,
1835 false,
1836 cx,
1837 );
1838 cx.stop_propagation();
1839 })
1840 .ok();
1841 })
1842 .into_any()
1843 }),
1844 merge_adjacent: true,
1845 ..FoldPlaceholder::default()
1846 };
1847 let display_map = display_map.unwrap_or_else(|| {
1848 cx.new(|cx| {
1849 DisplayMap::new(
1850 buffer.clone(),
1851 style.font(),
1852 font_size,
1853 None,
1854 FILE_HEADER_HEIGHT,
1855 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1856 fold_placeholder,
1857 diagnostics_max_severity,
1858 cx,
1859 )
1860 })
1861 });
1862
1863 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1864
1865 let blink_manager = cx.new(|cx| {
1866 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1867 if is_minimap {
1868 blink_manager.disable(cx);
1869 }
1870 blink_manager
1871 });
1872
1873 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1874 .then(|| language_settings::SoftWrap::None);
1875
1876 let mut project_subscriptions = Vec::new();
1877 if full_mode && let Some(project) = project.as_ref() {
1878 project_subscriptions.push(cx.subscribe_in(
1879 project,
1880 window,
1881 |editor, _, event, window, cx| match event {
1882 project::Event::RefreshCodeLens => {
1883 // we always query lens with actions, without storing them, always refreshing them
1884 }
1885 project::Event::RefreshInlayHints => {
1886 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1887 }
1888 project::Event::LanguageServerAdded(..)
1889 | project::Event::LanguageServerRemoved(..) => {
1890 if editor.tasks_update_task.is_none() {
1891 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1892 }
1893 }
1894 project::Event::SnippetEdit(id, snippet_edits) => {
1895 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1896 let focus_handle = editor.focus_handle(cx);
1897 if focus_handle.is_focused(window) {
1898 let snapshot = buffer.read(cx).snapshot();
1899 for (range, snippet) in snippet_edits {
1900 let editor_range =
1901 language::range_from_lsp(*range).to_offset(&snapshot);
1902 editor
1903 .insert_snippet(
1904 &[editor_range],
1905 snippet.clone(),
1906 window,
1907 cx,
1908 )
1909 .ok();
1910 }
1911 }
1912 }
1913 }
1914 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1915 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1916 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1917 }
1918 }
1919 _ => {}
1920 },
1921 ));
1922 if let Some(task_inventory) = project
1923 .read(cx)
1924 .task_store()
1925 .read(cx)
1926 .task_inventory()
1927 .cloned()
1928 {
1929 project_subscriptions.push(cx.observe_in(
1930 &task_inventory,
1931 window,
1932 |editor, _, window, cx| {
1933 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1934 },
1935 ));
1936 };
1937
1938 project_subscriptions.push(cx.subscribe_in(
1939 &project.read(cx).breakpoint_store(),
1940 window,
1941 |editor, _, event, window, cx| match event {
1942 BreakpointStoreEvent::ClearDebugLines => {
1943 editor.clear_row_highlights::<ActiveDebugLine>();
1944 editor.refresh_inline_values(cx);
1945 }
1946 BreakpointStoreEvent::SetDebugLine => {
1947 if editor.go_to_active_debug_line(window, cx) {
1948 cx.stop_propagation();
1949 }
1950
1951 editor.refresh_inline_values(cx);
1952 }
1953 _ => {}
1954 },
1955 ));
1956 let git_store = project.read(cx).git_store().clone();
1957 let project = project.clone();
1958 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1959 match event {
1960 GitStoreEvent::RepositoryUpdated(
1961 _,
1962 RepositoryEvent::Updated {
1963 new_instance: true, ..
1964 },
1965 _,
1966 ) => {
1967 this.load_diff_task = Some(
1968 update_uncommitted_diff_for_buffer(
1969 cx.entity(),
1970 &project,
1971 this.buffer.read(cx).all_buffers(),
1972 this.buffer.clone(),
1973 cx,
1974 )
1975 .shared(),
1976 );
1977 }
1978 _ => {}
1979 }
1980 }));
1981 }
1982
1983 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1984
1985 let inlay_hint_settings =
1986 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1987 let focus_handle = cx.focus_handle();
1988 if !is_minimap {
1989 cx.on_focus(&focus_handle, window, Self::handle_focus)
1990 .detach();
1991 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1992 .detach();
1993 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1994 .detach();
1995 cx.on_blur(&focus_handle, window, Self::handle_blur)
1996 .detach();
1997 cx.observe_pending_input(window, Self::observe_pending_input)
1998 .detach();
1999 }
2000
2001 let show_indent_guides = if matches!(
2002 mode,
2003 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
2004 ) {
2005 Some(false)
2006 } else {
2007 None
2008 };
2009
2010 let breakpoint_store = match (&mode, project.as_ref()) {
2011 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2012 _ => None,
2013 };
2014
2015 let mut code_action_providers = Vec::new();
2016 let mut load_uncommitted_diff = None;
2017 if let Some(project) = project.clone() {
2018 load_uncommitted_diff = Some(
2019 update_uncommitted_diff_for_buffer(
2020 cx.entity(),
2021 &project,
2022 buffer.read(cx).all_buffers(),
2023 buffer.clone(),
2024 cx,
2025 )
2026 .shared(),
2027 );
2028 code_action_providers.push(Rc::new(project) as Rc<_>);
2029 }
2030
2031 let mut editor = Self {
2032 focus_handle,
2033 show_cursor_when_unfocused: false,
2034 last_focused_descendant: None,
2035 buffer: buffer.clone(),
2036 display_map: display_map.clone(),
2037 selections,
2038 scroll_manager: ScrollManager::new(cx),
2039 columnar_selection_state: None,
2040 add_selections_state: None,
2041 select_next_state: None,
2042 select_prev_state: None,
2043 selection_history: SelectionHistory::default(),
2044 defer_selection_effects: false,
2045 deferred_selection_effects_state: None,
2046 autoclose_regions: Vec::new(),
2047 snippet_stack: InvalidationStack::default(),
2048 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2049 ime_transaction: None,
2050 active_diagnostics: ActiveDiagnostic::None,
2051 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2052 inline_diagnostics_update: Task::ready(()),
2053 inline_diagnostics: Vec::new(),
2054 soft_wrap_mode_override,
2055 diagnostics_max_severity,
2056 hard_wrap: None,
2057 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2058 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2059 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2060 project,
2061 blink_manager: blink_manager.clone(),
2062 show_local_selections: true,
2063 show_scrollbars: ScrollbarAxes {
2064 horizontal: full_mode,
2065 vertical: full_mode,
2066 },
2067 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2068 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2069 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2070 show_gutter: full_mode,
2071 show_line_numbers: (!full_mode).then_some(false),
2072 use_relative_line_numbers: None,
2073 disable_expand_excerpt_buttons: !full_mode,
2074 show_git_diff_gutter: None,
2075 show_code_actions: None,
2076 show_runnables: None,
2077 show_breakpoints: None,
2078 show_wrap_guides: None,
2079 show_indent_guides,
2080 placeholder_text: None,
2081 highlight_order: 0,
2082 highlighted_rows: HashMap::default(),
2083 background_highlights: TreeMap::default(),
2084 gutter_highlights: TreeMap::default(),
2085 scrollbar_marker_state: ScrollbarMarkerState::default(),
2086 active_indent_guides_state: ActiveIndentGuidesState::default(),
2087 nav_history: None,
2088 context_menu: RefCell::new(None),
2089 context_menu_options: None,
2090 mouse_context_menu: None,
2091 completion_tasks: Vec::new(),
2092 inline_blame_popover: None,
2093 inline_blame_popover_show_task: None,
2094 signature_help_state: SignatureHelpState::default(),
2095 auto_signature_help: None,
2096 find_all_references_task_sources: Vec::new(),
2097 next_completion_id: 0,
2098 next_inlay_id: 0,
2099 code_action_providers,
2100 available_code_actions: None,
2101 code_actions_task: None,
2102 quick_selection_highlight_task: None,
2103 debounced_selection_highlight_task: None,
2104 document_highlights_task: None,
2105 linked_editing_range_task: None,
2106 pending_rename: None,
2107 searchable: !is_minimap,
2108 cursor_shape: EditorSettings::get_global(cx)
2109 .cursor_shape
2110 .unwrap_or_default(),
2111 current_line_highlight: None,
2112 autoindent_mode: Some(AutoindentMode::EachLine),
2113 collapse_matches: false,
2114 workspace: None,
2115 input_enabled: !is_minimap,
2116 use_modal_editing: full_mode,
2117 read_only: is_minimap,
2118 use_autoclose: true,
2119 use_auto_surround: true,
2120 auto_replace_emoji_shortcode: false,
2121 jsx_tag_auto_close_enabled_in_any_buffer: false,
2122 leader_id: None,
2123 remote_id: None,
2124 hover_state: HoverState::default(),
2125 pending_mouse_down: None,
2126 hovered_link_state: None,
2127 edit_prediction_provider: None,
2128 active_edit_prediction: None,
2129 stale_edit_prediction_in_menu: None,
2130 edit_prediction_preview: EditPredictionPreview::Inactive {
2131 released_too_fast: false,
2132 },
2133 inline_diagnostics_enabled: full_mode,
2134 diagnostics_enabled: full_mode,
2135 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2136 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2137 gutter_hovered: false,
2138 pixel_position_of_newest_cursor: None,
2139 last_bounds: None,
2140 last_position_map: None,
2141 expect_bounds_change: None,
2142 gutter_dimensions: GutterDimensions::default(),
2143 style: None,
2144 show_cursor_names: false,
2145 hovered_cursors: HashMap::default(),
2146 next_editor_action_id: EditorActionId::default(),
2147 editor_actions: Rc::default(),
2148 edit_predictions_hidden_for_vim_mode: false,
2149 show_edit_predictions_override: None,
2150 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2151 edit_prediction_settings: EditPredictionSettings::Disabled,
2152 edit_prediction_indent_conflict: false,
2153 edit_prediction_requires_modifier_in_indent_conflict: true,
2154 custom_context_menu: None,
2155 show_git_blame_gutter: false,
2156 show_git_blame_inline: false,
2157 show_selection_menu: None,
2158 show_git_blame_inline_delay_task: None,
2159 git_blame_inline_enabled: full_mode
2160 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2161 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2162 serialize_dirty_buffers: !is_minimap
2163 && ProjectSettings::get_global(cx)
2164 .session
2165 .restore_unsaved_buffers,
2166 blame: None,
2167 blame_subscription: None,
2168 tasks: BTreeMap::default(),
2169
2170 breakpoint_store,
2171 gutter_breakpoint_indicator: (None, None),
2172 hovered_diff_hunk_row: None,
2173 _subscriptions: (!is_minimap)
2174 .then(|| {
2175 vec![
2176 cx.observe(&buffer, Self::on_buffer_changed),
2177 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2178 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2179 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2180 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2181 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2182 cx.observe_window_activation(window, |editor, window, cx| {
2183 let active = window.is_window_active();
2184 editor.blink_manager.update(cx, |blink_manager, cx| {
2185 if active {
2186 blink_manager.enable(cx);
2187 } else {
2188 blink_manager.disable(cx);
2189 }
2190 });
2191 if active {
2192 editor.show_mouse_cursor(cx);
2193 }
2194 }),
2195 ]
2196 })
2197 .unwrap_or_default(),
2198 tasks_update_task: None,
2199 pull_diagnostics_task: Task::ready(()),
2200 colors: None,
2201 next_color_inlay_id: 0,
2202 linked_edit_ranges: Default::default(),
2203 in_project_search: false,
2204 previous_search_ranges: None,
2205 breadcrumb_header: None,
2206 focused_block: None,
2207 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2208 addons: HashMap::default(),
2209 registered_buffers: HashMap::default(),
2210 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2211 selection_mark_mode: false,
2212 toggle_fold_multiple_buffers: Task::ready(()),
2213 serialize_selections: Task::ready(()),
2214 serialize_folds: Task::ready(()),
2215 text_style_refinement: None,
2216 load_diff_task: load_uncommitted_diff,
2217 temporary_diff_override: false,
2218 mouse_cursor_hidden: false,
2219 minimap: None,
2220 hide_mouse_mode: EditorSettings::get_global(cx)
2221 .hide_mouse
2222 .unwrap_or_default(),
2223 change_list: ChangeList::new(),
2224 mode,
2225 selection_drag_state: SelectionDragState::None,
2226 folding_newlines: Task::ready(()),
2227 };
2228
2229 if is_minimap {
2230 return editor;
2231 }
2232
2233 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2234 editor
2235 ._subscriptions
2236 .push(cx.observe(breakpoints, |_, _, cx| {
2237 cx.notify();
2238 }));
2239 }
2240 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2241 editor._subscriptions.extend(project_subscriptions);
2242
2243 editor._subscriptions.push(cx.subscribe_in(
2244 &cx.entity(),
2245 window,
2246 |editor, _, e: &EditorEvent, window, cx| match e {
2247 EditorEvent::ScrollPositionChanged { local, .. } => {
2248 if *local {
2249 let new_anchor = editor.scroll_manager.anchor();
2250 let snapshot = editor.snapshot(window, cx);
2251 editor.update_restoration_data(cx, move |data| {
2252 data.scroll_position = (
2253 new_anchor.top_row(&snapshot.buffer_snapshot),
2254 new_anchor.offset,
2255 );
2256 });
2257 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2258 editor.inline_blame_popover.take();
2259 }
2260 }
2261 EditorEvent::Edited { .. } => {
2262 if !vim_enabled(cx) {
2263 let (map, selections) = editor.selections.all_adjusted_display(cx);
2264 let pop_state = editor
2265 .change_list
2266 .last()
2267 .map(|previous| {
2268 previous.len() == selections.len()
2269 && previous.iter().enumerate().all(|(ix, p)| {
2270 p.to_display_point(&map).row()
2271 == selections[ix].head().row()
2272 })
2273 })
2274 .unwrap_or(false);
2275 let new_positions = selections
2276 .into_iter()
2277 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2278 .collect();
2279 editor
2280 .change_list
2281 .push_to_change_list(pop_state, new_positions);
2282 }
2283 }
2284 _ => (),
2285 },
2286 ));
2287
2288 if let Some(dap_store) = editor
2289 .project
2290 .as_ref()
2291 .map(|project| project.read(cx).dap_store())
2292 {
2293 let weak_editor = cx.weak_entity();
2294
2295 editor
2296 ._subscriptions
2297 .push(
2298 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2299 let session_entity = cx.entity();
2300 weak_editor
2301 .update(cx, |editor, cx| {
2302 editor._subscriptions.push(
2303 cx.subscribe(&session_entity, Self::on_debug_session_event),
2304 );
2305 })
2306 .ok();
2307 }),
2308 );
2309
2310 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2311 editor
2312 ._subscriptions
2313 .push(cx.subscribe(&session, Self::on_debug_session_event));
2314 }
2315 }
2316
2317 // skip adding the initial selection to selection history
2318 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2319 editor.end_selection(window, cx);
2320 editor.selection_history.mode = SelectionHistoryMode::Normal;
2321
2322 editor.scroll_manager.show_scrollbars(window, cx);
2323 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2324
2325 if full_mode {
2326 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2327 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2328
2329 if editor.git_blame_inline_enabled {
2330 editor.start_git_blame_inline(false, window, cx);
2331 }
2332
2333 editor.go_to_active_debug_line(window, cx);
2334
2335 if let Some(buffer) = buffer.read(cx).as_singleton()
2336 && let Some(project) = editor.project()
2337 {
2338 let handle = project.update(cx, |project, cx| {
2339 project.register_buffer_with_language_servers(&buffer, cx)
2340 });
2341 editor
2342 .registered_buffers
2343 .insert(buffer.read(cx).remote_id(), handle);
2344 }
2345
2346 editor.minimap =
2347 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2348 editor.colors = Some(LspColorData::new(cx));
2349 editor.update_lsp_data(false, None, window, cx);
2350 }
2351
2352 if editor.mode.is_full() {
2353 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2354 }
2355
2356 editor
2357 }
2358
2359 pub fn deploy_mouse_context_menu(
2360 &mut self,
2361 position: gpui::Point<Pixels>,
2362 context_menu: Entity<ContextMenu>,
2363 window: &mut Window,
2364 cx: &mut Context<Self>,
2365 ) {
2366 self.mouse_context_menu = Some(MouseContextMenu::new(
2367 self,
2368 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2369 context_menu,
2370 window,
2371 cx,
2372 ));
2373 }
2374
2375 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2376 self.mouse_context_menu
2377 .as_ref()
2378 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2379 }
2380
2381 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2382 if self
2383 .selections
2384 .pending
2385 .as_ref()
2386 .is_some_and(|pending_selection| {
2387 let snapshot = self.buffer().read(cx).snapshot(cx);
2388 pending_selection
2389 .selection
2390 .range()
2391 .includes(range, &snapshot)
2392 })
2393 {
2394 return true;
2395 }
2396
2397 self.selections
2398 .disjoint_in_range::<usize>(range.clone(), cx)
2399 .into_iter()
2400 .any(|selection| {
2401 // This is needed to cover a corner case, if we just check for an existing
2402 // selection in the fold range, having a cursor at the start of the fold
2403 // marks it as selected. Non-empty selections don't cause this.
2404 let length = selection.end - selection.start;
2405 length > 0
2406 })
2407 }
2408
2409 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2410 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2411 }
2412
2413 fn key_context_internal(
2414 &self,
2415 has_active_edit_prediction: bool,
2416 window: &Window,
2417 cx: &App,
2418 ) -> KeyContext {
2419 let mut key_context = KeyContext::new_with_defaults();
2420 key_context.add("Editor");
2421 let mode = match self.mode {
2422 EditorMode::SingleLine { .. } => "single_line",
2423 EditorMode::AutoHeight { .. } => "auto_height",
2424 EditorMode::Minimap { .. } => "minimap",
2425 EditorMode::Full { .. } => "full",
2426 };
2427
2428 if EditorSettings::jupyter_enabled(cx) {
2429 key_context.add("jupyter");
2430 }
2431
2432 key_context.set("mode", mode);
2433 if self.pending_rename.is_some() {
2434 key_context.add("renaming");
2435 }
2436
2437 match self.context_menu.borrow().as_ref() {
2438 Some(CodeContextMenu::Completions(menu)) => {
2439 if menu.visible() {
2440 key_context.add("menu");
2441 key_context.add("showing_completions");
2442 }
2443 }
2444 Some(CodeContextMenu::CodeActions(menu)) => {
2445 if menu.visible() {
2446 key_context.add("menu");
2447 key_context.add("showing_code_actions")
2448 }
2449 }
2450 None => {}
2451 }
2452
2453 if self.signature_help_state.has_multiple_signatures() {
2454 key_context.add("showing_signature_help");
2455 }
2456
2457 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2458 if !self.focus_handle(cx).contains_focused(window, cx)
2459 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2460 {
2461 for addon in self.addons.values() {
2462 addon.extend_key_context(&mut key_context, cx)
2463 }
2464 }
2465
2466 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2467 if let Some(extension) = singleton_buffer
2468 .read(cx)
2469 .file()
2470 .and_then(|file| file.path().extension()?.to_str())
2471 {
2472 key_context.set("extension", extension.to_string());
2473 }
2474 } else {
2475 key_context.add("multibuffer");
2476 }
2477
2478 if has_active_edit_prediction {
2479 if self.edit_prediction_in_conflict() {
2480 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2481 } else {
2482 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2483 key_context.add("copilot_suggestion");
2484 }
2485 }
2486
2487 if self.selection_mark_mode {
2488 key_context.add("selection_mode");
2489 }
2490
2491 key_context
2492 }
2493
2494 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2495 if self.mouse_cursor_hidden {
2496 self.mouse_cursor_hidden = false;
2497 cx.notify();
2498 }
2499 }
2500
2501 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2502 let hide_mouse_cursor = match origin {
2503 HideMouseCursorOrigin::TypingAction => {
2504 matches!(
2505 self.hide_mouse_mode,
2506 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2507 )
2508 }
2509 HideMouseCursorOrigin::MovementAction => {
2510 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2511 }
2512 };
2513 if self.mouse_cursor_hidden != hide_mouse_cursor {
2514 self.mouse_cursor_hidden = hide_mouse_cursor;
2515 cx.notify();
2516 }
2517 }
2518
2519 pub fn edit_prediction_in_conflict(&self) -> bool {
2520 if !self.show_edit_predictions_in_menu() {
2521 return false;
2522 }
2523
2524 let showing_completions = self
2525 .context_menu
2526 .borrow()
2527 .as_ref()
2528 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2529
2530 showing_completions
2531 || self.edit_prediction_requires_modifier()
2532 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2533 // bindings to insert tab characters.
2534 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2535 }
2536
2537 pub fn accept_edit_prediction_keybind(
2538 &self,
2539 accept_partial: bool,
2540 window: &Window,
2541 cx: &App,
2542 ) -> AcceptEditPredictionBinding {
2543 let key_context = self.key_context_internal(true, window, cx);
2544 let in_conflict = self.edit_prediction_in_conflict();
2545
2546 let bindings = if accept_partial {
2547 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2548 } else {
2549 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2550 };
2551
2552 // TODO: if the binding contains multiple keystrokes, display all of them, not
2553 // just the first one.
2554 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2555 !in_conflict
2556 || binding
2557 .keystrokes()
2558 .first()
2559 .is_some_and(|keystroke| keystroke.modifiers.modified())
2560 }))
2561 }
2562
2563 pub fn new_file(
2564 workspace: &mut Workspace,
2565 _: &workspace::NewFile,
2566 window: &mut Window,
2567 cx: &mut Context<Workspace>,
2568 ) {
2569 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2570 "Failed to create buffer",
2571 window,
2572 cx,
2573 |e, _, _| match e.error_code() {
2574 ErrorCode::RemoteUpgradeRequired => Some(format!(
2575 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2576 e.error_tag("required").unwrap_or("the latest version")
2577 )),
2578 _ => None,
2579 },
2580 );
2581 }
2582
2583 pub fn new_in_workspace(
2584 workspace: &mut Workspace,
2585 window: &mut Window,
2586 cx: &mut Context<Workspace>,
2587 ) -> Task<Result<Entity<Editor>>> {
2588 let project = workspace.project().clone();
2589 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2590
2591 cx.spawn_in(window, async move |workspace, cx| {
2592 let buffer = create.await?;
2593 workspace.update_in(cx, |workspace, window, cx| {
2594 let editor =
2595 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2596 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2597 editor
2598 })
2599 })
2600 }
2601
2602 fn new_file_vertical(
2603 workspace: &mut Workspace,
2604 _: &workspace::NewFileSplitVertical,
2605 window: &mut Window,
2606 cx: &mut Context<Workspace>,
2607 ) {
2608 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2609 }
2610
2611 fn new_file_horizontal(
2612 workspace: &mut Workspace,
2613 _: &workspace::NewFileSplitHorizontal,
2614 window: &mut Window,
2615 cx: &mut Context<Workspace>,
2616 ) {
2617 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2618 }
2619
2620 fn new_file_in_direction(
2621 workspace: &mut Workspace,
2622 direction: SplitDirection,
2623 window: &mut Window,
2624 cx: &mut Context<Workspace>,
2625 ) {
2626 let project = workspace.project().clone();
2627 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2628
2629 cx.spawn_in(window, async move |workspace, cx| {
2630 let buffer = create.await?;
2631 workspace.update_in(cx, move |workspace, window, cx| {
2632 workspace.split_item(
2633 direction,
2634 Box::new(
2635 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2636 ),
2637 window,
2638 cx,
2639 )
2640 })?;
2641 anyhow::Ok(())
2642 })
2643 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2644 match e.error_code() {
2645 ErrorCode::RemoteUpgradeRequired => Some(format!(
2646 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2647 e.error_tag("required").unwrap_or("the latest version")
2648 )),
2649 _ => None,
2650 }
2651 });
2652 }
2653
2654 pub fn leader_id(&self) -> Option<CollaboratorId> {
2655 self.leader_id
2656 }
2657
2658 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2659 &self.buffer
2660 }
2661
2662 pub fn project(&self) -> Option<&Entity<Project>> {
2663 self.project.as_ref()
2664 }
2665
2666 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2667 self.workspace.as_ref()?.0.upgrade()
2668 }
2669
2670 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2671 self.buffer().read(cx).title(cx)
2672 }
2673
2674 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2675 let git_blame_gutter_max_author_length = self
2676 .render_git_blame_gutter(cx)
2677 .then(|| {
2678 if let Some(blame) = self.blame.as_ref() {
2679 let max_author_length =
2680 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2681 Some(max_author_length)
2682 } else {
2683 None
2684 }
2685 })
2686 .flatten();
2687
2688 EditorSnapshot {
2689 mode: self.mode.clone(),
2690 show_gutter: self.show_gutter,
2691 show_line_numbers: self.show_line_numbers,
2692 show_git_diff_gutter: self.show_git_diff_gutter,
2693 show_code_actions: self.show_code_actions,
2694 show_runnables: self.show_runnables,
2695 show_breakpoints: self.show_breakpoints,
2696 git_blame_gutter_max_author_length,
2697 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2698 scroll_anchor: self.scroll_manager.anchor(),
2699 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2700 placeholder_text: self.placeholder_text.clone(),
2701 is_focused: self.focus_handle.is_focused(window),
2702 current_line_highlight: self
2703 .current_line_highlight
2704 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2705 gutter_hovered: self.gutter_hovered,
2706 }
2707 }
2708
2709 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2710 self.buffer.read(cx).language_at(point, cx)
2711 }
2712
2713 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2714 self.buffer.read(cx).read(cx).file_at(point).cloned()
2715 }
2716
2717 pub fn active_excerpt(
2718 &self,
2719 cx: &App,
2720 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2721 self.buffer
2722 .read(cx)
2723 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2724 }
2725
2726 pub fn mode(&self) -> &EditorMode {
2727 &self.mode
2728 }
2729
2730 pub fn set_mode(&mut self, mode: EditorMode) {
2731 self.mode = mode;
2732 }
2733
2734 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2735 self.collaboration_hub.as_deref()
2736 }
2737
2738 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2739 self.collaboration_hub = Some(hub);
2740 }
2741
2742 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2743 self.in_project_search = in_project_search;
2744 }
2745
2746 pub fn set_custom_context_menu(
2747 &mut self,
2748 f: impl 'static
2749 + Fn(
2750 &mut Self,
2751 DisplayPoint,
2752 &mut Window,
2753 &mut Context<Self>,
2754 ) -> Option<Entity<ui::ContextMenu>>,
2755 ) {
2756 self.custom_context_menu = Some(Box::new(f))
2757 }
2758
2759 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2760 self.completion_provider = provider;
2761 }
2762
2763 #[cfg(any(test, feature = "test-support"))]
2764 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2765 self.completion_provider.clone()
2766 }
2767
2768 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2769 self.semantics_provider.clone()
2770 }
2771
2772 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2773 self.semantics_provider = provider;
2774 }
2775
2776 pub fn set_edit_prediction_provider<T>(
2777 &mut self,
2778 provider: Option<Entity<T>>,
2779 window: &mut Window,
2780 cx: &mut Context<Self>,
2781 ) where
2782 T: EditPredictionProvider,
2783 {
2784 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2785 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2786 if this.focus_handle.is_focused(window) {
2787 this.update_visible_edit_prediction(window, cx);
2788 }
2789 }),
2790 provider: Arc::new(provider),
2791 });
2792 self.update_edit_prediction_settings(cx);
2793 self.refresh_edit_prediction(false, false, window, cx);
2794 }
2795
2796 pub fn placeholder_text(&self) -> Option<&str> {
2797 self.placeholder_text.as_deref()
2798 }
2799
2800 pub fn set_placeholder_text(
2801 &mut self,
2802 placeholder_text: impl Into<Arc<str>>,
2803 cx: &mut Context<Self>,
2804 ) {
2805 let placeholder_text = Some(placeholder_text.into());
2806 if self.placeholder_text != placeholder_text {
2807 self.placeholder_text = placeholder_text;
2808 cx.notify();
2809 }
2810 }
2811
2812 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2813 self.cursor_shape = cursor_shape;
2814
2815 // Disrupt blink for immediate user feedback that the cursor shape has changed
2816 self.blink_manager.update(cx, BlinkManager::show_cursor);
2817
2818 cx.notify();
2819 }
2820
2821 pub fn set_current_line_highlight(
2822 &mut self,
2823 current_line_highlight: Option<CurrentLineHighlight>,
2824 ) {
2825 self.current_line_highlight = current_line_highlight;
2826 }
2827
2828 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2829 self.collapse_matches = collapse_matches;
2830 }
2831
2832 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2833 let buffers = self.buffer.read(cx).all_buffers();
2834 let Some(project) = self.project.as_ref() else {
2835 return;
2836 };
2837 project.update(cx, |project, cx| {
2838 for buffer in buffers {
2839 self.registered_buffers
2840 .entry(buffer.read(cx).remote_id())
2841 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2842 }
2843 })
2844 }
2845
2846 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2847 if self.collapse_matches {
2848 return range.start..range.start;
2849 }
2850 range.clone()
2851 }
2852
2853 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2854 if self.display_map.read(cx).clip_at_line_ends != clip {
2855 self.display_map
2856 .update(cx, |map, _| map.clip_at_line_ends = clip);
2857 }
2858 }
2859
2860 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2861 self.input_enabled = input_enabled;
2862 }
2863
2864 pub fn set_edit_predictions_hidden_for_vim_mode(
2865 &mut self,
2866 hidden: bool,
2867 window: &mut Window,
2868 cx: &mut Context<Self>,
2869 ) {
2870 if hidden != self.edit_predictions_hidden_for_vim_mode {
2871 self.edit_predictions_hidden_for_vim_mode = hidden;
2872 if hidden {
2873 self.update_visible_edit_prediction(window, cx);
2874 } else {
2875 self.refresh_edit_prediction(true, false, window, cx);
2876 }
2877 }
2878 }
2879
2880 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2881 self.menu_edit_predictions_policy = value;
2882 }
2883
2884 pub fn set_autoindent(&mut self, autoindent: bool) {
2885 if autoindent {
2886 self.autoindent_mode = Some(AutoindentMode::EachLine);
2887 } else {
2888 self.autoindent_mode = None;
2889 }
2890 }
2891
2892 pub fn read_only(&self, cx: &App) -> bool {
2893 self.read_only || self.buffer.read(cx).read_only()
2894 }
2895
2896 pub fn set_read_only(&mut self, read_only: bool) {
2897 self.read_only = read_only;
2898 }
2899
2900 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2901 self.use_autoclose = autoclose;
2902 }
2903
2904 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2905 self.use_auto_surround = auto_surround;
2906 }
2907
2908 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2909 self.auto_replace_emoji_shortcode = auto_replace;
2910 }
2911
2912 pub fn toggle_edit_predictions(
2913 &mut self,
2914 _: &ToggleEditPrediction,
2915 window: &mut Window,
2916 cx: &mut Context<Self>,
2917 ) {
2918 if self.show_edit_predictions_override.is_some() {
2919 self.set_show_edit_predictions(None, window, cx);
2920 } else {
2921 let show_edit_predictions = !self.edit_predictions_enabled();
2922 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2923 }
2924 }
2925
2926 pub fn set_show_edit_predictions(
2927 &mut self,
2928 show_edit_predictions: Option<bool>,
2929 window: &mut Window,
2930 cx: &mut Context<Self>,
2931 ) {
2932 self.show_edit_predictions_override = show_edit_predictions;
2933 self.update_edit_prediction_settings(cx);
2934
2935 if let Some(false) = show_edit_predictions {
2936 self.discard_edit_prediction(false, cx);
2937 } else {
2938 self.refresh_edit_prediction(false, true, window, cx);
2939 }
2940 }
2941
2942 fn edit_predictions_disabled_in_scope(
2943 &self,
2944 buffer: &Entity<Buffer>,
2945 buffer_position: language::Anchor,
2946 cx: &App,
2947 ) -> bool {
2948 let snapshot = buffer.read(cx).snapshot();
2949 let settings = snapshot.settings_at(buffer_position, cx);
2950
2951 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2952 return false;
2953 };
2954
2955 scope.override_name().is_some_and(|scope_name| {
2956 settings
2957 .edit_predictions_disabled_in
2958 .iter()
2959 .any(|s| s == scope_name)
2960 })
2961 }
2962
2963 pub fn set_use_modal_editing(&mut self, to: bool) {
2964 self.use_modal_editing = to;
2965 }
2966
2967 pub fn use_modal_editing(&self) -> bool {
2968 self.use_modal_editing
2969 }
2970
2971 fn selections_did_change(
2972 &mut self,
2973 local: bool,
2974 old_cursor_position: &Anchor,
2975 effects: SelectionEffects,
2976 window: &mut Window,
2977 cx: &mut Context<Self>,
2978 ) {
2979 window.invalidate_character_coordinates();
2980
2981 // Copy selections to primary selection buffer
2982 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2983 if local {
2984 let selections = self.selections.all::<usize>(cx);
2985 let buffer_handle = self.buffer.read(cx).read(cx);
2986
2987 let mut text = String::new();
2988 for (index, selection) in selections.iter().enumerate() {
2989 let text_for_selection = buffer_handle
2990 .text_for_range(selection.start..selection.end)
2991 .collect::<String>();
2992
2993 text.push_str(&text_for_selection);
2994 if index != selections.len() - 1 {
2995 text.push('\n');
2996 }
2997 }
2998
2999 if !text.is_empty() {
3000 cx.write_to_primary(ClipboardItem::new_string(text));
3001 }
3002 }
3003
3004 let selection_anchors = self.selections.disjoint_anchors();
3005
3006 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3007 self.buffer.update(cx, |buffer, cx| {
3008 buffer.set_active_selections(
3009 &selection_anchors,
3010 self.selections.line_mode,
3011 self.cursor_shape,
3012 cx,
3013 )
3014 });
3015 }
3016 let display_map = self
3017 .display_map
3018 .update(cx, |display_map, cx| display_map.snapshot(cx));
3019 let buffer = &display_map.buffer_snapshot;
3020 if self.selections.count() == 1 {
3021 self.add_selections_state = None;
3022 }
3023 self.select_next_state = None;
3024 self.select_prev_state = None;
3025 self.select_syntax_node_history.try_clear();
3026 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3027 self.snippet_stack.invalidate(&selection_anchors, buffer);
3028 self.take_rename(false, window, cx);
3029
3030 let newest_selection = self.selections.newest_anchor();
3031 let new_cursor_position = newest_selection.head();
3032 let selection_start = newest_selection.start;
3033
3034 let new_cursor_point = new_cursor_position.to_point(buffer);
3035 if let Some(project) = self.project()
3036 && let Some((path, worktree_path)) =
3037 self.file_at(new_cursor_point, cx).and_then(|file| {
3038 file.as_local().and_then(|file| {
3039 let worktree =
3040 project.read(cx).worktree_for_id(file.worktree_id(cx), cx)?;
3041 Some((file.abs_path(cx), worktree.read(cx).abs_path()))
3042 })
3043 })
3044 {
3045 *LAST_CURSOR_POSITION_WATCH.0.lock().borrow_mut() = Some(LastCursorPosition {
3046 path,
3047 worktree_path,
3048 point: new_cursor_point,
3049 });
3050 }
3051
3052 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3053 self.push_to_nav_history(
3054 *old_cursor_position,
3055 Some(new_cursor_point),
3056 false,
3057 effects.nav_history == Some(true),
3058 cx,
3059 );
3060 }
3061
3062 if local {
3063 if let Some(buffer_id) = new_cursor_position.buffer_id
3064 && !self.registered_buffers.contains_key(&buffer_id)
3065 && let Some(project) = self.project.as_ref()
3066 {
3067 project.update(cx, |project, cx| {
3068 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3069 return;
3070 };
3071 self.registered_buffers.insert(
3072 buffer_id,
3073 project.register_buffer_with_language_servers(&buffer, cx),
3074 );
3075 })
3076 }
3077
3078 let mut context_menu = self.context_menu.borrow_mut();
3079 let completion_menu = match context_menu.as_ref() {
3080 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3081 Some(CodeContextMenu::CodeActions(_)) => {
3082 *context_menu = None;
3083 None
3084 }
3085 None => None,
3086 };
3087 let completion_position = completion_menu.map(|menu| menu.initial_position);
3088 drop(context_menu);
3089
3090 if effects.completions
3091 && let Some(completion_position) = completion_position
3092 {
3093 let start_offset = selection_start.to_offset(buffer);
3094 let position_matches = start_offset == completion_position.to_offset(buffer);
3095 let continue_showing = if position_matches {
3096 if self.snippet_stack.is_empty() {
3097 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3098 } else {
3099 // Snippet choices can be shown even when the cursor is in whitespace.
3100 // Dismissing the menu with actions like backspace is handled by
3101 // invalidation regions.
3102 true
3103 }
3104 } else {
3105 false
3106 };
3107
3108 if continue_showing {
3109 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3110 } else {
3111 self.hide_context_menu(window, cx);
3112 }
3113 }
3114
3115 hide_hover(self, cx);
3116
3117 if old_cursor_position.to_display_point(&display_map).row()
3118 != new_cursor_position.to_display_point(&display_map).row()
3119 {
3120 self.available_code_actions.take();
3121 }
3122 self.refresh_code_actions(window, cx);
3123 self.refresh_document_highlights(cx);
3124 self.refresh_selected_text_highlights(false, window, cx);
3125 refresh_matching_bracket_highlights(self, window, cx);
3126 self.update_visible_edit_prediction(window, cx);
3127 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3128 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3129 self.inline_blame_popover.take();
3130 if self.git_blame_inline_enabled {
3131 self.start_inline_blame_timer(window, cx);
3132 }
3133 }
3134
3135 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3136 cx.emit(EditorEvent::SelectionsChanged { local });
3137
3138 let selections = &self.selections.disjoint;
3139 if selections.len() == 1 {
3140 cx.emit(SearchEvent::ActiveMatchChanged)
3141 }
3142 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3143 let inmemory_selections = selections
3144 .iter()
3145 .map(|s| {
3146 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3147 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3148 })
3149 .collect();
3150 self.update_restoration_data(cx, |data| {
3151 data.selections = inmemory_selections;
3152 });
3153
3154 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3155 && let Some(workspace_id) =
3156 self.workspace.as_ref().and_then(|workspace| workspace.1)
3157 {
3158 let snapshot = self.buffer().read(cx).snapshot(cx);
3159 let selections = selections.clone();
3160 let background_executor = cx.background_executor().clone();
3161 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3162 self.serialize_selections = cx.background_spawn(async move {
3163 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3164 let db_selections = selections
3165 .iter()
3166 .map(|selection| {
3167 (
3168 selection.start.to_offset(&snapshot),
3169 selection.end.to_offset(&snapshot),
3170 )
3171 })
3172 .collect();
3173
3174 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3175 .await
3176 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3177 .log_err();
3178 });
3179 }
3180 }
3181
3182 cx.notify();
3183 }
3184
3185 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3186 use text::ToOffset as _;
3187 use text::ToPoint as _;
3188
3189 if self.mode.is_minimap()
3190 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3191 {
3192 return;
3193 }
3194
3195 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3196 return;
3197 };
3198
3199 let snapshot = singleton.read(cx).snapshot();
3200 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3201 let display_snapshot = display_map.snapshot(cx);
3202
3203 display_snapshot
3204 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3205 .map(|fold| {
3206 fold.range.start.text_anchor.to_point(&snapshot)
3207 ..fold.range.end.text_anchor.to_point(&snapshot)
3208 })
3209 .collect()
3210 });
3211 self.update_restoration_data(cx, |data| {
3212 data.folds = inmemory_folds;
3213 });
3214
3215 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3216 return;
3217 };
3218 let background_executor = cx.background_executor().clone();
3219 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3220 let db_folds = self.display_map.update(cx, |display_map, cx| {
3221 display_map
3222 .snapshot(cx)
3223 .folds_in_range(0..snapshot.len())
3224 .map(|fold| {
3225 (
3226 fold.range.start.text_anchor.to_offset(&snapshot),
3227 fold.range.end.text_anchor.to_offset(&snapshot),
3228 )
3229 })
3230 .collect()
3231 });
3232 self.serialize_folds = cx.background_spawn(async move {
3233 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3234 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3235 .await
3236 .with_context(|| {
3237 format!(
3238 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3239 )
3240 })
3241 .log_err();
3242 });
3243 }
3244
3245 pub fn sync_selections(
3246 &mut self,
3247 other: Entity<Editor>,
3248 cx: &mut Context<Self>,
3249 ) -> gpui::Subscription {
3250 let other_selections = other.read(cx).selections.disjoint.to_vec();
3251 self.selections.change_with(cx, |selections| {
3252 selections.select_anchors(other_selections);
3253 });
3254
3255 let other_subscription =
3256 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3257 EditorEvent::SelectionsChanged { local: true } => {
3258 let other_selections = other.read(cx).selections.disjoint.to_vec();
3259 if other_selections.is_empty() {
3260 return;
3261 }
3262 this.selections.change_with(cx, |selections| {
3263 selections.select_anchors(other_selections);
3264 });
3265 }
3266 _ => {}
3267 });
3268
3269 let this_subscription =
3270 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3271 EditorEvent::SelectionsChanged { local: true } => {
3272 let these_selections = this.selections.disjoint.to_vec();
3273 if these_selections.is_empty() {
3274 return;
3275 }
3276 other.update(cx, |other_editor, cx| {
3277 other_editor.selections.change_with(cx, |selections| {
3278 selections.select_anchors(these_selections);
3279 })
3280 });
3281 }
3282 _ => {}
3283 });
3284
3285 Subscription::join(other_subscription, this_subscription)
3286 }
3287
3288 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3289 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3290 /// effects of selection change occur at the end of the transaction.
3291 pub fn change_selections<R>(
3292 &mut self,
3293 effects: SelectionEffects,
3294 window: &mut Window,
3295 cx: &mut Context<Self>,
3296 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3297 ) -> R {
3298 if let Some(state) = &mut self.deferred_selection_effects_state {
3299 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3300 state.effects.completions = effects.completions;
3301 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3302 let (changed, result) = self.selections.change_with(cx, change);
3303 state.changed |= changed;
3304 return result;
3305 }
3306 let mut state = DeferredSelectionEffectsState {
3307 changed: false,
3308 effects,
3309 old_cursor_position: self.selections.newest_anchor().head(),
3310 history_entry: SelectionHistoryEntry {
3311 selections: self.selections.disjoint_anchors(),
3312 select_next_state: self.select_next_state.clone(),
3313 select_prev_state: self.select_prev_state.clone(),
3314 add_selections_state: self.add_selections_state.clone(),
3315 },
3316 };
3317 let (changed, result) = self.selections.change_with(cx, change);
3318 state.changed = state.changed || changed;
3319 if self.defer_selection_effects {
3320 self.deferred_selection_effects_state = Some(state);
3321 } else {
3322 self.apply_selection_effects(state, window, cx);
3323 }
3324 result
3325 }
3326
3327 /// Defers the effects of selection change, so that the effects of multiple calls to
3328 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3329 /// to selection history and the state of popovers based on selection position aren't
3330 /// erroneously updated.
3331 pub fn with_selection_effects_deferred<R>(
3332 &mut self,
3333 window: &mut Window,
3334 cx: &mut Context<Self>,
3335 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3336 ) -> R {
3337 let already_deferred = self.defer_selection_effects;
3338 self.defer_selection_effects = true;
3339 let result = update(self, window, cx);
3340 if !already_deferred {
3341 self.defer_selection_effects = false;
3342 if let Some(state) = self.deferred_selection_effects_state.take() {
3343 self.apply_selection_effects(state, window, cx);
3344 }
3345 }
3346 result
3347 }
3348
3349 fn apply_selection_effects(
3350 &mut self,
3351 state: DeferredSelectionEffectsState,
3352 window: &mut Window,
3353 cx: &mut Context<Self>,
3354 ) {
3355 if state.changed {
3356 self.selection_history.push(state.history_entry);
3357
3358 if let Some(autoscroll) = state.effects.scroll {
3359 self.request_autoscroll(autoscroll, cx);
3360 }
3361
3362 let old_cursor_position = &state.old_cursor_position;
3363
3364 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3365
3366 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3367 self.show_signature_help(&ShowSignatureHelp, window, cx);
3368 }
3369 }
3370 }
3371
3372 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3373 where
3374 I: IntoIterator<Item = (Range<S>, T)>,
3375 S: ToOffset,
3376 T: Into<Arc<str>>,
3377 {
3378 if self.read_only(cx) {
3379 return;
3380 }
3381
3382 self.buffer
3383 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3384 }
3385
3386 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3387 where
3388 I: IntoIterator<Item = (Range<S>, T)>,
3389 S: ToOffset,
3390 T: Into<Arc<str>>,
3391 {
3392 if self.read_only(cx) {
3393 return;
3394 }
3395
3396 self.buffer.update(cx, |buffer, cx| {
3397 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3398 });
3399 }
3400
3401 pub fn edit_with_block_indent<I, S, T>(
3402 &mut self,
3403 edits: I,
3404 original_indent_columns: Vec<Option<u32>>,
3405 cx: &mut Context<Self>,
3406 ) where
3407 I: IntoIterator<Item = (Range<S>, T)>,
3408 S: ToOffset,
3409 T: Into<Arc<str>>,
3410 {
3411 if self.read_only(cx) {
3412 return;
3413 }
3414
3415 self.buffer.update(cx, |buffer, cx| {
3416 buffer.edit(
3417 edits,
3418 Some(AutoindentMode::Block {
3419 original_indent_columns,
3420 }),
3421 cx,
3422 )
3423 });
3424 }
3425
3426 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3427 self.hide_context_menu(window, cx);
3428
3429 match phase {
3430 SelectPhase::Begin {
3431 position,
3432 add,
3433 click_count,
3434 } => self.begin_selection(position, add, click_count, window, cx),
3435 SelectPhase::BeginColumnar {
3436 position,
3437 goal_column,
3438 reset,
3439 mode,
3440 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3441 SelectPhase::Extend {
3442 position,
3443 click_count,
3444 } => self.extend_selection(position, click_count, window, cx),
3445 SelectPhase::Update {
3446 position,
3447 goal_column,
3448 scroll_delta,
3449 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3450 SelectPhase::End => self.end_selection(window, cx),
3451 }
3452 }
3453
3454 fn extend_selection(
3455 &mut self,
3456 position: DisplayPoint,
3457 click_count: usize,
3458 window: &mut Window,
3459 cx: &mut Context<Self>,
3460 ) {
3461 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3462 let tail = self.selections.newest::<usize>(cx).tail();
3463 self.begin_selection(position, false, click_count, window, cx);
3464
3465 let position = position.to_offset(&display_map, Bias::Left);
3466 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3467
3468 let mut pending_selection = self
3469 .selections
3470 .pending_anchor()
3471 .expect("extend_selection not called with pending selection");
3472 if position >= tail {
3473 pending_selection.start = tail_anchor;
3474 } else {
3475 pending_selection.end = tail_anchor;
3476 pending_selection.reversed = true;
3477 }
3478
3479 let mut pending_mode = self.selections.pending_mode().unwrap();
3480 match &mut pending_mode {
3481 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3482 _ => {}
3483 }
3484
3485 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3486 SelectionEffects::scroll(Autoscroll::fit())
3487 } else {
3488 SelectionEffects::no_scroll()
3489 };
3490
3491 self.change_selections(effects, window, cx, |s| {
3492 s.set_pending(pending_selection, pending_mode)
3493 });
3494 }
3495
3496 fn begin_selection(
3497 &mut self,
3498 position: DisplayPoint,
3499 add: bool,
3500 click_count: usize,
3501 window: &mut Window,
3502 cx: &mut Context<Self>,
3503 ) {
3504 if !self.focus_handle.is_focused(window) {
3505 self.last_focused_descendant = None;
3506 window.focus(&self.focus_handle);
3507 }
3508
3509 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3510 let buffer = &display_map.buffer_snapshot;
3511 let position = display_map.clip_point(position, Bias::Left);
3512
3513 let start;
3514 let end;
3515 let mode;
3516 let mut auto_scroll;
3517 match click_count {
3518 1 => {
3519 start = buffer.anchor_before(position.to_point(&display_map));
3520 end = start;
3521 mode = SelectMode::Character;
3522 auto_scroll = true;
3523 }
3524 2 => {
3525 let position = display_map
3526 .clip_point(position, Bias::Left)
3527 .to_offset(&display_map, Bias::Left);
3528 let (range, _) = buffer.surrounding_word(position, false);
3529 start = buffer.anchor_before(range.start);
3530 end = buffer.anchor_before(range.end);
3531 mode = SelectMode::Word(start..end);
3532 auto_scroll = true;
3533 }
3534 3 => {
3535 let position = display_map
3536 .clip_point(position, Bias::Left)
3537 .to_point(&display_map);
3538 let line_start = display_map.prev_line_boundary(position).0;
3539 let next_line_start = buffer.clip_point(
3540 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3541 Bias::Left,
3542 );
3543 start = buffer.anchor_before(line_start);
3544 end = buffer.anchor_before(next_line_start);
3545 mode = SelectMode::Line(start..end);
3546 auto_scroll = true;
3547 }
3548 _ => {
3549 start = buffer.anchor_before(0);
3550 end = buffer.anchor_before(buffer.len());
3551 mode = SelectMode::All;
3552 auto_scroll = false;
3553 }
3554 }
3555 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3556
3557 let point_to_delete: Option<usize> = {
3558 let selected_points: Vec<Selection<Point>> =
3559 self.selections.disjoint_in_range(start..end, cx);
3560
3561 if !add || click_count > 1 {
3562 None
3563 } else if !selected_points.is_empty() {
3564 Some(selected_points[0].id)
3565 } else {
3566 let clicked_point_already_selected =
3567 self.selections.disjoint.iter().find(|selection| {
3568 selection.start.to_point(buffer) == start.to_point(buffer)
3569 || selection.end.to_point(buffer) == end.to_point(buffer)
3570 });
3571
3572 clicked_point_already_selected.map(|selection| selection.id)
3573 }
3574 };
3575
3576 let selections_count = self.selections.count();
3577 let effects = if auto_scroll {
3578 SelectionEffects::default()
3579 } else {
3580 SelectionEffects::no_scroll()
3581 };
3582
3583 self.change_selections(effects, window, cx, |s| {
3584 if let Some(point_to_delete) = point_to_delete {
3585 s.delete(point_to_delete);
3586
3587 if selections_count == 1 {
3588 s.set_pending_anchor_range(start..end, mode);
3589 }
3590 } else {
3591 if !add {
3592 s.clear_disjoint();
3593 }
3594
3595 s.set_pending_anchor_range(start..end, mode);
3596 }
3597 });
3598 }
3599
3600 fn begin_columnar_selection(
3601 &mut self,
3602 position: DisplayPoint,
3603 goal_column: u32,
3604 reset: bool,
3605 mode: ColumnarMode,
3606 window: &mut Window,
3607 cx: &mut Context<Self>,
3608 ) {
3609 if !self.focus_handle.is_focused(window) {
3610 self.last_focused_descendant = None;
3611 window.focus(&self.focus_handle);
3612 }
3613
3614 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3615
3616 if reset {
3617 let pointer_position = display_map
3618 .buffer_snapshot
3619 .anchor_before(position.to_point(&display_map));
3620
3621 self.change_selections(
3622 SelectionEffects::scroll(Autoscroll::newest()),
3623 window,
3624 cx,
3625 |s| {
3626 s.clear_disjoint();
3627 s.set_pending_anchor_range(
3628 pointer_position..pointer_position,
3629 SelectMode::Character,
3630 );
3631 },
3632 );
3633 };
3634
3635 let tail = self.selections.newest::<Point>(cx).tail();
3636 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3637 self.columnar_selection_state = match mode {
3638 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3639 selection_tail: selection_anchor,
3640 display_point: if reset {
3641 if position.column() != goal_column {
3642 Some(DisplayPoint::new(position.row(), goal_column))
3643 } else {
3644 None
3645 }
3646 } else {
3647 None
3648 },
3649 }),
3650 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3651 selection_tail: selection_anchor,
3652 }),
3653 };
3654
3655 if !reset {
3656 self.select_columns(position, goal_column, &display_map, window, cx);
3657 }
3658 }
3659
3660 fn update_selection(
3661 &mut self,
3662 position: DisplayPoint,
3663 goal_column: u32,
3664 scroll_delta: gpui::Point<f32>,
3665 window: &mut Window,
3666 cx: &mut Context<Self>,
3667 ) {
3668 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3669
3670 if self.columnar_selection_state.is_some() {
3671 self.select_columns(position, goal_column, &display_map, window, cx);
3672 } else if let Some(mut pending) = self.selections.pending_anchor() {
3673 let buffer = &display_map.buffer_snapshot;
3674 let head;
3675 let tail;
3676 let mode = self.selections.pending_mode().unwrap();
3677 match &mode {
3678 SelectMode::Character => {
3679 head = position.to_point(&display_map);
3680 tail = pending.tail().to_point(buffer);
3681 }
3682 SelectMode::Word(original_range) => {
3683 let offset = display_map
3684 .clip_point(position, Bias::Left)
3685 .to_offset(&display_map, Bias::Left);
3686 let original_range = original_range.to_offset(buffer);
3687
3688 let head_offset = if buffer.is_inside_word(offset, false)
3689 || original_range.contains(&offset)
3690 {
3691 let (word_range, _) = buffer.surrounding_word(offset, false);
3692 if word_range.start < original_range.start {
3693 word_range.start
3694 } else {
3695 word_range.end
3696 }
3697 } else {
3698 offset
3699 };
3700
3701 head = head_offset.to_point(buffer);
3702 if head_offset <= original_range.start {
3703 tail = original_range.end.to_point(buffer);
3704 } else {
3705 tail = original_range.start.to_point(buffer);
3706 }
3707 }
3708 SelectMode::Line(original_range) => {
3709 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3710
3711 let position = display_map
3712 .clip_point(position, Bias::Left)
3713 .to_point(&display_map);
3714 let line_start = display_map.prev_line_boundary(position).0;
3715 let next_line_start = buffer.clip_point(
3716 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3717 Bias::Left,
3718 );
3719
3720 if line_start < original_range.start {
3721 head = line_start
3722 } else {
3723 head = next_line_start
3724 }
3725
3726 if head <= original_range.start {
3727 tail = original_range.end;
3728 } else {
3729 tail = original_range.start;
3730 }
3731 }
3732 SelectMode::All => {
3733 return;
3734 }
3735 };
3736
3737 if head < tail {
3738 pending.start = buffer.anchor_before(head);
3739 pending.end = buffer.anchor_before(tail);
3740 pending.reversed = true;
3741 } else {
3742 pending.start = buffer.anchor_before(tail);
3743 pending.end = buffer.anchor_before(head);
3744 pending.reversed = false;
3745 }
3746
3747 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3748 s.set_pending(pending, mode);
3749 });
3750 } else {
3751 log::error!("update_selection dispatched with no pending selection");
3752 return;
3753 }
3754
3755 self.apply_scroll_delta(scroll_delta, window, cx);
3756 cx.notify();
3757 }
3758
3759 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3760 self.columnar_selection_state.take();
3761 if self.selections.pending_anchor().is_some() {
3762 let selections = self.selections.all::<usize>(cx);
3763 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3764 s.select(selections);
3765 s.clear_pending();
3766 });
3767 }
3768 }
3769
3770 fn select_columns(
3771 &mut self,
3772 head: DisplayPoint,
3773 goal_column: u32,
3774 display_map: &DisplaySnapshot,
3775 window: &mut Window,
3776 cx: &mut Context<Self>,
3777 ) {
3778 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3779 return;
3780 };
3781
3782 let tail = match columnar_state {
3783 ColumnarSelectionState::FromMouse {
3784 selection_tail,
3785 display_point,
3786 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3787 ColumnarSelectionState::FromSelection { selection_tail } => {
3788 selection_tail.to_display_point(display_map)
3789 }
3790 };
3791
3792 let start_row = cmp::min(tail.row(), head.row());
3793 let end_row = cmp::max(tail.row(), head.row());
3794 let start_column = cmp::min(tail.column(), goal_column);
3795 let end_column = cmp::max(tail.column(), goal_column);
3796 let reversed = start_column < tail.column();
3797
3798 let selection_ranges = (start_row.0..=end_row.0)
3799 .map(DisplayRow)
3800 .filter_map(|row| {
3801 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3802 || start_column <= display_map.line_len(row))
3803 && !display_map.is_block_line(row)
3804 {
3805 let start = display_map
3806 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3807 .to_point(display_map);
3808 let end = display_map
3809 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3810 .to_point(display_map);
3811 if reversed {
3812 Some(end..start)
3813 } else {
3814 Some(start..end)
3815 }
3816 } else {
3817 None
3818 }
3819 })
3820 .collect::<Vec<_>>();
3821
3822 let ranges = match columnar_state {
3823 ColumnarSelectionState::FromMouse { .. } => {
3824 let mut non_empty_ranges = selection_ranges
3825 .iter()
3826 .filter(|selection_range| selection_range.start != selection_range.end)
3827 .peekable();
3828 if non_empty_ranges.peek().is_some() {
3829 non_empty_ranges.cloned().collect()
3830 } else {
3831 selection_ranges
3832 }
3833 }
3834 _ => selection_ranges,
3835 };
3836
3837 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3838 s.select_ranges(ranges);
3839 });
3840 cx.notify();
3841 }
3842
3843 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3844 self.selections
3845 .all_adjusted(cx)
3846 .iter()
3847 .any(|selection| !selection.is_empty())
3848 }
3849
3850 pub fn has_pending_nonempty_selection(&self) -> bool {
3851 let pending_nonempty_selection = match self.selections.pending_anchor() {
3852 Some(Selection { start, end, .. }) => start != end,
3853 None => false,
3854 };
3855
3856 pending_nonempty_selection
3857 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3858 }
3859
3860 pub fn has_pending_selection(&self) -> bool {
3861 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3862 }
3863
3864 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3865 self.selection_mark_mode = false;
3866 self.selection_drag_state = SelectionDragState::None;
3867
3868 if self.clear_expanded_diff_hunks(cx) {
3869 cx.notify();
3870 return;
3871 }
3872 if self.dismiss_menus_and_popups(true, window, cx) {
3873 return;
3874 }
3875
3876 if self.mode.is_full()
3877 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3878 {
3879 return;
3880 }
3881
3882 cx.propagate();
3883 }
3884
3885 pub fn dismiss_menus_and_popups(
3886 &mut self,
3887 is_user_requested: bool,
3888 window: &mut Window,
3889 cx: &mut Context<Self>,
3890 ) -> bool {
3891 if self.take_rename(false, window, cx).is_some() {
3892 return true;
3893 }
3894
3895 if hide_hover(self, cx) {
3896 return true;
3897 }
3898
3899 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3900 return true;
3901 }
3902
3903 if self.hide_context_menu(window, cx).is_some() {
3904 return true;
3905 }
3906
3907 if self.mouse_context_menu.take().is_some() {
3908 return true;
3909 }
3910
3911 if is_user_requested && self.discard_edit_prediction(true, cx) {
3912 return true;
3913 }
3914
3915 if self.snippet_stack.pop().is_some() {
3916 return true;
3917 }
3918
3919 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3920 self.dismiss_diagnostics(cx);
3921 return true;
3922 }
3923
3924 false
3925 }
3926
3927 fn linked_editing_ranges_for(
3928 &self,
3929 selection: Range<text::Anchor>,
3930 cx: &App,
3931 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3932 if self.linked_edit_ranges.is_empty() {
3933 return None;
3934 }
3935 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3936 selection.end.buffer_id.and_then(|end_buffer_id| {
3937 if selection.start.buffer_id != Some(end_buffer_id) {
3938 return None;
3939 }
3940 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3941 let snapshot = buffer.read(cx).snapshot();
3942 self.linked_edit_ranges
3943 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3944 .map(|ranges| (ranges, snapshot, buffer))
3945 })?;
3946 use text::ToOffset as TO;
3947 // find offset from the start of current range to current cursor position
3948 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3949
3950 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3951 let start_difference = start_offset - start_byte_offset;
3952 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3953 let end_difference = end_offset - start_byte_offset;
3954 // Current range has associated linked ranges.
3955 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3956 for range in linked_ranges.iter() {
3957 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3958 let end_offset = start_offset + end_difference;
3959 let start_offset = start_offset + start_difference;
3960 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3961 continue;
3962 }
3963 if self.selections.disjoint_anchor_ranges().any(|s| {
3964 if s.start.buffer_id != selection.start.buffer_id
3965 || s.end.buffer_id != selection.end.buffer_id
3966 {
3967 return false;
3968 }
3969 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3970 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3971 }) {
3972 continue;
3973 }
3974 let start = buffer_snapshot.anchor_after(start_offset);
3975 let end = buffer_snapshot.anchor_after(end_offset);
3976 linked_edits
3977 .entry(buffer.clone())
3978 .or_default()
3979 .push(start..end);
3980 }
3981 Some(linked_edits)
3982 }
3983
3984 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3985 let text: Arc<str> = text.into();
3986
3987 if self.read_only(cx) {
3988 return;
3989 }
3990
3991 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3992
3993 let selections = self.selections.all_adjusted(cx);
3994 let mut bracket_inserted = false;
3995 let mut edits = Vec::new();
3996 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3997 let mut new_selections = Vec::with_capacity(selections.len());
3998 let mut new_autoclose_regions = Vec::new();
3999 let snapshot = self.buffer.read(cx).read(cx);
4000 let mut clear_linked_edit_ranges = false;
4001
4002 for (selection, autoclose_region) in
4003 self.selections_with_autoclose_regions(selections, &snapshot)
4004 {
4005 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4006 // Determine if the inserted text matches the opening or closing
4007 // bracket of any of this language's bracket pairs.
4008 let mut bracket_pair = None;
4009 let mut is_bracket_pair_start = false;
4010 let mut is_bracket_pair_end = false;
4011 if !text.is_empty() {
4012 let mut bracket_pair_matching_end = None;
4013 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4014 // and they are removing the character that triggered IME popup.
4015 for (pair, enabled) in scope.brackets() {
4016 if !pair.close && !pair.surround {
4017 continue;
4018 }
4019
4020 if enabled && pair.start.ends_with(text.as_ref()) {
4021 let prefix_len = pair.start.len() - text.len();
4022 let preceding_text_matches_prefix = prefix_len == 0
4023 || (selection.start.column >= (prefix_len as u32)
4024 && snapshot.contains_str_at(
4025 Point::new(
4026 selection.start.row,
4027 selection.start.column - (prefix_len as u32),
4028 ),
4029 &pair.start[..prefix_len],
4030 ));
4031 if preceding_text_matches_prefix {
4032 bracket_pair = Some(pair.clone());
4033 is_bracket_pair_start = true;
4034 break;
4035 }
4036 }
4037 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4038 {
4039 // take first bracket pair matching end, but don't break in case a later bracket
4040 // pair matches start
4041 bracket_pair_matching_end = Some(pair.clone());
4042 }
4043 }
4044 if let Some(end) = bracket_pair_matching_end
4045 && bracket_pair.is_none()
4046 {
4047 bracket_pair = Some(end);
4048 is_bracket_pair_end = true;
4049 }
4050 }
4051
4052 if let Some(bracket_pair) = bracket_pair {
4053 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4054 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4055 let auto_surround =
4056 self.use_auto_surround && snapshot_settings.use_auto_surround;
4057 if selection.is_empty() {
4058 if is_bracket_pair_start {
4059 // If the inserted text is a suffix of an opening bracket and the
4060 // selection is preceded by the rest of the opening bracket, then
4061 // insert the closing bracket.
4062 let following_text_allows_autoclose = snapshot
4063 .chars_at(selection.start)
4064 .next()
4065 .is_none_or(|c| scope.should_autoclose_before(c));
4066
4067 let preceding_text_allows_autoclose = selection.start.column == 0
4068 || snapshot
4069 .reversed_chars_at(selection.start)
4070 .next()
4071 .is_none_or(|c| {
4072 bracket_pair.start != bracket_pair.end
4073 || !snapshot
4074 .char_classifier_at(selection.start)
4075 .is_word(c)
4076 });
4077
4078 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4079 && bracket_pair.start.len() == 1
4080 {
4081 let target = bracket_pair.start.chars().next().unwrap();
4082 let current_line_count = snapshot
4083 .reversed_chars_at(selection.start)
4084 .take_while(|&c| c != '\n')
4085 .filter(|&c| c == target)
4086 .count();
4087 current_line_count % 2 == 1
4088 } else {
4089 false
4090 };
4091
4092 if autoclose
4093 && bracket_pair.close
4094 && following_text_allows_autoclose
4095 && preceding_text_allows_autoclose
4096 && !is_closing_quote
4097 {
4098 let anchor = snapshot.anchor_before(selection.end);
4099 new_selections.push((selection.map(|_| anchor), text.len()));
4100 new_autoclose_regions.push((
4101 anchor,
4102 text.len(),
4103 selection.id,
4104 bracket_pair.clone(),
4105 ));
4106 edits.push((
4107 selection.range(),
4108 format!("{}{}", text, bracket_pair.end).into(),
4109 ));
4110 bracket_inserted = true;
4111 continue;
4112 }
4113 }
4114
4115 if let Some(region) = autoclose_region {
4116 // If the selection is followed by an auto-inserted closing bracket,
4117 // then don't insert that closing bracket again; just move the selection
4118 // past the closing bracket.
4119 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4120 && text.as_ref() == region.pair.end.as_str()
4121 && snapshot.contains_str_at(region.range.end, text.as_ref());
4122 if should_skip {
4123 let anchor = snapshot.anchor_after(selection.end);
4124 new_selections
4125 .push((selection.map(|_| anchor), region.pair.end.len()));
4126 continue;
4127 }
4128 }
4129
4130 let always_treat_brackets_as_autoclosed = snapshot
4131 .language_settings_at(selection.start, cx)
4132 .always_treat_brackets_as_autoclosed;
4133 if always_treat_brackets_as_autoclosed
4134 && is_bracket_pair_end
4135 && snapshot.contains_str_at(selection.end, text.as_ref())
4136 {
4137 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4138 // and the inserted text is a closing bracket and the selection is followed
4139 // by the closing bracket then move the selection past the closing bracket.
4140 let anchor = snapshot.anchor_after(selection.end);
4141 new_selections.push((selection.map(|_| anchor), text.len()));
4142 continue;
4143 }
4144 }
4145 // If an opening bracket is 1 character long and is typed while
4146 // text is selected, then surround that text with the bracket pair.
4147 else if auto_surround
4148 && bracket_pair.surround
4149 && is_bracket_pair_start
4150 && bracket_pair.start.chars().count() == 1
4151 {
4152 edits.push((selection.start..selection.start, text.clone()));
4153 edits.push((
4154 selection.end..selection.end,
4155 bracket_pair.end.as_str().into(),
4156 ));
4157 bracket_inserted = true;
4158 new_selections.push((
4159 Selection {
4160 id: selection.id,
4161 start: snapshot.anchor_after(selection.start),
4162 end: snapshot.anchor_before(selection.end),
4163 reversed: selection.reversed,
4164 goal: selection.goal,
4165 },
4166 0,
4167 ));
4168 continue;
4169 }
4170 }
4171 }
4172
4173 if self.auto_replace_emoji_shortcode
4174 && selection.is_empty()
4175 && text.as_ref().ends_with(':')
4176 && let Some(possible_emoji_short_code) =
4177 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4178 && !possible_emoji_short_code.is_empty()
4179 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4180 {
4181 let emoji_shortcode_start = Point::new(
4182 selection.start.row,
4183 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4184 );
4185
4186 // Remove shortcode from buffer
4187 edits.push((
4188 emoji_shortcode_start..selection.start,
4189 "".to_string().into(),
4190 ));
4191 new_selections.push((
4192 Selection {
4193 id: selection.id,
4194 start: snapshot.anchor_after(emoji_shortcode_start),
4195 end: snapshot.anchor_before(selection.start),
4196 reversed: selection.reversed,
4197 goal: selection.goal,
4198 },
4199 0,
4200 ));
4201
4202 // Insert emoji
4203 let selection_start_anchor = snapshot.anchor_after(selection.start);
4204 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4205 edits.push((selection.start..selection.end, emoji.to_string().into()));
4206
4207 continue;
4208 }
4209
4210 // If not handling any auto-close operation, then just replace the selected
4211 // text with the given input and move the selection to the end of the
4212 // newly inserted text.
4213 let anchor = snapshot.anchor_after(selection.end);
4214 if !self.linked_edit_ranges.is_empty() {
4215 let start_anchor = snapshot.anchor_before(selection.start);
4216
4217 let is_word_char = text.chars().next().is_none_or(|char| {
4218 let classifier = snapshot
4219 .char_classifier_at(start_anchor.to_offset(&snapshot))
4220 .ignore_punctuation(true);
4221 classifier.is_word(char)
4222 });
4223
4224 if is_word_char {
4225 if let Some(ranges) = self
4226 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4227 {
4228 for (buffer, edits) in ranges {
4229 linked_edits
4230 .entry(buffer.clone())
4231 .or_default()
4232 .extend(edits.into_iter().map(|range| (range, text.clone())));
4233 }
4234 }
4235 } else {
4236 clear_linked_edit_ranges = true;
4237 }
4238 }
4239
4240 new_selections.push((selection.map(|_| anchor), 0));
4241 edits.push((selection.start..selection.end, text.clone()));
4242 }
4243
4244 drop(snapshot);
4245
4246 self.transact(window, cx, |this, window, cx| {
4247 if clear_linked_edit_ranges {
4248 this.linked_edit_ranges.clear();
4249 }
4250 let initial_buffer_versions =
4251 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4252
4253 this.buffer.update(cx, |buffer, cx| {
4254 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4255 });
4256 for (buffer, edits) in linked_edits {
4257 buffer.update(cx, |buffer, cx| {
4258 let snapshot = buffer.snapshot();
4259 let edits = edits
4260 .into_iter()
4261 .map(|(range, text)| {
4262 use text::ToPoint as TP;
4263 let end_point = TP::to_point(&range.end, &snapshot);
4264 let start_point = TP::to_point(&range.start, &snapshot);
4265 (start_point..end_point, text)
4266 })
4267 .sorted_by_key(|(range, _)| range.start);
4268 buffer.edit(edits, None, cx);
4269 })
4270 }
4271 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4272 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4273 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4274 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4275 .zip(new_selection_deltas)
4276 .map(|(selection, delta)| Selection {
4277 id: selection.id,
4278 start: selection.start + delta,
4279 end: selection.end + delta,
4280 reversed: selection.reversed,
4281 goal: SelectionGoal::None,
4282 })
4283 .collect::<Vec<_>>();
4284
4285 let mut i = 0;
4286 for (position, delta, selection_id, pair) in new_autoclose_regions {
4287 let position = position.to_offset(&map.buffer_snapshot) + delta;
4288 let start = map.buffer_snapshot.anchor_before(position);
4289 let end = map.buffer_snapshot.anchor_after(position);
4290 while let Some(existing_state) = this.autoclose_regions.get(i) {
4291 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4292 Ordering::Less => i += 1,
4293 Ordering::Greater => break,
4294 Ordering::Equal => {
4295 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4296 Ordering::Less => i += 1,
4297 Ordering::Equal => break,
4298 Ordering::Greater => break,
4299 }
4300 }
4301 }
4302 }
4303 this.autoclose_regions.insert(
4304 i,
4305 AutocloseRegion {
4306 selection_id,
4307 range: start..end,
4308 pair,
4309 },
4310 );
4311 }
4312
4313 let had_active_edit_prediction = this.has_active_edit_prediction();
4314 this.change_selections(
4315 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4316 window,
4317 cx,
4318 |s| s.select(new_selections),
4319 );
4320
4321 if !bracket_inserted
4322 && let Some(on_type_format_task) =
4323 this.trigger_on_type_formatting(text.to_string(), window, cx)
4324 {
4325 on_type_format_task.detach_and_log_err(cx);
4326 }
4327
4328 let editor_settings = EditorSettings::get_global(cx);
4329 if bracket_inserted
4330 && (editor_settings.auto_signature_help
4331 || editor_settings.show_signature_help_after_edits)
4332 {
4333 this.show_signature_help(&ShowSignatureHelp, window, cx);
4334 }
4335
4336 let trigger_in_words =
4337 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4338 if this.hard_wrap.is_some() {
4339 let latest: Range<Point> = this.selections.newest(cx).range();
4340 if latest.is_empty()
4341 && this
4342 .buffer()
4343 .read(cx)
4344 .snapshot(cx)
4345 .line_len(MultiBufferRow(latest.start.row))
4346 == latest.start.column
4347 {
4348 this.rewrap_impl(
4349 RewrapOptions {
4350 override_language_settings: true,
4351 preserve_existing_whitespace: true,
4352 },
4353 cx,
4354 )
4355 }
4356 }
4357 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4358 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4359 this.refresh_edit_prediction(true, false, window, cx);
4360 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4361 });
4362 }
4363
4364 fn find_possible_emoji_shortcode_at_position(
4365 snapshot: &MultiBufferSnapshot,
4366 position: Point,
4367 ) -> Option<String> {
4368 let mut chars = Vec::new();
4369 let mut found_colon = false;
4370 for char in snapshot.reversed_chars_at(position).take(100) {
4371 // Found a possible emoji shortcode in the middle of the buffer
4372 if found_colon {
4373 if char.is_whitespace() {
4374 chars.reverse();
4375 return Some(chars.iter().collect());
4376 }
4377 // If the previous character is not a whitespace, we are in the middle of a word
4378 // and we only want to complete the shortcode if the word is made up of other emojis
4379 let mut containing_word = String::new();
4380 for ch in snapshot
4381 .reversed_chars_at(position)
4382 .skip(chars.len() + 1)
4383 .take(100)
4384 {
4385 if ch.is_whitespace() {
4386 break;
4387 }
4388 containing_word.push(ch);
4389 }
4390 let containing_word = containing_word.chars().rev().collect::<String>();
4391 if util::word_consists_of_emojis(containing_word.as_str()) {
4392 chars.reverse();
4393 return Some(chars.iter().collect());
4394 }
4395 }
4396
4397 if char.is_whitespace() || !char.is_ascii() {
4398 return None;
4399 }
4400 if char == ':' {
4401 found_colon = true;
4402 } else {
4403 chars.push(char);
4404 }
4405 }
4406 // Found a possible emoji shortcode at the beginning of the buffer
4407 chars.reverse();
4408 Some(chars.iter().collect())
4409 }
4410
4411 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4412 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4413 self.transact(window, cx, |this, window, cx| {
4414 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4415 let selections = this.selections.all::<usize>(cx);
4416 let multi_buffer = this.buffer.read(cx);
4417 let buffer = multi_buffer.snapshot(cx);
4418 selections
4419 .iter()
4420 .map(|selection| {
4421 let start_point = selection.start.to_point(&buffer);
4422 let mut existing_indent =
4423 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4424 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4425 let start = selection.start;
4426 let end = selection.end;
4427 let selection_is_empty = start == end;
4428 let language_scope = buffer.language_scope_at(start);
4429 let (
4430 comment_delimiter,
4431 doc_delimiter,
4432 insert_extra_newline,
4433 indent_on_newline,
4434 indent_on_extra_newline,
4435 ) = if let Some(language) = &language_scope {
4436 let mut insert_extra_newline =
4437 insert_extra_newline_brackets(&buffer, start..end, language)
4438 || insert_extra_newline_tree_sitter(&buffer, start..end);
4439
4440 // Comment extension on newline is allowed only for cursor selections
4441 let comment_delimiter = maybe!({
4442 if !selection_is_empty {
4443 return None;
4444 }
4445
4446 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4447 return None;
4448 }
4449
4450 let delimiters = language.line_comment_prefixes();
4451 let max_len_of_delimiter =
4452 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4453 let (snapshot, range) =
4454 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4455
4456 let num_of_whitespaces = snapshot
4457 .chars_for_range(range.clone())
4458 .take_while(|c| c.is_whitespace())
4459 .count();
4460 let comment_candidate = snapshot
4461 .chars_for_range(range.clone())
4462 .skip(num_of_whitespaces)
4463 .take(max_len_of_delimiter)
4464 .collect::<String>();
4465 let (delimiter, trimmed_len) = delimiters
4466 .iter()
4467 .filter_map(|delimiter| {
4468 let prefix = delimiter.trim_end();
4469 if comment_candidate.starts_with(prefix) {
4470 Some((delimiter, prefix.len()))
4471 } else {
4472 None
4473 }
4474 })
4475 .max_by_key(|(_, len)| *len)?;
4476
4477 if let Some(BlockCommentConfig {
4478 start: block_start, ..
4479 }) = language.block_comment()
4480 {
4481 let block_start_trimmed = block_start.trim_end();
4482 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4483 let line_content = snapshot
4484 .chars_for_range(range)
4485 .skip(num_of_whitespaces)
4486 .take(block_start_trimmed.len())
4487 .collect::<String>();
4488
4489 if line_content.starts_with(block_start_trimmed) {
4490 return None;
4491 }
4492 }
4493 }
4494
4495 let cursor_is_placed_after_comment_marker =
4496 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4497 if cursor_is_placed_after_comment_marker {
4498 Some(delimiter.clone())
4499 } else {
4500 None
4501 }
4502 });
4503
4504 let mut indent_on_newline = IndentSize::spaces(0);
4505 let mut indent_on_extra_newline = IndentSize::spaces(0);
4506
4507 let doc_delimiter = maybe!({
4508 if !selection_is_empty {
4509 return None;
4510 }
4511
4512 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4513 return None;
4514 }
4515
4516 let BlockCommentConfig {
4517 start: start_tag,
4518 end: end_tag,
4519 prefix: delimiter,
4520 tab_size: len,
4521 } = language.documentation_comment()?;
4522 let is_within_block_comment = buffer
4523 .language_scope_at(start_point)
4524 .is_some_and(|scope| scope.override_name() == Some("comment"));
4525 if !is_within_block_comment {
4526 return None;
4527 }
4528
4529 let (snapshot, range) =
4530 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4531
4532 let num_of_whitespaces = snapshot
4533 .chars_for_range(range.clone())
4534 .take_while(|c| c.is_whitespace())
4535 .count();
4536
4537 // 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.
4538 let column = start_point.column;
4539 let cursor_is_after_start_tag = {
4540 let start_tag_len = start_tag.len();
4541 let start_tag_line = snapshot
4542 .chars_for_range(range.clone())
4543 .skip(num_of_whitespaces)
4544 .take(start_tag_len)
4545 .collect::<String>();
4546 if start_tag_line.starts_with(start_tag.as_ref()) {
4547 num_of_whitespaces + start_tag_len <= column as usize
4548 } else {
4549 false
4550 }
4551 };
4552
4553 let cursor_is_after_delimiter = {
4554 let delimiter_trim = delimiter.trim_end();
4555 let delimiter_line = snapshot
4556 .chars_for_range(range.clone())
4557 .skip(num_of_whitespaces)
4558 .take(delimiter_trim.len())
4559 .collect::<String>();
4560 if delimiter_line.starts_with(delimiter_trim) {
4561 num_of_whitespaces + delimiter_trim.len() <= column as usize
4562 } else {
4563 false
4564 }
4565 };
4566
4567 let cursor_is_before_end_tag_if_exists = {
4568 let mut char_position = 0u32;
4569 let mut end_tag_offset = None;
4570
4571 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4572 if let Some(byte_pos) = chunk.find(&**end_tag) {
4573 let chars_before_match =
4574 chunk[..byte_pos].chars().count() as u32;
4575 end_tag_offset =
4576 Some(char_position + chars_before_match);
4577 break 'outer;
4578 }
4579 char_position += chunk.chars().count() as u32;
4580 }
4581
4582 if let Some(end_tag_offset) = end_tag_offset {
4583 let cursor_is_before_end_tag = column <= end_tag_offset;
4584 if cursor_is_after_start_tag {
4585 if cursor_is_before_end_tag {
4586 insert_extra_newline = true;
4587 }
4588 let cursor_is_at_start_of_end_tag =
4589 column == end_tag_offset;
4590 if cursor_is_at_start_of_end_tag {
4591 indent_on_extra_newline.len = *len;
4592 }
4593 }
4594 cursor_is_before_end_tag
4595 } else {
4596 true
4597 }
4598 };
4599
4600 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4601 && cursor_is_before_end_tag_if_exists
4602 {
4603 if cursor_is_after_start_tag {
4604 indent_on_newline.len = *len;
4605 }
4606 Some(delimiter.clone())
4607 } else {
4608 None
4609 }
4610 });
4611
4612 (
4613 comment_delimiter,
4614 doc_delimiter,
4615 insert_extra_newline,
4616 indent_on_newline,
4617 indent_on_extra_newline,
4618 )
4619 } else {
4620 (
4621 None,
4622 None,
4623 false,
4624 IndentSize::default(),
4625 IndentSize::default(),
4626 )
4627 };
4628
4629 let prevent_auto_indent = doc_delimiter.is_some();
4630 let delimiter = comment_delimiter.or(doc_delimiter);
4631
4632 let capacity_for_delimiter =
4633 delimiter.as_deref().map(str::len).unwrap_or_default();
4634 let mut new_text = String::with_capacity(
4635 1 + capacity_for_delimiter
4636 + existing_indent.len as usize
4637 + indent_on_newline.len as usize
4638 + indent_on_extra_newline.len as usize,
4639 );
4640 new_text.push('\n');
4641 new_text.extend(existing_indent.chars());
4642 new_text.extend(indent_on_newline.chars());
4643
4644 if let Some(delimiter) = &delimiter {
4645 new_text.push_str(delimiter);
4646 }
4647
4648 if insert_extra_newline {
4649 new_text.push('\n');
4650 new_text.extend(existing_indent.chars());
4651 new_text.extend(indent_on_extra_newline.chars());
4652 }
4653
4654 let anchor = buffer.anchor_after(end);
4655 let new_selection = selection.map(|_| anchor);
4656 (
4657 ((start..end, new_text), prevent_auto_indent),
4658 (insert_extra_newline, new_selection),
4659 )
4660 })
4661 .unzip()
4662 };
4663
4664 let mut auto_indent_edits = Vec::new();
4665 let mut edits = Vec::new();
4666 for (edit, prevent_auto_indent) in edits_with_flags {
4667 if prevent_auto_indent {
4668 edits.push(edit);
4669 } else {
4670 auto_indent_edits.push(edit);
4671 }
4672 }
4673 if !edits.is_empty() {
4674 this.edit(edits, cx);
4675 }
4676 if !auto_indent_edits.is_empty() {
4677 this.edit_with_autoindent(auto_indent_edits, cx);
4678 }
4679
4680 let buffer = this.buffer.read(cx).snapshot(cx);
4681 let new_selections = selection_info
4682 .into_iter()
4683 .map(|(extra_newline_inserted, new_selection)| {
4684 let mut cursor = new_selection.end.to_point(&buffer);
4685 if extra_newline_inserted {
4686 cursor.row -= 1;
4687 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4688 }
4689 new_selection.map(|_| cursor)
4690 })
4691 .collect();
4692
4693 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4694 this.refresh_edit_prediction(true, false, window, cx);
4695 });
4696 }
4697
4698 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4699 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4700
4701 let buffer = self.buffer.read(cx);
4702 let snapshot = buffer.snapshot(cx);
4703
4704 let mut edits = Vec::new();
4705 let mut rows = Vec::new();
4706
4707 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4708 let cursor = selection.head();
4709 let row = cursor.row;
4710
4711 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4712
4713 let newline = "\n".to_string();
4714 edits.push((start_of_line..start_of_line, newline));
4715
4716 rows.push(row + rows_inserted as u32);
4717 }
4718
4719 self.transact(window, cx, |editor, window, cx| {
4720 editor.edit(edits, cx);
4721
4722 editor.change_selections(Default::default(), window, cx, |s| {
4723 let mut index = 0;
4724 s.move_cursors_with(|map, _, _| {
4725 let row = rows[index];
4726 index += 1;
4727
4728 let point = Point::new(row, 0);
4729 let boundary = map.next_line_boundary(point).1;
4730 let clipped = map.clip_point(boundary, Bias::Left);
4731
4732 (clipped, SelectionGoal::None)
4733 });
4734 });
4735
4736 let mut indent_edits = Vec::new();
4737 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4738 for row in rows {
4739 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4740 for (row, indent) in indents {
4741 if indent.len == 0 {
4742 continue;
4743 }
4744
4745 let text = match indent.kind {
4746 IndentKind::Space => " ".repeat(indent.len as usize),
4747 IndentKind::Tab => "\t".repeat(indent.len as usize),
4748 };
4749 let point = Point::new(row.0, 0);
4750 indent_edits.push((point..point, text));
4751 }
4752 }
4753 editor.edit(indent_edits, cx);
4754 });
4755 }
4756
4757 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4759
4760 let buffer = self.buffer.read(cx);
4761 let snapshot = buffer.snapshot(cx);
4762
4763 let mut edits = Vec::new();
4764 let mut rows = Vec::new();
4765 let mut rows_inserted = 0;
4766
4767 for selection in self.selections.all_adjusted(cx) {
4768 let cursor = selection.head();
4769 let row = cursor.row;
4770
4771 let point = Point::new(row + 1, 0);
4772 let start_of_line = snapshot.clip_point(point, Bias::Left);
4773
4774 let newline = "\n".to_string();
4775 edits.push((start_of_line..start_of_line, newline));
4776
4777 rows_inserted += 1;
4778 rows.push(row + rows_inserted);
4779 }
4780
4781 self.transact(window, cx, |editor, window, cx| {
4782 editor.edit(edits, cx);
4783
4784 editor.change_selections(Default::default(), window, cx, |s| {
4785 let mut index = 0;
4786 s.move_cursors_with(|map, _, _| {
4787 let row = rows[index];
4788 index += 1;
4789
4790 let point = Point::new(row, 0);
4791 let boundary = map.next_line_boundary(point).1;
4792 let clipped = map.clip_point(boundary, Bias::Left);
4793
4794 (clipped, SelectionGoal::None)
4795 });
4796 });
4797
4798 let mut indent_edits = Vec::new();
4799 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4800 for row in rows {
4801 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4802 for (row, indent) in indents {
4803 if indent.len == 0 {
4804 continue;
4805 }
4806
4807 let text = match indent.kind {
4808 IndentKind::Space => " ".repeat(indent.len as usize),
4809 IndentKind::Tab => "\t".repeat(indent.len as usize),
4810 };
4811 let point = Point::new(row.0, 0);
4812 indent_edits.push((point..point, text));
4813 }
4814 }
4815 editor.edit(indent_edits, cx);
4816 });
4817 }
4818
4819 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4820 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4821 original_indent_columns: Vec::new(),
4822 });
4823 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4824 }
4825
4826 fn insert_with_autoindent_mode(
4827 &mut self,
4828 text: &str,
4829 autoindent_mode: Option<AutoindentMode>,
4830 window: &mut Window,
4831 cx: &mut Context<Self>,
4832 ) {
4833 if self.read_only(cx) {
4834 return;
4835 }
4836
4837 let text: Arc<str> = text.into();
4838 self.transact(window, cx, |this, window, cx| {
4839 let old_selections = this.selections.all_adjusted(cx);
4840 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4841 let anchors = {
4842 let snapshot = buffer.read(cx);
4843 old_selections
4844 .iter()
4845 .map(|s| {
4846 let anchor = snapshot.anchor_after(s.head());
4847 s.map(|_| anchor)
4848 })
4849 .collect::<Vec<_>>()
4850 };
4851 buffer.edit(
4852 old_selections
4853 .iter()
4854 .map(|s| (s.start..s.end, text.clone())),
4855 autoindent_mode,
4856 cx,
4857 );
4858 anchors
4859 });
4860
4861 this.change_selections(Default::default(), window, cx, |s| {
4862 s.select_anchors(selection_anchors);
4863 });
4864
4865 cx.notify();
4866 });
4867 }
4868
4869 fn trigger_completion_on_input(
4870 &mut self,
4871 text: &str,
4872 trigger_in_words: bool,
4873 window: &mut Window,
4874 cx: &mut Context<Self>,
4875 ) {
4876 let completions_source = self
4877 .context_menu
4878 .borrow()
4879 .as_ref()
4880 .and_then(|menu| match menu {
4881 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4882 CodeContextMenu::CodeActions(_) => None,
4883 });
4884
4885 match completions_source {
4886 Some(CompletionsMenuSource::Words) => {
4887 self.show_word_completions(&ShowWordCompletions, window, cx)
4888 }
4889 Some(CompletionsMenuSource::Normal)
4890 | Some(CompletionsMenuSource::SnippetChoices)
4891 | None
4892 if self.is_completion_trigger(
4893 text,
4894 trigger_in_words,
4895 completions_source.is_some(),
4896 cx,
4897 ) =>
4898 {
4899 self.show_completions(
4900 &ShowCompletions {
4901 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4902 },
4903 window,
4904 cx,
4905 )
4906 }
4907 _ => {
4908 self.hide_context_menu(window, cx);
4909 }
4910 }
4911 }
4912
4913 fn is_completion_trigger(
4914 &self,
4915 text: &str,
4916 trigger_in_words: bool,
4917 menu_is_open: bool,
4918 cx: &mut Context<Self>,
4919 ) -> bool {
4920 let position = self.selections.newest_anchor().head();
4921 let multibuffer = self.buffer.read(cx);
4922 let Some(buffer) = position
4923 .buffer_id
4924 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4925 else {
4926 return false;
4927 };
4928
4929 if let Some(completion_provider) = &self.completion_provider {
4930 completion_provider.is_completion_trigger(
4931 &buffer,
4932 position.text_anchor,
4933 text,
4934 trigger_in_words,
4935 menu_is_open,
4936 cx,
4937 )
4938 } else {
4939 false
4940 }
4941 }
4942
4943 /// If any empty selections is touching the start of its innermost containing autoclose
4944 /// region, expand it to select the brackets.
4945 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4946 let selections = self.selections.all::<usize>(cx);
4947 let buffer = self.buffer.read(cx).read(cx);
4948 let new_selections = self
4949 .selections_with_autoclose_regions(selections, &buffer)
4950 .map(|(mut selection, region)| {
4951 if !selection.is_empty() {
4952 return selection;
4953 }
4954
4955 if let Some(region) = region {
4956 let mut range = region.range.to_offset(&buffer);
4957 if selection.start == range.start && range.start >= region.pair.start.len() {
4958 range.start -= region.pair.start.len();
4959 if buffer.contains_str_at(range.start, ®ion.pair.start)
4960 && buffer.contains_str_at(range.end, ®ion.pair.end)
4961 {
4962 range.end += region.pair.end.len();
4963 selection.start = range.start;
4964 selection.end = range.end;
4965
4966 return selection;
4967 }
4968 }
4969 }
4970
4971 let always_treat_brackets_as_autoclosed = buffer
4972 .language_settings_at(selection.start, cx)
4973 .always_treat_brackets_as_autoclosed;
4974
4975 if !always_treat_brackets_as_autoclosed {
4976 return selection;
4977 }
4978
4979 if let Some(scope) = buffer.language_scope_at(selection.start) {
4980 for (pair, enabled) in scope.brackets() {
4981 if !enabled || !pair.close {
4982 continue;
4983 }
4984
4985 if buffer.contains_str_at(selection.start, &pair.end) {
4986 let pair_start_len = pair.start.len();
4987 if buffer.contains_str_at(
4988 selection.start.saturating_sub(pair_start_len),
4989 &pair.start,
4990 ) {
4991 selection.start -= pair_start_len;
4992 selection.end += pair.end.len();
4993
4994 return selection;
4995 }
4996 }
4997 }
4998 }
4999
5000 selection
5001 })
5002 .collect();
5003
5004 drop(buffer);
5005 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5006 selections.select(new_selections)
5007 });
5008 }
5009
5010 /// Iterate the given selections, and for each one, find the smallest surrounding
5011 /// autoclose region. This uses the ordering of the selections and the autoclose
5012 /// regions to avoid repeated comparisons.
5013 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5014 &'a self,
5015 selections: impl IntoIterator<Item = Selection<D>>,
5016 buffer: &'a MultiBufferSnapshot,
5017 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5018 let mut i = 0;
5019 let mut regions = self.autoclose_regions.as_slice();
5020 selections.into_iter().map(move |selection| {
5021 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5022
5023 let mut enclosing = None;
5024 while let Some(pair_state) = regions.get(i) {
5025 if pair_state.range.end.to_offset(buffer) < range.start {
5026 regions = ®ions[i + 1..];
5027 i = 0;
5028 } else if pair_state.range.start.to_offset(buffer) > range.end {
5029 break;
5030 } else {
5031 if pair_state.selection_id == selection.id {
5032 enclosing = Some(pair_state);
5033 }
5034 i += 1;
5035 }
5036 }
5037
5038 (selection, enclosing)
5039 })
5040 }
5041
5042 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5043 fn invalidate_autoclose_regions(
5044 &mut self,
5045 mut selections: &[Selection<Anchor>],
5046 buffer: &MultiBufferSnapshot,
5047 ) {
5048 self.autoclose_regions.retain(|state| {
5049 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5050 return false;
5051 }
5052
5053 let mut i = 0;
5054 while let Some(selection) = selections.get(i) {
5055 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5056 selections = &selections[1..];
5057 continue;
5058 }
5059 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5060 break;
5061 }
5062 if selection.id == state.selection_id {
5063 return true;
5064 } else {
5065 i += 1;
5066 }
5067 }
5068 false
5069 });
5070 }
5071
5072 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5073 let offset = position.to_offset(buffer);
5074 let (word_range, kind) = buffer.surrounding_word(offset, true);
5075 if offset > word_range.start && kind == Some(CharKind::Word) {
5076 Some(
5077 buffer
5078 .text_for_range(word_range.start..offset)
5079 .collect::<String>(),
5080 )
5081 } else {
5082 None
5083 }
5084 }
5085
5086 pub fn toggle_inline_values(
5087 &mut self,
5088 _: &ToggleInlineValues,
5089 _: &mut Window,
5090 cx: &mut Context<Self>,
5091 ) {
5092 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5093
5094 self.refresh_inline_values(cx);
5095 }
5096
5097 pub fn toggle_inlay_hints(
5098 &mut self,
5099 _: &ToggleInlayHints,
5100 _: &mut Window,
5101 cx: &mut Context<Self>,
5102 ) {
5103 self.refresh_inlay_hints(
5104 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5105 cx,
5106 );
5107 }
5108
5109 pub fn inlay_hints_enabled(&self) -> bool {
5110 self.inlay_hint_cache.enabled
5111 }
5112
5113 pub fn inline_values_enabled(&self) -> bool {
5114 self.inline_value_cache.enabled
5115 }
5116
5117 #[cfg(any(test, feature = "test-support"))]
5118 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5119 self.display_map
5120 .read(cx)
5121 .current_inlays()
5122 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5123 .cloned()
5124 .collect()
5125 }
5126
5127 #[cfg(any(test, feature = "test-support"))]
5128 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5129 self.display_map
5130 .read(cx)
5131 .current_inlays()
5132 .cloned()
5133 .collect()
5134 }
5135
5136 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5137 if self.semantics_provider.is_none() || !self.mode.is_full() {
5138 return;
5139 }
5140
5141 let reason_description = reason.description();
5142 let ignore_debounce = matches!(
5143 reason,
5144 InlayHintRefreshReason::SettingsChange(_)
5145 | InlayHintRefreshReason::Toggle(_)
5146 | InlayHintRefreshReason::ExcerptsRemoved(_)
5147 | InlayHintRefreshReason::ModifiersChanged(_)
5148 );
5149 let (invalidate_cache, required_languages) = match reason {
5150 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5151 match self.inlay_hint_cache.modifiers_override(enabled) {
5152 Some(enabled) => {
5153 if enabled {
5154 (InvalidationStrategy::RefreshRequested, None)
5155 } else {
5156 self.splice_inlays(
5157 &self
5158 .visible_inlay_hints(cx)
5159 .iter()
5160 .map(|inlay| inlay.id)
5161 .collect::<Vec<InlayId>>(),
5162 Vec::new(),
5163 cx,
5164 );
5165 return;
5166 }
5167 }
5168 None => return,
5169 }
5170 }
5171 InlayHintRefreshReason::Toggle(enabled) => {
5172 if self.inlay_hint_cache.toggle(enabled) {
5173 if enabled {
5174 (InvalidationStrategy::RefreshRequested, None)
5175 } else {
5176 self.splice_inlays(
5177 &self
5178 .visible_inlay_hints(cx)
5179 .iter()
5180 .map(|inlay| inlay.id)
5181 .collect::<Vec<InlayId>>(),
5182 Vec::new(),
5183 cx,
5184 );
5185 return;
5186 }
5187 } else {
5188 return;
5189 }
5190 }
5191 InlayHintRefreshReason::SettingsChange(new_settings) => {
5192 match self.inlay_hint_cache.update_settings(
5193 &self.buffer,
5194 new_settings,
5195 self.visible_inlay_hints(cx),
5196 cx,
5197 ) {
5198 ControlFlow::Break(Some(InlaySplice {
5199 to_remove,
5200 to_insert,
5201 })) => {
5202 self.splice_inlays(&to_remove, to_insert, cx);
5203 return;
5204 }
5205 ControlFlow::Break(None) => return,
5206 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5207 }
5208 }
5209 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5210 if let Some(InlaySplice {
5211 to_remove,
5212 to_insert,
5213 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5214 {
5215 self.splice_inlays(&to_remove, to_insert, cx);
5216 }
5217 self.display_map.update(cx, |display_map, _| {
5218 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5219 });
5220 return;
5221 }
5222 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5223 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5224 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5225 }
5226 InlayHintRefreshReason::RefreshRequested => {
5227 (InvalidationStrategy::RefreshRequested, None)
5228 }
5229 };
5230
5231 if let Some(InlaySplice {
5232 to_remove,
5233 to_insert,
5234 }) = self.inlay_hint_cache.spawn_hint_refresh(
5235 reason_description,
5236 self.visible_excerpts(required_languages.as_ref(), cx),
5237 invalidate_cache,
5238 ignore_debounce,
5239 cx,
5240 ) {
5241 self.splice_inlays(&to_remove, to_insert, cx);
5242 }
5243 }
5244
5245 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5246 self.display_map
5247 .read(cx)
5248 .current_inlays()
5249 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5250 .cloned()
5251 .collect()
5252 }
5253
5254 pub fn visible_excerpts(
5255 &self,
5256 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5257 cx: &mut Context<Editor>,
5258 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5259 let Some(project) = self.project() else {
5260 return HashMap::default();
5261 };
5262 let project = project.read(cx);
5263 let multi_buffer = self.buffer().read(cx);
5264 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5265 let multi_buffer_visible_start = self
5266 .scroll_manager
5267 .anchor()
5268 .anchor
5269 .to_point(&multi_buffer_snapshot);
5270 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5271 multi_buffer_visible_start
5272 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5273 Bias::Left,
5274 );
5275 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5276 multi_buffer_snapshot
5277 .range_to_buffer_ranges(multi_buffer_visible_range)
5278 .into_iter()
5279 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5280 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5281 let buffer_file = project::File::from_dyn(buffer.file())?;
5282 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5283 let worktree_entry = buffer_worktree
5284 .read(cx)
5285 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5286 if worktree_entry.is_ignored {
5287 return None;
5288 }
5289
5290 let language = buffer.language()?;
5291 if let Some(restrict_to_languages) = restrict_to_languages
5292 && !restrict_to_languages.contains(language)
5293 {
5294 return None;
5295 }
5296 Some((
5297 excerpt_id,
5298 (
5299 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5300 buffer.version().clone(),
5301 excerpt_visible_range,
5302 ),
5303 ))
5304 })
5305 .collect()
5306 }
5307
5308 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5309 TextLayoutDetails {
5310 text_system: window.text_system().clone(),
5311 editor_style: self.style.clone().unwrap(),
5312 rem_size: window.rem_size(),
5313 scroll_anchor: self.scroll_manager.anchor(),
5314 visible_rows: self.visible_line_count(),
5315 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5316 }
5317 }
5318
5319 pub fn splice_inlays(
5320 &self,
5321 to_remove: &[InlayId],
5322 to_insert: Vec<Inlay>,
5323 cx: &mut Context<Self>,
5324 ) {
5325 self.display_map.update(cx, |display_map, cx| {
5326 display_map.splice_inlays(to_remove, to_insert, cx)
5327 });
5328 cx.notify();
5329 }
5330
5331 fn trigger_on_type_formatting(
5332 &self,
5333 input: String,
5334 window: &mut Window,
5335 cx: &mut Context<Self>,
5336 ) -> Option<Task<Result<()>>> {
5337 if input.len() != 1 {
5338 return None;
5339 }
5340
5341 let project = self.project()?;
5342 let position = self.selections.newest_anchor().head();
5343 let (buffer, buffer_position) = self
5344 .buffer
5345 .read(cx)
5346 .text_anchor_for_position(position, cx)?;
5347
5348 let settings = language_settings::language_settings(
5349 buffer
5350 .read(cx)
5351 .language_at(buffer_position)
5352 .map(|l| l.name()),
5353 buffer.read(cx).file(),
5354 cx,
5355 );
5356 if !settings.use_on_type_format {
5357 return None;
5358 }
5359
5360 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5361 // hence we do LSP request & edit on host side only — add formats to host's history.
5362 let push_to_lsp_host_history = true;
5363 // If this is not the host, append its history with new edits.
5364 let push_to_client_history = project.read(cx).is_via_collab();
5365
5366 let on_type_formatting = project.update(cx, |project, cx| {
5367 project.on_type_format(
5368 buffer.clone(),
5369 buffer_position,
5370 input,
5371 push_to_lsp_host_history,
5372 cx,
5373 )
5374 });
5375 Some(cx.spawn_in(window, async move |editor, cx| {
5376 if let Some(transaction) = on_type_formatting.await? {
5377 if push_to_client_history {
5378 buffer
5379 .update(cx, |buffer, _| {
5380 buffer.push_transaction(transaction, Instant::now());
5381 buffer.finalize_last_transaction();
5382 })
5383 .ok();
5384 }
5385 editor.update(cx, |editor, cx| {
5386 editor.refresh_document_highlights(cx);
5387 })?;
5388 }
5389 Ok(())
5390 }))
5391 }
5392
5393 pub fn show_word_completions(
5394 &mut self,
5395 _: &ShowWordCompletions,
5396 window: &mut Window,
5397 cx: &mut Context<Self>,
5398 ) {
5399 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5400 }
5401
5402 pub fn show_completions(
5403 &mut self,
5404 options: &ShowCompletions,
5405 window: &mut Window,
5406 cx: &mut Context<Self>,
5407 ) {
5408 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5409 }
5410
5411 fn open_or_update_completions_menu(
5412 &mut self,
5413 requested_source: Option<CompletionsMenuSource>,
5414 trigger: Option<&str>,
5415 window: &mut Window,
5416 cx: &mut Context<Self>,
5417 ) {
5418 if self.pending_rename.is_some() {
5419 return;
5420 }
5421
5422 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5423
5424 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5425 // inserted and selected. To handle that case, the start of the selection is used so that
5426 // the menu starts with all choices.
5427 let position = self
5428 .selections
5429 .newest_anchor()
5430 .start
5431 .bias_right(&multibuffer_snapshot);
5432 if position.diff_base_anchor.is_some() {
5433 return;
5434 }
5435 let (buffer, buffer_position) =
5436 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5437 output
5438 } else {
5439 return;
5440 };
5441 let buffer_snapshot = buffer.read(cx).snapshot();
5442
5443 let query: Option<Arc<String>> =
5444 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5445
5446 drop(multibuffer_snapshot);
5447
5448 let provider = match requested_source {
5449 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5450 Some(CompletionsMenuSource::Words) => None,
5451 Some(CompletionsMenuSource::SnippetChoices) => {
5452 log::error!("bug: SnippetChoices requested_source is not handled");
5453 None
5454 }
5455 };
5456
5457 let sort_completions = provider
5458 .as_ref()
5459 .is_some_and(|provider| provider.sort_completions());
5460
5461 let filter_completions = provider
5462 .as_ref()
5463 .is_none_or(|provider| provider.filter_completions());
5464
5465 let trigger_kind = match trigger {
5466 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5467 CompletionTriggerKind::TRIGGER_CHARACTER
5468 }
5469 _ => CompletionTriggerKind::INVOKED,
5470 };
5471 let completion_context = CompletionContext {
5472 trigger_character: trigger.and_then(|trigger| {
5473 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5474 Some(String::from(trigger))
5475 } else {
5476 None
5477 }
5478 }),
5479 trigger_kind,
5480 };
5481
5482 // Hide the current completions menu when a trigger char is typed. Without this, cached
5483 // completions from before the trigger char may be reused (#32774). Snippet choices could
5484 // involve trigger chars, so this is skipped in that case.
5485 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5486 {
5487 let menu_is_open = matches!(
5488 self.context_menu.borrow().as_ref(),
5489 Some(CodeContextMenu::Completions(_))
5490 );
5491 if menu_is_open {
5492 self.hide_context_menu(window, cx);
5493 }
5494 }
5495
5496 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5497 if filter_completions {
5498 menu.filter(query.clone(), provider.clone(), window, cx);
5499 }
5500 // When `is_incomplete` is false, no need to re-query completions when the current query
5501 // is a suffix of the initial query.
5502 if !menu.is_incomplete {
5503 // If the new query is a suffix of the old query (typing more characters) and
5504 // the previous result was complete, the existing completions can be filtered.
5505 //
5506 // Note that this is always true for snippet completions.
5507 let query_matches = match (&menu.initial_query, &query) {
5508 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5509 (None, _) => true,
5510 _ => false,
5511 };
5512 if query_matches {
5513 let position_matches = if menu.initial_position == position {
5514 true
5515 } else {
5516 let snapshot = self.buffer.read(cx).read(cx);
5517 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5518 };
5519 if position_matches {
5520 return;
5521 }
5522 }
5523 }
5524 };
5525
5526 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5527 buffer_snapshot.surrounding_word(buffer_position, false)
5528 {
5529 let word_to_exclude = buffer_snapshot
5530 .text_for_range(word_range.clone())
5531 .collect::<String>();
5532 (
5533 buffer_snapshot.anchor_before(word_range.start)
5534 ..buffer_snapshot.anchor_after(buffer_position),
5535 Some(word_to_exclude),
5536 )
5537 } else {
5538 (buffer_position..buffer_position, None)
5539 };
5540
5541 let language = buffer_snapshot
5542 .language_at(buffer_position)
5543 .map(|language| language.name());
5544
5545 let completion_settings =
5546 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5547
5548 let show_completion_documentation = buffer_snapshot
5549 .settings_at(buffer_position, cx)
5550 .show_completion_documentation;
5551
5552 // The document can be large, so stay in reasonable bounds when searching for words,
5553 // otherwise completion pop-up might be slow to appear.
5554 const WORD_LOOKUP_ROWS: u32 = 5_000;
5555 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5556 let min_word_search = buffer_snapshot.clip_point(
5557 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5558 Bias::Left,
5559 );
5560 let max_word_search = buffer_snapshot.clip_point(
5561 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5562 Bias::Right,
5563 );
5564 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5565 ..buffer_snapshot.point_to_offset(max_word_search);
5566
5567 let skip_digits = query
5568 .as_ref()
5569 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5570
5571 let (mut words, provider_responses) = match &provider {
5572 Some(provider) => {
5573 let provider_responses = provider.completions(
5574 position.excerpt_id,
5575 &buffer,
5576 buffer_position,
5577 completion_context,
5578 window,
5579 cx,
5580 );
5581
5582 let words = match completion_settings.words {
5583 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5584 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5585 .background_spawn(async move {
5586 buffer_snapshot.words_in_range(WordsQuery {
5587 fuzzy_contents: None,
5588 range: word_search_range,
5589 skip_digits,
5590 })
5591 }),
5592 };
5593
5594 (words, provider_responses)
5595 }
5596 None => (
5597 cx.background_spawn(async move {
5598 buffer_snapshot.words_in_range(WordsQuery {
5599 fuzzy_contents: None,
5600 range: word_search_range,
5601 skip_digits,
5602 })
5603 }),
5604 Task::ready(Ok(Vec::new())),
5605 ),
5606 };
5607
5608 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5609
5610 let id = post_inc(&mut self.next_completion_id);
5611 let task = cx.spawn_in(window, async move |editor, cx| {
5612 let Ok(()) = editor.update(cx, |this, _| {
5613 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5614 }) else {
5615 return;
5616 };
5617
5618 // TODO: Ideally completions from different sources would be selectively re-queried, so
5619 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5620 let mut completions = Vec::new();
5621 let mut is_incomplete = false;
5622 if let Some(provider_responses) = provider_responses.await.log_err()
5623 && !provider_responses.is_empty()
5624 {
5625 for response in provider_responses {
5626 completions.extend(response.completions);
5627 is_incomplete = is_incomplete || response.is_incomplete;
5628 }
5629 if completion_settings.words == WordsCompletionMode::Fallback {
5630 words = Task::ready(BTreeMap::default());
5631 }
5632 }
5633
5634 let mut words = words.await;
5635 if let Some(word_to_exclude) = &word_to_exclude {
5636 words.remove(word_to_exclude);
5637 }
5638 for lsp_completion in &completions {
5639 words.remove(&lsp_completion.new_text);
5640 }
5641 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5642 replace_range: word_replace_range.clone(),
5643 new_text: word.clone(),
5644 label: CodeLabel::plain(word, None),
5645 icon_path: None,
5646 documentation: None,
5647 source: CompletionSource::BufferWord {
5648 word_range,
5649 resolved: false,
5650 },
5651 insert_text_mode: Some(InsertTextMode::AS_IS),
5652 confirm: None,
5653 }));
5654
5655 let menu = if completions.is_empty() {
5656 None
5657 } else {
5658 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5659 let languages = editor
5660 .workspace
5661 .as_ref()
5662 .and_then(|(workspace, _)| workspace.upgrade())
5663 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5664 let menu = CompletionsMenu::new(
5665 id,
5666 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5667 sort_completions,
5668 show_completion_documentation,
5669 position,
5670 query.clone(),
5671 is_incomplete,
5672 buffer.clone(),
5673 completions.into(),
5674 snippet_sort_order,
5675 languages,
5676 language,
5677 cx,
5678 );
5679
5680 let query = if filter_completions { query } else { None };
5681 let matches_task = if let Some(query) = query {
5682 menu.do_async_filtering(query, cx)
5683 } else {
5684 Task::ready(menu.unfiltered_matches())
5685 };
5686 (menu, matches_task)
5687 }) else {
5688 return;
5689 };
5690
5691 let matches = matches_task.await;
5692
5693 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5694 // Newer menu already set, so exit.
5695 match editor.context_menu.borrow().as_ref() {
5696 Some(CodeContextMenu::Completions(prev_menu)) => {
5697 if prev_menu.id > id {
5698 return;
5699 }
5700 }
5701 _ => {}
5702 };
5703
5704 // Only valid to take prev_menu because it the new menu is immediately set
5705 // below, or the menu is hidden.
5706 match editor.context_menu.borrow_mut().take() {
5707 Some(CodeContextMenu::Completions(prev_menu)) => {
5708 let position_matches =
5709 if prev_menu.initial_position == menu.initial_position {
5710 true
5711 } else {
5712 let snapshot = editor.buffer.read(cx).read(cx);
5713 prev_menu.initial_position.to_offset(&snapshot)
5714 == menu.initial_position.to_offset(&snapshot)
5715 };
5716 if position_matches {
5717 // Preserve markdown cache before `set_filter_results` because it will
5718 // try to populate the documentation cache.
5719 menu.preserve_markdown_cache(prev_menu);
5720 }
5721 }
5722 _ => {}
5723 };
5724
5725 menu.set_filter_results(matches, provider, window, cx);
5726 }) else {
5727 return;
5728 };
5729
5730 menu.visible().then_some(menu)
5731 };
5732
5733 editor
5734 .update_in(cx, |editor, window, cx| {
5735 if editor.focus_handle.is_focused(window)
5736 && let Some(menu) = menu
5737 {
5738 *editor.context_menu.borrow_mut() =
5739 Some(CodeContextMenu::Completions(menu));
5740
5741 crate::hover_popover::hide_hover(editor, cx);
5742 if editor.show_edit_predictions_in_menu() {
5743 editor.update_visible_edit_prediction(window, cx);
5744 } else {
5745 editor.discard_edit_prediction(false, cx);
5746 }
5747
5748 cx.notify();
5749 return;
5750 }
5751
5752 if editor.completion_tasks.len() <= 1 {
5753 // If there are no more completion tasks and the last menu was empty, we should hide it.
5754 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5755 // If it was already hidden and we don't show edit predictions in the menu,
5756 // we should also show the edit prediction when available.
5757 if was_hidden && editor.show_edit_predictions_in_menu() {
5758 editor.update_visible_edit_prediction(window, cx);
5759 }
5760 }
5761 })
5762 .ok();
5763 });
5764
5765 self.completion_tasks.push((id, task));
5766 }
5767
5768 #[cfg(feature = "test-support")]
5769 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5770 let menu = self.context_menu.borrow();
5771 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5772 let completions = menu.completions.borrow();
5773 Some(completions.to_vec())
5774 } else {
5775 None
5776 }
5777 }
5778
5779 pub fn with_completions_menu_matching_id<R>(
5780 &self,
5781 id: CompletionId,
5782 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5783 ) -> R {
5784 let mut context_menu = self.context_menu.borrow_mut();
5785 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5786 return f(None);
5787 };
5788 if completions_menu.id != id {
5789 return f(None);
5790 }
5791 f(Some(completions_menu))
5792 }
5793
5794 pub fn confirm_completion(
5795 &mut self,
5796 action: &ConfirmCompletion,
5797 window: &mut Window,
5798 cx: &mut Context<Self>,
5799 ) -> Option<Task<Result<()>>> {
5800 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5801 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5802 }
5803
5804 pub fn confirm_completion_insert(
5805 &mut self,
5806 _: &ConfirmCompletionInsert,
5807 window: &mut Window,
5808 cx: &mut Context<Self>,
5809 ) -> Option<Task<Result<()>>> {
5810 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5811 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5812 }
5813
5814 pub fn confirm_completion_replace(
5815 &mut self,
5816 _: &ConfirmCompletionReplace,
5817 window: &mut Window,
5818 cx: &mut Context<Self>,
5819 ) -> Option<Task<Result<()>>> {
5820 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5821 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5822 }
5823
5824 pub fn compose_completion(
5825 &mut self,
5826 action: &ComposeCompletion,
5827 window: &mut Window,
5828 cx: &mut Context<Self>,
5829 ) -> Option<Task<Result<()>>> {
5830 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5831 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5832 }
5833
5834 fn do_completion(
5835 &mut self,
5836 item_ix: Option<usize>,
5837 intent: CompletionIntent,
5838 window: &mut Window,
5839 cx: &mut Context<Editor>,
5840 ) -> Option<Task<Result<()>>> {
5841 use language::ToOffset as _;
5842
5843 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5844 else {
5845 return None;
5846 };
5847
5848 let candidate_id = {
5849 let entries = completions_menu.entries.borrow();
5850 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5851 if self.show_edit_predictions_in_menu() {
5852 self.discard_edit_prediction(true, cx);
5853 }
5854 mat.candidate_id
5855 };
5856
5857 let completion = completions_menu
5858 .completions
5859 .borrow()
5860 .get(candidate_id)?
5861 .clone();
5862 cx.stop_propagation();
5863
5864 let buffer_handle = completions_menu.buffer.clone();
5865
5866 let CompletionEdit {
5867 new_text,
5868 snippet,
5869 replace_range,
5870 } = process_completion_for_edit(
5871 &completion,
5872 intent,
5873 &buffer_handle,
5874 &completions_menu.initial_position.text_anchor,
5875 cx,
5876 );
5877
5878 let buffer = buffer_handle.read(cx);
5879 let snapshot = self.buffer.read(cx).snapshot(cx);
5880 let newest_anchor = self.selections.newest_anchor();
5881 let replace_range_multibuffer = {
5882 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5883 let multibuffer_anchor = snapshot
5884 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5885 .unwrap()
5886 ..snapshot
5887 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5888 .unwrap();
5889 multibuffer_anchor.start.to_offset(&snapshot)
5890 ..multibuffer_anchor.end.to_offset(&snapshot)
5891 };
5892 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5893 return None;
5894 }
5895
5896 let old_text = buffer
5897 .text_for_range(replace_range.clone())
5898 .collect::<String>();
5899 let lookbehind = newest_anchor
5900 .start
5901 .text_anchor
5902 .to_offset(buffer)
5903 .saturating_sub(replace_range.start);
5904 let lookahead = replace_range
5905 .end
5906 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5907 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5908 let suffix = &old_text[lookbehind.min(old_text.len())..];
5909
5910 let selections = self.selections.all::<usize>(cx);
5911 let mut ranges = Vec::new();
5912 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5913
5914 for selection in &selections {
5915 let range = if selection.id == newest_anchor.id {
5916 replace_range_multibuffer.clone()
5917 } else {
5918 let mut range = selection.range();
5919
5920 // if prefix is present, don't duplicate it
5921 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5922 range.start = range.start.saturating_sub(lookbehind);
5923
5924 // if suffix is also present, mimic the newest cursor and replace it
5925 if selection.id != newest_anchor.id
5926 && snapshot.contains_str_at(range.end, suffix)
5927 {
5928 range.end += lookahead;
5929 }
5930 }
5931 range
5932 };
5933
5934 ranges.push(range.clone());
5935
5936 if !self.linked_edit_ranges.is_empty() {
5937 let start_anchor = snapshot.anchor_before(range.start);
5938 let end_anchor = snapshot.anchor_after(range.end);
5939 if let Some(ranges) = self
5940 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5941 {
5942 for (buffer, edits) in ranges {
5943 linked_edits
5944 .entry(buffer.clone())
5945 .or_default()
5946 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5947 }
5948 }
5949 }
5950 }
5951
5952 let common_prefix_len = old_text
5953 .chars()
5954 .zip(new_text.chars())
5955 .take_while(|(a, b)| a == b)
5956 .map(|(a, _)| a.len_utf8())
5957 .sum::<usize>();
5958
5959 cx.emit(EditorEvent::InputHandled {
5960 utf16_range_to_replace: None,
5961 text: new_text[common_prefix_len..].into(),
5962 });
5963
5964 self.transact(window, cx, |editor, window, cx| {
5965 if let Some(mut snippet) = snippet {
5966 snippet.text = new_text.to_string();
5967 editor
5968 .insert_snippet(&ranges, snippet, window, cx)
5969 .log_err();
5970 } else {
5971 editor.buffer.update(cx, |multi_buffer, cx| {
5972 let auto_indent = match completion.insert_text_mode {
5973 Some(InsertTextMode::AS_IS) => None,
5974 _ => editor.autoindent_mode.clone(),
5975 };
5976 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5977 multi_buffer.edit(edits, auto_indent, cx);
5978 });
5979 }
5980 for (buffer, edits) in linked_edits {
5981 buffer.update(cx, |buffer, cx| {
5982 let snapshot = buffer.snapshot();
5983 let edits = edits
5984 .into_iter()
5985 .map(|(range, text)| {
5986 use text::ToPoint as TP;
5987 let end_point = TP::to_point(&range.end, &snapshot);
5988 let start_point = TP::to_point(&range.start, &snapshot);
5989 (start_point..end_point, text)
5990 })
5991 .sorted_by_key(|(range, _)| range.start);
5992 buffer.edit(edits, None, cx);
5993 })
5994 }
5995
5996 editor.refresh_edit_prediction(true, false, window, cx);
5997 });
5998 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5999
6000 let show_new_completions_on_confirm = completion
6001 .confirm
6002 .as_ref()
6003 .is_some_and(|confirm| confirm(intent, window, cx));
6004 if show_new_completions_on_confirm {
6005 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6006 }
6007
6008 let provider = self.completion_provider.as_ref()?;
6009 drop(completion);
6010 let apply_edits = provider.apply_additional_edits_for_completion(
6011 buffer_handle,
6012 completions_menu.completions.clone(),
6013 candidate_id,
6014 true,
6015 cx,
6016 );
6017
6018 let editor_settings = EditorSettings::get_global(cx);
6019 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6020 // After the code completion is finished, users often want to know what signatures are needed.
6021 // so we should automatically call signature_help
6022 self.show_signature_help(&ShowSignatureHelp, window, cx);
6023 }
6024
6025 Some(cx.foreground_executor().spawn(async move {
6026 apply_edits.await?;
6027 Ok(())
6028 }))
6029 }
6030
6031 pub fn toggle_code_actions(
6032 &mut self,
6033 action: &ToggleCodeActions,
6034 window: &mut Window,
6035 cx: &mut Context<Self>,
6036 ) {
6037 let quick_launch = action.quick_launch;
6038 let mut context_menu = self.context_menu.borrow_mut();
6039 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6040 if code_actions.deployed_from == action.deployed_from {
6041 // Toggle if we're selecting the same one
6042 *context_menu = None;
6043 cx.notify();
6044 return;
6045 } else {
6046 // Otherwise, clear it and start a new one
6047 *context_menu = None;
6048 cx.notify();
6049 }
6050 }
6051 drop(context_menu);
6052 let snapshot = self.snapshot(window, cx);
6053 let deployed_from = action.deployed_from.clone();
6054 let action = action.clone();
6055 self.completion_tasks.clear();
6056 self.discard_edit_prediction(false, cx);
6057
6058 let multibuffer_point = match &action.deployed_from {
6059 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6060 DisplayPoint::new(*row, 0).to_point(&snapshot)
6061 }
6062 _ => self.selections.newest::<Point>(cx).head(),
6063 };
6064 let Some((buffer, buffer_row)) = snapshot
6065 .buffer_snapshot
6066 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6067 .and_then(|(buffer_snapshot, range)| {
6068 self.buffer()
6069 .read(cx)
6070 .buffer(buffer_snapshot.remote_id())
6071 .map(|buffer| (buffer, range.start.row))
6072 })
6073 else {
6074 return;
6075 };
6076 let buffer_id = buffer.read(cx).remote_id();
6077 let tasks = self
6078 .tasks
6079 .get(&(buffer_id, buffer_row))
6080 .map(|t| Arc::new(t.to_owned()));
6081
6082 if !self.focus_handle.is_focused(window) {
6083 return;
6084 }
6085 let project = self.project.clone();
6086
6087 let code_actions_task = match deployed_from {
6088 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6089 _ => self.code_actions(buffer_row, window, cx),
6090 };
6091
6092 let runnable_task = match deployed_from {
6093 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6094 _ => {
6095 let mut task_context_task = Task::ready(None);
6096 if let Some(tasks) = &tasks
6097 && let Some(project) = project
6098 {
6099 task_context_task =
6100 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6101 }
6102
6103 cx.spawn_in(window, {
6104 let buffer = buffer.clone();
6105 async move |editor, cx| {
6106 let task_context = task_context_task.await;
6107
6108 let resolved_tasks =
6109 tasks
6110 .zip(task_context.clone())
6111 .map(|(tasks, task_context)| ResolvedTasks {
6112 templates: tasks.resolve(&task_context).collect(),
6113 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6114 multibuffer_point.row,
6115 tasks.column,
6116 )),
6117 });
6118 let debug_scenarios = editor
6119 .update(cx, |editor, cx| {
6120 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6121 })?
6122 .await;
6123 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6124 }
6125 })
6126 }
6127 };
6128
6129 cx.spawn_in(window, async move |editor, cx| {
6130 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6131 let code_actions = code_actions_task.await;
6132 let spawn_straight_away = quick_launch
6133 && resolved_tasks
6134 .as_ref()
6135 .is_some_and(|tasks| tasks.templates.len() == 1)
6136 && code_actions
6137 .as_ref()
6138 .is_none_or(|actions| actions.is_empty())
6139 && debug_scenarios.is_empty();
6140
6141 editor.update_in(cx, |editor, window, cx| {
6142 crate::hover_popover::hide_hover(editor, cx);
6143 let actions = CodeActionContents::new(
6144 resolved_tasks,
6145 code_actions,
6146 debug_scenarios,
6147 task_context.unwrap_or_default(),
6148 );
6149
6150 // Don't show the menu if there are no actions available
6151 if actions.is_empty() {
6152 cx.notify();
6153 return Task::ready(Ok(()));
6154 }
6155
6156 *editor.context_menu.borrow_mut() =
6157 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6158 buffer,
6159 actions,
6160 selected_item: Default::default(),
6161 scroll_handle: UniformListScrollHandle::default(),
6162 deployed_from,
6163 }));
6164 cx.notify();
6165 if spawn_straight_away
6166 && let Some(task) = editor.confirm_code_action(
6167 &ConfirmCodeAction { item_ix: Some(0) },
6168 window,
6169 cx,
6170 )
6171 {
6172 return task;
6173 }
6174
6175 Task::ready(Ok(()))
6176 })
6177 })
6178 .detach_and_log_err(cx);
6179 }
6180
6181 fn debug_scenarios(
6182 &mut self,
6183 resolved_tasks: &Option<ResolvedTasks>,
6184 buffer: &Entity<Buffer>,
6185 cx: &mut App,
6186 ) -> Task<Vec<task::DebugScenario>> {
6187 maybe!({
6188 let project = self.project()?;
6189 let dap_store = project.read(cx).dap_store();
6190 let mut scenarios = vec![];
6191 let resolved_tasks = resolved_tasks.as_ref()?;
6192 let buffer = buffer.read(cx);
6193 let language = buffer.language()?;
6194 let file = buffer.file();
6195 let debug_adapter = language_settings(language.name().into(), file, cx)
6196 .debuggers
6197 .first()
6198 .map(SharedString::from)
6199 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6200
6201 dap_store.update(cx, |dap_store, cx| {
6202 for (_, task) in &resolved_tasks.templates {
6203 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6204 task.original_task().clone(),
6205 debug_adapter.clone().into(),
6206 task.display_label().to_owned().into(),
6207 cx,
6208 );
6209 scenarios.push(maybe_scenario);
6210 }
6211 });
6212 Some(cx.background_spawn(async move {
6213 let scenarios = futures::future::join_all(scenarios)
6214 .await
6215 .into_iter()
6216 .flatten()
6217 .collect::<Vec<_>>();
6218 scenarios
6219 }))
6220 })
6221 .unwrap_or_else(|| Task::ready(vec![]))
6222 }
6223
6224 fn code_actions(
6225 &mut self,
6226 buffer_row: u32,
6227 window: &mut Window,
6228 cx: &mut Context<Self>,
6229 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6230 let mut task = self.code_actions_task.take();
6231 cx.spawn_in(window, async move |editor, cx| {
6232 while let Some(prev_task) = task {
6233 prev_task.await.log_err();
6234 task = editor
6235 .update(cx, |this, _| this.code_actions_task.take())
6236 .ok()?;
6237 }
6238
6239 editor
6240 .update(cx, |editor, cx| {
6241 editor
6242 .available_code_actions
6243 .clone()
6244 .and_then(|(location, code_actions)| {
6245 let snapshot = location.buffer.read(cx).snapshot();
6246 let point_range = location.range.to_point(&snapshot);
6247 let point_range = point_range.start.row..=point_range.end.row;
6248 if point_range.contains(&buffer_row) {
6249 Some(code_actions)
6250 } else {
6251 None
6252 }
6253 })
6254 })
6255 .ok()
6256 .flatten()
6257 })
6258 }
6259
6260 pub fn confirm_code_action(
6261 &mut self,
6262 action: &ConfirmCodeAction,
6263 window: &mut Window,
6264 cx: &mut Context<Self>,
6265 ) -> Option<Task<Result<()>>> {
6266 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6267
6268 let actions_menu =
6269 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6270 menu
6271 } else {
6272 return None;
6273 };
6274
6275 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6276 let action = actions_menu.actions.get(action_ix)?;
6277 let title = action.label();
6278 let buffer = actions_menu.buffer;
6279 let workspace = self.workspace()?;
6280
6281 match action {
6282 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6283 workspace.update(cx, |workspace, cx| {
6284 workspace.schedule_resolved_task(
6285 task_source_kind,
6286 resolved_task,
6287 false,
6288 window,
6289 cx,
6290 );
6291
6292 Some(Task::ready(Ok(())))
6293 })
6294 }
6295 CodeActionsItem::CodeAction {
6296 excerpt_id,
6297 action,
6298 provider,
6299 } => {
6300 let apply_code_action =
6301 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6302 let workspace = workspace.downgrade();
6303 Some(cx.spawn_in(window, async move |editor, cx| {
6304 let project_transaction = apply_code_action.await?;
6305 Self::open_project_transaction(
6306 &editor,
6307 workspace,
6308 project_transaction,
6309 title,
6310 cx,
6311 )
6312 .await
6313 }))
6314 }
6315 CodeActionsItem::DebugScenario(scenario) => {
6316 let context = actions_menu.actions.context.clone();
6317
6318 workspace.update(cx, |workspace, cx| {
6319 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6320 workspace.start_debug_session(
6321 scenario,
6322 context,
6323 Some(buffer),
6324 None,
6325 window,
6326 cx,
6327 );
6328 });
6329 Some(Task::ready(Ok(())))
6330 }
6331 }
6332 }
6333
6334 pub async fn open_project_transaction(
6335 this: &WeakEntity<Editor>,
6336 workspace: WeakEntity<Workspace>,
6337 transaction: ProjectTransaction,
6338 title: String,
6339 cx: &mut AsyncWindowContext,
6340 ) -> Result<()> {
6341 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6342 cx.update(|_, cx| {
6343 entries.sort_unstable_by_key(|(buffer, _)| {
6344 buffer.read(cx).file().map(|f| f.path().clone())
6345 });
6346 })?;
6347
6348 // If the project transaction's edits are all contained within this editor, then
6349 // avoid opening a new editor to display them.
6350
6351 if let Some((buffer, transaction)) = entries.first() {
6352 if entries.len() == 1 {
6353 let excerpt = this.update(cx, |editor, cx| {
6354 editor
6355 .buffer()
6356 .read(cx)
6357 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6358 })?;
6359 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6360 && excerpted_buffer == *buffer
6361 {
6362 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6363 let excerpt_range = excerpt_range.to_offset(buffer);
6364 buffer
6365 .edited_ranges_for_transaction::<usize>(transaction)
6366 .all(|range| {
6367 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6368 })
6369 })?;
6370
6371 if all_edits_within_excerpt {
6372 return Ok(());
6373 }
6374 }
6375 }
6376 } else {
6377 return Ok(());
6378 }
6379
6380 let mut ranges_to_highlight = Vec::new();
6381 let excerpt_buffer = cx.new(|cx| {
6382 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6383 for (buffer_handle, transaction) in &entries {
6384 let edited_ranges = buffer_handle
6385 .read(cx)
6386 .edited_ranges_for_transaction::<Point>(transaction)
6387 .collect::<Vec<_>>();
6388 let (ranges, _) = multibuffer.set_excerpts_for_path(
6389 PathKey::for_buffer(buffer_handle, cx),
6390 buffer_handle.clone(),
6391 edited_ranges,
6392 DEFAULT_MULTIBUFFER_CONTEXT,
6393 cx,
6394 );
6395
6396 ranges_to_highlight.extend(ranges);
6397 }
6398 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6399 multibuffer
6400 })?;
6401
6402 workspace.update_in(cx, |workspace, window, cx| {
6403 let project = workspace.project().clone();
6404 let editor =
6405 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6406 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6407 editor.update(cx, |editor, cx| {
6408 editor.highlight_background::<Self>(
6409 &ranges_to_highlight,
6410 |theme| theme.colors().editor_highlighted_line_background,
6411 cx,
6412 );
6413 });
6414 })?;
6415
6416 Ok(())
6417 }
6418
6419 pub fn clear_code_action_providers(&mut self) {
6420 self.code_action_providers.clear();
6421 self.available_code_actions.take();
6422 }
6423
6424 pub fn add_code_action_provider(
6425 &mut self,
6426 provider: Rc<dyn CodeActionProvider>,
6427 window: &mut Window,
6428 cx: &mut Context<Self>,
6429 ) {
6430 if self
6431 .code_action_providers
6432 .iter()
6433 .any(|existing_provider| existing_provider.id() == provider.id())
6434 {
6435 return;
6436 }
6437
6438 self.code_action_providers.push(provider);
6439 self.refresh_code_actions(window, cx);
6440 }
6441
6442 pub fn remove_code_action_provider(
6443 &mut self,
6444 id: Arc<str>,
6445 window: &mut Window,
6446 cx: &mut Context<Self>,
6447 ) {
6448 self.code_action_providers
6449 .retain(|provider| provider.id() != id);
6450 self.refresh_code_actions(window, cx);
6451 }
6452
6453 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6454 !self.code_action_providers.is_empty()
6455 && EditorSettings::get_global(cx).toolbar.code_actions
6456 }
6457
6458 pub fn has_available_code_actions(&self) -> bool {
6459 self.available_code_actions
6460 .as_ref()
6461 .is_some_and(|(_, actions)| !actions.is_empty())
6462 }
6463
6464 fn render_inline_code_actions(
6465 &self,
6466 icon_size: ui::IconSize,
6467 display_row: DisplayRow,
6468 is_active: bool,
6469 cx: &mut Context<Self>,
6470 ) -> AnyElement {
6471 let show_tooltip = !self.context_menu_visible();
6472 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6473 .icon_size(icon_size)
6474 .shape(ui::IconButtonShape::Square)
6475 .icon_color(ui::Color::Hidden)
6476 .toggle_state(is_active)
6477 .when(show_tooltip, |this| {
6478 this.tooltip({
6479 let focus_handle = self.focus_handle.clone();
6480 move |window, cx| {
6481 Tooltip::for_action_in(
6482 "Toggle Code Actions",
6483 &ToggleCodeActions {
6484 deployed_from: None,
6485 quick_launch: false,
6486 },
6487 &focus_handle,
6488 window,
6489 cx,
6490 )
6491 }
6492 })
6493 })
6494 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6495 window.focus(&editor.focus_handle(cx));
6496 editor.toggle_code_actions(
6497 &crate::actions::ToggleCodeActions {
6498 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6499 display_row,
6500 )),
6501 quick_launch: false,
6502 },
6503 window,
6504 cx,
6505 );
6506 }))
6507 .into_any_element()
6508 }
6509
6510 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6511 &self.context_menu
6512 }
6513
6514 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6515 let newest_selection = self.selections.newest_anchor().clone();
6516 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6517 let buffer = self.buffer.read(cx);
6518 if newest_selection.head().diff_base_anchor.is_some() {
6519 return None;
6520 }
6521 let (start_buffer, start) =
6522 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6523 let (end_buffer, end) =
6524 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6525 if start_buffer != end_buffer {
6526 return None;
6527 }
6528
6529 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6530 cx.background_executor()
6531 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6532 .await;
6533
6534 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6535 let providers = this.code_action_providers.clone();
6536 let tasks = this
6537 .code_action_providers
6538 .iter()
6539 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6540 .collect::<Vec<_>>();
6541 (providers, tasks)
6542 })?;
6543
6544 let mut actions = Vec::new();
6545 for (provider, provider_actions) in
6546 providers.into_iter().zip(future::join_all(tasks).await)
6547 {
6548 if let Some(provider_actions) = provider_actions.log_err() {
6549 actions.extend(provider_actions.into_iter().map(|action| {
6550 AvailableCodeAction {
6551 excerpt_id: newest_selection.start.excerpt_id,
6552 action,
6553 provider: provider.clone(),
6554 }
6555 }));
6556 }
6557 }
6558
6559 this.update(cx, |this, cx| {
6560 this.available_code_actions = if actions.is_empty() {
6561 None
6562 } else {
6563 Some((
6564 Location {
6565 buffer: start_buffer,
6566 range: start..end,
6567 },
6568 actions.into(),
6569 ))
6570 };
6571 cx.notify();
6572 })
6573 }));
6574 None
6575 }
6576
6577 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6578 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6579 self.show_git_blame_inline = false;
6580
6581 self.show_git_blame_inline_delay_task =
6582 Some(cx.spawn_in(window, async move |this, cx| {
6583 cx.background_executor().timer(delay).await;
6584
6585 this.update(cx, |this, cx| {
6586 this.show_git_blame_inline = true;
6587 cx.notify();
6588 })
6589 .log_err();
6590 }));
6591 }
6592 }
6593
6594 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6595 let snapshot = self.snapshot(window, cx);
6596 let cursor = self.selections.newest::<Point>(cx).head();
6597 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6598 else {
6599 return;
6600 };
6601
6602 let Some(blame) = self.blame.as_ref() else {
6603 return;
6604 };
6605
6606 let row_info = RowInfo {
6607 buffer_id: Some(buffer.remote_id()),
6608 buffer_row: Some(point.row),
6609 ..Default::default()
6610 };
6611 let Some(blame_entry) = blame
6612 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6613 .flatten()
6614 else {
6615 return;
6616 };
6617
6618 let anchor = self.selections.newest_anchor().head();
6619 let position = self.to_pixel_point(anchor, &snapshot, window);
6620 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6621 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6622 };
6623 }
6624
6625 fn show_blame_popover(
6626 &mut self,
6627 blame_entry: &BlameEntry,
6628 position: gpui::Point<Pixels>,
6629 ignore_timeout: bool,
6630 cx: &mut Context<Self>,
6631 ) {
6632 if let Some(state) = &mut self.inline_blame_popover {
6633 state.hide_task.take();
6634 } else {
6635 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6636 let blame_entry = blame_entry.clone();
6637 let show_task = cx.spawn(async move |editor, cx| {
6638 if !ignore_timeout {
6639 cx.background_executor()
6640 .timer(std::time::Duration::from_millis(blame_popover_delay))
6641 .await;
6642 }
6643 editor
6644 .update(cx, |editor, cx| {
6645 editor.inline_blame_popover_show_task.take();
6646 let Some(blame) = editor.blame.as_ref() else {
6647 return;
6648 };
6649 let blame = blame.read(cx);
6650 let details = blame.details_for_entry(&blame_entry);
6651 let markdown = cx.new(|cx| {
6652 Markdown::new(
6653 details
6654 .as_ref()
6655 .map(|message| message.message.clone())
6656 .unwrap_or_default(),
6657 None,
6658 None,
6659 cx,
6660 )
6661 });
6662 editor.inline_blame_popover = Some(InlineBlamePopover {
6663 position,
6664 hide_task: None,
6665 popover_bounds: None,
6666 popover_state: InlineBlamePopoverState {
6667 scroll_handle: ScrollHandle::new(),
6668 commit_message: details,
6669 markdown,
6670 },
6671 keyboard_grace: ignore_timeout,
6672 });
6673 cx.notify();
6674 })
6675 .ok();
6676 });
6677 self.inline_blame_popover_show_task = Some(show_task);
6678 }
6679 }
6680
6681 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6682 self.inline_blame_popover_show_task.take();
6683 if let Some(state) = &mut self.inline_blame_popover {
6684 let hide_task = cx.spawn(async move |editor, cx| {
6685 cx.background_executor()
6686 .timer(std::time::Duration::from_millis(100))
6687 .await;
6688 editor
6689 .update(cx, |editor, cx| {
6690 editor.inline_blame_popover.take();
6691 cx.notify();
6692 })
6693 .ok();
6694 });
6695 state.hide_task = Some(hide_task);
6696 }
6697 }
6698
6699 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6700 if self.pending_rename.is_some() {
6701 return None;
6702 }
6703
6704 let provider = self.semantics_provider.clone()?;
6705 let buffer = self.buffer.read(cx);
6706 let newest_selection = self.selections.newest_anchor().clone();
6707 let cursor_position = newest_selection.head();
6708 let (cursor_buffer, cursor_buffer_position) =
6709 buffer.text_anchor_for_position(cursor_position, cx)?;
6710 let (tail_buffer, tail_buffer_position) =
6711 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6712 if cursor_buffer != tail_buffer {
6713 return None;
6714 }
6715
6716 let snapshot = cursor_buffer.read(cx).snapshot();
6717 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6718 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6719 if start_word_range != end_word_range {
6720 self.document_highlights_task.take();
6721 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6722 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6723 return None;
6724 }
6725
6726 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6727 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6728 cx.background_executor()
6729 .timer(Duration::from_millis(debounce))
6730 .await;
6731
6732 let highlights = if let Some(highlights) = cx
6733 .update(|cx| {
6734 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6735 })
6736 .ok()
6737 .flatten()
6738 {
6739 highlights.await.log_err()
6740 } else {
6741 None
6742 };
6743
6744 if let Some(highlights) = highlights {
6745 this.update(cx, |this, cx| {
6746 if this.pending_rename.is_some() {
6747 return;
6748 }
6749
6750 let buffer_id = cursor_position.buffer_id;
6751 let buffer = this.buffer.read(cx);
6752 if buffer
6753 .text_anchor_for_position(cursor_position, cx)
6754 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6755 {
6756 return;
6757 }
6758
6759 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6760 let mut write_ranges = Vec::new();
6761 let mut read_ranges = Vec::new();
6762 for highlight in highlights {
6763 for (excerpt_id, excerpt_range) in
6764 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6765 {
6766 let start = highlight
6767 .range
6768 .start
6769 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6770 let end = highlight
6771 .range
6772 .end
6773 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6774 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6775 continue;
6776 }
6777
6778 let range = Anchor {
6779 buffer_id,
6780 excerpt_id,
6781 text_anchor: start,
6782 diff_base_anchor: None,
6783 }..Anchor {
6784 buffer_id,
6785 excerpt_id,
6786 text_anchor: end,
6787 diff_base_anchor: None,
6788 };
6789 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6790 write_ranges.push(range);
6791 } else {
6792 read_ranges.push(range);
6793 }
6794 }
6795 }
6796
6797 this.highlight_background::<DocumentHighlightRead>(
6798 &read_ranges,
6799 |theme| theme.colors().editor_document_highlight_read_background,
6800 cx,
6801 );
6802 this.highlight_background::<DocumentHighlightWrite>(
6803 &write_ranges,
6804 |theme| theme.colors().editor_document_highlight_write_background,
6805 cx,
6806 );
6807 cx.notify();
6808 })
6809 .log_err();
6810 }
6811 }));
6812 None
6813 }
6814
6815 fn prepare_highlight_query_from_selection(
6816 &mut self,
6817 cx: &mut Context<Editor>,
6818 ) -> Option<(String, Range<Anchor>)> {
6819 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6820 return None;
6821 }
6822 if !EditorSettings::get_global(cx).selection_highlight {
6823 return None;
6824 }
6825 if self.selections.count() != 1 || self.selections.line_mode {
6826 return None;
6827 }
6828 let selection = self.selections.newest::<Point>(cx);
6829 if selection.is_empty() || selection.start.row != selection.end.row {
6830 return None;
6831 }
6832 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6833 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6834 let query = multi_buffer_snapshot
6835 .text_for_range(selection_anchor_range.clone())
6836 .collect::<String>();
6837 if query.trim().is_empty() {
6838 return None;
6839 }
6840 Some((query, selection_anchor_range))
6841 }
6842
6843 fn update_selection_occurrence_highlights(
6844 &mut self,
6845 query_text: String,
6846 query_range: Range<Anchor>,
6847 multi_buffer_range_to_query: Range<Point>,
6848 use_debounce: bool,
6849 window: &mut Window,
6850 cx: &mut Context<Editor>,
6851 ) -> Task<()> {
6852 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6853 cx.spawn_in(window, async move |editor, cx| {
6854 if use_debounce {
6855 cx.background_executor()
6856 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6857 .await;
6858 }
6859 let match_task = cx.background_spawn(async move {
6860 let buffer_ranges = multi_buffer_snapshot
6861 .range_to_buffer_ranges(multi_buffer_range_to_query)
6862 .into_iter()
6863 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6864 let mut match_ranges = Vec::new();
6865 let Ok(regex) = project::search::SearchQuery::text(
6866 query_text.clone(),
6867 false,
6868 false,
6869 false,
6870 Default::default(),
6871 Default::default(),
6872 false,
6873 None,
6874 ) else {
6875 return Vec::default();
6876 };
6877 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6878 match_ranges.extend(
6879 regex
6880 .search(buffer_snapshot, Some(search_range.clone()))
6881 .await
6882 .into_iter()
6883 .filter_map(|match_range| {
6884 let match_start = buffer_snapshot
6885 .anchor_after(search_range.start + match_range.start);
6886 let match_end = buffer_snapshot
6887 .anchor_before(search_range.start + match_range.end);
6888 let match_anchor_range = Anchor::range_in_buffer(
6889 excerpt_id,
6890 buffer_snapshot.remote_id(),
6891 match_start..match_end,
6892 );
6893 (match_anchor_range != query_range).then_some(match_anchor_range)
6894 }),
6895 );
6896 }
6897 match_ranges
6898 });
6899 let match_ranges = match_task.await;
6900 editor
6901 .update_in(cx, |editor, _, cx| {
6902 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6903 if !match_ranges.is_empty() {
6904 editor.highlight_background::<SelectedTextHighlight>(
6905 &match_ranges,
6906 |theme| theme.colors().editor_document_highlight_bracket_background,
6907 cx,
6908 )
6909 }
6910 })
6911 .log_err();
6912 })
6913 }
6914
6915 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6916 struct NewlineFold;
6917 let type_id = std::any::TypeId::of::<NewlineFold>();
6918 if !self.mode.is_single_line() {
6919 return;
6920 }
6921 let snapshot = self.snapshot(window, cx);
6922 if snapshot.buffer_snapshot.max_point().row == 0 {
6923 return;
6924 }
6925 let task = cx.background_spawn(async move {
6926 let new_newlines = snapshot
6927 .buffer_chars_at(0)
6928 .filter_map(|(c, i)| {
6929 if c == '\n' {
6930 Some(
6931 snapshot.buffer_snapshot.anchor_after(i)
6932 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6933 )
6934 } else {
6935 None
6936 }
6937 })
6938 .collect::<Vec<_>>();
6939 let existing_newlines = snapshot
6940 .folds_in_range(0..snapshot.buffer_snapshot.len())
6941 .filter_map(|fold| {
6942 if fold.placeholder.type_tag == Some(type_id) {
6943 Some(fold.range.start..fold.range.end)
6944 } else {
6945 None
6946 }
6947 })
6948 .collect::<Vec<_>>();
6949
6950 (new_newlines, existing_newlines)
6951 });
6952 self.folding_newlines = cx.spawn(async move |this, cx| {
6953 let (new_newlines, existing_newlines) = task.await;
6954 if new_newlines == existing_newlines {
6955 return;
6956 }
6957 let placeholder = FoldPlaceholder {
6958 render: Arc::new(move |_, _, cx| {
6959 div()
6960 .bg(cx.theme().status().hint_background)
6961 .border_b_1()
6962 .size_full()
6963 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6964 .border_color(cx.theme().status().hint)
6965 .child("\\n")
6966 .into_any()
6967 }),
6968 constrain_width: false,
6969 merge_adjacent: false,
6970 type_tag: Some(type_id),
6971 };
6972 let creases = new_newlines
6973 .into_iter()
6974 .map(|range| Crease::simple(range, placeholder.clone()))
6975 .collect();
6976 this.update(cx, |this, cx| {
6977 this.display_map.update(cx, |display_map, cx| {
6978 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6979 display_map.fold(creases, cx);
6980 });
6981 })
6982 .ok();
6983 });
6984 }
6985
6986 fn refresh_selected_text_highlights(
6987 &mut self,
6988 on_buffer_edit: bool,
6989 window: &mut Window,
6990 cx: &mut Context<Editor>,
6991 ) {
6992 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6993 else {
6994 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6995 self.quick_selection_highlight_task.take();
6996 self.debounced_selection_highlight_task.take();
6997 return;
6998 };
6999 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7000 if on_buffer_edit
7001 || self
7002 .quick_selection_highlight_task
7003 .as_ref()
7004 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7005 {
7006 let multi_buffer_visible_start = self
7007 .scroll_manager
7008 .anchor()
7009 .anchor
7010 .to_point(&multi_buffer_snapshot);
7011 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7012 multi_buffer_visible_start
7013 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7014 Bias::Left,
7015 );
7016 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7017 self.quick_selection_highlight_task = Some((
7018 query_range.clone(),
7019 self.update_selection_occurrence_highlights(
7020 query_text.clone(),
7021 query_range.clone(),
7022 multi_buffer_visible_range,
7023 false,
7024 window,
7025 cx,
7026 ),
7027 ));
7028 }
7029 if on_buffer_edit
7030 || self
7031 .debounced_selection_highlight_task
7032 .as_ref()
7033 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7034 {
7035 let multi_buffer_start = multi_buffer_snapshot
7036 .anchor_before(0)
7037 .to_point(&multi_buffer_snapshot);
7038 let multi_buffer_end = multi_buffer_snapshot
7039 .anchor_after(multi_buffer_snapshot.len())
7040 .to_point(&multi_buffer_snapshot);
7041 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7042 self.debounced_selection_highlight_task = Some((
7043 query_range.clone(),
7044 self.update_selection_occurrence_highlights(
7045 query_text,
7046 query_range,
7047 multi_buffer_full_range,
7048 true,
7049 window,
7050 cx,
7051 ),
7052 ));
7053 }
7054 }
7055
7056 pub fn refresh_edit_prediction(
7057 &mut self,
7058 debounce: bool,
7059 user_requested: bool,
7060 window: &mut Window,
7061 cx: &mut Context<Self>,
7062 ) -> Option<()> {
7063 if DisableAiSettings::get_global(cx).disable_ai {
7064 return None;
7065 }
7066
7067 let provider = self.edit_prediction_provider()?;
7068 let cursor = self.selections.newest_anchor().head();
7069 let (buffer, cursor_buffer_position) =
7070 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7071
7072 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7073 self.discard_edit_prediction(false, cx);
7074 return None;
7075 }
7076
7077 if !user_requested
7078 && (!self.should_show_edit_predictions()
7079 || !self.is_focused(window)
7080 || buffer.read(cx).is_empty())
7081 {
7082 self.discard_edit_prediction(false, cx);
7083 return None;
7084 }
7085
7086 self.update_visible_edit_prediction(window, cx);
7087 provider.refresh(
7088 self.project.clone(),
7089 buffer,
7090 cursor_buffer_position,
7091 debounce,
7092 cx,
7093 );
7094 Some(())
7095 }
7096
7097 fn show_edit_predictions_in_menu(&self) -> bool {
7098 match self.edit_prediction_settings {
7099 EditPredictionSettings::Disabled => false,
7100 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7101 }
7102 }
7103
7104 pub fn edit_predictions_enabled(&self) -> bool {
7105 match self.edit_prediction_settings {
7106 EditPredictionSettings::Disabled => false,
7107 EditPredictionSettings::Enabled { .. } => true,
7108 }
7109 }
7110
7111 fn edit_prediction_requires_modifier(&self) -> bool {
7112 match self.edit_prediction_settings {
7113 EditPredictionSettings::Disabled => false,
7114 EditPredictionSettings::Enabled {
7115 preview_requires_modifier,
7116 ..
7117 } => preview_requires_modifier,
7118 }
7119 }
7120
7121 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7122 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7123 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7124 self.discard_edit_prediction(false, cx);
7125 } else {
7126 let selection = self.selections.newest_anchor();
7127 let cursor = selection.head();
7128
7129 if let Some((buffer, cursor_buffer_position)) =
7130 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7131 {
7132 self.edit_prediction_settings =
7133 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7134 }
7135 }
7136 }
7137
7138 fn edit_prediction_settings_at_position(
7139 &self,
7140 buffer: &Entity<Buffer>,
7141 buffer_position: language::Anchor,
7142 cx: &App,
7143 ) -> EditPredictionSettings {
7144 if !self.mode.is_full()
7145 || !self.show_edit_predictions_override.unwrap_or(true)
7146 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7147 {
7148 return EditPredictionSettings::Disabled;
7149 }
7150
7151 let buffer = buffer.read(cx);
7152
7153 let file = buffer.file();
7154
7155 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7156 return EditPredictionSettings::Disabled;
7157 };
7158
7159 let by_provider = matches!(
7160 self.menu_edit_predictions_policy,
7161 MenuEditPredictionsPolicy::ByProvider
7162 );
7163
7164 let show_in_menu = by_provider
7165 && self
7166 .edit_prediction_provider
7167 .as_ref()
7168 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7169
7170 let preview_requires_modifier =
7171 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7172
7173 EditPredictionSettings::Enabled {
7174 show_in_menu,
7175 preview_requires_modifier,
7176 }
7177 }
7178
7179 fn should_show_edit_predictions(&self) -> bool {
7180 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7181 }
7182
7183 pub fn edit_prediction_preview_is_active(&self) -> bool {
7184 matches!(
7185 self.edit_prediction_preview,
7186 EditPredictionPreview::Active { .. }
7187 )
7188 }
7189
7190 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7191 let cursor = self.selections.newest_anchor().head();
7192 if let Some((buffer, cursor_position)) =
7193 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7194 {
7195 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7196 } else {
7197 false
7198 }
7199 }
7200
7201 pub fn supports_minimap(&self, cx: &App) -> bool {
7202 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7203 }
7204
7205 fn edit_predictions_enabled_in_buffer(
7206 &self,
7207 buffer: &Entity<Buffer>,
7208 buffer_position: language::Anchor,
7209 cx: &App,
7210 ) -> bool {
7211 maybe!({
7212 if self.read_only(cx) {
7213 return Some(false);
7214 }
7215 let provider = self.edit_prediction_provider()?;
7216 if !provider.is_enabled(buffer, buffer_position, cx) {
7217 return Some(false);
7218 }
7219 let buffer = buffer.read(cx);
7220 let Some(file) = buffer.file() else {
7221 return Some(true);
7222 };
7223 let settings = all_language_settings(Some(file), cx);
7224 Some(settings.edit_predictions_enabled_for_file(file, cx))
7225 })
7226 .unwrap_or(false)
7227 }
7228
7229 fn cycle_edit_prediction(
7230 &mut self,
7231 direction: Direction,
7232 window: &mut Window,
7233 cx: &mut Context<Self>,
7234 ) -> Option<()> {
7235 let provider = self.edit_prediction_provider()?;
7236 let cursor = self.selections.newest_anchor().head();
7237 let (buffer, cursor_buffer_position) =
7238 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7239 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7240 return None;
7241 }
7242
7243 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7244 self.update_visible_edit_prediction(window, cx);
7245
7246 Some(())
7247 }
7248
7249 pub fn show_edit_prediction(
7250 &mut self,
7251 _: &ShowEditPrediction,
7252 window: &mut Window,
7253 cx: &mut Context<Self>,
7254 ) {
7255 if !self.has_active_edit_prediction() {
7256 self.refresh_edit_prediction(false, true, window, cx);
7257 return;
7258 }
7259
7260 self.update_visible_edit_prediction(window, cx);
7261 }
7262
7263 pub fn display_cursor_names(
7264 &mut self,
7265 _: &DisplayCursorNames,
7266 window: &mut Window,
7267 cx: &mut Context<Self>,
7268 ) {
7269 self.show_cursor_names(window, cx);
7270 }
7271
7272 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7273 self.show_cursor_names = true;
7274 cx.notify();
7275 cx.spawn_in(window, async move |this, cx| {
7276 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7277 this.update(cx, |this, cx| {
7278 this.show_cursor_names = false;
7279 cx.notify()
7280 })
7281 .ok()
7282 })
7283 .detach();
7284 }
7285
7286 pub fn next_edit_prediction(
7287 &mut self,
7288 _: &NextEditPrediction,
7289 window: &mut Window,
7290 cx: &mut Context<Self>,
7291 ) {
7292 if self.has_active_edit_prediction() {
7293 self.cycle_edit_prediction(Direction::Next, window, cx);
7294 } else {
7295 let is_copilot_disabled = self
7296 .refresh_edit_prediction(false, true, window, cx)
7297 .is_none();
7298 if is_copilot_disabled {
7299 cx.propagate();
7300 }
7301 }
7302 }
7303
7304 pub fn previous_edit_prediction(
7305 &mut self,
7306 _: &PreviousEditPrediction,
7307 window: &mut Window,
7308 cx: &mut Context<Self>,
7309 ) {
7310 if self.has_active_edit_prediction() {
7311 self.cycle_edit_prediction(Direction::Prev, window, cx);
7312 } else {
7313 let is_copilot_disabled = self
7314 .refresh_edit_prediction(false, true, window, cx)
7315 .is_none();
7316 if is_copilot_disabled {
7317 cx.propagate();
7318 }
7319 }
7320 }
7321
7322 pub fn accept_edit_prediction(
7323 &mut self,
7324 _: &AcceptEditPrediction,
7325 window: &mut Window,
7326 cx: &mut Context<Self>,
7327 ) {
7328 if self.show_edit_predictions_in_menu() {
7329 self.hide_context_menu(window, cx);
7330 }
7331
7332 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7333 return;
7334 };
7335
7336 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7337
7338 match &active_edit_prediction.completion {
7339 EditPrediction::Move { target, .. } => {
7340 let target = *target;
7341
7342 if let Some(position_map) = &self.last_position_map {
7343 if position_map
7344 .visible_row_range
7345 .contains(&target.to_display_point(&position_map.snapshot).row())
7346 || !self.edit_prediction_requires_modifier()
7347 {
7348 self.unfold_ranges(&[target..target], true, false, cx);
7349 // Note that this is also done in vim's handler of the Tab action.
7350 self.change_selections(
7351 SelectionEffects::scroll(Autoscroll::newest()),
7352 window,
7353 cx,
7354 |selections| {
7355 selections.select_anchor_ranges([target..target]);
7356 },
7357 );
7358 self.clear_row_highlights::<EditPredictionPreview>();
7359
7360 self.edit_prediction_preview
7361 .set_previous_scroll_position(None);
7362 } else {
7363 self.edit_prediction_preview
7364 .set_previous_scroll_position(Some(
7365 position_map.snapshot.scroll_anchor,
7366 ));
7367
7368 self.highlight_rows::<EditPredictionPreview>(
7369 target..target,
7370 cx.theme().colors().editor_highlighted_line_background,
7371 RowHighlightOptions {
7372 autoscroll: true,
7373 ..Default::default()
7374 },
7375 cx,
7376 );
7377 self.request_autoscroll(Autoscroll::fit(), cx);
7378 }
7379 }
7380 }
7381 EditPrediction::Edit { edits, .. } => {
7382 if let Some(provider) = self.edit_prediction_provider() {
7383 provider.accept(cx);
7384 }
7385
7386 // Store the transaction ID and selections before applying the edit
7387 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7388
7389 let snapshot = self.buffer.read(cx).snapshot(cx);
7390 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7391
7392 self.buffer.update(cx, |buffer, cx| {
7393 buffer.edit(edits.iter().cloned(), None, cx)
7394 });
7395
7396 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7397 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7398 });
7399
7400 let selections = self.selections.disjoint_anchors();
7401 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7402 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7403 if has_new_transaction {
7404 self.selection_history
7405 .insert_transaction(transaction_id_now, selections);
7406 }
7407 }
7408
7409 self.update_visible_edit_prediction(window, cx);
7410 if self.active_edit_prediction.is_none() {
7411 self.refresh_edit_prediction(true, true, window, cx);
7412 }
7413
7414 cx.notify();
7415 }
7416 }
7417
7418 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7419 }
7420
7421 pub fn accept_partial_edit_prediction(
7422 &mut self,
7423 _: &AcceptPartialEditPrediction,
7424 window: &mut Window,
7425 cx: &mut Context<Self>,
7426 ) {
7427 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7428 return;
7429 };
7430 if self.selections.count() != 1 {
7431 return;
7432 }
7433
7434 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7435
7436 match &active_edit_prediction.completion {
7437 EditPrediction::Move { target, .. } => {
7438 let target = *target;
7439 self.change_selections(
7440 SelectionEffects::scroll(Autoscroll::newest()),
7441 window,
7442 cx,
7443 |selections| {
7444 selections.select_anchor_ranges([target..target]);
7445 },
7446 );
7447 }
7448 EditPrediction::Edit { edits, .. } => {
7449 // Find an insertion that starts at the cursor position.
7450 let snapshot = self.buffer.read(cx).snapshot(cx);
7451 let cursor_offset = self.selections.newest::<usize>(cx).head();
7452 let insertion = edits.iter().find_map(|(range, text)| {
7453 let range = range.to_offset(&snapshot);
7454 if range.is_empty() && range.start == cursor_offset {
7455 Some(text)
7456 } else {
7457 None
7458 }
7459 });
7460
7461 if let Some(text) = insertion {
7462 let mut partial_completion = text
7463 .chars()
7464 .by_ref()
7465 .take_while(|c| c.is_alphabetic())
7466 .collect::<String>();
7467 if partial_completion.is_empty() {
7468 partial_completion = text
7469 .chars()
7470 .by_ref()
7471 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7472 .collect::<String>();
7473 }
7474
7475 cx.emit(EditorEvent::InputHandled {
7476 utf16_range_to_replace: None,
7477 text: partial_completion.clone().into(),
7478 });
7479
7480 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7481
7482 self.refresh_edit_prediction(true, true, window, cx);
7483 cx.notify();
7484 } else {
7485 self.accept_edit_prediction(&Default::default(), window, cx);
7486 }
7487 }
7488 }
7489 }
7490
7491 fn discard_edit_prediction(
7492 &mut self,
7493 should_report_edit_prediction_event: bool,
7494 cx: &mut Context<Self>,
7495 ) -> bool {
7496 if should_report_edit_prediction_event {
7497 let completion_id = self
7498 .active_edit_prediction
7499 .as_ref()
7500 .and_then(|active_completion| active_completion.completion_id.clone());
7501
7502 self.report_edit_prediction_event(completion_id, false, cx);
7503 }
7504
7505 if let Some(provider) = self.edit_prediction_provider() {
7506 provider.discard(cx);
7507 }
7508
7509 self.take_active_edit_prediction(cx)
7510 }
7511
7512 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7513 let Some(provider) = self.edit_prediction_provider() else {
7514 return;
7515 };
7516
7517 let Some((_, buffer, _)) = self
7518 .buffer
7519 .read(cx)
7520 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7521 else {
7522 return;
7523 };
7524
7525 let extension = buffer
7526 .read(cx)
7527 .file()
7528 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7529
7530 let event_type = match accepted {
7531 true => "Edit Prediction Accepted",
7532 false => "Edit Prediction Discarded",
7533 };
7534 telemetry::event!(
7535 event_type,
7536 provider = provider.name(),
7537 prediction_id = id,
7538 suggestion_accepted = accepted,
7539 file_extension = extension,
7540 );
7541 }
7542
7543 pub fn has_active_edit_prediction(&self) -> bool {
7544 self.active_edit_prediction.is_some()
7545 }
7546
7547 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7548 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7549 return false;
7550 };
7551
7552 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7553 self.clear_highlights::<EditPredictionHighlight>(cx);
7554 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7555 true
7556 }
7557
7558 /// Returns true when we're displaying the edit prediction popover below the cursor
7559 /// like we are not previewing and the LSP autocomplete menu is visible
7560 /// or we are in `when_holding_modifier` mode.
7561 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7562 if self.edit_prediction_preview_is_active()
7563 || !self.show_edit_predictions_in_menu()
7564 || !self.edit_predictions_enabled()
7565 {
7566 return false;
7567 }
7568
7569 if self.has_visible_completions_menu() {
7570 return true;
7571 }
7572
7573 has_completion && self.edit_prediction_requires_modifier()
7574 }
7575
7576 fn handle_modifiers_changed(
7577 &mut self,
7578 modifiers: Modifiers,
7579 position_map: &PositionMap,
7580 window: &mut Window,
7581 cx: &mut Context<Self>,
7582 ) {
7583 if self.show_edit_predictions_in_menu() {
7584 self.update_edit_prediction_preview(&modifiers, window, cx);
7585 }
7586
7587 self.update_selection_mode(&modifiers, position_map, window, cx);
7588
7589 let mouse_position = window.mouse_position();
7590 if !position_map.text_hitbox.is_hovered(window) {
7591 return;
7592 }
7593
7594 self.update_hovered_link(
7595 position_map.point_for_position(mouse_position),
7596 &position_map.snapshot,
7597 modifiers,
7598 window,
7599 cx,
7600 )
7601 }
7602
7603 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7604 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7605 if invert {
7606 match multi_cursor_setting {
7607 MultiCursorModifier::Alt => modifiers.alt,
7608 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7609 }
7610 } else {
7611 match multi_cursor_setting {
7612 MultiCursorModifier::Alt => modifiers.secondary(),
7613 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7614 }
7615 }
7616 }
7617
7618 fn columnar_selection_mode(
7619 modifiers: &Modifiers,
7620 cx: &mut Context<Self>,
7621 ) -> Option<ColumnarMode> {
7622 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7623 if Self::multi_cursor_modifier(false, modifiers, cx) {
7624 Some(ColumnarMode::FromMouse)
7625 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7626 Some(ColumnarMode::FromSelection)
7627 } else {
7628 None
7629 }
7630 } else {
7631 None
7632 }
7633 }
7634
7635 fn update_selection_mode(
7636 &mut self,
7637 modifiers: &Modifiers,
7638 position_map: &PositionMap,
7639 window: &mut Window,
7640 cx: &mut Context<Self>,
7641 ) {
7642 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7643 return;
7644 };
7645 if self.selections.pending.is_none() {
7646 return;
7647 }
7648
7649 let mouse_position = window.mouse_position();
7650 let point_for_position = position_map.point_for_position(mouse_position);
7651 let position = point_for_position.previous_valid;
7652
7653 self.select(
7654 SelectPhase::BeginColumnar {
7655 position,
7656 reset: false,
7657 mode,
7658 goal_column: point_for_position.exact_unclipped.column(),
7659 },
7660 window,
7661 cx,
7662 );
7663 }
7664
7665 fn update_edit_prediction_preview(
7666 &mut self,
7667 modifiers: &Modifiers,
7668 window: &mut Window,
7669 cx: &mut Context<Self>,
7670 ) {
7671 let mut modifiers_held = false;
7672 if let Some(accept_keystroke) = self
7673 .accept_edit_prediction_keybind(false, window, cx)
7674 .keystroke()
7675 {
7676 modifiers_held = modifiers_held
7677 || (&accept_keystroke.modifiers == modifiers
7678 && accept_keystroke.modifiers.modified());
7679 };
7680 if let Some(accept_partial_keystroke) = self
7681 .accept_edit_prediction_keybind(true, window, cx)
7682 .keystroke()
7683 {
7684 modifiers_held = modifiers_held
7685 || (&accept_partial_keystroke.modifiers == modifiers
7686 && accept_partial_keystroke.modifiers.modified());
7687 }
7688
7689 if modifiers_held {
7690 if matches!(
7691 self.edit_prediction_preview,
7692 EditPredictionPreview::Inactive { .. }
7693 ) {
7694 self.edit_prediction_preview = EditPredictionPreview::Active {
7695 previous_scroll_position: None,
7696 since: Instant::now(),
7697 };
7698
7699 self.update_visible_edit_prediction(window, cx);
7700 cx.notify();
7701 }
7702 } else if let EditPredictionPreview::Active {
7703 previous_scroll_position,
7704 since,
7705 } = self.edit_prediction_preview
7706 {
7707 if let (Some(previous_scroll_position), Some(position_map)) =
7708 (previous_scroll_position, self.last_position_map.as_ref())
7709 {
7710 self.set_scroll_position(
7711 previous_scroll_position
7712 .scroll_position(&position_map.snapshot.display_snapshot),
7713 window,
7714 cx,
7715 );
7716 }
7717
7718 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7719 released_too_fast: since.elapsed() < Duration::from_millis(200),
7720 };
7721 self.clear_row_highlights::<EditPredictionPreview>();
7722 self.update_visible_edit_prediction(window, cx);
7723 cx.notify();
7724 }
7725 }
7726
7727 fn update_visible_edit_prediction(
7728 &mut self,
7729 _window: &mut Window,
7730 cx: &mut Context<Self>,
7731 ) -> Option<()> {
7732 if DisableAiSettings::get_global(cx).disable_ai {
7733 return None;
7734 }
7735
7736 let selection = self.selections.newest_anchor();
7737 let cursor = selection.head();
7738 let multibuffer = self.buffer.read(cx).snapshot(cx);
7739 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7740 let excerpt_id = cursor.excerpt_id;
7741
7742 let show_in_menu = self.show_edit_predictions_in_menu();
7743 let completions_menu_has_precedence = !show_in_menu
7744 && (self.context_menu.borrow().is_some()
7745 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7746
7747 if completions_menu_has_precedence
7748 || !offset_selection.is_empty()
7749 || self
7750 .active_edit_prediction
7751 .as_ref()
7752 .is_some_and(|completion| {
7753 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7754 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7755 !invalidation_range.contains(&offset_selection.head())
7756 })
7757 {
7758 self.discard_edit_prediction(false, cx);
7759 return None;
7760 }
7761
7762 self.take_active_edit_prediction(cx);
7763 let Some(provider) = self.edit_prediction_provider() else {
7764 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7765 return None;
7766 };
7767
7768 let (buffer, cursor_buffer_position) =
7769 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7770
7771 self.edit_prediction_settings =
7772 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7773
7774 match self.edit_prediction_settings {
7775 EditPredictionSettings::Disabled => {
7776 self.discard_edit_prediction(false, cx);
7777 return None;
7778 }
7779 _ => {}
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 let breakpoint = breakpoint.clone();
8239
8240 move |editor, event: &ClickEvent, window, cx| {
8241 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8242 BreakpointEditAction::InvertState
8243 } else {
8244 BreakpointEditAction::Toggle
8245 };
8246
8247 window.focus(&editor.focus_handle(cx));
8248 editor.edit_breakpoint_at_anchor(
8249 position,
8250 breakpoint.as_ref().clone(),
8251 edit_action,
8252 cx,
8253 );
8254 }
8255 }))
8256 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8257 editor.set_breakpoint_context_menu(
8258 row,
8259 Some(position),
8260 event.position(),
8261 window,
8262 cx,
8263 );
8264 }))
8265 .tooltip(move |window, cx| {
8266 Tooltip::with_meta_in(
8267 primary_action_text,
8268 Some(&ToggleBreakpoint),
8269 meta.clone(),
8270 &focus_handle,
8271 window,
8272 cx,
8273 )
8274 })
8275 }
8276
8277 fn build_tasks_context(
8278 project: &Entity<Project>,
8279 buffer: &Entity<Buffer>,
8280 buffer_row: u32,
8281 tasks: &Arc<RunnableTasks>,
8282 cx: &mut Context<Self>,
8283 ) -> Task<Option<task::TaskContext>> {
8284 let position = Point::new(buffer_row, tasks.column);
8285 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8286 let location = Location {
8287 buffer: buffer.clone(),
8288 range: range_start..range_start,
8289 };
8290 // Fill in the environmental variables from the tree-sitter captures
8291 let mut captured_task_variables = TaskVariables::default();
8292 for (capture_name, value) in tasks.extra_variables.clone() {
8293 captured_task_variables.insert(
8294 task::VariableName::Custom(capture_name.into()),
8295 value.clone(),
8296 );
8297 }
8298 project.update(cx, |project, cx| {
8299 project.task_store().update(cx, |task_store, cx| {
8300 task_store.task_context_for_location(captured_task_variables, location, cx)
8301 })
8302 })
8303 }
8304
8305 pub fn spawn_nearest_task(
8306 &mut self,
8307 action: &SpawnNearestTask,
8308 window: &mut Window,
8309 cx: &mut Context<Self>,
8310 ) {
8311 let Some((workspace, _)) = self.workspace.clone() else {
8312 return;
8313 };
8314 let Some(project) = self.project.clone() else {
8315 return;
8316 };
8317
8318 // Try to find a closest, enclosing node using tree-sitter that has a task
8319 let Some((buffer, buffer_row, tasks)) = self
8320 .find_enclosing_node_task(cx)
8321 // Or find the task that's closest in row-distance.
8322 .or_else(|| self.find_closest_task(cx))
8323 else {
8324 return;
8325 };
8326
8327 let reveal_strategy = action.reveal;
8328 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8329 cx.spawn_in(window, async move |_, cx| {
8330 let context = task_context.await?;
8331 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8332
8333 let resolved = &mut resolved_task.resolved;
8334 resolved.reveal = reveal_strategy;
8335
8336 workspace
8337 .update_in(cx, |workspace, window, cx| {
8338 workspace.schedule_resolved_task(
8339 task_source_kind,
8340 resolved_task,
8341 false,
8342 window,
8343 cx,
8344 );
8345 })
8346 .ok()
8347 })
8348 .detach();
8349 }
8350
8351 fn find_closest_task(
8352 &mut self,
8353 cx: &mut Context<Self>,
8354 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8355 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8356
8357 let ((buffer_id, row), tasks) = self
8358 .tasks
8359 .iter()
8360 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8361
8362 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8363 let tasks = Arc::new(tasks.to_owned());
8364 Some((buffer, *row, tasks))
8365 }
8366
8367 fn find_enclosing_node_task(
8368 &mut self,
8369 cx: &mut Context<Self>,
8370 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8371 let snapshot = self.buffer.read(cx).snapshot(cx);
8372 let offset = self.selections.newest::<usize>(cx).head();
8373 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8374 let buffer_id = excerpt.buffer().remote_id();
8375
8376 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8377 let mut cursor = layer.node().walk();
8378
8379 while cursor.goto_first_child_for_byte(offset).is_some() {
8380 if cursor.node().end_byte() == offset {
8381 cursor.goto_next_sibling();
8382 }
8383 }
8384
8385 // Ascend to the smallest ancestor that contains the range and has a task.
8386 loop {
8387 let node = cursor.node();
8388 let node_range = node.byte_range();
8389 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8390
8391 // Check if this node contains our offset
8392 if node_range.start <= offset && node_range.end >= offset {
8393 // If it contains offset, check for task
8394 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8395 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8396 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8397 }
8398 }
8399
8400 if !cursor.goto_parent() {
8401 break;
8402 }
8403 }
8404 None
8405 }
8406
8407 fn render_run_indicator(
8408 &self,
8409 _style: &EditorStyle,
8410 is_active: bool,
8411 row: DisplayRow,
8412 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8413 cx: &mut Context<Self>,
8414 ) -> IconButton {
8415 let color = Color::Muted;
8416 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8417
8418 IconButton::new(
8419 ("run_indicator", row.0 as usize),
8420 ui::IconName::PlayOutlined,
8421 )
8422 .shape(ui::IconButtonShape::Square)
8423 .icon_size(IconSize::XSmall)
8424 .icon_color(color)
8425 .toggle_state(is_active)
8426 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8427 let quick_launch = match e {
8428 ClickEvent::Keyboard(_) => true,
8429 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8430 };
8431
8432 window.focus(&editor.focus_handle(cx));
8433 editor.toggle_code_actions(
8434 &ToggleCodeActions {
8435 deployed_from: Some(CodeActionSource::RunMenu(row)),
8436 quick_launch,
8437 },
8438 window,
8439 cx,
8440 );
8441 }))
8442 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8443 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8444 }))
8445 }
8446
8447 pub fn context_menu_visible(&self) -> bool {
8448 !self.edit_prediction_preview_is_active()
8449 && self
8450 .context_menu
8451 .borrow()
8452 .as_ref()
8453 .is_some_and(|menu| menu.visible())
8454 }
8455
8456 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8457 self.context_menu
8458 .borrow()
8459 .as_ref()
8460 .map(|menu| menu.origin())
8461 }
8462
8463 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8464 self.context_menu_options = Some(options);
8465 }
8466
8467 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8468 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8469
8470 fn render_edit_prediction_popover(
8471 &mut self,
8472 text_bounds: &Bounds<Pixels>,
8473 content_origin: gpui::Point<Pixels>,
8474 right_margin: Pixels,
8475 editor_snapshot: &EditorSnapshot,
8476 visible_row_range: Range<DisplayRow>,
8477 scroll_top: f32,
8478 scroll_bottom: f32,
8479 line_layouts: &[LineWithInvisibles],
8480 line_height: Pixels,
8481 scroll_pixel_position: gpui::Point<Pixels>,
8482 newest_selection_head: Option<DisplayPoint>,
8483 editor_width: Pixels,
8484 style: &EditorStyle,
8485 window: &mut Window,
8486 cx: &mut App,
8487 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8488 if self.mode().is_minimap() {
8489 return None;
8490 }
8491 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8492
8493 if self.edit_prediction_visible_in_cursor_popover(true) {
8494 return None;
8495 }
8496
8497 match &active_edit_prediction.completion {
8498 EditPrediction::Move { target, .. } => {
8499 let target_display_point = target.to_display_point(editor_snapshot);
8500
8501 if self.edit_prediction_requires_modifier() {
8502 if !self.edit_prediction_preview_is_active() {
8503 return None;
8504 }
8505
8506 self.render_edit_prediction_modifier_jump_popover(
8507 text_bounds,
8508 content_origin,
8509 visible_row_range,
8510 line_layouts,
8511 line_height,
8512 scroll_pixel_position,
8513 newest_selection_head,
8514 target_display_point,
8515 window,
8516 cx,
8517 )
8518 } else {
8519 self.render_edit_prediction_eager_jump_popover(
8520 text_bounds,
8521 content_origin,
8522 editor_snapshot,
8523 visible_row_range,
8524 scroll_top,
8525 scroll_bottom,
8526 line_height,
8527 scroll_pixel_position,
8528 target_display_point,
8529 editor_width,
8530 window,
8531 cx,
8532 )
8533 }
8534 }
8535 EditPrediction::Edit {
8536 display_mode: EditDisplayMode::Inline,
8537 ..
8538 } => None,
8539 EditPrediction::Edit {
8540 display_mode: EditDisplayMode::TabAccept,
8541 edits,
8542 ..
8543 } => {
8544 let range = &edits.first()?.0;
8545 let target_display_point = range.end.to_display_point(editor_snapshot);
8546
8547 self.render_edit_prediction_end_of_line_popover(
8548 "Accept",
8549 editor_snapshot,
8550 visible_row_range,
8551 target_display_point,
8552 line_height,
8553 scroll_pixel_position,
8554 content_origin,
8555 editor_width,
8556 window,
8557 cx,
8558 )
8559 }
8560 EditPrediction::Edit {
8561 edits,
8562 edit_preview,
8563 display_mode: EditDisplayMode::DiffPopover,
8564 snapshot,
8565 } => self.render_edit_prediction_diff_popover(
8566 text_bounds,
8567 content_origin,
8568 right_margin,
8569 editor_snapshot,
8570 visible_row_range,
8571 line_layouts,
8572 line_height,
8573 scroll_pixel_position,
8574 newest_selection_head,
8575 editor_width,
8576 style,
8577 edits,
8578 edit_preview,
8579 snapshot,
8580 window,
8581 cx,
8582 ),
8583 }
8584 }
8585
8586 fn render_edit_prediction_modifier_jump_popover(
8587 &mut self,
8588 text_bounds: &Bounds<Pixels>,
8589 content_origin: gpui::Point<Pixels>,
8590 visible_row_range: Range<DisplayRow>,
8591 line_layouts: &[LineWithInvisibles],
8592 line_height: Pixels,
8593 scroll_pixel_position: gpui::Point<Pixels>,
8594 newest_selection_head: Option<DisplayPoint>,
8595 target_display_point: DisplayPoint,
8596 window: &mut Window,
8597 cx: &mut App,
8598 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8599 let scrolled_content_origin =
8600 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8601
8602 const SCROLL_PADDING_Y: Pixels = px(12.);
8603
8604 if target_display_point.row() < visible_row_range.start {
8605 return self.render_edit_prediction_scroll_popover(
8606 |_| SCROLL_PADDING_Y,
8607 IconName::ArrowUp,
8608 visible_row_range,
8609 line_layouts,
8610 newest_selection_head,
8611 scrolled_content_origin,
8612 window,
8613 cx,
8614 );
8615 } else if target_display_point.row() >= visible_row_range.end {
8616 return self.render_edit_prediction_scroll_popover(
8617 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8618 IconName::ArrowDown,
8619 visible_row_range,
8620 line_layouts,
8621 newest_selection_head,
8622 scrolled_content_origin,
8623 window,
8624 cx,
8625 );
8626 }
8627
8628 const POLE_WIDTH: Pixels = px(2.);
8629
8630 let line_layout =
8631 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8632 let target_column = target_display_point.column() as usize;
8633
8634 let target_x = line_layout.x_for_index(target_column);
8635 let target_y =
8636 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8637
8638 let flag_on_right = target_x < text_bounds.size.width / 2.;
8639
8640 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8641 border_color.l += 0.001;
8642
8643 let mut element = v_flex()
8644 .items_end()
8645 .when(flag_on_right, |el| el.items_start())
8646 .child(if flag_on_right {
8647 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8648 .rounded_bl(px(0.))
8649 .rounded_tl(px(0.))
8650 .border_l_2()
8651 .border_color(border_color)
8652 } else {
8653 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8654 .rounded_br(px(0.))
8655 .rounded_tr(px(0.))
8656 .border_r_2()
8657 .border_color(border_color)
8658 })
8659 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8660 .into_any();
8661
8662 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8663
8664 let mut origin = scrolled_content_origin + point(target_x, target_y)
8665 - point(
8666 if flag_on_right {
8667 POLE_WIDTH
8668 } else {
8669 size.width - POLE_WIDTH
8670 },
8671 size.height - line_height,
8672 );
8673
8674 origin.x = origin.x.max(content_origin.x);
8675
8676 element.prepaint_at(origin, window, cx);
8677
8678 Some((element, origin))
8679 }
8680
8681 fn render_edit_prediction_scroll_popover(
8682 &mut self,
8683 to_y: impl Fn(Size<Pixels>) -> Pixels,
8684 scroll_icon: IconName,
8685 visible_row_range: Range<DisplayRow>,
8686 line_layouts: &[LineWithInvisibles],
8687 newest_selection_head: Option<DisplayPoint>,
8688 scrolled_content_origin: gpui::Point<Pixels>,
8689 window: &mut Window,
8690 cx: &mut App,
8691 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8692 let mut element = self
8693 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8694 .into_any();
8695
8696 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8697
8698 let cursor = newest_selection_head?;
8699 let cursor_row_layout =
8700 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8701 let cursor_column = cursor.column() as usize;
8702
8703 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8704
8705 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8706
8707 element.prepaint_at(origin, window, cx);
8708 Some((element, origin))
8709 }
8710
8711 fn render_edit_prediction_eager_jump_popover(
8712 &mut self,
8713 text_bounds: &Bounds<Pixels>,
8714 content_origin: gpui::Point<Pixels>,
8715 editor_snapshot: &EditorSnapshot,
8716 visible_row_range: Range<DisplayRow>,
8717 scroll_top: f32,
8718 scroll_bottom: f32,
8719 line_height: Pixels,
8720 scroll_pixel_position: gpui::Point<Pixels>,
8721 target_display_point: DisplayPoint,
8722 editor_width: Pixels,
8723 window: &mut Window,
8724 cx: &mut App,
8725 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8726 if target_display_point.row().as_f32() < scroll_top {
8727 let mut element = self
8728 .render_edit_prediction_line_popover(
8729 "Jump to Edit",
8730 Some(IconName::ArrowUp),
8731 window,
8732 cx,
8733 )?
8734 .into_any();
8735
8736 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8737 let offset = point(
8738 (text_bounds.size.width - size.width) / 2.,
8739 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8740 );
8741
8742 let origin = text_bounds.origin + offset;
8743 element.prepaint_at(origin, window, cx);
8744 Some((element, origin))
8745 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8746 let mut element = self
8747 .render_edit_prediction_line_popover(
8748 "Jump to Edit",
8749 Some(IconName::ArrowDown),
8750 window,
8751 cx,
8752 )?
8753 .into_any();
8754
8755 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8756 let offset = point(
8757 (text_bounds.size.width - size.width) / 2.,
8758 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8759 );
8760
8761 let origin = text_bounds.origin + offset;
8762 element.prepaint_at(origin, window, cx);
8763 Some((element, origin))
8764 } else {
8765 self.render_edit_prediction_end_of_line_popover(
8766 "Jump to Edit",
8767 editor_snapshot,
8768 visible_row_range,
8769 target_display_point,
8770 line_height,
8771 scroll_pixel_position,
8772 content_origin,
8773 editor_width,
8774 window,
8775 cx,
8776 )
8777 }
8778 }
8779
8780 fn render_edit_prediction_end_of_line_popover(
8781 self: &mut Editor,
8782 label: &'static str,
8783 editor_snapshot: &EditorSnapshot,
8784 visible_row_range: Range<DisplayRow>,
8785 target_display_point: DisplayPoint,
8786 line_height: Pixels,
8787 scroll_pixel_position: gpui::Point<Pixels>,
8788 content_origin: gpui::Point<Pixels>,
8789 editor_width: Pixels,
8790 window: &mut Window,
8791 cx: &mut App,
8792 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8793 let target_line_end = DisplayPoint::new(
8794 target_display_point.row(),
8795 editor_snapshot.line_len(target_display_point.row()),
8796 );
8797
8798 let mut element = self
8799 .render_edit_prediction_line_popover(label, None, window, cx)?
8800 .into_any();
8801
8802 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8803
8804 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8805
8806 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8807 let mut origin = start_point
8808 + line_origin
8809 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8810 origin.x = origin.x.max(content_origin.x);
8811
8812 let max_x = content_origin.x + editor_width - size.width;
8813
8814 if origin.x > max_x {
8815 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8816
8817 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8818 origin.y += offset;
8819 IconName::ArrowUp
8820 } else {
8821 origin.y -= offset;
8822 IconName::ArrowDown
8823 };
8824
8825 element = self
8826 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8827 .into_any();
8828
8829 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8830
8831 origin.x = content_origin.x + editor_width - size.width - px(2.);
8832 }
8833
8834 element.prepaint_at(origin, window, cx);
8835 Some((element, origin))
8836 }
8837
8838 fn render_edit_prediction_diff_popover(
8839 self: &Editor,
8840 text_bounds: &Bounds<Pixels>,
8841 content_origin: gpui::Point<Pixels>,
8842 right_margin: Pixels,
8843 editor_snapshot: &EditorSnapshot,
8844 visible_row_range: Range<DisplayRow>,
8845 line_layouts: &[LineWithInvisibles],
8846 line_height: Pixels,
8847 scroll_pixel_position: gpui::Point<Pixels>,
8848 newest_selection_head: Option<DisplayPoint>,
8849 editor_width: Pixels,
8850 style: &EditorStyle,
8851 edits: &Vec<(Range<Anchor>, String)>,
8852 edit_preview: &Option<language::EditPreview>,
8853 snapshot: &language::BufferSnapshot,
8854 window: &mut Window,
8855 cx: &mut App,
8856 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8857 let edit_start = edits
8858 .first()
8859 .unwrap()
8860 .0
8861 .start
8862 .to_display_point(editor_snapshot);
8863 let edit_end = edits
8864 .last()
8865 .unwrap()
8866 .0
8867 .end
8868 .to_display_point(editor_snapshot);
8869
8870 let is_visible = visible_row_range.contains(&edit_start.row())
8871 || visible_row_range.contains(&edit_end.row());
8872 if !is_visible {
8873 return None;
8874 }
8875
8876 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8877 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8878 } else {
8879 // Fallback for providers without edit_preview
8880 crate::edit_prediction_fallback_text(edits, cx)
8881 };
8882
8883 let styled_text = highlighted_edits.to_styled_text(&style.text);
8884 let line_count = highlighted_edits.text.lines().count();
8885
8886 const BORDER_WIDTH: Pixels = px(1.);
8887
8888 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8889 let has_keybind = keybind.is_some();
8890
8891 let mut element = h_flex()
8892 .items_start()
8893 .child(
8894 h_flex()
8895 .bg(cx.theme().colors().editor_background)
8896 .border(BORDER_WIDTH)
8897 .shadow_xs()
8898 .border_color(cx.theme().colors().border)
8899 .rounded_l_lg()
8900 .when(line_count > 1, |el| el.rounded_br_lg())
8901 .pr_1()
8902 .child(styled_text),
8903 )
8904 .child(
8905 h_flex()
8906 .h(line_height + BORDER_WIDTH * 2.)
8907 .px_1p5()
8908 .gap_1()
8909 // Workaround: For some reason, there's a gap if we don't do this
8910 .ml(-BORDER_WIDTH)
8911 .shadow(vec![gpui::BoxShadow {
8912 color: gpui::black().opacity(0.05),
8913 offset: point(px(1.), px(1.)),
8914 blur_radius: px(2.),
8915 spread_radius: px(0.),
8916 }])
8917 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8918 .border(BORDER_WIDTH)
8919 .border_color(cx.theme().colors().border)
8920 .rounded_r_lg()
8921 .id("edit_prediction_diff_popover_keybind")
8922 .when(!has_keybind, |el| {
8923 let status_colors = cx.theme().status();
8924
8925 el.bg(status_colors.error_background)
8926 .border_color(status_colors.error.opacity(0.6))
8927 .child(Icon::new(IconName::Info).color(Color::Error))
8928 .cursor_default()
8929 .hoverable_tooltip(move |_window, cx| {
8930 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8931 })
8932 })
8933 .children(keybind),
8934 )
8935 .into_any();
8936
8937 let longest_row =
8938 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8939 let longest_line_width = if visible_row_range.contains(&longest_row) {
8940 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8941 } else {
8942 layout_line(
8943 longest_row,
8944 editor_snapshot,
8945 style,
8946 editor_width,
8947 |_| false,
8948 window,
8949 cx,
8950 )
8951 .width
8952 };
8953
8954 let viewport_bounds =
8955 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8956 right: -right_margin,
8957 ..Default::default()
8958 });
8959
8960 let x_after_longest =
8961 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8962 - scroll_pixel_position.x;
8963
8964 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8965
8966 // Fully visible if it can be displayed within the window (allow overlapping other
8967 // panes). However, this is only allowed if the popover starts within text_bounds.
8968 let can_position_to_the_right = x_after_longest < text_bounds.right()
8969 && x_after_longest + element_bounds.width < viewport_bounds.right();
8970
8971 let mut origin = if can_position_to_the_right {
8972 point(
8973 x_after_longest,
8974 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8975 - scroll_pixel_position.y,
8976 )
8977 } else {
8978 let cursor_row = newest_selection_head.map(|head| head.row());
8979 let above_edit = edit_start
8980 .row()
8981 .0
8982 .checked_sub(line_count as u32)
8983 .map(DisplayRow);
8984 let below_edit = Some(edit_end.row() + 1);
8985 let above_cursor =
8986 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8987 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8988
8989 // Place the edit popover adjacent to the edit if there is a location
8990 // available that is onscreen and does not obscure the cursor. Otherwise,
8991 // place it adjacent to the cursor.
8992 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8993 .into_iter()
8994 .flatten()
8995 .find(|&start_row| {
8996 let end_row = start_row + line_count as u32;
8997 visible_row_range.contains(&start_row)
8998 && visible_row_range.contains(&end_row)
8999 && cursor_row
9000 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9001 })?;
9002
9003 content_origin
9004 + point(
9005 -scroll_pixel_position.x,
9006 row_target.as_f32() * line_height - scroll_pixel_position.y,
9007 )
9008 };
9009
9010 origin.x -= BORDER_WIDTH;
9011
9012 window.defer_draw(element, origin, 1);
9013
9014 // Do not return an element, since it will already be drawn due to defer_draw.
9015 None
9016 }
9017
9018 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9019 px(30.)
9020 }
9021
9022 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9023 if self.read_only(cx) {
9024 cx.theme().players().read_only()
9025 } else {
9026 self.style.as_ref().unwrap().local_player
9027 }
9028 }
9029
9030 fn render_edit_prediction_accept_keybind(
9031 &self,
9032 window: &mut Window,
9033 cx: &App,
9034 ) -> Option<AnyElement> {
9035 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9036 let accept_keystroke = accept_binding.keystroke()?;
9037
9038 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9039
9040 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
9041 Color::Accent
9042 } else {
9043 Color::Muted
9044 };
9045
9046 h_flex()
9047 .px_0p5()
9048 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9049 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9050 .text_size(TextSize::XSmall.rems(cx))
9051 .child(h_flex().children(ui::render_modifiers(
9052 &accept_keystroke.modifiers,
9053 PlatformStyle::platform(),
9054 Some(modifiers_color),
9055 Some(IconSize::XSmall.rems().into()),
9056 true,
9057 )))
9058 .when(is_platform_style_mac, |parent| {
9059 parent.child(accept_keystroke.key.clone())
9060 })
9061 .when(!is_platform_style_mac, |parent| {
9062 parent.child(
9063 Key::new(
9064 util::capitalize(&accept_keystroke.key),
9065 Some(Color::Default),
9066 )
9067 .size(Some(IconSize::XSmall.rems().into())),
9068 )
9069 })
9070 .into_any()
9071 .into()
9072 }
9073
9074 fn render_edit_prediction_line_popover(
9075 &self,
9076 label: impl Into<SharedString>,
9077 icon: Option<IconName>,
9078 window: &mut Window,
9079 cx: &App,
9080 ) -> Option<Stateful<Div>> {
9081 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9082
9083 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9084 let has_keybind = keybind.is_some();
9085
9086 let result = h_flex()
9087 .id("ep-line-popover")
9088 .py_0p5()
9089 .pl_1()
9090 .pr(padding_right)
9091 .gap_1()
9092 .rounded_md()
9093 .border_1()
9094 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9095 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9096 .shadow_xs()
9097 .when(!has_keybind, |el| {
9098 let status_colors = cx.theme().status();
9099
9100 el.bg(status_colors.error_background)
9101 .border_color(status_colors.error.opacity(0.6))
9102 .pl_2()
9103 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9104 .cursor_default()
9105 .hoverable_tooltip(move |_window, cx| {
9106 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9107 })
9108 })
9109 .children(keybind)
9110 .child(
9111 Label::new(label)
9112 .size(LabelSize::Small)
9113 .when(!has_keybind, |el| {
9114 el.color(cx.theme().status().error.into()).strikethrough()
9115 }),
9116 )
9117 .when(!has_keybind, |el| {
9118 el.child(
9119 h_flex().ml_1().child(
9120 Icon::new(IconName::Info)
9121 .size(IconSize::Small)
9122 .color(cx.theme().status().error.into()),
9123 ),
9124 )
9125 })
9126 .when_some(icon, |element, icon| {
9127 element.child(
9128 div()
9129 .mt(px(1.5))
9130 .child(Icon::new(icon).size(IconSize::Small)),
9131 )
9132 });
9133
9134 Some(result)
9135 }
9136
9137 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9138 let accent_color = cx.theme().colors().text_accent;
9139 let editor_bg_color = cx.theme().colors().editor_background;
9140 editor_bg_color.blend(accent_color.opacity(0.1))
9141 }
9142
9143 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9144 let accent_color = cx.theme().colors().text_accent;
9145 let editor_bg_color = cx.theme().colors().editor_background;
9146 editor_bg_color.blend(accent_color.opacity(0.6))
9147 }
9148 fn get_prediction_provider_icon_name(
9149 provider: &Option<RegisteredEditPredictionProvider>,
9150 ) -> IconName {
9151 match provider {
9152 Some(provider) => match provider.provider.name() {
9153 "copilot" => IconName::Copilot,
9154 "supermaven" => IconName::Supermaven,
9155 _ => IconName::ZedPredict,
9156 },
9157 None => IconName::ZedPredict,
9158 }
9159 }
9160
9161 fn render_edit_prediction_cursor_popover(
9162 &self,
9163 min_width: Pixels,
9164 max_width: Pixels,
9165 cursor_point: Point,
9166 style: &EditorStyle,
9167 accept_keystroke: Option<&gpui::Keystroke>,
9168 _window: &Window,
9169 cx: &mut Context<Editor>,
9170 ) -> Option<AnyElement> {
9171 let provider = self.edit_prediction_provider.as_ref()?;
9172 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9173
9174 if provider.provider.needs_terms_acceptance(cx) {
9175 return Some(
9176 h_flex()
9177 .min_w(min_width)
9178 .flex_1()
9179 .px_2()
9180 .py_1()
9181 .gap_3()
9182 .elevation_2(cx)
9183 .hover(|style| style.bg(cx.theme().colors().element_hover))
9184 .id("accept-terms")
9185 .cursor_pointer()
9186 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9187 .on_click(cx.listener(|this, _event, window, cx| {
9188 cx.stop_propagation();
9189 this.report_editor_event(ReportEditorEvent::ZetaTosClicked, None, cx);
9190 window.dispatch_action(
9191 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9192 cx,
9193 );
9194 }))
9195 .child(
9196 h_flex()
9197 .flex_1()
9198 .gap_2()
9199 .child(Icon::new(provider_icon))
9200 .child(Label::new("Accept Terms of Service"))
9201 .child(div().w_full())
9202 .child(
9203 Icon::new(IconName::ArrowUpRight)
9204 .color(Color::Muted)
9205 .size(IconSize::Small),
9206 )
9207 .into_any_element(),
9208 )
9209 .into_any(),
9210 );
9211 }
9212
9213 let is_refreshing = provider.provider.is_refreshing(cx);
9214
9215 fn pending_completion_container(icon: IconName) -> Div {
9216 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9217 }
9218
9219 let completion = match &self.active_edit_prediction {
9220 Some(prediction) => {
9221 if !self.has_visible_completions_menu() {
9222 const RADIUS: Pixels = px(6.);
9223 const BORDER_WIDTH: Pixels = px(1.);
9224
9225 return Some(
9226 h_flex()
9227 .elevation_2(cx)
9228 .border(BORDER_WIDTH)
9229 .border_color(cx.theme().colors().border)
9230 .when(accept_keystroke.is_none(), |el| {
9231 el.border_color(cx.theme().status().error)
9232 })
9233 .rounded(RADIUS)
9234 .rounded_tl(px(0.))
9235 .overflow_hidden()
9236 .child(div().px_1p5().child(match &prediction.completion {
9237 EditPrediction::Move { target, snapshot } => {
9238 use text::ToPoint as _;
9239 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9240 {
9241 Icon::new(IconName::ZedPredictDown)
9242 } else {
9243 Icon::new(IconName::ZedPredictUp)
9244 }
9245 }
9246 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9247 }))
9248 .child(
9249 h_flex()
9250 .gap_1()
9251 .py_1()
9252 .px_2()
9253 .rounded_r(RADIUS - BORDER_WIDTH)
9254 .border_l_1()
9255 .border_color(cx.theme().colors().border)
9256 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9257 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9258 el.child(
9259 Label::new("Hold")
9260 .size(LabelSize::Small)
9261 .when(accept_keystroke.is_none(), |el| {
9262 el.strikethrough()
9263 })
9264 .line_height_style(LineHeightStyle::UiLabel),
9265 )
9266 })
9267 .id("edit_prediction_cursor_popover_keybind")
9268 .when(accept_keystroke.is_none(), |el| {
9269 let status_colors = cx.theme().status();
9270
9271 el.bg(status_colors.error_background)
9272 .border_color(status_colors.error.opacity(0.6))
9273 .child(Icon::new(IconName::Info).color(Color::Error))
9274 .cursor_default()
9275 .hoverable_tooltip(move |_window, cx| {
9276 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9277 .into()
9278 })
9279 })
9280 .when_some(
9281 accept_keystroke.as_ref(),
9282 |el, accept_keystroke| {
9283 el.child(h_flex().children(ui::render_modifiers(
9284 &accept_keystroke.modifiers,
9285 PlatformStyle::platform(),
9286 Some(Color::Default),
9287 Some(IconSize::XSmall.rems().into()),
9288 false,
9289 )))
9290 },
9291 ),
9292 )
9293 .into_any(),
9294 );
9295 }
9296
9297 self.render_edit_prediction_cursor_popover_preview(
9298 prediction,
9299 cursor_point,
9300 style,
9301 cx,
9302 )?
9303 }
9304
9305 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9306 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9307 stale_completion,
9308 cursor_point,
9309 style,
9310 cx,
9311 )?,
9312
9313 None => pending_completion_container(provider_icon)
9314 .child(Label::new("...").size(LabelSize::Small)),
9315 },
9316
9317 None => pending_completion_container(provider_icon)
9318 .child(Label::new("...").size(LabelSize::Small)),
9319 };
9320
9321 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9322 completion
9323 .with_animation(
9324 "loading-completion",
9325 Animation::new(Duration::from_secs(2))
9326 .repeat()
9327 .with_easing(pulsating_between(0.4, 0.8)),
9328 |label, delta| label.opacity(delta),
9329 )
9330 .into_any_element()
9331 } else {
9332 completion.into_any_element()
9333 };
9334
9335 let has_completion = self.active_edit_prediction.is_some();
9336
9337 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9338 Some(
9339 h_flex()
9340 .min_w(min_width)
9341 .max_w(max_width)
9342 .flex_1()
9343 .elevation_2(cx)
9344 .border_color(cx.theme().colors().border)
9345 .child(
9346 div()
9347 .flex_1()
9348 .py_1()
9349 .px_2()
9350 .overflow_hidden()
9351 .child(completion),
9352 )
9353 .when_some(accept_keystroke, |el, accept_keystroke| {
9354 if !accept_keystroke.modifiers.modified() {
9355 return el;
9356 }
9357
9358 el.child(
9359 h_flex()
9360 .h_full()
9361 .border_l_1()
9362 .rounded_r_lg()
9363 .border_color(cx.theme().colors().border)
9364 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9365 .gap_1()
9366 .py_1()
9367 .px_2()
9368 .child(
9369 h_flex()
9370 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9371 .when(is_platform_style_mac, |parent| parent.gap_1())
9372 .child(h_flex().children(ui::render_modifiers(
9373 &accept_keystroke.modifiers,
9374 PlatformStyle::platform(),
9375 Some(if !has_completion {
9376 Color::Muted
9377 } else {
9378 Color::Default
9379 }),
9380 None,
9381 false,
9382 ))),
9383 )
9384 .child(Label::new("Preview").into_any_element())
9385 .opacity(if has_completion { 1.0 } else { 0.4 }),
9386 )
9387 })
9388 .into_any(),
9389 )
9390 }
9391
9392 fn render_edit_prediction_cursor_popover_preview(
9393 &self,
9394 completion: &EditPredictionState,
9395 cursor_point: Point,
9396 style: &EditorStyle,
9397 cx: &mut Context<Editor>,
9398 ) -> Option<Div> {
9399 use text::ToPoint as _;
9400
9401 fn render_relative_row_jump(
9402 prefix: impl Into<String>,
9403 current_row: u32,
9404 target_row: u32,
9405 ) -> Div {
9406 let (row_diff, arrow) = if target_row < current_row {
9407 (current_row - target_row, IconName::ArrowUp)
9408 } else {
9409 (target_row - current_row, IconName::ArrowDown)
9410 };
9411
9412 h_flex()
9413 .child(
9414 Label::new(format!("{}{}", prefix.into(), row_diff))
9415 .color(Color::Muted)
9416 .size(LabelSize::Small),
9417 )
9418 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9419 }
9420
9421 let supports_jump = self
9422 .edit_prediction_provider
9423 .as_ref()
9424 .map(|provider| provider.provider.supports_jump_to_edit())
9425 .unwrap_or(true);
9426
9427 match &completion.completion {
9428 EditPrediction::Move {
9429 target, snapshot, ..
9430 } => {
9431 if !supports_jump {
9432 return None;
9433 }
9434
9435 Some(
9436 h_flex()
9437 .px_2()
9438 .gap_2()
9439 .flex_1()
9440 .child(
9441 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9442 Icon::new(IconName::ZedPredictDown)
9443 } else {
9444 Icon::new(IconName::ZedPredictUp)
9445 },
9446 )
9447 .child(Label::new("Jump to Edit")),
9448 )
9449 }
9450
9451 EditPrediction::Edit {
9452 edits,
9453 edit_preview,
9454 snapshot,
9455 display_mode: _,
9456 } => {
9457 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9458
9459 let (highlighted_edits, has_more_lines) =
9460 if let Some(edit_preview) = edit_preview.as_ref() {
9461 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9462 .first_line_preview()
9463 } else {
9464 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9465 };
9466
9467 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9468 .with_default_highlights(&style.text, highlighted_edits.highlights);
9469
9470 let preview = h_flex()
9471 .gap_1()
9472 .min_w_16()
9473 .child(styled_text)
9474 .when(has_more_lines, |parent| parent.child("…"));
9475
9476 let left = if supports_jump && first_edit_row != cursor_point.row {
9477 render_relative_row_jump("", cursor_point.row, first_edit_row)
9478 .into_any_element()
9479 } else {
9480 let icon_name =
9481 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9482 Icon::new(icon_name).into_any_element()
9483 };
9484
9485 Some(
9486 h_flex()
9487 .h_full()
9488 .flex_1()
9489 .gap_2()
9490 .pr_1()
9491 .overflow_x_hidden()
9492 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9493 .child(left)
9494 .child(preview),
9495 )
9496 }
9497 }
9498 }
9499
9500 pub fn render_context_menu(
9501 &self,
9502 style: &EditorStyle,
9503 max_height_in_lines: u32,
9504 window: &mut Window,
9505 cx: &mut Context<Editor>,
9506 ) -> Option<AnyElement> {
9507 let menu = self.context_menu.borrow();
9508 let menu = menu.as_ref()?;
9509 if !menu.visible() {
9510 return None;
9511 };
9512 Some(menu.render(style, max_height_in_lines, window, cx))
9513 }
9514
9515 fn render_context_menu_aside(
9516 &mut self,
9517 max_size: Size<Pixels>,
9518 window: &mut Window,
9519 cx: &mut Context<Editor>,
9520 ) -> Option<AnyElement> {
9521 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9522 if menu.visible() {
9523 menu.render_aside(max_size, window, cx)
9524 } else {
9525 None
9526 }
9527 })
9528 }
9529
9530 fn hide_context_menu(
9531 &mut self,
9532 window: &mut Window,
9533 cx: &mut Context<Self>,
9534 ) -> Option<CodeContextMenu> {
9535 cx.notify();
9536 self.completion_tasks.clear();
9537 let context_menu = self.context_menu.borrow_mut().take();
9538 self.stale_edit_prediction_in_menu.take();
9539 self.update_visible_edit_prediction(window, cx);
9540 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9541 && let Some(completion_provider) = &self.completion_provider
9542 {
9543 completion_provider.selection_changed(None, window, cx);
9544 }
9545 context_menu
9546 }
9547
9548 fn show_snippet_choices(
9549 &mut self,
9550 choices: &Vec<String>,
9551 selection: Range<Anchor>,
9552 cx: &mut Context<Self>,
9553 ) {
9554 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9555 (Some(a), Some(b)) if a == b => a,
9556 _ => {
9557 log::error!("expected anchor range to have matching buffer IDs");
9558 return;
9559 }
9560 };
9561 let multi_buffer = self.buffer().read(cx);
9562 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9563 return;
9564 };
9565
9566 let id = post_inc(&mut self.next_completion_id);
9567 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9568 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9569 CompletionsMenu::new_snippet_choices(
9570 id,
9571 true,
9572 choices,
9573 selection,
9574 buffer,
9575 snippet_sort_order,
9576 ),
9577 ));
9578 }
9579
9580 pub fn insert_snippet(
9581 &mut self,
9582 insertion_ranges: &[Range<usize>],
9583 snippet: Snippet,
9584 window: &mut Window,
9585 cx: &mut Context<Self>,
9586 ) -> Result<()> {
9587 struct Tabstop<T> {
9588 is_end_tabstop: bool,
9589 ranges: Vec<Range<T>>,
9590 choices: Option<Vec<String>>,
9591 }
9592
9593 let tabstops = self.buffer.update(cx, |buffer, cx| {
9594 let snippet_text: Arc<str> = snippet.text.clone().into();
9595 let edits = insertion_ranges
9596 .iter()
9597 .cloned()
9598 .map(|range| (range, snippet_text.clone()));
9599 let autoindent_mode = AutoindentMode::Block {
9600 original_indent_columns: Vec::new(),
9601 };
9602 buffer.edit(edits, Some(autoindent_mode), cx);
9603
9604 let snapshot = &*buffer.read(cx);
9605 let snippet = &snippet;
9606 snippet
9607 .tabstops
9608 .iter()
9609 .map(|tabstop| {
9610 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9611 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9612 });
9613 let mut tabstop_ranges = tabstop
9614 .ranges
9615 .iter()
9616 .flat_map(|tabstop_range| {
9617 let mut delta = 0_isize;
9618 insertion_ranges.iter().map(move |insertion_range| {
9619 let insertion_start = insertion_range.start as isize + delta;
9620 delta +=
9621 snippet.text.len() as isize - insertion_range.len() as isize;
9622
9623 let start = ((insertion_start + tabstop_range.start) as usize)
9624 .min(snapshot.len());
9625 let end = ((insertion_start + tabstop_range.end) as usize)
9626 .min(snapshot.len());
9627 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9628 })
9629 })
9630 .collect::<Vec<_>>();
9631 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9632
9633 Tabstop {
9634 is_end_tabstop,
9635 ranges: tabstop_ranges,
9636 choices: tabstop.choices.clone(),
9637 }
9638 })
9639 .collect::<Vec<_>>()
9640 });
9641 if let Some(tabstop) = tabstops.first() {
9642 self.change_selections(Default::default(), window, cx, |s| {
9643 // Reverse order so that the first range is the newest created selection.
9644 // Completions will use it and autoscroll will prioritize it.
9645 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9646 });
9647
9648 if let Some(choices) = &tabstop.choices
9649 && let Some(selection) = tabstop.ranges.first()
9650 {
9651 self.show_snippet_choices(choices, selection.clone(), cx)
9652 }
9653
9654 // If we're already at the last tabstop and it's at the end of the snippet,
9655 // we're done, we don't need to keep the state around.
9656 if !tabstop.is_end_tabstop {
9657 let choices = tabstops
9658 .iter()
9659 .map(|tabstop| tabstop.choices.clone())
9660 .collect();
9661
9662 let ranges = tabstops
9663 .into_iter()
9664 .map(|tabstop| tabstop.ranges)
9665 .collect::<Vec<_>>();
9666
9667 self.snippet_stack.push(SnippetState {
9668 active_index: 0,
9669 ranges,
9670 choices,
9671 });
9672 }
9673
9674 // Check whether the just-entered snippet ends with an auto-closable bracket.
9675 if self.autoclose_regions.is_empty() {
9676 let snapshot = self.buffer.read(cx).snapshot(cx);
9677 let mut all_selections = self.selections.all::<Point>(cx);
9678 for selection in &mut all_selections {
9679 let selection_head = selection.head();
9680 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9681 continue;
9682 };
9683
9684 let mut bracket_pair = None;
9685 let max_lookup_length = scope
9686 .brackets()
9687 .map(|(pair, _)| {
9688 pair.start
9689 .as_str()
9690 .chars()
9691 .count()
9692 .max(pair.end.as_str().chars().count())
9693 })
9694 .max();
9695 if let Some(max_lookup_length) = max_lookup_length {
9696 let next_text = snapshot
9697 .chars_at(selection_head)
9698 .take(max_lookup_length)
9699 .collect::<String>();
9700 let prev_text = snapshot
9701 .reversed_chars_at(selection_head)
9702 .take(max_lookup_length)
9703 .collect::<String>();
9704
9705 for (pair, enabled) in scope.brackets() {
9706 if enabled
9707 && pair.close
9708 && prev_text.starts_with(pair.start.as_str())
9709 && next_text.starts_with(pair.end.as_str())
9710 {
9711 bracket_pair = Some(pair.clone());
9712 break;
9713 }
9714 }
9715 }
9716
9717 if let Some(pair) = bracket_pair {
9718 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9719 let autoclose_enabled =
9720 self.use_autoclose && snapshot_settings.use_autoclose;
9721 if autoclose_enabled {
9722 let start = snapshot.anchor_after(selection_head);
9723 let end = snapshot.anchor_after(selection_head);
9724 self.autoclose_regions.push(AutocloseRegion {
9725 selection_id: selection.id,
9726 range: start..end,
9727 pair,
9728 });
9729 }
9730 }
9731 }
9732 }
9733 }
9734 Ok(())
9735 }
9736
9737 pub fn move_to_next_snippet_tabstop(
9738 &mut self,
9739 window: &mut Window,
9740 cx: &mut Context<Self>,
9741 ) -> bool {
9742 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9743 }
9744
9745 pub fn move_to_prev_snippet_tabstop(
9746 &mut self,
9747 window: &mut Window,
9748 cx: &mut Context<Self>,
9749 ) -> bool {
9750 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9751 }
9752
9753 pub fn move_to_snippet_tabstop(
9754 &mut self,
9755 bias: Bias,
9756 window: &mut Window,
9757 cx: &mut Context<Self>,
9758 ) -> bool {
9759 if let Some(mut snippet) = self.snippet_stack.pop() {
9760 match bias {
9761 Bias::Left => {
9762 if snippet.active_index > 0 {
9763 snippet.active_index -= 1;
9764 } else {
9765 self.snippet_stack.push(snippet);
9766 return false;
9767 }
9768 }
9769 Bias::Right => {
9770 if snippet.active_index + 1 < snippet.ranges.len() {
9771 snippet.active_index += 1;
9772 } else {
9773 self.snippet_stack.push(snippet);
9774 return false;
9775 }
9776 }
9777 }
9778 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9779 self.change_selections(Default::default(), window, cx, |s| {
9780 // Reverse order so that the first range is the newest created selection.
9781 // Completions will use it and autoscroll will prioritize it.
9782 s.select_ranges(current_ranges.iter().rev().cloned())
9783 });
9784
9785 if let Some(choices) = &snippet.choices[snippet.active_index]
9786 && let Some(selection) = current_ranges.first()
9787 {
9788 self.show_snippet_choices(choices, selection.clone(), cx);
9789 }
9790
9791 // If snippet state is not at the last tabstop, push it back on the stack
9792 if snippet.active_index + 1 < snippet.ranges.len() {
9793 self.snippet_stack.push(snippet);
9794 }
9795 return true;
9796 }
9797 }
9798
9799 false
9800 }
9801
9802 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9803 self.transact(window, cx, |this, window, cx| {
9804 this.select_all(&SelectAll, window, cx);
9805 this.insert("", window, cx);
9806 });
9807 }
9808
9809 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9810 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9811 self.transact(window, cx, |this, window, cx| {
9812 this.select_autoclose_pair(window, cx);
9813 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9814 if !this.linked_edit_ranges.is_empty() {
9815 let selections = this.selections.all::<MultiBufferPoint>(cx);
9816 let snapshot = this.buffer.read(cx).snapshot(cx);
9817
9818 for selection in selections.iter() {
9819 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9820 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9821 if selection_start.buffer_id != selection_end.buffer_id {
9822 continue;
9823 }
9824 if let Some(ranges) =
9825 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9826 {
9827 for (buffer, entries) in ranges {
9828 linked_ranges.entry(buffer).or_default().extend(entries);
9829 }
9830 }
9831 }
9832 }
9833
9834 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9835 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9836 for selection in &mut selections {
9837 if selection.is_empty() {
9838 let old_head = selection.head();
9839 let mut new_head =
9840 movement::left(&display_map, old_head.to_display_point(&display_map))
9841 .to_point(&display_map);
9842 if let Some((buffer, line_buffer_range)) = display_map
9843 .buffer_snapshot
9844 .buffer_line_for_row(MultiBufferRow(old_head.row))
9845 {
9846 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9847 let indent_len = match indent_size.kind {
9848 IndentKind::Space => {
9849 buffer.settings_at(line_buffer_range.start, cx).tab_size
9850 }
9851 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9852 };
9853 if old_head.column <= indent_size.len && old_head.column > 0 {
9854 let indent_len = indent_len.get();
9855 new_head = cmp::min(
9856 new_head,
9857 MultiBufferPoint::new(
9858 old_head.row,
9859 ((old_head.column - 1) / indent_len) * indent_len,
9860 ),
9861 );
9862 }
9863 }
9864
9865 selection.set_head(new_head, SelectionGoal::None);
9866 }
9867 }
9868
9869 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9870 this.insert("", window, cx);
9871 let empty_str: Arc<str> = Arc::from("");
9872 for (buffer, edits) in linked_ranges {
9873 let snapshot = buffer.read(cx).snapshot();
9874 use text::ToPoint as TP;
9875
9876 let edits = edits
9877 .into_iter()
9878 .map(|range| {
9879 let end_point = TP::to_point(&range.end, &snapshot);
9880 let mut start_point = TP::to_point(&range.start, &snapshot);
9881
9882 if end_point == start_point {
9883 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9884 .saturating_sub(1);
9885 start_point =
9886 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9887 };
9888
9889 (start_point..end_point, empty_str.clone())
9890 })
9891 .sorted_by_key(|(range, _)| range.start)
9892 .collect::<Vec<_>>();
9893 buffer.update(cx, |this, cx| {
9894 this.edit(edits, None, cx);
9895 })
9896 }
9897 this.refresh_edit_prediction(true, false, window, cx);
9898 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9899 });
9900 }
9901
9902 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9903 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9904 self.transact(window, cx, |this, window, cx| {
9905 this.change_selections(Default::default(), window, cx, |s| {
9906 s.move_with(|map, selection| {
9907 if selection.is_empty() {
9908 let cursor = movement::right(map, selection.head());
9909 selection.end = cursor;
9910 selection.reversed = true;
9911 selection.goal = SelectionGoal::None;
9912 }
9913 })
9914 });
9915 this.insert("", window, cx);
9916 this.refresh_edit_prediction(true, false, window, cx);
9917 });
9918 }
9919
9920 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9921 if self.mode.is_single_line() {
9922 cx.propagate();
9923 return;
9924 }
9925
9926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9927 if self.move_to_prev_snippet_tabstop(window, cx) {
9928 return;
9929 }
9930 self.outdent(&Outdent, window, cx);
9931 }
9932
9933 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9934 if self.mode.is_single_line() {
9935 cx.propagate();
9936 return;
9937 }
9938
9939 if self.move_to_next_snippet_tabstop(window, cx) {
9940 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9941 return;
9942 }
9943 if self.read_only(cx) {
9944 return;
9945 }
9946 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9947 let mut selections = self.selections.all_adjusted(cx);
9948 let buffer = self.buffer.read(cx);
9949 let snapshot = buffer.snapshot(cx);
9950 let rows_iter = selections.iter().map(|s| s.head().row);
9951 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9952
9953 let has_some_cursor_in_whitespace = selections
9954 .iter()
9955 .filter(|selection| selection.is_empty())
9956 .any(|selection| {
9957 let cursor = selection.head();
9958 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9959 cursor.column < current_indent.len
9960 });
9961
9962 let mut edits = Vec::new();
9963 let mut prev_edited_row = 0;
9964 let mut row_delta = 0;
9965 for selection in &mut selections {
9966 if selection.start.row != prev_edited_row {
9967 row_delta = 0;
9968 }
9969 prev_edited_row = selection.end.row;
9970
9971 // If the selection is non-empty, then increase the indentation of the selected lines.
9972 if !selection.is_empty() {
9973 row_delta =
9974 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9975 continue;
9976 }
9977
9978 let cursor = selection.head();
9979 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9980 if let Some(suggested_indent) =
9981 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9982 {
9983 // Don't do anything if already at suggested indent
9984 // and there is any other cursor which is not
9985 if has_some_cursor_in_whitespace
9986 && cursor.column == current_indent.len
9987 && current_indent.len == suggested_indent.len
9988 {
9989 continue;
9990 }
9991
9992 // Adjust line and move cursor to suggested indent
9993 // if cursor is not at suggested indent
9994 if cursor.column < suggested_indent.len
9995 && cursor.column <= current_indent.len
9996 && current_indent.len <= suggested_indent.len
9997 {
9998 selection.start = Point::new(cursor.row, suggested_indent.len);
9999 selection.end = selection.start;
10000 if row_delta == 0 {
10001 edits.extend(Buffer::edit_for_indent_size_adjustment(
10002 cursor.row,
10003 current_indent,
10004 suggested_indent,
10005 ));
10006 row_delta = suggested_indent.len - current_indent.len;
10007 }
10008 continue;
10009 }
10010
10011 // If current indent is more than suggested indent
10012 // only move cursor to current indent and skip indent
10013 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10014 selection.start = Point::new(cursor.row, current_indent.len);
10015 selection.end = selection.start;
10016 continue;
10017 }
10018 }
10019
10020 // Otherwise, insert a hard or soft tab.
10021 let settings = buffer.language_settings_at(cursor, cx);
10022 let tab_size = if settings.hard_tabs {
10023 IndentSize::tab()
10024 } else {
10025 let tab_size = settings.tab_size.get();
10026 let indent_remainder = snapshot
10027 .text_for_range(Point::new(cursor.row, 0)..cursor)
10028 .flat_map(str::chars)
10029 .fold(row_delta % tab_size, |counter: u32, c| {
10030 if c == '\t' {
10031 0
10032 } else {
10033 (counter + 1) % tab_size
10034 }
10035 });
10036
10037 let chars_to_next_tab_stop = tab_size - indent_remainder;
10038 IndentSize::spaces(chars_to_next_tab_stop)
10039 };
10040 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10041 selection.end = selection.start;
10042 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10043 row_delta += tab_size.len;
10044 }
10045
10046 self.transact(window, cx, |this, window, cx| {
10047 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10048 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10049 this.refresh_edit_prediction(true, false, window, cx);
10050 });
10051 }
10052
10053 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10054 if self.read_only(cx) {
10055 return;
10056 }
10057 if self.mode.is_single_line() {
10058 cx.propagate();
10059 return;
10060 }
10061
10062 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10063 let mut selections = self.selections.all::<Point>(cx);
10064 let mut prev_edited_row = 0;
10065 let mut row_delta = 0;
10066 let mut edits = Vec::new();
10067 let buffer = self.buffer.read(cx);
10068 let snapshot = buffer.snapshot(cx);
10069 for selection in &mut selections {
10070 if selection.start.row != prev_edited_row {
10071 row_delta = 0;
10072 }
10073 prev_edited_row = selection.end.row;
10074
10075 row_delta =
10076 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10077 }
10078
10079 self.transact(window, cx, |this, window, cx| {
10080 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10081 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10082 });
10083 }
10084
10085 fn indent_selection(
10086 buffer: &MultiBuffer,
10087 snapshot: &MultiBufferSnapshot,
10088 selection: &mut Selection<Point>,
10089 edits: &mut Vec<(Range<Point>, String)>,
10090 delta_for_start_row: u32,
10091 cx: &App,
10092 ) -> u32 {
10093 let settings = buffer.language_settings_at(selection.start, cx);
10094 let tab_size = settings.tab_size.get();
10095 let indent_kind = if settings.hard_tabs {
10096 IndentKind::Tab
10097 } else {
10098 IndentKind::Space
10099 };
10100 let mut start_row = selection.start.row;
10101 let mut end_row = selection.end.row + 1;
10102
10103 // If a selection ends at the beginning of a line, don't indent
10104 // that last line.
10105 if selection.end.column == 0 && selection.end.row > selection.start.row {
10106 end_row -= 1;
10107 }
10108
10109 // Avoid re-indenting a row that has already been indented by a
10110 // previous selection, but still update this selection's column
10111 // to reflect that indentation.
10112 if delta_for_start_row > 0 {
10113 start_row += 1;
10114 selection.start.column += delta_for_start_row;
10115 if selection.end.row == selection.start.row {
10116 selection.end.column += delta_for_start_row;
10117 }
10118 }
10119
10120 let mut delta_for_end_row = 0;
10121 let has_multiple_rows = start_row + 1 != end_row;
10122 for row in start_row..end_row {
10123 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10124 let indent_delta = match (current_indent.kind, indent_kind) {
10125 (IndentKind::Space, IndentKind::Space) => {
10126 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10127 IndentSize::spaces(columns_to_next_tab_stop)
10128 }
10129 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10130 (_, IndentKind::Tab) => IndentSize::tab(),
10131 };
10132
10133 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10134 0
10135 } else {
10136 selection.start.column
10137 };
10138 let row_start = Point::new(row, start);
10139 edits.push((
10140 row_start..row_start,
10141 indent_delta.chars().collect::<String>(),
10142 ));
10143
10144 // Update this selection's endpoints to reflect the indentation.
10145 if row == selection.start.row {
10146 selection.start.column += indent_delta.len;
10147 }
10148 if row == selection.end.row {
10149 selection.end.column += indent_delta.len;
10150 delta_for_end_row = indent_delta.len;
10151 }
10152 }
10153
10154 if selection.start.row == selection.end.row {
10155 delta_for_start_row + delta_for_end_row
10156 } else {
10157 delta_for_end_row
10158 }
10159 }
10160
10161 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10162 if self.read_only(cx) {
10163 return;
10164 }
10165 if self.mode.is_single_line() {
10166 cx.propagate();
10167 return;
10168 }
10169
10170 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10171 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10172 let selections = self.selections.all::<Point>(cx);
10173 let mut deletion_ranges = Vec::new();
10174 let mut last_outdent = None;
10175 {
10176 let buffer = self.buffer.read(cx);
10177 let snapshot = buffer.snapshot(cx);
10178 for selection in &selections {
10179 let settings = buffer.language_settings_at(selection.start, cx);
10180 let tab_size = settings.tab_size.get();
10181 let mut rows = selection.spanned_rows(false, &display_map);
10182
10183 // Avoid re-outdenting a row that has already been outdented by a
10184 // previous selection.
10185 if let Some(last_row) = last_outdent
10186 && last_row == rows.start
10187 {
10188 rows.start = rows.start.next_row();
10189 }
10190 let has_multiple_rows = rows.len() > 1;
10191 for row in rows.iter_rows() {
10192 let indent_size = snapshot.indent_size_for_line(row);
10193 if indent_size.len > 0 {
10194 let deletion_len = match indent_size.kind {
10195 IndentKind::Space => {
10196 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10197 if columns_to_prev_tab_stop == 0 {
10198 tab_size
10199 } else {
10200 columns_to_prev_tab_stop
10201 }
10202 }
10203 IndentKind::Tab => 1,
10204 };
10205 let start = if has_multiple_rows
10206 || deletion_len > selection.start.column
10207 || indent_size.len < selection.start.column
10208 {
10209 0
10210 } else {
10211 selection.start.column - deletion_len
10212 };
10213 deletion_ranges.push(
10214 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10215 );
10216 last_outdent = Some(row);
10217 }
10218 }
10219 }
10220 }
10221
10222 self.transact(window, cx, |this, window, cx| {
10223 this.buffer.update(cx, |buffer, cx| {
10224 let empty_str: Arc<str> = Arc::default();
10225 buffer.edit(
10226 deletion_ranges
10227 .into_iter()
10228 .map(|range| (range, empty_str.clone())),
10229 None,
10230 cx,
10231 );
10232 });
10233 let selections = this.selections.all::<usize>(cx);
10234 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10235 });
10236 }
10237
10238 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10239 if self.read_only(cx) {
10240 return;
10241 }
10242 if self.mode.is_single_line() {
10243 cx.propagate();
10244 return;
10245 }
10246
10247 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10248 let selections = self
10249 .selections
10250 .all::<usize>(cx)
10251 .into_iter()
10252 .map(|s| s.range());
10253
10254 self.transact(window, cx, |this, window, cx| {
10255 this.buffer.update(cx, |buffer, cx| {
10256 buffer.autoindent_ranges(selections, cx);
10257 });
10258 let selections = this.selections.all::<usize>(cx);
10259 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10260 });
10261 }
10262
10263 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10264 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10265 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10266 let selections = self.selections.all::<Point>(cx);
10267
10268 let mut new_cursors = Vec::new();
10269 let mut edit_ranges = Vec::new();
10270 let mut selections = selections.iter().peekable();
10271 while let Some(selection) = selections.next() {
10272 let mut rows = selection.spanned_rows(false, &display_map);
10273 let goal_display_column = selection.head().to_display_point(&display_map).column();
10274
10275 // Accumulate contiguous regions of rows that we want to delete.
10276 while let Some(next_selection) = selections.peek() {
10277 let next_rows = next_selection.spanned_rows(false, &display_map);
10278 if next_rows.start <= rows.end {
10279 rows.end = next_rows.end;
10280 selections.next().unwrap();
10281 } else {
10282 break;
10283 }
10284 }
10285
10286 let buffer = &display_map.buffer_snapshot;
10287 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10288 let edit_end;
10289 let cursor_buffer_row;
10290 if buffer.max_point().row >= rows.end.0 {
10291 // If there's a line after the range, delete the \n from the end of the row range
10292 // and position the cursor on the next line.
10293 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10294 cursor_buffer_row = rows.end;
10295 } else {
10296 // If there isn't a line after the range, delete the \n from the line before the
10297 // start of the row range and position the cursor there.
10298 edit_start = edit_start.saturating_sub(1);
10299 edit_end = buffer.len();
10300 cursor_buffer_row = rows.start.previous_row();
10301 }
10302
10303 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10304 *cursor.column_mut() =
10305 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10306
10307 new_cursors.push((
10308 selection.id,
10309 buffer.anchor_after(cursor.to_point(&display_map)),
10310 ));
10311 edit_ranges.push(edit_start..edit_end);
10312 }
10313
10314 self.transact(window, cx, |this, window, cx| {
10315 let buffer = this.buffer.update(cx, |buffer, cx| {
10316 let empty_str: Arc<str> = Arc::default();
10317 buffer.edit(
10318 edit_ranges
10319 .into_iter()
10320 .map(|range| (range, empty_str.clone())),
10321 None,
10322 cx,
10323 );
10324 buffer.snapshot(cx)
10325 });
10326 let new_selections = new_cursors
10327 .into_iter()
10328 .map(|(id, cursor)| {
10329 let cursor = cursor.to_point(&buffer);
10330 Selection {
10331 id,
10332 start: cursor,
10333 end: cursor,
10334 reversed: false,
10335 goal: SelectionGoal::None,
10336 }
10337 })
10338 .collect();
10339
10340 this.change_selections(Default::default(), window, cx, |s| {
10341 s.select(new_selections);
10342 });
10343 });
10344 }
10345
10346 pub fn join_lines_impl(
10347 &mut self,
10348 insert_whitespace: bool,
10349 window: &mut Window,
10350 cx: &mut Context<Self>,
10351 ) {
10352 if self.read_only(cx) {
10353 return;
10354 }
10355 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10356 for selection in self.selections.all::<Point>(cx) {
10357 let start = MultiBufferRow(selection.start.row);
10358 // Treat single line selections as if they include the next line. Otherwise this action
10359 // would do nothing for single line selections individual cursors.
10360 let end = if selection.start.row == selection.end.row {
10361 MultiBufferRow(selection.start.row + 1)
10362 } else {
10363 MultiBufferRow(selection.end.row)
10364 };
10365
10366 if let Some(last_row_range) = row_ranges.last_mut()
10367 && start <= last_row_range.end
10368 {
10369 last_row_range.end = end;
10370 continue;
10371 }
10372 row_ranges.push(start..end);
10373 }
10374
10375 let snapshot = self.buffer.read(cx).snapshot(cx);
10376 let mut cursor_positions = Vec::new();
10377 for row_range in &row_ranges {
10378 let anchor = snapshot.anchor_before(Point::new(
10379 row_range.end.previous_row().0,
10380 snapshot.line_len(row_range.end.previous_row()),
10381 ));
10382 cursor_positions.push(anchor..anchor);
10383 }
10384
10385 self.transact(window, cx, |this, window, cx| {
10386 for row_range in row_ranges.into_iter().rev() {
10387 for row in row_range.iter_rows().rev() {
10388 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10389 let next_line_row = row.next_row();
10390 let indent = snapshot.indent_size_for_line(next_line_row);
10391 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10392
10393 let replace =
10394 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10395 " "
10396 } else {
10397 ""
10398 };
10399
10400 this.buffer.update(cx, |buffer, cx| {
10401 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10402 });
10403 }
10404 }
10405
10406 this.change_selections(Default::default(), window, cx, |s| {
10407 s.select_anchor_ranges(cursor_positions)
10408 });
10409 });
10410 }
10411
10412 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10413 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10414 self.join_lines_impl(true, window, cx);
10415 }
10416
10417 pub fn sort_lines_case_sensitive(
10418 &mut self,
10419 _: &SortLinesCaseSensitive,
10420 window: &mut Window,
10421 cx: &mut Context<Self>,
10422 ) {
10423 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10424 }
10425
10426 pub fn sort_lines_by_length(
10427 &mut self,
10428 _: &SortLinesByLength,
10429 window: &mut Window,
10430 cx: &mut Context<Self>,
10431 ) {
10432 self.manipulate_immutable_lines(window, cx, |lines| {
10433 lines.sort_by_key(|&line| line.chars().count())
10434 })
10435 }
10436
10437 pub fn sort_lines_case_insensitive(
10438 &mut self,
10439 _: &SortLinesCaseInsensitive,
10440 window: &mut Window,
10441 cx: &mut Context<Self>,
10442 ) {
10443 self.manipulate_immutable_lines(window, cx, |lines| {
10444 lines.sort_by_key(|line| line.to_lowercase())
10445 })
10446 }
10447
10448 pub fn unique_lines_case_insensitive(
10449 &mut self,
10450 _: &UniqueLinesCaseInsensitive,
10451 window: &mut Window,
10452 cx: &mut Context<Self>,
10453 ) {
10454 self.manipulate_immutable_lines(window, cx, |lines| {
10455 let mut seen = HashSet::default();
10456 lines.retain(|line| seen.insert(line.to_lowercase()));
10457 })
10458 }
10459
10460 pub fn unique_lines_case_sensitive(
10461 &mut self,
10462 _: &UniqueLinesCaseSensitive,
10463 window: &mut Window,
10464 cx: &mut Context<Self>,
10465 ) {
10466 self.manipulate_immutable_lines(window, cx, |lines| {
10467 let mut seen = HashSet::default();
10468 lines.retain(|line| seen.insert(*line));
10469 })
10470 }
10471
10472 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10473 let Some(project) = self.project.clone() else {
10474 return;
10475 };
10476 self.reload(project, window, cx)
10477 .detach_and_notify_err(window, cx);
10478 }
10479
10480 pub fn restore_file(
10481 &mut self,
10482 _: &::git::RestoreFile,
10483 window: &mut Window,
10484 cx: &mut Context<Self>,
10485 ) {
10486 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10487 let mut buffer_ids = HashSet::default();
10488 let snapshot = self.buffer().read(cx).snapshot(cx);
10489 for selection in self.selections.all::<usize>(cx) {
10490 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10491 }
10492
10493 let buffer = self.buffer().read(cx);
10494 let ranges = buffer_ids
10495 .into_iter()
10496 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10497 .collect::<Vec<_>>();
10498
10499 self.restore_hunks_in_ranges(ranges, window, cx);
10500 }
10501
10502 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10503 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10504 let selections = self
10505 .selections
10506 .all(cx)
10507 .into_iter()
10508 .map(|s| s.range())
10509 .collect();
10510 self.restore_hunks_in_ranges(selections, window, cx);
10511 }
10512
10513 pub fn restore_hunks_in_ranges(
10514 &mut self,
10515 ranges: Vec<Range<Point>>,
10516 window: &mut Window,
10517 cx: &mut Context<Editor>,
10518 ) {
10519 let mut revert_changes = HashMap::default();
10520 let chunk_by = self
10521 .snapshot(window, cx)
10522 .hunks_for_ranges(ranges)
10523 .into_iter()
10524 .chunk_by(|hunk| hunk.buffer_id);
10525 for (buffer_id, hunks) in &chunk_by {
10526 let hunks = hunks.collect::<Vec<_>>();
10527 for hunk in &hunks {
10528 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10529 }
10530 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10531 }
10532 drop(chunk_by);
10533 if !revert_changes.is_empty() {
10534 self.transact(window, cx, |editor, window, cx| {
10535 editor.restore(revert_changes, window, cx);
10536 });
10537 }
10538 }
10539
10540 pub fn open_active_item_in_terminal(
10541 &mut self,
10542 _: &OpenInTerminal,
10543 window: &mut Window,
10544 cx: &mut Context<Self>,
10545 ) {
10546 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10547 let project_path = buffer.read(cx).project_path(cx)?;
10548 let project = self.project()?.read(cx);
10549 let entry = project.entry_for_path(&project_path, cx)?;
10550 let parent = match &entry.canonical_path {
10551 Some(canonical_path) => canonical_path.to_path_buf(),
10552 None => project.absolute_path(&project_path, cx)?,
10553 }
10554 .parent()?
10555 .to_path_buf();
10556 Some(parent)
10557 }) {
10558 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10559 }
10560 }
10561
10562 fn set_breakpoint_context_menu(
10563 &mut self,
10564 display_row: DisplayRow,
10565 position: Option<Anchor>,
10566 clicked_point: gpui::Point<Pixels>,
10567 window: &mut Window,
10568 cx: &mut Context<Self>,
10569 ) {
10570 let source = self
10571 .buffer
10572 .read(cx)
10573 .snapshot(cx)
10574 .anchor_before(Point::new(display_row.0, 0u32));
10575
10576 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10577
10578 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10579 self,
10580 source,
10581 clicked_point,
10582 context_menu,
10583 window,
10584 cx,
10585 );
10586 }
10587
10588 fn add_edit_breakpoint_block(
10589 &mut self,
10590 anchor: Anchor,
10591 breakpoint: &Breakpoint,
10592 edit_action: BreakpointPromptEditAction,
10593 window: &mut Window,
10594 cx: &mut Context<Self>,
10595 ) {
10596 let weak_editor = cx.weak_entity();
10597 let bp_prompt = cx.new(|cx| {
10598 BreakpointPromptEditor::new(
10599 weak_editor,
10600 anchor,
10601 breakpoint.clone(),
10602 edit_action,
10603 window,
10604 cx,
10605 )
10606 });
10607
10608 let height = bp_prompt.update(cx, |this, cx| {
10609 this.prompt
10610 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10611 });
10612 let cloned_prompt = bp_prompt.clone();
10613 let blocks = vec![BlockProperties {
10614 style: BlockStyle::Sticky,
10615 placement: BlockPlacement::Above(anchor),
10616 height: Some(height),
10617 render: Arc::new(move |cx| {
10618 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10619 cloned_prompt.clone().into_any_element()
10620 }),
10621 priority: 0,
10622 }];
10623
10624 let focus_handle = bp_prompt.focus_handle(cx);
10625 window.focus(&focus_handle);
10626
10627 let block_ids = self.insert_blocks(blocks, None, cx);
10628 bp_prompt.update(cx, |prompt, _| {
10629 prompt.add_block_ids(block_ids);
10630 });
10631 }
10632
10633 pub(crate) fn breakpoint_at_row(
10634 &self,
10635 row: u32,
10636 window: &mut Window,
10637 cx: &mut Context<Self>,
10638 ) -> Option<(Anchor, Breakpoint)> {
10639 let snapshot = self.snapshot(window, cx);
10640 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10641
10642 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10643 }
10644
10645 pub(crate) fn breakpoint_at_anchor(
10646 &self,
10647 breakpoint_position: Anchor,
10648 snapshot: &EditorSnapshot,
10649 cx: &mut Context<Self>,
10650 ) -> Option<(Anchor, Breakpoint)> {
10651 let project = self.project.clone()?;
10652
10653 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10654 snapshot
10655 .buffer_snapshot
10656 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10657 })?;
10658
10659 let enclosing_excerpt = breakpoint_position.excerpt_id;
10660 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10661 let buffer_snapshot = buffer.read(cx).snapshot();
10662
10663 let row = buffer_snapshot
10664 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10665 .row;
10666
10667 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10668 let anchor_end = snapshot
10669 .buffer_snapshot
10670 .anchor_after(Point::new(row, line_len));
10671
10672 let bp = self
10673 .breakpoint_store
10674 .as_ref()?
10675 .read_with(cx, |breakpoint_store, cx| {
10676 breakpoint_store
10677 .breakpoints(
10678 &buffer,
10679 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10680 &buffer_snapshot,
10681 cx,
10682 )
10683 .next()
10684 .and_then(|(bp, _)| {
10685 let breakpoint_row = buffer_snapshot
10686 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10687 .row;
10688
10689 if breakpoint_row == row {
10690 snapshot
10691 .buffer_snapshot
10692 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10693 .map(|position| (position, bp.bp.clone()))
10694 } else {
10695 None
10696 }
10697 })
10698 });
10699 bp
10700 }
10701
10702 pub fn edit_log_breakpoint(
10703 &mut self,
10704 _: &EditLogBreakpoint,
10705 window: &mut Window,
10706 cx: &mut Context<Self>,
10707 ) {
10708 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10709 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10710 message: None,
10711 state: BreakpointState::Enabled,
10712 condition: None,
10713 hit_condition: None,
10714 });
10715
10716 self.add_edit_breakpoint_block(
10717 anchor,
10718 &breakpoint,
10719 BreakpointPromptEditAction::Log,
10720 window,
10721 cx,
10722 );
10723 }
10724 }
10725
10726 fn breakpoints_at_cursors(
10727 &self,
10728 window: &mut Window,
10729 cx: &mut Context<Self>,
10730 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10731 let snapshot = self.snapshot(window, cx);
10732 let cursors = self
10733 .selections
10734 .disjoint_anchors()
10735 .into_iter()
10736 .map(|selection| {
10737 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10738
10739 let breakpoint_position = self
10740 .breakpoint_at_row(cursor_position.row, window, cx)
10741 .map(|bp| bp.0)
10742 .unwrap_or_else(|| {
10743 snapshot
10744 .display_snapshot
10745 .buffer_snapshot
10746 .anchor_after(Point::new(cursor_position.row, 0))
10747 });
10748
10749 let breakpoint = self
10750 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10751 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10752
10753 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10754 })
10755 // 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.
10756 .collect::<HashMap<Anchor, _>>();
10757
10758 cursors.into_iter().collect()
10759 }
10760
10761 pub fn enable_breakpoint(
10762 &mut self,
10763 _: &crate::actions::EnableBreakpoint,
10764 window: &mut Window,
10765 cx: &mut Context<Self>,
10766 ) {
10767 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10768 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10769 continue;
10770 };
10771 self.edit_breakpoint_at_anchor(
10772 anchor,
10773 breakpoint,
10774 BreakpointEditAction::InvertState,
10775 cx,
10776 );
10777 }
10778 }
10779
10780 pub fn disable_breakpoint(
10781 &mut self,
10782 _: &crate::actions::DisableBreakpoint,
10783 window: &mut Window,
10784 cx: &mut Context<Self>,
10785 ) {
10786 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10787 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10788 continue;
10789 };
10790 self.edit_breakpoint_at_anchor(
10791 anchor,
10792 breakpoint,
10793 BreakpointEditAction::InvertState,
10794 cx,
10795 );
10796 }
10797 }
10798
10799 pub fn toggle_breakpoint(
10800 &mut self,
10801 _: &crate::actions::ToggleBreakpoint,
10802 window: &mut Window,
10803 cx: &mut Context<Self>,
10804 ) {
10805 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10806 if let Some(breakpoint) = breakpoint {
10807 self.edit_breakpoint_at_anchor(
10808 anchor,
10809 breakpoint,
10810 BreakpointEditAction::Toggle,
10811 cx,
10812 );
10813 } else {
10814 self.edit_breakpoint_at_anchor(
10815 anchor,
10816 Breakpoint::new_standard(),
10817 BreakpointEditAction::Toggle,
10818 cx,
10819 );
10820 }
10821 }
10822 }
10823
10824 pub fn edit_breakpoint_at_anchor(
10825 &mut self,
10826 breakpoint_position: Anchor,
10827 breakpoint: Breakpoint,
10828 edit_action: BreakpointEditAction,
10829 cx: &mut Context<Self>,
10830 ) {
10831 let Some(breakpoint_store) = &self.breakpoint_store else {
10832 return;
10833 };
10834
10835 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10836 if breakpoint_position == Anchor::min() {
10837 self.buffer()
10838 .read(cx)
10839 .excerpt_buffer_ids()
10840 .into_iter()
10841 .next()
10842 } else {
10843 None
10844 }
10845 }) else {
10846 return;
10847 };
10848
10849 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10850 return;
10851 };
10852
10853 breakpoint_store.update(cx, |breakpoint_store, cx| {
10854 breakpoint_store.toggle_breakpoint(
10855 buffer,
10856 BreakpointWithPosition {
10857 position: breakpoint_position.text_anchor,
10858 bp: breakpoint,
10859 },
10860 edit_action,
10861 cx,
10862 );
10863 });
10864
10865 cx.notify();
10866 }
10867
10868 #[cfg(any(test, feature = "test-support"))]
10869 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10870 self.breakpoint_store.clone()
10871 }
10872
10873 pub fn prepare_restore_change(
10874 &self,
10875 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10876 hunk: &MultiBufferDiffHunk,
10877 cx: &mut App,
10878 ) -> Option<()> {
10879 if hunk.is_created_file() {
10880 return None;
10881 }
10882 let buffer = self.buffer.read(cx);
10883 let diff = buffer.diff_for(hunk.buffer_id)?;
10884 let buffer = buffer.buffer(hunk.buffer_id)?;
10885 let buffer = buffer.read(cx);
10886 let original_text = diff
10887 .read(cx)
10888 .base_text()
10889 .as_rope()
10890 .slice(hunk.diff_base_byte_range.clone());
10891 let buffer_snapshot = buffer.snapshot();
10892 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10893 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10894 probe
10895 .0
10896 .start
10897 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10898 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10899 }) {
10900 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10901 Some(())
10902 } else {
10903 None
10904 }
10905 }
10906
10907 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10908 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10909 }
10910
10911 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10912 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10913 }
10914
10915 fn manipulate_lines<M>(
10916 &mut self,
10917 window: &mut Window,
10918 cx: &mut Context<Self>,
10919 mut manipulate: M,
10920 ) where
10921 M: FnMut(&str) -> LineManipulationResult,
10922 {
10923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10924
10925 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10926 let buffer = self.buffer.read(cx).snapshot(cx);
10927
10928 let mut edits = Vec::new();
10929
10930 let selections = self.selections.all::<Point>(cx);
10931 let mut selections = selections.iter().peekable();
10932 let mut contiguous_row_selections = Vec::new();
10933 let mut new_selections = Vec::new();
10934 let mut added_lines = 0;
10935 let mut removed_lines = 0;
10936
10937 while let Some(selection) = selections.next() {
10938 let (start_row, end_row) = consume_contiguous_rows(
10939 &mut contiguous_row_selections,
10940 selection,
10941 &display_map,
10942 &mut selections,
10943 );
10944
10945 let start_point = Point::new(start_row.0, 0);
10946 let end_point = Point::new(
10947 end_row.previous_row().0,
10948 buffer.line_len(end_row.previous_row()),
10949 );
10950 let text = buffer
10951 .text_for_range(start_point..end_point)
10952 .collect::<String>();
10953
10954 let LineManipulationResult {
10955 new_text,
10956 line_count_before,
10957 line_count_after,
10958 } = manipulate(&text);
10959
10960 edits.push((start_point..end_point, new_text));
10961
10962 // Selections must change based on added and removed line count
10963 let start_row =
10964 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10965 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10966 new_selections.push(Selection {
10967 id: selection.id,
10968 start: start_row,
10969 end: end_row,
10970 goal: SelectionGoal::None,
10971 reversed: selection.reversed,
10972 });
10973
10974 if line_count_after > line_count_before {
10975 added_lines += line_count_after - line_count_before;
10976 } else if line_count_before > line_count_after {
10977 removed_lines += line_count_before - line_count_after;
10978 }
10979 }
10980
10981 self.transact(window, cx, |this, window, cx| {
10982 let buffer = this.buffer.update(cx, |buffer, cx| {
10983 buffer.edit(edits, None, cx);
10984 buffer.snapshot(cx)
10985 });
10986
10987 // Recalculate offsets on newly edited buffer
10988 let new_selections = new_selections
10989 .iter()
10990 .map(|s| {
10991 let start_point = Point::new(s.start.0, 0);
10992 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10993 Selection {
10994 id: s.id,
10995 start: buffer.point_to_offset(start_point),
10996 end: buffer.point_to_offset(end_point),
10997 goal: s.goal,
10998 reversed: s.reversed,
10999 }
11000 })
11001 .collect();
11002
11003 this.change_selections(Default::default(), window, cx, |s| {
11004 s.select(new_selections);
11005 });
11006
11007 this.request_autoscroll(Autoscroll::fit(), cx);
11008 });
11009 }
11010
11011 fn manipulate_immutable_lines<Fn>(
11012 &mut self,
11013 window: &mut Window,
11014 cx: &mut Context<Self>,
11015 mut callback: Fn,
11016 ) where
11017 Fn: FnMut(&mut Vec<&str>),
11018 {
11019 self.manipulate_lines(window, cx, |text| {
11020 let mut lines: Vec<&str> = text.split('\n').collect();
11021 let line_count_before = lines.len();
11022
11023 callback(&mut lines);
11024
11025 LineManipulationResult {
11026 new_text: lines.join("\n"),
11027 line_count_before,
11028 line_count_after: lines.len(),
11029 }
11030 });
11031 }
11032
11033 fn manipulate_mutable_lines<Fn>(
11034 &mut self,
11035 window: &mut Window,
11036 cx: &mut Context<Self>,
11037 mut callback: Fn,
11038 ) where
11039 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11040 {
11041 self.manipulate_lines(window, cx, |text| {
11042 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11043 let line_count_before = lines.len();
11044
11045 callback(&mut lines);
11046
11047 LineManipulationResult {
11048 new_text: lines.join("\n"),
11049 line_count_before,
11050 line_count_after: lines.len(),
11051 }
11052 });
11053 }
11054
11055 pub fn convert_indentation_to_spaces(
11056 &mut self,
11057 _: &ConvertIndentationToSpaces,
11058 window: &mut Window,
11059 cx: &mut Context<Self>,
11060 ) {
11061 let settings = self.buffer.read(cx).language_settings(cx);
11062 let tab_size = settings.tab_size.get() as usize;
11063
11064 self.manipulate_mutable_lines(window, cx, |lines| {
11065 // Allocates a reasonably sized scratch buffer once for the whole loop
11066 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11067 // Avoids recomputing spaces that could be inserted many times
11068 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11069 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11070 .collect();
11071
11072 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11073 let mut chars = line.as_ref().chars();
11074 let mut col = 0;
11075 let mut changed = false;
11076
11077 while let Some(ch) = chars.next() {
11078 match ch {
11079 ' ' => {
11080 reindented_line.push(' ');
11081 col += 1;
11082 }
11083 '\t' => {
11084 // \t are converted to spaces depending on the current column
11085 let spaces_len = tab_size - (col % tab_size);
11086 reindented_line.extend(&space_cache[spaces_len - 1]);
11087 col += spaces_len;
11088 changed = true;
11089 }
11090 _ => {
11091 // If we dont append before break, the character is consumed
11092 reindented_line.push(ch);
11093 break;
11094 }
11095 }
11096 }
11097
11098 if !changed {
11099 reindented_line.clear();
11100 continue;
11101 }
11102 // Append the rest of the line and replace old reference with new one
11103 reindented_line.extend(chars);
11104 *line = Cow::Owned(reindented_line.clone());
11105 reindented_line.clear();
11106 }
11107 });
11108 }
11109
11110 pub fn convert_indentation_to_tabs(
11111 &mut self,
11112 _: &ConvertIndentationToTabs,
11113 window: &mut Window,
11114 cx: &mut Context<Self>,
11115 ) {
11116 let settings = self.buffer.read(cx).language_settings(cx);
11117 let tab_size = settings.tab_size.get() as usize;
11118
11119 self.manipulate_mutable_lines(window, cx, |lines| {
11120 // Allocates a reasonably sized buffer once for the whole loop
11121 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11122 // Avoids recomputing spaces that could be inserted many times
11123 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11124 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11125 .collect();
11126
11127 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11128 let mut chars = line.chars();
11129 let mut spaces_count = 0;
11130 let mut first_non_indent_char = None;
11131 let mut changed = false;
11132
11133 while let Some(ch) = chars.next() {
11134 match ch {
11135 ' ' => {
11136 // Keep track of spaces. Append \t when we reach tab_size
11137 spaces_count += 1;
11138 changed = true;
11139 if spaces_count == tab_size {
11140 reindented_line.push('\t');
11141 spaces_count = 0;
11142 }
11143 }
11144 '\t' => {
11145 reindented_line.push('\t');
11146 spaces_count = 0;
11147 }
11148 _ => {
11149 // Dont append it yet, we might have remaining spaces
11150 first_non_indent_char = Some(ch);
11151 break;
11152 }
11153 }
11154 }
11155
11156 if !changed {
11157 reindented_line.clear();
11158 continue;
11159 }
11160 // Remaining spaces that didn't make a full tab stop
11161 if spaces_count > 0 {
11162 reindented_line.extend(&space_cache[spaces_count - 1]);
11163 }
11164 // If we consume an extra character that was not indentation, add it back
11165 if let Some(extra_char) = first_non_indent_char {
11166 reindented_line.push(extra_char);
11167 }
11168 // Append the rest of the line and replace old reference with new one
11169 reindented_line.extend(chars);
11170 *line = Cow::Owned(reindented_line.clone());
11171 reindented_line.clear();
11172 }
11173 });
11174 }
11175
11176 pub fn convert_to_upper_case(
11177 &mut self,
11178 _: &ConvertToUpperCase,
11179 window: &mut Window,
11180 cx: &mut Context<Self>,
11181 ) {
11182 self.manipulate_text(window, cx, |text| text.to_uppercase())
11183 }
11184
11185 pub fn convert_to_lower_case(
11186 &mut self,
11187 _: &ConvertToLowerCase,
11188 window: &mut Window,
11189 cx: &mut Context<Self>,
11190 ) {
11191 self.manipulate_text(window, cx, |text| text.to_lowercase())
11192 }
11193
11194 pub fn convert_to_title_case(
11195 &mut self,
11196 _: &ConvertToTitleCase,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 self.manipulate_text(window, cx, |text| {
11201 text.split('\n')
11202 .map(|line| line.to_case(Case::Title))
11203 .join("\n")
11204 })
11205 }
11206
11207 pub fn convert_to_snake_case(
11208 &mut self,
11209 _: &ConvertToSnakeCase,
11210 window: &mut Window,
11211 cx: &mut Context<Self>,
11212 ) {
11213 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11214 }
11215
11216 pub fn convert_to_kebab_case(
11217 &mut self,
11218 _: &ConvertToKebabCase,
11219 window: &mut Window,
11220 cx: &mut Context<Self>,
11221 ) {
11222 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11223 }
11224
11225 pub fn convert_to_upper_camel_case(
11226 &mut self,
11227 _: &ConvertToUpperCamelCase,
11228 window: &mut Window,
11229 cx: &mut Context<Self>,
11230 ) {
11231 self.manipulate_text(window, cx, |text| {
11232 text.split('\n')
11233 .map(|line| line.to_case(Case::UpperCamel))
11234 .join("\n")
11235 })
11236 }
11237
11238 pub fn convert_to_lower_camel_case(
11239 &mut self,
11240 _: &ConvertToLowerCamelCase,
11241 window: &mut Window,
11242 cx: &mut Context<Self>,
11243 ) {
11244 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11245 }
11246
11247 pub fn convert_to_opposite_case(
11248 &mut self,
11249 _: &ConvertToOppositeCase,
11250 window: &mut Window,
11251 cx: &mut Context<Self>,
11252 ) {
11253 self.manipulate_text(window, cx, |text| {
11254 text.chars()
11255 .fold(String::with_capacity(text.len()), |mut t, c| {
11256 if c.is_uppercase() {
11257 t.extend(c.to_lowercase());
11258 } else {
11259 t.extend(c.to_uppercase());
11260 }
11261 t
11262 })
11263 })
11264 }
11265
11266 pub fn convert_to_sentence_case(
11267 &mut self,
11268 _: &ConvertToSentenceCase,
11269 window: &mut Window,
11270 cx: &mut Context<Self>,
11271 ) {
11272 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11273 }
11274
11275 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11276 self.manipulate_text(window, cx, |text| {
11277 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11278 if has_upper_case_characters {
11279 text.to_lowercase()
11280 } else {
11281 text.to_uppercase()
11282 }
11283 })
11284 }
11285
11286 pub fn convert_to_rot13(
11287 &mut self,
11288 _: &ConvertToRot13,
11289 window: &mut Window,
11290 cx: &mut Context<Self>,
11291 ) {
11292 self.manipulate_text(window, cx, |text| {
11293 text.chars()
11294 .map(|c| match c {
11295 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11296 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11297 _ => c,
11298 })
11299 .collect()
11300 })
11301 }
11302
11303 pub fn convert_to_rot47(
11304 &mut self,
11305 _: &ConvertToRot47,
11306 window: &mut Window,
11307 cx: &mut Context<Self>,
11308 ) {
11309 self.manipulate_text(window, cx, |text| {
11310 text.chars()
11311 .map(|c| {
11312 let code_point = c as u32;
11313 if code_point >= 33 && code_point <= 126 {
11314 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11315 }
11316 c
11317 })
11318 .collect()
11319 })
11320 }
11321
11322 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11323 where
11324 Fn: FnMut(&str) -> String,
11325 {
11326 let buffer = self.buffer.read(cx).snapshot(cx);
11327
11328 let mut new_selections = Vec::new();
11329 let mut edits = Vec::new();
11330 let mut selection_adjustment = 0i32;
11331
11332 for selection in self.selections.all::<usize>(cx) {
11333 let selection_is_empty = selection.is_empty();
11334
11335 let (start, end) = if selection_is_empty {
11336 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11337 (word_range.start, word_range.end)
11338 } else {
11339 (selection.start, selection.end)
11340 };
11341
11342 let text = buffer.text_for_range(start..end).collect::<String>();
11343 let old_length = text.len() as i32;
11344 let text = callback(&text);
11345
11346 new_selections.push(Selection {
11347 start: (start as i32 - selection_adjustment) as usize,
11348 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11349 goal: SelectionGoal::None,
11350 ..selection
11351 });
11352
11353 selection_adjustment += old_length - text.len() as i32;
11354
11355 edits.push((start..end, text));
11356 }
11357
11358 self.transact(window, cx, |this, window, cx| {
11359 this.buffer.update(cx, |buffer, cx| {
11360 buffer.edit(edits, None, cx);
11361 });
11362
11363 this.change_selections(Default::default(), window, cx, |s| {
11364 s.select(new_selections);
11365 });
11366
11367 this.request_autoscroll(Autoscroll::fit(), cx);
11368 });
11369 }
11370
11371 pub fn move_selection_on_drop(
11372 &mut self,
11373 selection: &Selection<Anchor>,
11374 target: DisplayPoint,
11375 is_cut: bool,
11376 window: &mut Window,
11377 cx: &mut Context<Self>,
11378 ) {
11379 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11380 let buffer = &display_map.buffer_snapshot;
11381 let mut edits = Vec::new();
11382 let insert_point = display_map
11383 .clip_point(target, Bias::Left)
11384 .to_point(&display_map);
11385 let text = buffer
11386 .text_for_range(selection.start..selection.end)
11387 .collect::<String>();
11388 if is_cut {
11389 edits.push(((selection.start..selection.end), String::new()));
11390 }
11391 let insert_anchor = buffer.anchor_before(insert_point);
11392 edits.push(((insert_anchor..insert_anchor), text));
11393 let last_edit_start = insert_anchor.bias_left(buffer);
11394 let last_edit_end = insert_anchor.bias_right(buffer);
11395 self.transact(window, cx, |this, window, cx| {
11396 this.buffer.update(cx, |buffer, cx| {
11397 buffer.edit(edits, None, cx);
11398 });
11399 this.change_selections(Default::default(), window, cx, |s| {
11400 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11401 });
11402 });
11403 }
11404
11405 pub fn clear_selection_drag_state(&mut self) {
11406 self.selection_drag_state = SelectionDragState::None;
11407 }
11408
11409 pub fn duplicate(
11410 &mut self,
11411 upwards: bool,
11412 whole_lines: bool,
11413 window: &mut Window,
11414 cx: &mut Context<Self>,
11415 ) {
11416 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11417
11418 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11419 let buffer = &display_map.buffer_snapshot;
11420 let selections = self.selections.all::<Point>(cx);
11421
11422 let mut edits = Vec::new();
11423 let mut selections_iter = selections.iter().peekable();
11424 while let Some(selection) = selections_iter.next() {
11425 let mut rows = selection.spanned_rows(false, &display_map);
11426 // duplicate line-wise
11427 if whole_lines || selection.start == selection.end {
11428 // Avoid duplicating the same lines twice.
11429 while let Some(next_selection) = selections_iter.peek() {
11430 let next_rows = next_selection.spanned_rows(false, &display_map);
11431 if next_rows.start < rows.end {
11432 rows.end = next_rows.end;
11433 selections_iter.next().unwrap();
11434 } else {
11435 break;
11436 }
11437 }
11438
11439 // Copy the text from the selected row region and splice it either at the start
11440 // or end of the region.
11441 let start = Point::new(rows.start.0, 0);
11442 let end = Point::new(
11443 rows.end.previous_row().0,
11444 buffer.line_len(rows.end.previous_row()),
11445 );
11446 let text = buffer
11447 .text_for_range(start..end)
11448 .chain(Some("\n"))
11449 .collect::<String>();
11450 let insert_location = if upwards {
11451 Point::new(rows.end.0, 0)
11452 } else {
11453 start
11454 };
11455 edits.push((insert_location..insert_location, text));
11456 } else {
11457 // duplicate character-wise
11458 let start = selection.start;
11459 let end = selection.end;
11460 let text = buffer.text_for_range(start..end).collect::<String>();
11461 edits.push((selection.end..selection.end, text));
11462 }
11463 }
11464
11465 self.transact(window, cx, |this, _, cx| {
11466 this.buffer.update(cx, |buffer, cx| {
11467 buffer.edit(edits, None, cx);
11468 });
11469
11470 this.request_autoscroll(Autoscroll::fit(), cx);
11471 });
11472 }
11473
11474 pub fn duplicate_line_up(
11475 &mut self,
11476 _: &DuplicateLineUp,
11477 window: &mut Window,
11478 cx: &mut Context<Self>,
11479 ) {
11480 self.duplicate(true, true, window, cx);
11481 }
11482
11483 pub fn duplicate_line_down(
11484 &mut self,
11485 _: &DuplicateLineDown,
11486 window: &mut Window,
11487 cx: &mut Context<Self>,
11488 ) {
11489 self.duplicate(false, true, window, cx);
11490 }
11491
11492 pub fn duplicate_selection(
11493 &mut self,
11494 _: &DuplicateSelection,
11495 window: &mut Window,
11496 cx: &mut Context<Self>,
11497 ) {
11498 self.duplicate(false, false, window, cx);
11499 }
11500
11501 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11502 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11503 if self.mode.is_single_line() {
11504 cx.propagate();
11505 return;
11506 }
11507
11508 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11509 let buffer = self.buffer.read(cx).snapshot(cx);
11510
11511 let mut edits = Vec::new();
11512 let mut unfold_ranges = Vec::new();
11513 let mut refold_creases = Vec::new();
11514
11515 let selections = self.selections.all::<Point>(cx);
11516 let mut selections = selections.iter().peekable();
11517 let mut contiguous_row_selections = Vec::new();
11518 let mut new_selections = Vec::new();
11519
11520 while let Some(selection) = selections.next() {
11521 // Find all the selections that span a contiguous row range
11522 let (start_row, end_row) = consume_contiguous_rows(
11523 &mut contiguous_row_selections,
11524 selection,
11525 &display_map,
11526 &mut selections,
11527 );
11528
11529 // Move the text spanned by the row range to be before the line preceding the row range
11530 if start_row.0 > 0 {
11531 let range_to_move = Point::new(
11532 start_row.previous_row().0,
11533 buffer.line_len(start_row.previous_row()),
11534 )
11535 ..Point::new(
11536 end_row.previous_row().0,
11537 buffer.line_len(end_row.previous_row()),
11538 );
11539 let insertion_point = display_map
11540 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11541 .0;
11542
11543 // Don't move lines across excerpts
11544 if buffer
11545 .excerpt_containing(insertion_point..range_to_move.end)
11546 .is_some()
11547 {
11548 let text = buffer
11549 .text_for_range(range_to_move.clone())
11550 .flat_map(|s| s.chars())
11551 .skip(1)
11552 .chain(['\n'])
11553 .collect::<String>();
11554
11555 edits.push((
11556 buffer.anchor_after(range_to_move.start)
11557 ..buffer.anchor_before(range_to_move.end),
11558 String::new(),
11559 ));
11560 let insertion_anchor = buffer.anchor_after(insertion_point);
11561 edits.push((insertion_anchor..insertion_anchor, text));
11562
11563 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11564
11565 // Move selections up
11566 new_selections.extend(contiguous_row_selections.drain(..).map(
11567 |mut selection| {
11568 selection.start.row -= row_delta;
11569 selection.end.row -= row_delta;
11570 selection
11571 },
11572 ));
11573
11574 // Move folds up
11575 unfold_ranges.push(range_to_move.clone());
11576 for fold in display_map.folds_in_range(
11577 buffer.anchor_before(range_to_move.start)
11578 ..buffer.anchor_after(range_to_move.end),
11579 ) {
11580 let mut start = fold.range.start.to_point(&buffer);
11581 let mut end = fold.range.end.to_point(&buffer);
11582 start.row -= row_delta;
11583 end.row -= row_delta;
11584 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11585 }
11586 }
11587 }
11588
11589 // If we didn't move line(s), preserve the existing selections
11590 new_selections.append(&mut contiguous_row_selections);
11591 }
11592
11593 self.transact(window, cx, |this, window, cx| {
11594 this.unfold_ranges(&unfold_ranges, true, true, cx);
11595 this.buffer.update(cx, |buffer, cx| {
11596 for (range, text) in edits {
11597 buffer.edit([(range, text)], None, cx);
11598 }
11599 });
11600 this.fold_creases(refold_creases, true, window, cx);
11601 this.change_selections(Default::default(), window, cx, |s| {
11602 s.select(new_selections);
11603 })
11604 });
11605 }
11606
11607 pub fn move_line_down(
11608 &mut self,
11609 _: &MoveLineDown,
11610 window: &mut Window,
11611 cx: &mut Context<Self>,
11612 ) {
11613 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11614 if self.mode.is_single_line() {
11615 cx.propagate();
11616 return;
11617 }
11618
11619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11620 let buffer = self.buffer.read(cx).snapshot(cx);
11621
11622 let mut edits = Vec::new();
11623 let mut unfold_ranges = Vec::new();
11624 let mut refold_creases = Vec::new();
11625
11626 let selections = self.selections.all::<Point>(cx);
11627 let mut selections = selections.iter().peekable();
11628 let mut contiguous_row_selections = Vec::new();
11629 let mut new_selections = Vec::new();
11630
11631 while let Some(selection) = selections.next() {
11632 // Find all the selections that span a contiguous row range
11633 let (start_row, end_row) = consume_contiguous_rows(
11634 &mut contiguous_row_selections,
11635 selection,
11636 &display_map,
11637 &mut selections,
11638 );
11639
11640 // Move the text spanned by the row range to be after the last line of the row range
11641 if end_row.0 <= buffer.max_point().row {
11642 let range_to_move =
11643 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11644 let insertion_point = display_map
11645 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11646 .0;
11647
11648 // Don't move lines across excerpt boundaries
11649 if buffer
11650 .excerpt_containing(range_to_move.start..insertion_point)
11651 .is_some()
11652 {
11653 let mut text = String::from("\n");
11654 text.extend(buffer.text_for_range(range_to_move.clone()));
11655 text.pop(); // Drop trailing newline
11656 edits.push((
11657 buffer.anchor_after(range_to_move.start)
11658 ..buffer.anchor_before(range_to_move.end),
11659 String::new(),
11660 ));
11661 let insertion_anchor = buffer.anchor_after(insertion_point);
11662 edits.push((insertion_anchor..insertion_anchor, text));
11663
11664 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11665
11666 // Move selections down
11667 new_selections.extend(contiguous_row_selections.drain(..).map(
11668 |mut selection| {
11669 selection.start.row += row_delta;
11670 selection.end.row += row_delta;
11671 selection
11672 },
11673 ));
11674
11675 // Move folds down
11676 unfold_ranges.push(range_to_move.clone());
11677 for fold in display_map.folds_in_range(
11678 buffer.anchor_before(range_to_move.start)
11679 ..buffer.anchor_after(range_to_move.end),
11680 ) {
11681 let mut start = fold.range.start.to_point(&buffer);
11682 let mut end = fold.range.end.to_point(&buffer);
11683 start.row += row_delta;
11684 end.row += row_delta;
11685 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11686 }
11687 }
11688 }
11689
11690 // If we didn't move line(s), preserve the existing selections
11691 new_selections.append(&mut contiguous_row_selections);
11692 }
11693
11694 self.transact(window, cx, |this, window, cx| {
11695 this.unfold_ranges(&unfold_ranges, true, true, cx);
11696 this.buffer.update(cx, |buffer, cx| {
11697 for (range, text) in edits {
11698 buffer.edit([(range, text)], None, cx);
11699 }
11700 });
11701 this.fold_creases(refold_creases, true, window, cx);
11702 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11703 });
11704 }
11705
11706 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11707 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11708 let text_layout_details = &self.text_layout_details(window);
11709 self.transact(window, cx, |this, window, cx| {
11710 let edits = this.change_selections(Default::default(), window, cx, |s| {
11711 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11712 s.move_with(|display_map, selection| {
11713 if !selection.is_empty() {
11714 return;
11715 }
11716
11717 let mut head = selection.head();
11718 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11719 if head.column() == display_map.line_len(head.row()) {
11720 transpose_offset = display_map
11721 .buffer_snapshot
11722 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11723 }
11724
11725 if transpose_offset == 0 {
11726 return;
11727 }
11728
11729 *head.column_mut() += 1;
11730 head = display_map.clip_point(head, Bias::Right);
11731 let goal = SelectionGoal::HorizontalPosition(
11732 display_map
11733 .x_for_display_point(head, text_layout_details)
11734 .into(),
11735 );
11736 selection.collapse_to(head, goal);
11737
11738 let transpose_start = display_map
11739 .buffer_snapshot
11740 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11741 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11742 let transpose_end = display_map
11743 .buffer_snapshot
11744 .clip_offset(transpose_offset + 1, Bias::Right);
11745 if let Some(ch) =
11746 display_map.buffer_snapshot.chars_at(transpose_start).next()
11747 {
11748 edits.push((transpose_start..transpose_offset, String::new()));
11749 edits.push((transpose_end..transpose_end, ch.to_string()));
11750 }
11751 }
11752 });
11753 edits
11754 });
11755 this.buffer
11756 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11757 let selections = this.selections.all::<usize>(cx);
11758 this.change_selections(Default::default(), window, cx, |s| {
11759 s.select(selections);
11760 });
11761 });
11762 }
11763
11764 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11765 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11766 if self.mode.is_single_line() {
11767 cx.propagate();
11768 return;
11769 }
11770
11771 self.rewrap_impl(RewrapOptions::default(), cx)
11772 }
11773
11774 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11775 let buffer = self.buffer.read(cx).snapshot(cx);
11776 let selections = self.selections.all::<Point>(cx);
11777
11778 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11779 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11780 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11781 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11782 .peekable();
11783
11784 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11785 row
11786 } else {
11787 return Vec::new();
11788 };
11789
11790 let language_settings = buffer.language_settings_at(selection.head(), cx);
11791 let language_scope = buffer.language_scope_at(selection.head());
11792
11793 let indent_and_prefix_for_row =
11794 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11795 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11796 let (comment_prefix, rewrap_prefix) =
11797 if let Some(language_scope) = &language_scope {
11798 let indent_end = Point::new(row, indent.len);
11799 let comment_prefix = language_scope
11800 .line_comment_prefixes()
11801 .iter()
11802 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11803 .map(|prefix| prefix.to_string());
11804 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11805 let line_text_after_indent = buffer
11806 .text_for_range(indent_end..line_end)
11807 .collect::<String>();
11808 let rewrap_prefix = language_scope
11809 .rewrap_prefixes()
11810 .iter()
11811 .find_map(|prefix_regex| {
11812 prefix_regex.find(&line_text_after_indent).map(|mat| {
11813 if mat.start() == 0 {
11814 Some(mat.as_str().to_string())
11815 } else {
11816 None
11817 }
11818 })
11819 })
11820 .flatten();
11821 (comment_prefix, rewrap_prefix)
11822 } else {
11823 (None, None)
11824 };
11825 (indent, comment_prefix, rewrap_prefix)
11826 };
11827
11828 let mut ranges = Vec::new();
11829 let from_empty_selection = selection.is_empty();
11830
11831 let mut current_range_start = first_row;
11832 let mut prev_row = first_row;
11833 let (
11834 mut current_range_indent,
11835 mut current_range_comment_prefix,
11836 mut current_range_rewrap_prefix,
11837 ) = indent_and_prefix_for_row(first_row);
11838
11839 for row in non_blank_rows_iter.skip(1) {
11840 let has_paragraph_break = row > prev_row + 1;
11841
11842 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11843 indent_and_prefix_for_row(row);
11844
11845 let has_indent_change = row_indent != current_range_indent;
11846 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11847
11848 let has_boundary_change = has_comment_change
11849 || row_rewrap_prefix.is_some()
11850 || (has_indent_change && current_range_comment_prefix.is_some());
11851
11852 if has_paragraph_break || has_boundary_change {
11853 ranges.push((
11854 language_settings.clone(),
11855 Point::new(current_range_start, 0)
11856 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11857 current_range_indent,
11858 current_range_comment_prefix.clone(),
11859 current_range_rewrap_prefix.clone(),
11860 from_empty_selection,
11861 ));
11862 current_range_start = row;
11863 current_range_indent = row_indent;
11864 current_range_comment_prefix = row_comment_prefix;
11865 current_range_rewrap_prefix = row_rewrap_prefix;
11866 }
11867 prev_row = row;
11868 }
11869
11870 ranges.push((
11871 language_settings.clone(),
11872 Point::new(current_range_start, 0)
11873 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11874 current_range_indent,
11875 current_range_comment_prefix,
11876 current_range_rewrap_prefix,
11877 from_empty_selection,
11878 ));
11879
11880 ranges
11881 });
11882
11883 let mut edits = Vec::new();
11884 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11885
11886 for (
11887 language_settings,
11888 wrap_range,
11889 indent_size,
11890 comment_prefix,
11891 rewrap_prefix,
11892 from_empty_selection,
11893 ) in wrap_ranges
11894 {
11895 let mut start_row = wrap_range.start.row;
11896 let mut end_row = wrap_range.end.row;
11897
11898 // Skip selections that overlap with a range that has already been rewrapped.
11899 let selection_range = start_row..end_row;
11900 if rewrapped_row_ranges
11901 .iter()
11902 .any(|range| range.overlaps(&selection_range))
11903 {
11904 continue;
11905 }
11906
11907 let tab_size = language_settings.tab_size;
11908
11909 let indent_prefix = indent_size.chars().collect::<String>();
11910 let mut line_prefix = indent_prefix.clone();
11911 let mut inside_comment = false;
11912 if let Some(prefix) = &comment_prefix {
11913 line_prefix.push_str(prefix);
11914 inside_comment = true;
11915 }
11916 if let Some(prefix) = &rewrap_prefix {
11917 line_prefix.push_str(prefix);
11918 }
11919
11920 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11921 RewrapBehavior::InComments => inside_comment,
11922 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11923 RewrapBehavior::Anywhere => true,
11924 };
11925
11926 let should_rewrap = options.override_language_settings
11927 || allow_rewrap_based_on_language
11928 || self.hard_wrap.is_some();
11929 if !should_rewrap {
11930 continue;
11931 }
11932
11933 if from_empty_selection {
11934 'expand_upwards: while start_row > 0 {
11935 let prev_row = start_row - 1;
11936 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11937 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11938 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11939 {
11940 start_row = prev_row;
11941 } else {
11942 break 'expand_upwards;
11943 }
11944 }
11945
11946 'expand_downwards: while end_row < buffer.max_point().row {
11947 let next_row = end_row + 1;
11948 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11949 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11950 && !buffer.is_line_blank(MultiBufferRow(next_row))
11951 {
11952 end_row = next_row;
11953 } else {
11954 break 'expand_downwards;
11955 }
11956 }
11957 }
11958
11959 let start = Point::new(start_row, 0);
11960 let start_offset = start.to_offset(&buffer);
11961 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11962 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11963 let Some(lines_without_prefixes) = selection_text
11964 .lines()
11965 .enumerate()
11966 .map(|(ix, line)| {
11967 let line_trimmed = line.trim_start();
11968 if rewrap_prefix.is_some() && ix > 0 {
11969 Ok(line_trimmed)
11970 } else {
11971 line_trimmed
11972 .strip_prefix(&line_prefix.trim_start())
11973 .with_context(|| {
11974 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11975 })
11976 }
11977 })
11978 .collect::<Result<Vec<_>, _>>()
11979 .log_err()
11980 else {
11981 continue;
11982 };
11983
11984 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11985 buffer
11986 .language_settings_at(Point::new(start_row, 0), cx)
11987 .preferred_line_length as usize
11988 });
11989
11990 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11991 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11992 } else {
11993 line_prefix.clone()
11994 };
11995
11996 let wrapped_text = wrap_with_prefix(
11997 line_prefix,
11998 subsequent_lines_prefix,
11999 lines_without_prefixes.join("\n"),
12000 wrap_column,
12001 tab_size,
12002 options.preserve_existing_whitespace,
12003 );
12004
12005 // TODO: should always use char-based diff while still supporting cursor behavior that
12006 // matches vim.
12007 let mut diff_options = DiffOptions::default();
12008 if options.override_language_settings {
12009 diff_options.max_word_diff_len = 0;
12010 diff_options.max_word_diff_line_count = 0;
12011 } else {
12012 diff_options.max_word_diff_len = usize::MAX;
12013 diff_options.max_word_diff_line_count = usize::MAX;
12014 }
12015
12016 for (old_range, new_text) in
12017 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12018 {
12019 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12020 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12021 edits.push((edit_start..edit_end, new_text));
12022 }
12023
12024 rewrapped_row_ranges.push(start_row..=end_row);
12025 }
12026
12027 self.buffer
12028 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12029 }
12030
12031 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
12032 let mut text = String::new();
12033 let buffer = self.buffer.read(cx).snapshot(cx);
12034 let mut selections = self.selections.all::<Point>(cx);
12035 let mut clipboard_selections = Vec::with_capacity(selections.len());
12036 {
12037 let max_point = buffer.max_point();
12038 let mut is_first = true;
12039 for selection in &mut selections {
12040 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12041 if is_entire_line {
12042 selection.start = Point::new(selection.start.row, 0);
12043 if !selection.is_empty() && selection.end.column == 0 {
12044 selection.end = cmp::min(max_point, selection.end);
12045 } else {
12046 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12047 }
12048 selection.goal = SelectionGoal::None;
12049 }
12050 if is_first {
12051 is_first = false;
12052 } else {
12053 text += "\n";
12054 }
12055 let mut len = 0;
12056 for chunk in buffer.text_for_range(selection.start..selection.end) {
12057 text.push_str(chunk);
12058 len += chunk.len();
12059 }
12060 clipboard_selections.push(ClipboardSelection {
12061 len,
12062 is_entire_line,
12063 first_line_indent: buffer
12064 .indent_size_for_line(MultiBufferRow(selection.start.row))
12065 .len,
12066 });
12067 }
12068 }
12069
12070 self.transact(window, cx, |this, window, cx| {
12071 this.change_selections(Default::default(), window, cx, |s| {
12072 s.select(selections);
12073 });
12074 this.insert("", window, cx);
12075 });
12076 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12077 }
12078
12079 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12080 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12081 let item = self.cut_common(window, cx);
12082 cx.write_to_clipboard(item);
12083 }
12084
12085 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12086 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12087 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12088 s.move_with(|snapshot, sel| {
12089 if sel.is_empty() {
12090 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12091 }
12092 });
12093 });
12094 let item = self.cut_common(window, cx);
12095 cx.set_global(KillRing(item))
12096 }
12097
12098 pub fn kill_ring_yank(
12099 &mut self,
12100 _: &KillRingYank,
12101 window: &mut Window,
12102 cx: &mut Context<Self>,
12103 ) {
12104 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12105 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12106 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12107 (kill_ring.text().to_string(), kill_ring.metadata_json())
12108 } else {
12109 return;
12110 }
12111 } else {
12112 return;
12113 };
12114 self.do_paste(&text, metadata, false, window, cx);
12115 }
12116
12117 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12118 self.do_copy(true, cx);
12119 }
12120
12121 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12122 self.do_copy(false, cx);
12123 }
12124
12125 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12126 let selections = self.selections.all::<Point>(cx);
12127 let buffer = self.buffer.read(cx).read(cx);
12128 let mut text = String::new();
12129
12130 let mut clipboard_selections = Vec::with_capacity(selections.len());
12131 {
12132 let max_point = buffer.max_point();
12133 let mut is_first = true;
12134 for selection in &selections {
12135 let mut start = selection.start;
12136 let mut end = selection.end;
12137 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12138 if is_entire_line {
12139 start = Point::new(start.row, 0);
12140 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12141 }
12142
12143 let mut trimmed_selections = Vec::new();
12144 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12145 let row = MultiBufferRow(start.row);
12146 let first_indent = buffer.indent_size_for_line(row);
12147 if first_indent.len == 0 || start.column > first_indent.len {
12148 trimmed_selections.push(start..end);
12149 } else {
12150 trimmed_selections.push(
12151 Point::new(row.0, first_indent.len)
12152 ..Point::new(row.0, buffer.line_len(row)),
12153 );
12154 for row in start.row + 1..=end.row {
12155 let mut line_len = buffer.line_len(MultiBufferRow(row));
12156 if row == end.row {
12157 line_len = end.column;
12158 }
12159 if line_len == 0 {
12160 trimmed_selections
12161 .push(Point::new(row, 0)..Point::new(row, line_len));
12162 continue;
12163 }
12164 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12165 if row_indent_size.len >= first_indent.len {
12166 trimmed_selections.push(
12167 Point::new(row, first_indent.len)..Point::new(row, line_len),
12168 );
12169 } else {
12170 trimmed_selections.clear();
12171 trimmed_selections.push(start..end);
12172 break;
12173 }
12174 }
12175 }
12176 } else {
12177 trimmed_selections.push(start..end);
12178 }
12179
12180 for trimmed_range in trimmed_selections {
12181 if is_first {
12182 is_first = false;
12183 } else {
12184 text += "\n";
12185 }
12186 let mut len = 0;
12187 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12188 text.push_str(chunk);
12189 len += chunk.len();
12190 }
12191 clipboard_selections.push(ClipboardSelection {
12192 len,
12193 is_entire_line,
12194 first_line_indent: buffer
12195 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12196 .len,
12197 });
12198 }
12199 }
12200 }
12201
12202 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12203 text,
12204 clipboard_selections,
12205 ));
12206 }
12207
12208 pub fn do_paste(
12209 &mut self,
12210 text: &String,
12211 clipboard_selections: Option<Vec<ClipboardSelection>>,
12212 handle_entire_lines: bool,
12213 window: &mut Window,
12214 cx: &mut Context<Self>,
12215 ) {
12216 if self.read_only(cx) {
12217 return;
12218 }
12219
12220 let clipboard_text = Cow::Borrowed(text);
12221
12222 self.transact(window, cx, |this, window, cx| {
12223 let had_active_edit_prediction = this.has_active_edit_prediction();
12224
12225 if let Some(mut clipboard_selections) = clipboard_selections {
12226 let old_selections = this.selections.all::<usize>(cx);
12227 let all_selections_were_entire_line =
12228 clipboard_selections.iter().all(|s| s.is_entire_line);
12229 let first_selection_indent_column =
12230 clipboard_selections.first().map(|s| s.first_line_indent);
12231 if clipboard_selections.len() != old_selections.len() {
12232 clipboard_selections.drain(..);
12233 }
12234 let cursor_offset = this.selections.last::<usize>(cx).head();
12235 let mut auto_indent_on_paste = true;
12236
12237 this.buffer.update(cx, |buffer, cx| {
12238 let snapshot = buffer.read(cx);
12239 auto_indent_on_paste = snapshot
12240 .language_settings_at(cursor_offset, cx)
12241 .auto_indent_on_paste;
12242
12243 let mut start_offset = 0;
12244 let mut edits = Vec::new();
12245 let mut original_indent_columns = Vec::new();
12246 for (ix, selection) in old_selections.iter().enumerate() {
12247 let to_insert;
12248 let entire_line;
12249 let original_indent_column;
12250 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12251 let end_offset = start_offset + clipboard_selection.len;
12252 to_insert = &clipboard_text[start_offset..end_offset];
12253 entire_line = clipboard_selection.is_entire_line;
12254 start_offset = end_offset + 1;
12255 original_indent_column = Some(clipboard_selection.first_line_indent);
12256 } else {
12257 to_insert = clipboard_text.as_str();
12258 entire_line = all_selections_were_entire_line;
12259 original_indent_column = first_selection_indent_column
12260 }
12261
12262 // If the corresponding selection was empty when this slice of the
12263 // clipboard text was written, then the entire line containing the
12264 // selection was copied. If this selection is also currently empty,
12265 // then paste the line before the current line of the buffer.
12266 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12267 let column = selection.start.to_point(&snapshot).column as usize;
12268 let line_start = selection.start - column;
12269 line_start..line_start
12270 } else {
12271 selection.range()
12272 };
12273
12274 edits.push((range, to_insert));
12275 original_indent_columns.push(original_indent_column);
12276 }
12277 drop(snapshot);
12278
12279 buffer.edit(
12280 edits,
12281 if auto_indent_on_paste {
12282 Some(AutoindentMode::Block {
12283 original_indent_columns,
12284 })
12285 } else {
12286 None
12287 },
12288 cx,
12289 );
12290 });
12291
12292 let selections = this.selections.all::<usize>(cx);
12293 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12294 } else {
12295 this.insert(&clipboard_text, window, cx);
12296 }
12297
12298 let trigger_in_words =
12299 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12300
12301 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12302 });
12303 }
12304
12305 pub fn diff_clipboard_with_selection(
12306 &mut self,
12307 _: &DiffClipboardWithSelection,
12308 window: &mut Window,
12309 cx: &mut Context<Self>,
12310 ) {
12311 let selections = self.selections.all::<usize>(cx);
12312
12313 if selections.is_empty() {
12314 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12315 return;
12316 };
12317
12318 let clipboard_text = match cx.read_from_clipboard() {
12319 Some(item) => match item.entries().first() {
12320 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12321 _ => None,
12322 },
12323 None => None,
12324 };
12325
12326 let Some(clipboard_text) = clipboard_text else {
12327 log::warn!("Clipboard doesn't contain text.");
12328 return;
12329 };
12330
12331 window.dispatch_action(
12332 Box::new(DiffClipboardWithSelectionData {
12333 clipboard_text,
12334 editor: cx.entity(),
12335 }),
12336 cx,
12337 );
12338 }
12339
12340 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12341 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12342 if let Some(item) = cx.read_from_clipboard() {
12343 let entries = item.entries();
12344
12345 match entries.first() {
12346 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12347 // of all the pasted entries.
12348 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12349 .do_paste(
12350 clipboard_string.text(),
12351 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12352 true,
12353 window,
12354 cx,
12355 ),
12356 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12357 }
12358 }
12359 }
12360
12361 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12362 if self.read_only(cx) {
12363 return;
12364 }
12365
12366 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12367
12368 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12369 if let Some((selections, _)) =
12370 self.selection_history.transaction(transaction_id).cloned()
12371 {
12372 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12373 s.select_anchors(selections.to_vec());
12374 });
12375 } else {
12376 log::error!(
12377 "No entry in selection_history found for undo. \
12378 This may correspond to a bug where undo does not update the selection. \
12379 If this is occurring, please add details to \
12380 https://github.com/zed-industries/zed/issues/22692"
12381 );
12382 }
12383 self.request_autoscroll(Autoscroll::fit(), cx);
12384 self.unmark_text(window, cx);
12385 self.refresh_edit_prediction(true, false, window, cx);
12386 cx.emit(EditorEvent::Edited { transaction_id });
12387 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12388 }
12389 }
12390
12391 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12392 if self.read_only(cx) {
12393 return;
12394 }
12395
12396 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12397
12398 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12399 if let Some((_, Some(selections))) =
12400 self.selection_history.transaction(transaction_id).cloned()
12401 {
12402 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12403 s.select_anchors(selections.to_vec());
12404 });
12405 } else {
12406 log::error!(
12407 "No entry in selection_history found for redo. \
12408 This may correspond to a bug where undo does not update the selection. \
12409 If this is occurring, please add details to \
12410 https://github.com/zed-industries/zed/issues/22692"
12411 );
12412 }
12413 self.request_autoscroll(Autoscroll::fit(), cx);
12414 self.unmark_text(window, cx);
12415 self.refresh_edit_prediction(true, false, window, cx);
12416 cx.emit(EditorEvent::Edited { transaction_id });
12417 }
12418 }
12419
12420 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12421 self.buffer
12422 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12423 }
12424
12425 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12426 self.buffer
12427 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12428 }
12429
12430 pub fn move_left(&mut self, _: &MoveLeft, 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_with(|map, selection| {
12434 let cursor = if selection.is_empty() {
12435 movement::left(map, selection.start)
12436 } else {
12437 selection.start
12438 };
12439 selection.collapse_to(cursor, SelectionGoal::None);
12440 });
12441 })
12442 }
12443
12444 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12445 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12446 self.change_selections(Default::default(), window, cx, |s| {
12447 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12448 })
12449 }
12450
12451 pub fn move_right(&mut self, _: &MoveRight, 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_with(|map, selection| {
12455 let cursor = if selection.is_empty() {
12456 movement::right(map, selection.end)
12457 } else {
12458 selection.end
12459 };
12460 selection.collapse_to(cursor, SelectionGoal::None)
12461 });
12462 })
12463 }
12464
12465 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12466 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12467 self.change_selections(Default::default(), window, cx, |s| {
12468 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12469 })
12470 }
12471
12472 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12473 if self.take_rename(true, window, cx).is_some() {
12474 return;
12475 }
12476
12477 if self.mode.is_single_line() {
12478 cx.propagate();
12479 return;
12480 }
12481
12482 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12483
12484 let text_layout_details = &self.text_layout_details(window);
12485 let selection_count = self.selections.count();
12486 let first_selection = self.selections.first_anchor();
12487
12488 self.change_selections(Default::default(), window, cx, |s| {
12489 s.move_with(|map, selection| {
12490 if !selection.is_empty() {
12491 selection.goal = SelectionGoal::None;
12492 }
12493 let (cursor, goal) = movement::up(
12494 map,
12495 selection.start,
12496 selection.goal,
12497 false,
12498 text_layout_details,
12499 );
12500 selection.collapse_to(cursor, goal);
12501 });
12502 });
12503
12504 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12505 {
12506 cx.propagate();
12507 }
12508 }
12509
12510 pub fn move_up_by_lines(
12511 &mut self,
12512 action: &MoveUpByLines,
12513 window: &mut Window,
12514 cx: &mut Context<Self>,
12515 ) {
12516 if self.take_rename(true, window, cx).is_some() {
12517 return;
12518 }
12519
12520 if self.mode.is_single_line() {
12521 cx.propagate();
12522 return;
12523 }
12524
12525 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12526
12527 let text_layout_details = &self.text_layout_details(window);
12528
12529 self.change_selections(Default::default(), window, cx, |s| {
12530 s.move_with(|map, selection| {
12531 if !selection.is_empty() {
12532 selection.goal = SelectionGoal::None;
12533 }
12534 let (cursor, goal) = movement::up_by_rows(
12535 map,
12536 selection.start,
12537 action.lines,
12538 selection.goal,
12539 false,
12540 text_layout_details,
12541 );
12542 selection.collapse_to(cursor, goal);
12543 });
12544 })
12545 }
12546
12547 pub fn move_down_by_lines(
12548 &mut self,
12549 action: &MoveDownByLines,
12550 window: &mut Window,
12551 cx: &mut Context<Self>,
12552 ) {
12553 if self.take_rename(true, window, cx).is_some() {
12554 return;
12555 }
12556
12557 if self.mode.is_single_line() {
12558 cx.propagate();
12559 return;
12560 }
12561
12562 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12563
12564 let text_layout_details = &self.text_layout_details(window);
12565
12566 self.change_selections(Default::default(), window, cx, |s| {
12567 s.move_with(|map, selection| {
12568 if !selection.is_empty() {
12569 selection.goal = SelectionGoal::None;
12570 }
12571 let (cursor, goal) = movement::down_by_rows(
12572 map,
12573 selection.start,
12574 action.lines,
12575 selection.goal,
12576 false,
12577 text_layout_details,
12578 );
12579 selection.collapse_to(cursor, goal);
12580 });
12581 })
12582 }
12583
12584 pub fn select_down_by_lines(
12585 &mut self,
12586 action: &SelectDownByLines,
12587 window: &mut Window,
12588 cx: &mut Context<Self>,
12589 ) {
12590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12591 let text_layout_details = &self.text_layout_details(window);
12592 self.change_selections(Default::default(), window, cx, |s| {
12593 s.move_heads_with(|map, head, goal| {
12594 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12595 })
12596 })
12597 }
12598
12599 pub fn select_up_by_lines(
12600 &mut self,
12601 action: &SelectUpByLines,
12602 window: &mut Window,
12603 cx: &mut Context<Self>,
12604 ) {
12605 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12606 let text_layout_details = &self.text_layout_details(window);
12607 self.change_selections(Default::default(), window, cx, |s| {
12608 s.move_heads_with(|map, head, goal| {
12609 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12610 })
12611 })
12612 }
12613
12614 pub fn select_page_up(
12615 &mut self,
12616 _: &SelectPageUp,
12617 window: &mut Window,
12618 cx: &mut Context<Self>,
12619 ) {
12620 let Some(row_count) = self.visible_row_count() else {
12621 return;
12622 };
12623
12624 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12625
12626 let text_layout_details = &self.text_layout_details(window);
12627
12628 self.change_selections(Default::default(), window, cx, |s| {
12629 s.move_heads_with(|map, head, goal| {
12630 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12631 })
12632 })
12633 }
12634
12635 pub fn move_page_up(
12636 &mut self,
12637 action: &MovePageUp,
12638 window: &mut Window,
12639 cx: &mut Context<Self>,
12640 ) {
12641 if self.take_rename(true, window, cx).is_some() {
12642 return;
12643 }
12644
12645 if self
12646 .context_menu
12647 .borrow_mut()
12648 .as_mut()
12649 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12650 .unwrap_or(false)
12651 {
12652 return;
12653 }
12654
12655 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12656 cx.propagate();
12657 return;
12658 }
12659
12660 let Some(row_count) = self.visible_row_count() else {
12661 return;
12662 };
12663
12664 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12665
12666 let effects = if action.center_cursor {
12667 SelectionEffects::scroll(Autoscroll::center())
12668 } else {
12669 SelectionEffects::default()
12670 };
12671
12672 let text_layout_details = &self.text_layout_details(window);
12673
12674 self.change_selections(effects, window, cx, |s| {
12675 s.move_with(|map, selection| {
12676 if !selection.is_empty() {
12677 selection.goal = SelectionGoal::None;
12678 }
12679 let (cursor, goal) = movement::up_by_rows(
12680 map,
12681 selection.end,
12682 row_count,
12683 selection.goal,
12684 false,
12685 text_layout_details,
12686 );
12687 selection.collapse_to(cursor, goal);
12688 });
12689 });
12690 }
12691
12692 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12693 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12694 let text_layout_details = &self.text_layout_details(window);
12695 self.change_selections(Default::default(), window, cx, |s| {
12696 s.move_heads_with(|map, head, goal| {
12697 movement::up(map, head, goal, false, text_layout_details)
12698 })
12699 })
12700 }
12701
12702 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12703 self.take_rename(true, window, cx);
12704
12705 if self.mode.is_single_line() {
12706 cx.propagate();
12707 return;
12708 }
12709
12710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12711
12712 let text_layout_details = &self.text_layout_details(window);
12713 let selection_count = self.selections.count();
12714 let first_selection = self.selections.first_anchor();
12715
12716 self.change_selections(Default::default(), window, cx, |s| {
12717 s.move_with(|map, selection| {
12718 if !selection.is_empty() {
12719 selection.goal = SelectionGoal::None;
12720 }
12721 let (cursor, goal) = movement::down(
12722 map,
12723 selection.end,
12724 selection.goal,
12725 false,
12726 text_layout_details,
12727 );
12728 selection.collapse_to(cursor, goal);
12729 });
12730 });
12731
12732 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12733 {
12734 cx.propagate();
12735 }
12736 }
12737
12738 pub fn select_page_down(
12739 &mut self,
12740 _: &SelectPageDown,
12741 window: &mut Window,
12742 cx: &mut Context<Self>,
12743 ) {
12744 let Some(row_count) = self.visible_row_count() else {
12745 return;
12746 };
12747
12748 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12749
12750 let text_layout_details = &self.text_layout_details(window);
12751
12752 self.change_selections(Default::default(), window, cx, |s| {
12753 s.move_heads_with(|map, head, goal| {
12754 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12755 })
12756 })
12757 }
12758
12759 pub fn move_page_down(
12760 &mut self,
12761 action: &MovePageDown,
12762 window: &mut Window,
12763 cx: &mut Context<Self>,
12764 ) {
12765 if self.take_rename(true, window, cx).is_some() {
12766 return;
12767 }
12768
12769 if self
12770 .context_menu
12771 .borrow_mut()
12772 .as_mut()
12773 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12774 .unwrap_or(false)
12775 {
12776 return;
12777 }
12778
12779 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12780 cx.propagate();
12781 return;
12782 }
12783
12784 let Some(row_count) = self.visible_row_count() else {
12785 return;
12786 };
12787
12788 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12789
12790 let effects = if action.center_cursor {
12791 SelectionEffects::scroll(Autoscroll::center())
12792 } else {
12793 SelectionEffects::default()
12794 };
12795
12796 let text_layout_details = &self.text_layout_details(window);
12797 self.change_selections(effects, window, cx, |s| {
12798 s.move_with(|map, selection| {
12799 if !selection.is_empty() {
12800 selection.goal = SelectionGoal::None;
12801 }
12802 let (cursor, goal) = movement::down_by_rows(
12803 map,
12804 selection.end,
12805 row_count,
12806 selection.goal,
12807 false,
12808 text_layout_details,
12809 );
12810 selection.collapse_to(cursor, goal);
12811 });
12812 });
12813 }
12814
12815 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12816 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12817 let text_layout_details = &self.text_layout_details(window);
12818 self.change_selections(Default::default(), window, cx, |s| {
12819 s.move_heads_with(|map, head, goal| {
12820 movement::down(map, head, goal, false, text_layout_details)
12821 })
12822 });
12823 }
12824
12825 pub fn context_menu_first(
12826 &mut self,
12827 _: &ContextMenuFirst,
12828 window: &mut Window,
12829 cx: &mut Context<Self>,
12830 ) {
12831 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12832 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12833 }
12834 }
12835
12836 pub fn context_menu_prev(
12837 &mut self,
12838 _: &ContextMenuPrevious,
12839 window: &mut Window,
12840 cx: &mut Context<Self>,
12841 ) {
12842 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12843 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12844 }
12845 }
12846
12847 pub fn context_menu_next(
12848 &mut self,
12849 _: &ContextMenuNext,
12850 window: &mut Window,
12851 cx: &mut Context<Self>,
12852 ) {
12853 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12854 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12855 }
12856 }
12857
12858 pub fn context_menu_last(
12859 &mut self,
12860 _: &ContextMenuLast,
12861 window: &mut Window,
12862 cx: &mut Context<Self>,
12863 ) {
12864 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12865 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12866 }
12867 }
12868
12869 pub fn signature_help_prev(
12870 &mut self,
12871 _: &SignatureHelpPrevious,
12872 _: &mut Window,
12873 cx: &mut Context<Self>,
12874 ) {
12875 if let Some(popover) = self.signature_help_state.popover_mut() {
12876 if popover.current_signature == 0 {
12877 popover.current_signature = popover.signatures.len() - 1;
12878 } else {
12879 popover.current_signature -= 1;
12880 }
12881 cx.notify();
12882 }
12883 }
12884
12885 pub fn signature_help_next(
12886 &mut self,
12887 _: &SignatureHelpNext,
12888 _: &mut Window,
12889 cx: &mut Context<Self>,
12890 ) {
12891 if let Some(popover) = self.signature_help_state.popover_mut() {
12892 if popover.current_signature + 1 == popover.signatures.len() {
12893 popover.current_signature = 0;
12894 } else {
12895 popover.current_signature += 1;
12896 }
12897 cx.notify();
12898 }
12899 }
12900
12901 pub fn move_to_previous_word_start(
12902 &mut self,
12903 _: &MoveToPreviousWordStart,
12904 window: &mut Window,
12905 cx: &mut Context<Self>,
12906 ) {
12907 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12908 self.change_selections(Default::default(), window, cx, |s| {
12909 s.move_cursors_with(|map, head, _| {
12910 (
12911 movement::previous_word_start(map, head),
12912 SelectionGoal::None,
12913 )
12914 });
12915 })
12916 }
12917
12918 pub fn move_to_previous_subword_start(
12919 &mut self,
12920 _: &MoveToPreviousSubwordStart,
12921 window: &mut Window,
12922 cx: &mut Context<Self>,
12923 ) {
12924 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12925 self.change_selections(Default::default(), window, cx, |s| {
12926 s.move_cursors_with(|map, head, _| {
12927 (
12928 movement::previous_subword_start(map, head),
12929 SelectionGoal::None,
12930 )
12931 });
12932 })
12933 }
12934
12935 pub fn select_to_previous_word_start(
12936 &mut self,
12937 _: &SelectToPreviousWordStart,
12938 window: &mut Window,
12939 cx: &mut Context<Self>,
12940 ) {
12941 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12942 self.change_selections(Default::default(), window, cx, |s| {
12943 s.move_heads_with(|map, head, _| {
12944 (
12945 movement::previous_word_start(map, head),
12946 SelectionGoal::None,
12947 )
12948 });
12949 })
12950 }
12951
12952 pub fn select_to_previous_subword_start(
12953 &mut self,
12954 _: &SelectToPreviousSubwordStart,
12955 window: &mut Window,
12956 cx: &mut Context<Self>,
12957 ) {
12958 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12959 self.change_selections(Default::default(), window, cx, |s| {
12960 s.move_heads_with(|map, head, _| {
12961 (
12962 movement::previous_subword_start(map, head),
12963 SelectionGoal::None,
12964 )
12965 });
12966 })
12967 }
12968
12969 pub fn delete_to_previous_word_start(
12970 &mut self,
12971 action: &DeleteToPreviousWordStart,
12972 window: &mut Window,
12973 cx: &mut Context<Self>,
12974 ) {
12975 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12976 self.transact(window, cx, |this, window, cx| {
12977 this.select_autoclose_pair(window, cx);
12978 this.change_selections(Default::default(), window, cx, |s| {
12979 s.move_with(|map, selection| {
12980 if selection.is_empty() {
12981 let cursor = if action.ignore_newlines {
12982 movement::previous_word_start(map, selection.head())
12983 } else {
12984 movement::previous_word_start_or_newline(map, selection.head())
12985 };
12986 selection.set_head(cursor, SelectionGoal::None);
12987 }
12988 });
12989 });
12990 this.insert("", window, cx);
12991 });
12992 }
12993
12994 pub fn delete_to_previous_subword_start(
12995 &mut self,
12996 _: &DeleteToPreviousSubwordStart,
12997 window: &mut Window,
12998 cx: &mut Context<Self>,
12999 ) {
13000 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13001 self.transact(window, cx, |this, window, cx| {
13002 this.select_autoclose_pair(window, cx);
13003 this.change_selections(Default::default(), window, cx, |s| {
13004 s.move_with(|map, selection| {
13005 if selection.is_empty() {
13006 let cursor = movement::previous_subword_start(map, selection.head());
13007 selection.set_head(cursor, SelectionGoal::None);
13008 }
13009 });
13010 });
13011 this.insert("", window, cx);
13012 });
13013 }
13014
13015 pub fn move_to_next_word_end(
13016 &mut self,
13017 _: &MoveToNextWordEnd,
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_word_end(map, head), SelectionGoal::None)
13025 });
13026 })
13027 }
13028
13029 pub fn move_to_next_subword_end(
13030 &mut self,
13031 _: &MoveToNextSubwordEnd,
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_cursors_with(|map, head, _| {
13038 (movement::next_subword_end(map, head), SelectionGoal::None)
13039 });
13040 })
13041 }
13042
13043 pub fn select_to_next_word_end(
13044 &mut self,
13045 _: &SelectToNextWordEnd,
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_word_end(map, head), SelectionGoal::None)
13053 });
13054 })
13055 }
13056
13057 pub fn select_to_next_subword_end(
13058 &mut self,
13059 _: &SelectToNextSubwordEnd,
13060 window: &mut Window,
13061 cx: &mut Context<Self>,
13062 ) {
13063 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13064 self.change_selections(Default::default(), window, cx, |s| {
13065 s.move_heads_with(|map, head, _| {
13066 (movement::next_subword_end(map, head), SelectionGoal::None)
13067 });
13068 })
13069 }
13070
13071 pub fn delete_to_next_word_end(
13072 &mut self,
13073 action: &DeleteToNextWordEnd,
13074 window: &mut Window,
13075 cx: &mut Context<Self>,
13076 ) {
13077 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13078 self.transact(window, cx, |this, window, cx| {
13079 this.change_selections(Default::default(), window, cx, |s| {
13080 s.move_with(|map, selection| {
13081 if selection.is_empty() {
13082 let cursor = if action.ignore_newlines {
13083 movement::next_word_end(map, selection.head())
13084 } else {
13085 movement::next_word_end_or_newline(map, selection.head())
13086 };
13087 selection.set_head(cursor, SelectionGoal::None);
13088 }
13089 });
13090 });
13091 this.insert("", window, cx);
13092 });
13093 }
13094
13095 pub fn delete_to_next_subword_end(
13096 &mut self,
13097 _: &DeleteToNextSubwordEnd,
13098 window: &mut Window,
13099 cx: &mut Context<Self>,
13100 ) {
13101 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13102 self.transact(window, cx, |this, window, cx| {
13103 this.change_selections(Default::default(), window, cx, |s| {
13104 s.move_with(|map, selection| {
13105 if selection.is_empty() {
13106 let cursor = movement::next_subword_end(map, selection.head());
13107 selection.set_head(cursor, SelectionGoal::None);
13108 }
13109 });
13110 });
13111 this.insert("", window, cx);
13112 });
13113 }
13114
13115 pub fn move_to_beginning_of_line(
13116 &mut self,
13117 action: &MoveToBeginningOfLine,
13118 window: &mut Window,
13119 cx: &mut Context<Self>,
13120 ) {
13121 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13122 self.change_selections(Default::default(), window, cx, |s| {
13123 s.move_cursors_with(|map, head, _| {
13124 (
13125 movement::indented_line_beginning(
13126 map,
13127 head,
13128 action.stop_at_soft_wraps,
13129 action.stop_at_indent,
13130 ),
13131 SelectionGoal::None,
13132 )
13133 });
13134 })
13135 }
13136
13137 pub fn select_to_beginning_of_line(
13138 &mut self,
13139 action: &SelectToBeginningOfLine,
13140 window: &mut Window,
13141 cx: &mut Context<Self>,
13142 ) {
13143 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13144 self.change_selections(Default::default(), window, cx, |s| {
13145 s.move_heads_with(|map, head, _| {
13146 (
13147 movement::indented_line_beginning(
13148 map,
13149 head,
13150 action.stop_at_soft_wraps,
13151 action.stop_at_indent,
13152 ),
13153 SelectionGoal::None,
13154 )
13155 });
13156 });
13157 }
13158
13159 pub fn delete_to_beginning_of_line(
13160 &mut self,
13161 action: &DeleteToBeginningOfLine,
13162 window: &mut Window,
13163 cx: &mut Context<Self>,
13164 ) {
13165 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13166 self.transact(window, cx, |this, window, cx| {
13167 this.change_selections(Default::default(), window, cx, |s| {
13168 s.move_with(|_, selection| {
13169 selection.reversed = true;
13170 });
13171 });
13172
13173 this.select_to_beginning_of_line(
13174 &SelectToBeginningOfLine {
13175 stop_at_soft_wraps: false,
13176 stop_at_indent: action.stop_at_indent,
13177 },
13178 window,
13179 cx,
13180 );
13181 this.backspace(&Backspace, window, cx);
13182 });
13183 }
13184
13185 pub fn move_to_end_of_line(
13186 &mut self,
13187 action: &MoveToEndOfLine,
13188 window: &mut Window,
13189 cx: &mut Context<Self>,
13190 ) {
13191 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13192 self.change_selections(Default::default(), window, cx, |s| {
13193 s.move_cursors_with(|map, head, _| {
13194 (
13195 movement::line_end(map, head, action.stop_at_soft_wraps),
13196 SelectionGoal::None,
13197 )
13198 });
13199 })
13200 }
13201
13202 pub fn select_to_end_of_line(
13203 &mut self,
13204 action: &SelectToEndOfLine,
13205 window: &mut Window,
13206 cx: &mut Context<Self>,
13207 ) {
13208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13209 self.change_selections(Default::default(), window, cx, |s| {
13210 s.move_heads_with(|map, head, _| {
13211 (
13212 movement::line_end(map, head, action.stop_at_soft_wraps),
13213 SelectionGoal::None,
13214 )
13215 });
13216 })
13217 }
13218
13219 pub fn delete_to_end_of_line(
13220 &mut self,
13221 _: &DeleteToEndOfLine,
13222 window: &mut Window,
13223 cx: &mut Context<Self>,
13224 ) {
13225 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13226 self.transact(window, cx, |this, window, cx| {
13227 this.select_to_end_of_line(
13228 &SelectToEndOfLine {
13229 stop_at_soft_wraps: false,
13230 },
13231 window,
13232 cx,
13233 );
13234 this.delete(&Delete, window, cx);
13235 });
13236 }
13237
13238 pub fn cut_to_end_of_line(
13239 &mut self,
13240 _: &CutToEndOfLine,
13241 window: &mut Window,
13242 cx: &mut Context<Self>,
13243 ) {
13244 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13245 self.transact(window, cx, |this, window, cx| {
13246 this.select_to_end_of_line(
13247 &SelectToEndOfLine {
13248 stop_at_soft_wraps: false,
13249 },
13250 window,
13251 cx,
13252 );
13253 this.cut(&Cut, window, cx);
13254 });
13255 }
13256
13257 pub fn move_to_start_of_paragraph(
13258 &mut self,
13259 _: &MoveToStartOfParagraph,
13260 window: &mut Window,
13261 cx: &mut Context<Self>,
13262 ) {
13263 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13264 cx.propagate();
13265 return;
13266 }
13267 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13268 self.change_selections(Default::default(), window, cx, |s| {
13269 s.move_with(|map, selection| {
13270 selection.collapse_to(
13271 movement::start_of_paragraph(map, selection.head(), 1),
13272 SelectionGoal::None,
13273 )
13274 });
13275 })
13276 }
13277
13278 pub fn move_to_end_of_paragraph(
13279 &mut self,
13280 _: &MoveToEndOfParagraph,
13281 window: &mut Window,
13282 cx: &mut Context<Self>,
13283 ) {
13284 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13285 cx.propagate();
13286 return;
13287 }
13288 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13289 self.change_selections(Default::default(), window, cx, |s| {
13290 s.move_with(|map, selection| {
13291 selection.collapse_to(
13292 movement::end_of_paragraph(map, selection.head(), 1),
13293 SelectionGoal::None,
13294 )
13295 });
13296 })
13297 }
13298
13299 pub fn select_to_start_of_paragraph(
13300 &mut self,
13301 _: &SelectToStartOfParagraph,
13302 window: &mut Window,
13303 cx: &mut Context<Self>,
13304 ) {
13305 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13306 cx.propagate();
13307 return;
13308 }
13309 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13310 self.change_selections(Default::default(), window, cx, |s| {
13311 s.move_heads_with(|map, head, _| {
13312 (
13313 movement::start_of_paragraph(map, head, 1),
13314 SelectionGoal::None,
13315 )
13316 });
13317 })
13318 }
13319
13320 pub fn select_to_end_of_paragraph(
13321 &mut self,
13322 _: &SelectToEndOfParagraph,
13323 window: &mut Window,
13324 cx: &mut Context<Self>,
13325 ) {
13326 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13327 cx.propagate();
13328 return;
13329 }
13330 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13331 self.change_selections(Default::default(), window, cx, |s| {
13332 s.move_heads_with(|map, head, _| {
13333 (
13334 movement::end_of_paragraph(map, head, 1),
13335 SelectionGoal::None,
13336 )
13337 });
13338 })
13339 }
13340
13341 pub fn move_to_start_of_excerpt(
13342 &mut self,
13343 _: &MoveToStartOfExcerpt,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) {
13347 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13348 cx.propagate();
13349 return;
13350 }
13351 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13352 self.change_selections(Default::default(), window, cx, |s| {
13353 s.move_with(|map, selection| {
13354 selection.collapse_to(
13355 movement::start_of_excerpt(
13356 map,
13357 selection.head(),
13358 workspace::searchable::Direction::Prev,
13359 ),
13360 SelectionGoal::None,
13361 )
13362 });
13363 })
13364 }
13365
13366 pub fn move_to_start_of_next_excerpt(
13367 &mut self,
13368 _: &MoveToStartOfNextExcerpt,
13369 window: &mut Window,
13370 cx: &mut Context<Self>,
13371 ) {
13372 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13373 cx.propagate();
13374 return;
13375 }
13376
13377 self.change_selections(Default::default(), window, cx, |s| {
13378 s.move_with(|map, selection| {
13379 selection.collapse_to(
13380 movement::start_of_excerpt(
13381 map,
13382 selection.head(),
13383 workspace::searchable::Direction::Next,
13384 ),
13385 SelectionGoal::None,
13386 )
13387 });
13388 })
13389 }
13390
13391 pub fn move_to_end_of_excerpt(
13392 &mut self,
13393 _: &MoveToEndOfExcerpt,
13394 window: &mut Window,
13395 cx: &mut Context<Self>,
13396 ) {
13397 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13398 cx.propagate();
13399 return;
13400 }
13401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13402 self.change_selections(Default::default(), window, cx, |s| {
13403 s.move_with(|map, selection| {
13404 selection.collapse_to(
13405 movement::end_of_excerpt(
13406 map,
13407 selection.head(),
13408 workspace::searchable::Direction::Next,
13409 ),
13410 SelectionGoal::None,
13411 )
13412 });
13413 })
13414 }
13415
13416 pub fn move_to_end_of_previous_excerpt(
13417 &mut self,
13418 _: &MoveToEndOfPreviousExcerpt,
13419 window: &mut Window,
13420 cx: &mut Context<Self>,
13421 ) {
13422 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13423 cx.propagate();
13424 return;
13425 }
13426 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13427 self.change_selections(Default::default(), window, cx, |s| {
13428 s.move_with(|map, selection| {
13429 selection.collapse_to(
13430 movement::end_of_excerpt(
13431 map,
13432 selection.head(),
13433 workspace::searchable::Direction::Prev,
13434 ),
13435 SelectionGoal::None,
13436 )
13437 });
13438 })
13439 }
13440
13441 pub fn select_to_start_of_excerpt(
13442 &mut self,
13443 _: &SelectToStartOfExcerpt,
13444 window: &mut Window,
13445 cx: &mut Context<Self>,
13446 ) {
13447 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13448 cx.propagate();
13449 return;
13450 }
13451 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13452 self.change_selections(Default::default(), window, cx, |s| {
13453 s.move_heads_with(|map, head, _| {
13454 (
13455 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13456 SelectionGoal::None,
13457 )
13458 });
13459 })
13460 }
13461
13462 pub fn select_to_start_of_next_excerpt(
13463 &mut self,
13464 _: &SelectToStartOfNextExcerpt,
13465 window: &mut Window,
13466 cx: &mut Context<Self>,
13467 ) {
13468 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13469 cx.propagate();
13470 return;
13471 }
13472 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13473 self.change_selections(Default::default(), window, cx, |s| {
13474 s.move_heads_with(|map, head, _| {
13475 (
13476 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13477 SelectionGoal::None,
13478 )
13479 });
13480 })
13481 }
13482
13483 pub fn select_to_end_of_excerpt(
13484 &mut self,
13485 _: &SelectToEndOfExcerpt,
13486 window: &mut Window,
13487 cx: &mut Context<Self>,
13488 ) {
13489 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13490 cx.propagate();
13491 return;
13492 }
13493 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13494 self.change_selections(Default::default(), window, cx, |s| {
13495 s.move_heads_with(|map, head, _| {
13496 (
13497 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13498 SelectionGoal::None,
13499 )
13500 });
13501 })
13502 }
13503
13504 pub fn select_to_end_of_previous_excerpt(
13505 &mut self,
13506 _: &SelectToEndOfPreviousExcerpt,
13507 window: &mut Window,
13508 cx: &mut Context<Self>,
13509 ) {
13510 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13511 cx.propagate();
13512 return;
13513 }
13514 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13515 self.change_selections(Default::default(), window, cx, |s| {
13516 s.move_heads_with(|map, head, _| {
13517 (
13518 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13519 SelectionGoal::None,
13520 )
13521 });
13522 })
13523 }
13524
13525 pub fn move_to_beginning(
13526 &mut self,
13527 _: &MoveToBeginning,
13528 window: &mut Window,
13529 cx: &mut Context<Self>,
13530 ) {
13531 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13532 cx.propagate();
13533 return;
13534 }
13535 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13536 self.change_selections(Default::default(), window, cx, |s| {
13537 s.select_ranges(vec![0..0]);
13538 });
13539 }
13540
13541 pub fn select_to_beginning(
13542 &mut self,
13543 _: &SelectToBeginning,
13544 window: &mut Window,
13545 cx: &mut Context<Self>,
13546 ) {
13547 let mut selection = self.selections.last::<Point>(cx);
13548 selection.set_head(Point::zero(), SelectionGoal::None);
13549 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13550 self.change_selections(Default::default(), window, cx, |s| {
13551 s.select(vec![selection]);
13552 });
13553 }
13554
13555 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13556 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13557 cx.propagate();
13558 return;
13559 }
13560 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13561 let cursor = self.buffer.read(cx).read(cx).len();
13562 self.change_selections(Default::default(), window, cx, |s| {
13563 s.select_ranges(vec![cursor..cursor])
13564 });
13565 }
13566
13567 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13568 self.nav_history = nav_history;
13569 }
13570
13571 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13572 self.nav_history.as_ref()
13573 }
13574
13575 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13576 self.push_to_nav_history(
13577 self.selections.newest_anchor().head(),
13578 None,
13579 false,
13580 true,
13581 cx,
13582 );
13583 }
13584
13585 fn push_to_nav_history(
13586 &mut self,
13587 cursor_anchor: Anchor,
13588 new_position: Option<Point>,
13589 is_deactivate: bool,
13590 always: bool,
13591 cx: &mut Context<Self>,
13592 ) {
13593 if let Some(nav_history) = self.nav_history.as_mut() {
13594 let buffer = self.buffer.read(cx).read(cx);
13595 let cursor_position = cursor_anchor.to_point(&buffer);
13596 let scroll_state = self.scroll_manager.anchor();
13597 let scroll_top_row = scroll_state.top_row(&buffer);
13598 drop(buffer);
13599
13600 if let Some(new_position) = new_position {
13601 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13602 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13603 return;
13604 }
13605 }
13606
13607 nav_history.push(
13608 Some(NavigationData {
13609 cursor_anchor,
13610 cursor_position,
13611 scroll_anchor: scroll_state,
13612 scroll_top_row,
13613 }),
13614 cx,
13615 );
13616 cx.emit(EditorEvent::PushedToNavHistory {
13617 anchor: cursor_anchor,
13618 is_deactivate,
13619 })
13620 }
13621 }
13622
13623 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13624 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13625 let buffer = self.buffer.read(cx).snapshot(cx);
13626 let mut selection = self.selections.first::<usize>(cx);
13627 selection.set_head(buffer.len(), SelectionGoal::None);
13628 self.change_selections(Default::default(), window, cx, |s| {
13629 s.select(vec![selection]);
13630 });
13631 }
13632
13633 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13634 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13635 let end = self.buffer.read(cx).read(cx).len();
13636 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13637 s.select_ranges(vec![0..end]);
13638 });
13639 }
13640
13641 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13643 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13644 let mut selections = self.selections.all::<Point>(cx);
13645 let max_point = display_map.buffer_snapshot.max_point();
13646 for selection in &mut selections {
13647 let rows = selection.spanned_rows(true, &display_map);
13648 selection.start = Point::new(rows.start.0, 0);
13649 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13650 selection.reversed = false;
13651 }
13652 self.change_selections(Default::default(), window, cx, |s| {
13653 s.select(selections);
13654 });
13655 }
13656
13657 pub fn split_selection_into_lines(
13658 &mut self,
13659 action: &SplitSelectionIntoLines,
13660 window: &mut Window,
13661 cx: &mut Context<Self>,
13662 ) {
13663 let selections = self
13664 .selections
13665 .all::<Point>(cx)
13666 .into_iter()
13667 .map(|selection| selection.start..selection.end)
13668 .collect::<Vec<_>>();
13669 self.unfold_ranges(&selections, true, true, cx);
13670
13671 let mut new_selection_ranges = Vec::new();
13672 {
13673 let buffer = self.buffer.read(cx).read(cx);
13674 for selection in selections {
13675 for row in selection.start.row..selection.end.row {
13676 let line_start = Point::new(row, 0);
13677 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13678
13679 if action.keep_selections {
13680 // Keep the selection range for each line
13681 let selection_start = if row == selection.start.row {
13682 selection.start
13683 } else {
13684 line_start
13685 };
13686 new_selection_ranges.push(selection_start..line_end);
13687 } else {
13688 // Collapse to cursor at end of line
13689 new_selection_ranges.push(line_end..line_end);
13690 }
13691 }
13692
13693 let is_multiline_selection = selection.start.row != selection.end.row;
13694 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13695 // so this action feels more ergonomic when paired with other selection operations
13696 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13697 if !should_skip_last {
13698 if action.keep_selections {
13699 if is_multiline_selection {
13700 let line_start = Point::new(selection.end.row, 0);
13701 new_selection_ranges.push(line_start..selection.end);
13702 } else {
13703 new_selection_ranges.push(selection.start..selection.end);
13704 }
13705 } else {
13706 new_selection_ranges.push(selection.end..selection.end);
13707 }
13708 }
13709 }
13710 }
13711 self.change_selections(Default::default(), window, cx, |s| {
13712 s.select_ranges(new_selection_ranges);
13713 });
13714 }
13715
13716 pub fn add_selection_above(
13717 &mut self,
13718 _: &AddSelectionAbove,
13719 window: &mut Window,
13720 cx: &mut Context<Self>,
13721 ) {
13722 self.add_selection(true, window, cx);
13723 }
13724
13725 pub fn add_selection_below(
13726 &mut self,
13727 _: &AddSelectionBelow,
13728 window: &mut Window,
13729 cx: &mut Context<Self>,
13730 ) {
13731 self.add_selection(false, window, cx);
13732 }
13733
13734 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13736
13737 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13738 let all_selections = self.selections.all::<Point>(cx);
13739 let text_layout_details = self.text_layout_details(window);
13740
13741 let (mut columnar_selections, new_selections_to_columnarize) = {
13742 if let Some(state) = self.add_selections_state.as_ref() {
13743 let columnar_selection_ids: HashSet<_> = state
13744 .groups
13745 .iter()
13746 .flat_map(|group| group.stack.iter())
13747 .copied()
13748 .collect();
13749
13750 all_selections
13751 .into_iter()
13752 .partition(|s| columnar_selection_ids.contains(&s.id))
13753 } else {
13754 (Vec::new(), all_selections)
13755 }
13756 };
13757
13758 let mut state = self
13759 .add_selections_state
13760 .take()
13761 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13762
13763 for selection in new_selections_to_columnarize {
13764 let range = selection.display_range(&display_map).sorted();
13765 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13766 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13767 let positions = start_x.min(end_x)..start_x.max(end_x);
13768 let mut stack = Vec::new();
13769 for row in range.start.row().0..=range.end.row().0 {
13770 if let Some(selection) = self.selections.build_columnar_selection(
13771 &display_map,
13772 DisplayRow(row),
13773 &positions,
13774 selection.reversed,
13775 &text_layout_details,
13776 ) {
13777 stack.push(selection.id);
13778 columnar_selections.push(selection);
13779 }
13780 }
13781 if !stack.is_empty() {
13782 if above {
13783 stack.reverse();
13784 }
13785 state.groups.push(AddSelectionsGroup { above, stack });
13786 }
13787 }
13788
13789 let mut final_selections = Vec::new();
13790 let end_row = if above {
13791 DisplayRow(0)
13792 } else {
13793 display_map.max_point().row()
13794 };
13795
13796 let mut last_added_item_per_group = HashMap::default();
13797 for group in state.groups.iter_mut() {
13798 if let Some(last_id) = group.stack.last() {
13799 last_added_item_per_group.insert(*last_id, group);
13800 }
13801 }
13802
13803 for selection in columnar_selections {
13804 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13805 if above == group.above {
13806 let range = selection.display_range(&display_map).sorted();
13807 debug_assert_eq!(range.start.row(), range.end.row());
13808 let mut row = range.start.row();
13809 let positions =
13810 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13811 px(start)..px(end)
13812 } else {
13813 let start_x =
13814 display_map.x_for_display_point(range.start, &text_layout_details);
13815 let end_x =
13816 display_map.x_for_display_point(range.end, &text_layout_details);
13817 start_x.min(end_x)..start_x.max(end_x)
13818 };
13819
13820 let mut maybe_new_selection = None;
13821 while row != end_row {
13822 if above {
13823 row.0 -= 1;
13824 } else {
13825 row.0 += 1;
13826 }
13827 if let Some(new_selection) = self.selections.build_columnar_selection(
13828 &display_map,
13829 row,
13830 &positions,
13831 selection.reversed,
13832 &text_layout_details,
13833 ) {
13834 maybe_new_selection = Some(new_selection);
13835 break;
13836 }
13837 }
13838
13839 if let Some(new_selection) = maybe_new_selection {
13840 group.stack.push(new_selection.id);
13841 if above {
13842 final_selections.push(new_selection);
13843 final_selections.push(selection);
13844 } else {
13845 final_selections.push(selection);
13846 final_selections.push(new_selection);
13847 }
13848 } else {
13849 final_selections.push(selection);
13850 }
13851 } else {
13852 group.stack.pop();
13853 }
13854 } else {
13855 final_selections.push(selection);
13856 }
13857 }
13858
13859 self.change_selections(Default::default(), window, cx, |s| {
13860 s.select(final_selections);
13861 });
13862
13863 let final_selection_ids: HashSet<_> = self
13864 .selections
13865 .all::<Point>(cx)
13866 .iter()
13867 .map(|s| s.id)
13868 .collect();
13869 state.groups.retain_mut(|group| {
13870 // selections might get merged above so we remove invalid items from stacks
13871 group.stack.retain(|id| final_selection_ids.contains(id));
13872
13873 // single selection in stack can be treated as initial state
13874 group.stack.len() > 1
13875 });
13876
13877 if !state.groups.is_empty() {
13878 self.add_selections_state = Some(state);
13879 }
13880 }
13881
13882 fn select_match_ranges(
13883 &mut self,
13884 range: Range<usize>,
13885 reversed: bool,
13886 replace_newest: bool,
13887 auto_scroll: Option<Autoscroll>,
13888 window: &mut Window,
13889 cx: &mut Context<Editor>,
13890 ) {
13891 self.unfold_ranges(
13892 std::slice::from_ref(&range),
13893 false,
13894 auto_scroll.is_some(),
13895 cx,
13896 );
13897 let effects = if let Some(scroll) = auto_scroll {
13898 SelectionEffects::scroll(scroll)
13899 } else {
13900 SelectionEffects::no_scroll()
13901 };
13902 self.change_selections(effects, window, cx, |s| {
13903 if replace_newest {
13904 s.delete(s.newest_anchor().id);
13905 }
13906 if reversed {
13907 s.insert_range(range.end..range.start);
13908 } else {
13909 s.insert_range(range);
13910 }
13911 });
13912 }
13913
13914 pub fn select_next_match_internal(
13915 &mut self,
13916 display_map: &DisplaySnapshot,
13917 replace_newest: bool,
13918 autoscroll: Option<Autoscroll>,
13919 window: &mut Window,
13920 cx: &mut Context<Self>,
13921 ) -> Result<()> {
13922 let buffer = &display_map.buffer_snapshot;
13923 let mut selections = self.selections.all::<usize>(cx);
13924 if let Some(mut select_next_state) = self.select_next_state.take() {
13925 let query = &select_next_state.query;
13926 if !select_next_state.done {
13927 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13928 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13929 let mut next_selected_range = None;
13930
13931 let bytes_after_last_selection =
13932 buffer.bytes_in_range(last_selection.end..buffer.len());
13933 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13934 let query_matches = query
13935 .stream_find_iter(bytes_after_last_selection)
13936 .map(|result| (last_selection.end, result))
13937 .chain(
13938 query
13939 .stream_find_iter(bytes_before_first_selection)
13940 .map(|result| (0, result)),
13941 );
13942
13943 for (start_offset, query_match) in query_matches {
13944 let query_match = query_match.unwrap(); // can only fail due to I/O
13945 let offset_range =
13946 start_offset + query_match.start()..start_offset + query_match.end();
13947
13948 if !select_next_state.wordwise
13949 || (!buffer.is_inside_word(offset_range.start, false)
13950 && !buffer.is_inside_word(offset_range.end, false))
13951 {
13952 // TODO: This is n^2, because we might check all the selections
13953 if !selections
13954 .iter()
13955 .any(|selection| selection.range().overlaps(&offset_range))
13956 {
13957 next_selected_range = Some(offset_range);
13958 break;
13959 }
13960 }
13961 }
13962
13963 if let Some(next_selected_range) = next_selected_range {
13964 self.select_match_ranges(
13965 next_selected_range,
13966 last_selection.reversed,
13967 replace_newest,
13968 autoscroll,
13969 window,
13970 cx,
13971 );
13972 } else {
13973 select_next_state.done = true;
13974 }
13975 }
13976
13977 self.select_next_state = Some(select_next_state);
13978 } else {
13979 let mut only_carets = true;
13980 let mut same_text_selected = true;
13981 let mut selected_text = None;
13982
13983 let mut selections_iter = selections.iter().peekable();
13984 while let Some(selection) = selections_iter.next() {
13985 if selection.start != selection.end {
13986 only_carets = false;
13987 }
13988
13989 if same_text_selected {
13990 if selected_text.is_none() {
13991 selected_text =
13992 Some(buffer.text_for_range(selection.range()).collect::<String>());
13993 }
13994
13995 if let Some(next_selection) = selections_iter.peek() {
13996 if next_selection.range().len() == selection.range().len() {
13997 let next_selected_text = buffer
13998 .text_for_range(next_selection.range())
13999 .collect::<String>();
14000 if Some(next_selected_text) != selected_text {
14001 same_text_selected = false;
14002 selected_text = None;
14003 }
14004 } else {
14005 same_text_selected = false;
14006 selected_text = None;
14007 }
14008 }
14009 }
14010 }
14011
14012 if only_carets {
14013 for selection in &mut selections {
14014 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14015 selection.start = word_range.start;
14016 selection.end = word_range.end;
14017 selection.goal = SelectionGoal::None;
14018 selection.reversed = false;
14019 self.select_match_ranges(
14020 selection.start..selection.end,
14021 selection.reversed,
14022 replace_newest,
14023 autoscroll,
14024 window,
14025 cx,
14026 );
14027 }
14028
14029 if selections.len() == 1 {
14030 let selection = selections
14031 .last()
14032 .expect("ensured that there's only one selection");
14033 let query = buffer
14034 .text_for_range(selection.start..selection.end)
14035 .collect::<String>();
14036 let is_empty = query.is_empty();
14037 let select_state = SelectNextState {
14038 query: AhoCorasick::new(&[query])?,
14039 wordwise: true,
14040 done: is_empty,
14041 };
14042 self.select_next_state = Some(select_state);
14043 } else {
14044 self.select_next_state = None;
14045 }
14046 } else if let Some(selected_text) = selected_text {
14047 self.select_next_state = Some(SelectNextState {
14048 query: AhoCorasick::new(&[selected_text])?,
14049 wordwise: false,
14050 done: false,
14051 });
14052 self.select_next_match_internal(
14053 display_map,
14054 replace_newest,
14055 autoscroll,
14056 window,
14057 cx,
14058 )?;
14059 }
14060 }
14061 Ok(())
14062 }
14063
14064 pub fn select_all_matches(
14065 &mut self,
14066 _action: &SelectAllMatches,
14067 window: &mut Window,
14068 cx: &mut Context<Self>,
14069 ) -> Result<()> {
14070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14071
14072 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14073
14074 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14075 let Some(select_next_state) = self.select_next_state.as_mut() else {
14076 return Ok(());
14077 };
14078 if select_next_state.done {
14079 return Ok(());
14080 }
14081
14082 let mut new_selections = Vec::new();
14083
14084 let reversed = self.selections.oldest::<usize>(cx).reversed;
14085 let buffer = &display_map.buffer_snapshot;
14086 let query_matches = select_next_state
14087 .query
14088 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14089
14090 for query_match in query_matches.into_iter() {
14091 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14092 let offset_range = if reversed {
14093 query_match.end()..query_match.start()
14094 } else {
14095 query_match.start()..query_match.end()
14096 };
14097
14098 if !select_next_state.wordwise
14099 || (!buffer.is_inside_word(offset_range.start, false)
14100 && !buffer.is_inside_word(offset_range.end, false))
14101 {
14102 new_selections.push(offset_range.start..offset_range.end);
14103 }
14104 }
14105
14106 select_next_state.done = true;
14107
14108 if new_selections.is_empty() {
14109 log::error!("bug: new_selections is empty in select_all_matches");
14110 return Ok(());
14111 }
14112
14113 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14114 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14115 selections.select_ranges(new_selections)
14116 });
14117
14118 Ok(())
14119 }
14120
14121 pub fn select_next(
14122 &mut self,
14123 action: &SelectNext,
14124 window: &mut Window,
14125 cx: &mut Context<Self>,
14126 ) -> Result<()> {
14127 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14128 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14129 self.select_next_match_internal(
14130 &display_map,
14131 action.replace_newest,
14132 Some(Autoscroll::newest()),
14133 window,
14134 cx,
14135 )?;
14136 Ok(())
14137 }
14138
14139 pub fn select_previous(
14140 &mut self,
14141 action: &SelectPrevious,
14142 window: &mut Window,
14143 cx: &mut Context<Self>,
14144 ) -> Result<()> {
14145 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14146 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14147 let buffer = &display_map.buffer_snapshot;
14148 let mut selections = self.selections.all::<usize>(cx);
14149 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14150 let query = &select_prev_state.query;
14151 if !select_prev_state.done {
14152 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14153 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14154 let mut next_selected_range = None;
14155 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14156 let bytes_before_last_selection =
14157 buffer.reversed_bytes_in_range(0..last_selection.start);
14158 let bytes_after_first_selection =
14159 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14160 let query_matches = query
14161 .stream_find_iter(bytes_before_last_selection)
14162 .map(|result| (last_selection.start, result))
14163 .chain(
14164 query
14165 .stream_find_iter(bytes_after_first_selection)
14166 .map(|result| (buffer.len(), result)),
14167 );
14168 for (end_offset, query_match) in query_matches {
14169 let query_match = query_match.unwrap(); // can only fail due to I/O
14170 let offset_range =
14171 end_offset - query_match.end()..end_offset - query_match.start();
14172
14173 if !select_prev_state.wordwise
14174 || (!buffer.is_inside_word(offset_range.start, false)
14175 && !buffer.is_inside_word(offset_range.end, false))
14176 {
14177 next_selected_range = Some(offset_range);
14178 break;
14179 }
14180 }
14181
14182 if let Some(next_selected_range) = next_selected_range {
14183 self.select_match_ranges(
14184 next_selected_range,
14185 last_selection.reversed,
14186 action.replace_newest,
14187 Some(Autoscroll::newest()),
14188 window,
14189 cx,
14190 );
14191 } else {
14192 select_prev_state.done = true;
14193 }
14194 }
14195
14196 self.select_prev_state = Some(select_prev_state);
14197 } else {
14198 let mut only_carets = true;
14199 let mut same_text_selected = true;
14200 let mut selected_text = None;
14201
14202 let mut selections_iter = selections.iter().peekable();
14203 while let Some(selection) = selections_iter.next() {
14204 if selection.start != selection.end {
14205 only_carets = false;
14206 }
14207
14208 if same_text_selected {
14209 if selected_text.is_none() {
14210 selected_text =
14211 Some(buffer.text_for_range(selection.range()).collect::<String>());
14212 }
14213
14214 if let Some(next_selection) = selections_iter.peek() {
14215 if next_selection.range().len() == selection.range().len() {
14216 let next_selected_text = buffer
14217 .text_for_range(next_selection.range())
14218 .collect::<String>();
14219 if Some(next_selected_text) != selected_text {
14220 same_text_selected = false;
14221 selected_text = None;
14222 }
14223 } else {
14224 same_text_selected = false;
14225 selected_text = None;
14226 }
14227 }
14228 }
14229 }
14230
14231 if only_carets {
14232 for selection in &mut selections {
14233 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14234 selection.start = word_range.start;
14235 selection.end = word_range.end;
14236 selection.goal = SelectionGoal::None;
14237 selection.reversed = false;
14238 self.select_match_ranges(
14239 selection.start..selection.end,
14240 selection.reversed,
14241 action.replace_newest,
14242 Some(Autoscroll::newest()),
14243 window,
14244 cx,
14245 );
14246 }
14247 if selections.len() == 1 {
14248 let selection = selections
14249 .last()
14250 .expect("ensured that there's only one selection");
14251 let query = buffer
14252 .text_for_range(selection.start..selection.end)
14253 .collect::<String>();
14254 let is_empty = query.is_empty();
14255 let select_state = SelectNextState {
14256 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14257 wordwise: true,
14258 done: is_empty,
14259 };
14260 self.select_prev_state = Some(select_state);
14261 } else {
14262 self.select_prev_state = None;
14263 }
14264 } else if let Some(selected_text) = selected_text {
14265 self.select_prev_state = Some(SelectNextState {
14266 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14267 wordwise: false,
14268 done: false,
14269 });
14270 self.select_previous(action, window, cx)?;
14271 }
14272 }
14273 Ok(())
14274 }
14275
14276 pub fn find_next_match(
14277 &mut self,
14278 _: &FindNextMatch,
14279 window: &mut Window,
14280 cx: &mut Context<Self>,
14281 ) -> Result<()> {
14282 let selections = self.selections.disjoint_anchors();
14283 match selections.first() {
14284 Some(first) if selections.len() >= 2 => {
14285 self.change_selections(Default::default(), window, cx, |s| {
14286 s.select_ranges([first.range()]);
14287 });
14288 }
14289 _ => self.select_next(
14290 &SelectNext {
14291 replace_newest: true,
14292 },
14293 window,
14294 cx,
14295 )?,
14296 }
14297 Ok(())
14298 }
14299
14300 pub fn find_previous_match(
14301 &mut self,
14302 _: &FindPreviousMatch,
14303 window: &mut Window,
14304 cx: &mut Context<Self>,
14305 ) -> Result<()> {
14306 let selections = self.selections.disjoint_anchors();
14307 match selections.last() {
14308 Some(last) if selections.len() >= 2 => {
14309 self.change_selections(Default::default(), window, cx, |s| {
14310 s.select_ranges([last.range()]);
14311 });
14312 }
14313 _ => self.select_previous(
14314 &SelectPrevious {
14315 replace_newest: true,
14316 },
14317 window,
14318 cx,
14319 )?,
14320 }
14321 Ok(())
14322 }
14323
14324 pub fn toggle_comments(
14325 &mut self,
14326 action: &ToggleComments,
14327 window: &mut Window,
14328 cx: &mut Context<Self>,
14329 ) {
14330 if self.read_only(cx) {
14331 return;
14332 }
14333 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14334 let text_layout_details = &self.text_layout_details(window);
14335 self.transact(window, cx, |this, window, cx| {
14336 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14337 let mut edits = Vec::new();
14338 let mut selection_edit_ranges = Vec::new();
14339 let mut last_toggled_row = None;
14340 let snapshot = this.buffer.read(cx).read(cx);
14341 let empty_str: Arc<str> = Arc::default();
14342 let mut suffixes_inserted = Vec::new();
14343 let ignore_indent = action.ignore_indent;
14344
14345 fn comment_prefix_range(
14346 snapshot: &MultiBufferSnapshot,
14347 row: MultiBufferRow,
14348 comment_prefix: &str,
14349 comment_prefix_whitespace: &str,
14350 ignore_indent: bool,
14351 ) -> Range<Point> {
14352 let indent_size = if ignore_indent {
14353 0
14354 } else {
14355 snapshot.indent_size_for_line(row).len
14356 };
14357
14358 let start = Point::new(row.0, indent_size);
14359
14360 let mut line_bytes = snapshot
14361 .bytes_in_range(start..snapshot.max_point())
14362 .flatten()
14363 .copied();
14364
14365 // If this line currently begins with the line comment prefix, then record
14366 // the range containing the prefix.
14367 if line_bytes
14368 .by_ref()
14369 .take(comment_prefix.len())
14370 .eq(comment_prefix.bytes())
14371 {
14372 // Include any whitespace that matches the comment prefix.
14373 let matching_whitespace_len = line_bytes
14374 .zip(comment_prefix_whitespace.bytes())
14375 .take_while(|(a, b)| a == b)
14376 .count() as u32;
14377 let end = Point::new(
14378 start.row,
14379 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14380 );
14381 start..end
14382 } else {
14383 start..start
14384 }
14385 }
14386
14387 fn comment_suffix_range(
14388 snapshot: &MultiBufferSnapshot,
14389 row: MultiBufferRow,
14390 comment_suffix: &str,
14391 comment_suffix_has_leading_space: bool,
14392 ) -> Range<Point> {
14393 let end = Point::new(row.0, snapshot.line_len(row));
14394 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14395
14396 let mut line_end_bytes = snapshot
14397 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14398 .flatten()
14399 .copied();
14400
14401 let leading_space_len = if suffix_start_column > 0
14402 && line_end_bytes.next() == Some(b' ')
14403 && comment_suffix_has_leading_space
14404 {
14405 1
14406 } else {
14407 0
14408 };
14409
14410 // If this line currently begins with the line comment prefix, then record
14411 // the range containing the prefix.
14412 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14413 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14414 start..end
14415 } else {
14416 end..end
14417 }
14418 }
14419
14420 // TODO: Handle selections that cross excerpts
14421 for selection in &mut selections {
14422 let start_column = snapshot
14423 .indent_size_for_line(MultiBufferRow(selection.start.row))
14424 .len;
14425 let language = if let Some(language) =
14426 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14427 {
14428 language
14429 } else {
14430 continue;
14431 };
14432
14433 selection_edit_ranges.clear();
14434
14435 // If multiple selections contain a given row, avoid processing that
14436 // row more than once.
14437 let mut start_row = MultiBufferRow(selection.start.row);
14438 if last_toggled_row == Some(start_row) {
14439 start_row = start_row.next_row();
14440 }
14441 let end_row =
14442 if selection.end.row > selection.start.row && selection.end.column == 0 {
14443 MultiBufferRow(selection.end.row - 1)
14444 } else {
14445 MultiBufferRow(selection.end.row)
14446 };
14447 last_toggled_row = Some(end_row);
14448
14449 if start_row > end_row {
14450 continue;
14451 }
14452
14453 // If the language has line comments, toggle those.
14454 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14455
14456 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14457 if ignore_indent {
14458 full_comment_prefixes = full_comment_prefixes
14459 .into_iter()
14460 .map(|s| Arc::from(s.trim_end()))
14461 .collect();
14462 }
14463
14464 if !full_comment_prefixes.is_empty() {
14465 let first_prefix = full_comment_prefixes
14466 .first()
14467 .expect("prefixes is non-empty");
14468 let prefix_trimmed_lengths = full_comment_prefixes
14469 .iter()
14470 .map(|p| p.trim_end_matches(' ').len())
14471 .collect::<SmallVec<[usize; 4]>>();
14472
14473 let mut all_selection_lines_are_comments = true;
14474
14475 for row in start_row.0..=end_row.0 {
14476 let row = MultiBufferRow(row);
14477 if start_row < end_row && snapshot.is_line_blank(row) {
14478 continue;
14479 }
14480
14481 let prefix_range = full_comment_prefixes
14482 .iter()
14483 .zip(prefix_trimmed_lengths.iter().copied())
14484 .map(|(prefix, trimmed_prefix_len)| {
14485 comment_prefix_range(
14486 snapshot.deref(),
14487 row,
14488 &prefix[..trimmed_prefix_len],
14489 &prefix[trimmed_prefix_len..],
14490 ignore_indent,
14491 )
14492 })
14493 .max_by_key(|range| range.end.column - range.start.column)
14494 .expect("prefixes is non-empty");
14495
14496 if prefix_range.is_empty() {
14497 all_selection_lines_are_comments = false;
14498 }
14499
14500 selection_edit_ranges.push(prefix_range);
14501 }
14502
14503 if all_selection_lines_are_comments {
14504 edits.extend(
14505 selection_edit_ranges
14506 .iter()
14507 .cloned()
14508 .map(|range| (range, empty_str.clone())),
14509 );
14510 } else {
14511 let min_column = selection_edit_ranges
14512 .iter()
14513 .map(|range| range.start.column)
14514 .min()
14515 .unwrap_or(0);
14516 edits.extend(selection_edit_ranges.iter().map(|range| {
14517 let position = Point::new(range.start.row, min_column);
14518 (position..position, first_prefix.clone())
14519 }));
14520 }
14521 } else if let Some(BlockCommentConfig {
14522 start: full_comment_prefix,
14523 end: comment_suffix,
14524 ..
14525 }) = language.block_comment()
14526 {
14527 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14528 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14529 let prefix_range = comment_prefix_range(
14530 snapshot.deref(),
14531 start_row,
14532 comment_prefix,
14533 comment_prefix_whitespace,
14534 ignore_indent,
14535 );
14536 let suffix_range = comment_suffix_range(
14537 snapshot.deref(),
14538 end_row,
14539 comment_suffix.trim_start_matches(' '),
14540 comment_suffix.starts_with(' '),
14541 );
14542
14543 if prefix_range.is_empty() || suffix_range.is_empty() {
14544 edits.push((
14545 prefix_range.start..prefix_range.start,
14546 full_comment_prefix.clone(),
14547 ));
14548 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14549 suffixes_inserted.push((end_row, comment_suffix.len()));
14550 } else {
14551 edits.push((prefix_range, empty_str.clone()));
14552 edits.push((suffix_range, empty_str.clone()));
14553 }
14554 } else {
14555 continue;
14556 }
14557 }
14558
14559 drop(snapshot);
14560 this.buffer.update(cx, |buffer, cx| {
14561 buffer.edit(edits, None, cx);
14562 });
14563
14564 // Adjust selections so that they end before any comment suffixes that
14565 // were inserted.
14566 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14567 let mut selections = this.selections.all::<Point>(cx);
14568 let snapshot = this.buffer.read(cx).read(cx);
14569 for selection in &mut selections {
14570 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14571 match row.cmp(&MultiBufferRow(selection.end.row)) {
14572 Ordering::Less => {
14573 suffixes_inserted.next();
14574 continue;
14575 }
14576 Ordering::Greater => break,
14577 Ordering::Equal => {
14578 if selection.end.column == snapshot.line_len(row) {
14579 if selection.is_empty() {
14580 selection.start.column -= suffix_len as u32;
14581 }
14582 selection.end.column -= suffix_len as u32;
14583 }
14584 break;
14585 }
14586 }
14587 }
14588 }
14589
14590 drop(snapshot);
14591 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14592
14593 let selections = this.selections.all::<Point>(cx);
14594 let selections_on_single_row = selections.windows(2).all(|selections| {
14595 selections[0].start.row == selections[1].start.row
14596 && selections[0].end.row == selections[1].end.row
14597 && selections[0].start.row == selections[0].end.row
14598 });
14599 let selections_selecting = selections
14600 .iter()
14601 .any(|selection| selection.start != selection.end);
14602 let advance_downwards = action.advance_downwards
14603 && selections_on_single_row
14604 && !selections_selecting
14605 && !matches!(this.mode, EditorMode::SingleLine { .. });
14606
14607 if advance_downwards {
14608 let snapshot = this.buffer.read(cx).snapshot(cx);
14609
14610 this.change_selections(Default::default(), window, cx, |s| {
14611 s.move_cursors_with(|display_snapshot, display_point, _| {
14612 let mut point = display_point.to_point(display_snapshot);
14613 point.row += 1;
14614 point = snapshot.clip_point(point, Bias::Left);
14615 let display_point = point.to_display_point(display_snapshot);
14616 let goal = SelectionGoal::HorizontalPosition(
14617 display_snapshot
14618 .x_for_display_point(display_point, text_layout_details)
14619 .into(),
14620 );
14621 (display_point, goal)
14622 })
14623 });
14624 }
14625 });
14626 }
14627
14628 pub fn select_enclosing_symbol(
14629 &mut self,
14630 _: &SelectEnclosingSymbol,
14631 window: &mut Window,
14632 cx: &mut Context<Self>,
14633 ) {
14634 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14635
14636 let buffer = self.buffer.read(cx).snapshot(cx);
14637 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14638
14639 fn update_selection(
14640 selection: &Selection<usize>,
14641 buffer_snap: &MultiBufferSnapshot,
14642 ) -> Option<Selection<usize>> {
14643 let cursor = selection.head();
14644 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14645 for symbol in symbols.iter().rev() {
14646 let start = symbol.range.start.to_offset(buffer_snap);
14647 let end = symbol.range.end.to_offset(buffer_snap);
14648 let new_range = start..end;
14649 if start < selection.start || end > selection.end {
14650 return Some(Selection {
14651 id: selection.id,
14652 start: new_range.start,
14653 end: new_range.end,
14654 goal: SelectionGoal::None,
14655 reversed: selection.reversed,
14656 });
14657 }
14658 }
14659 None
14660 }
14661
14662 let mut selected_larger_symbol = false;
14663 let new_selections = old_selections
14664 .iter()
14665 .map(|selection| match update_selection(selection, &buffer) {
14666 Some(new_selection) => {
14667 if new_selection.range() != selection.range() {
14668 selected_larger_symbol = true;
14669 }
14670 new_selection
14671 }
14672 None => selection.clone(),
14673 })
14674 .collect::<Vec<_>>();
14675
14676 if selected_larger_symbol {
14677 self.change_selections(Default::default(), window, cx, |s| {
14678 s.select(new_selections);
14679 });
14680 }
14681 }
14682
14683 pub fn select_larger_syntax_node(
14684 &mut self,
14685 _: &SelectLargerSyntaxNode,
14686 window: &mut Window,
14687 cx: &mut Context<Self>,
14688 ) {
14689 let Some(visible_row_count) = self.visible_row_count() else {
14690 return;
14691 };
14692 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14693 if old_selections.is_empty() {
14694 return;
14695 }
14696
14697 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14698
14699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14700 let buffer = self.buffer.read(cx).snapshot(cx);
14701
14702 let mut selected_larger_node = false;
14703 let mut new_selections = old_selections
14704 .iter()
14705 .map(|selection| {
14706 let old_range = selection.start..selection.end;
14707
14708 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14709 // manually select word at selection
14710 if ["string_content", "inline"].contains(&node.kind()) {
14711 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14712 // ignore if word is already selected
14713 if !word_range.is_empty() && old_range != word_range {
14714 let (last_word_range, _) =
14715 buffer.surrounding_word(old_range.end, false);
14716 // only select word if start and end point belongs to same word
14717 if word_range == last_word_range {
14718 selected_larger_node = true;
14719 return Selection {
14720 id: selection.id,
14721 start: word_range.start,
14722 end: word_range.end,
14723 goal: SelectionGoal::None,
14724 reversed: selection.reversed,
14725 };
14726 }
14727 }
14728 }
14729 }
14730
14731 let mut new_range = old_range.clone();
14732 while let Some((_node, containing_range)) =
14733 buffer.syntax_ancestor(new_range.clone())
14734 {
14735 new_range = match containing_range {
14736 MultiOrSingleBufferOffsetRange::Single(_) => break,
14737 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14738 };
14739 if !display_map.intersects_fold(new_range.start)
14740 && !display_map.intersects_fold(new_range.end)
14741 {
14742 break;
14743 }
14744 }
14745
14746 selected_larger_node |= new_range != old_range;
14747 Selection {
14748 id: selection.id,
14749 start: new_range.start,
14750 end: new_range.end,
14751 goal: SelectionGoal::None,
14752 reversed: selection.reversed,
14753 }
14754 })
14755 .collect::<Vec<_>>();
14756
14757 if !selected_larger_node {
14758 return; // don't put this call in the history
14759 }
14760
14761 // scroll based on transformation done to the last selection created by the user
14762 let (last_old, last_new) = old_selections
14763 .last()
14764 .zip(new_selections.last().cloned())
14765 .expect("old_selections isn't empty");
14766
14767 // revert selection
14768 let is_selection_reversed = {
14769 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14770 new_selections.last_mut().expect("checked above").reversed =
14771 should_newest_selection_be_reversed;
14772 should_newest_selection_be_reversed
14773 };
14774
14775 if selected_larger_node {
14776 self.select_syntax_node_history.disable_clearing = true;
14777 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14778 s.select(new_selections.clone());
14779 });
14780 self.select_syntax_node_history.disable_clearing = false;
14781 }
14782
14783 let start_row = last_new.start.to_display_point(&display_map).row().0;
14784 let end_row = last_new.end.to_display_point(&display_map).row().0;
14785 let selection_height = end_row - start_row + 1;
14786 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14787
14788 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14789 let scroll_behavior = if fits_on_the_screen {
14790 self.request_autoscroll(Autoscroll::fit(), cx);
14791 SelectSyntaxNodeScrollBehavior::FitSelection
14792 } else if is_selection_reversed {
14793 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14794 SelectSyntaxNodeScrollBehavior::CursorTop
14795 } else {
14796 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14797 SelectSyntaxNodeScrollBehavior::CursorBottom
14798 };
14799
14800 self.select_syntax_node_history.push((
14801 old_selections,
14802 scroll_behavior,
14803 is_selection_reversed,
14804 ));
14805 }
14806
14807 pub fn select_smaller_syntax_node(
14808 &mut self,
14809 _: &SelectSmallerSyntaxNode,
14810 window: &mut Window,
14811 cx: &mut Context<Self>,
14812 ) {
14813 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14814
14815 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14816 self.select_syntax_node_history.pop()
14817 {
14818 if let Some(selection) = selections.last_mut() {
14819 selection.reversed = is_selection_reversed;
14820 }
14821
14822 self.select_syntax_node_history.disable_clearing = true;
14823 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14824 s.select(selections.to_vec());
14825 });
14826 self.select_syntax_node_history.disable_clearing = false;
14827
14828 match scroll_behavior {
14829 SelectSyntaxNodeScrollBehavior::CursorTop => {
14830 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14831 }
14832 SelectSyntaxNodeScrollBehavior::FitSelection => {
14833 self.request_autoscroll(Autoscroll::fit(), cx);
14834 }
14835 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14836 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14837 }
14838 }
14839 }
14840 }
14841
14842 pub fn unwrap_syntax_node(
14843 &mut self,
14844 _: &UnwrapSyntaxNode,
14845 window: &mut Window,
14846 cx: &mut Context<Self>,
14847 ) {
14848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14849
14850 let buffer = self.buffer.read(cx).snapshot(cx);
14851 let selections = self
14852 .selections
14853 .all::<usize>(cx)
14854 .into_iter()
14855 // subtracting the offset requires sorting
14856 .sorted_by_key(|i| i.start);
14857
14858 let full_edits = selections
14859 .into_iter()
14860 .filter_map(|selection| {
14861 // Only requires two branches once if-let-chains stabilize (#53667)
14862 let child = if !selection.is_empty() {
14863 selection.range()
14864 } else if let Some((_, ancestor_range)) =
14865 buffer.syntax_ancestor(selection.start..selection.end)
14866 {
14867 match ancestor_range {
14868 MultiOrSingleBufferOffsetRange::Single(range) => range,
14869 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14870 }
14871 } else {
14872 selection.range()
14873 };
14874
14875 let mut parent = child.clone();
14876 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
14877 parent = match ancestor_range {
14878 MultiOrSingleBufferOffsetRange::Single(range) => range,
14879 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14880 };
14881 if parent.start < child.start || parent.end > child.end {
14882 break;
14883 }
14884 }
14885
14886 if parent == child {
14887 return None;
14888 }
14889 let text = buffer.text_for_range(child.clone()).collect::<String>();
14890 Some((selection.id, parent, text))
14891 })
14892 .collect::<Vec<_>>();
14893
14894 self.transact(window, cx, |this, window, cx| {
14895 this.buffer.update(cx, |buffer, cx| {
14896 buffer.edit(
14897 full_edits
14898 .iter()
14899 .map(|(_, p, t)| (p.clone(), t.clone()))
14900 .collect::<Vec<_>>(),
14901 None,
14902 cx,
14903 );
14904 });
14905 this.change_selections(Default::default(), window, cx, |s| {
14906 let mut offset = 0;
14907 let mut selections = vec![];
14908 for (id, parent, text) in full_edits {
14909 let start = parent.start - offset;
14910 offset += parent.len() - text.len();
14911 selections.push(Selection {
14912 id: id,
14913 start,
14914 end: start + text.len(),
14915 reversed: false,
14916 goal: Default::default(),
14917 });
14918 }
14919 s.select(selections);
14920 });
14921 });
14922 }
14923
14924 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14925 if !EditorSettings::get_global(cx).gutter.runnables {
14926 self.clear_tasks();
14927 return Task::ready(());
14928 }
14929 let project = self.project().map(Entity::downgrade);
14930 let task_sources = self.lsp_task_sources(cx);
14931 let multi_buffer = self.buffer.downgrade();
14932 cx.spawn_in(window, async move |editor, cx| {
14933 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14934 let Some(project) = project.and_then(|p| p.upgrade()) else {
14935 return;
14936 };
14937 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14938 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14939 }) else {
14940 return;
14941 };
14942
14943 let hide_runnables = project
14944 .update(cx, |project, cx| {
14945 // Do not display any test indicators in non-dev server remote projects.
14946 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14947 })
14948 .unwrap_or(true);
14949 if hide_runnables {
14950 return;
14951 }
14952 let new_rows =
14953 cx.background_spawn({
14954 let snapshot = display_snapshot.clone();
14955 async move {
14956 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14957 }
14958 })
14959 .await;
14960 let Ok(lsp_tasks) =
14961 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14962 else {
14963 return;
14964 };
14965 let lsp_tasks = lsp_tasks.await;
14966
14967 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14968 lsp_tasks
14969 .into_iter()
14970 .flat_map(|(kind, tasks)| {
14971 tasks.into_iter().filter_map(move |(location, task)| {
14972 Some((kind.clone(), location?, task))
14973 })
14974 })
14975 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14976 let buffer = location.target.buffer;
14977 let buffer_snapshot = buffer.read(cx).snapshot();
14978 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14979 |(excerpt_id, snapshot, _)| {
14980 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14981 display_snapshot
14982 .buffer_snapshot
14983 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14984 } else {
14985 None
14986 }
14987 },
14988 );
14989 if let Some(offset) = offset {
14990 let task_buffer_range =
14991 location.target.range.to_point(&buffer_snapshot);
14992 let context_buffer_range =
14993 task_buffer_range.to_offset(&buffer_snapshot);
14994 let context_range = BufferOffset(context_buffer_range.start)
14995 ..BufferOffset(context_buffer_range.end);
14996
14997 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14998 .or_insert_with(|| RunnableTasks {
14999 templates: Vec::new(),
15000 offset,
15001 column: task_buffer_range.start.column,
15002 extra_variables: HashMap::default(),
15003 context_range,
15004 })
15005 .templates
15006 .push((kind, task.original_task().clone()));
15007 }
15008
15009 acc
15010 })
15011 }) else {
15012 return;
15013 };
15014
15015 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15016 buffer.language_settings(cx).tasks.prefer_lsp
15017 }) else {
15018 return;
15019 };
15020
15021 let rows = Self::runnable_rows(
15022 project,
15023 display_snapshot,
15024 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15025 new_rows,
15026 cx.clone(),
15027 )
15028 .await;
15029 editor
15030 .update(cx, |editor, _| {
15031 editor.clear_tasks();
15032 for (key, mut value) in rows {
15033 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15034 value.templates.extend(lsp_tasks.templates);
15035 }
15036
15037 editor.insert_tasks(key, value);
15038 }
15039 for (key, value) in lsp_tasks_by_rows {
15040 editor.insert_tasks(key, value);
15041 }
15042 })
15043 .ok();
15044 })
15045 }
15046 fn fetch_runnable_ranges(
15047 snapshot: &DisplaySnapshot,
15048 range: Range<Anchor>,
15049 ) -> Vec<language::RunnableRange> {
15050 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15051 }
15052
15053 fn runnable_rows(
15054 project: Entity<Project>,
15055 snapshot: DisplaySnapshot,
15056 prefer_lsp: bool,
15057 runnable_ranges: Vec<RunnableRange>,
15058 cx: AsyncWindowContext,
15059 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15060 cx.spawn(async move |cx| {
15061 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15062 for mut runnable in runnable_ranges {
15063 let Some(tasks) = cx
15064 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15065 .ok()
15066 else {
15067 continue;
15068 };
15069 let mut tasks = tasks.await;
15070
15071 if prefer_lsp {
15072 tasks.retain(|(task_kind, _)| {
15073 !matches!(task_kind, TaskSourceKind::Language { .. })
15074 });
15075 }
15076 if tasks.is_empty() {
15077 continue;
15078 }
15079
15080 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15081 let Some(row) = snapshot
15082 .buffer_snapshot
15083 .buffer_line_for_row(MultiBufferRow(point.row))
15084 .map(|(_, range)| range.start.row)
15085 else {
15086 continue;
15087 };
15088
15089 let context_range =
15090 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15091 runnable_rows.push((
15092 (runnable.buffer_id, row),
15093 RunnableTasks {
15094 templates: tasks,
15095 offset: snapshot
15096 .buffer_snapshot
15097 .anchor_before(runnable.run_range.start),
15098 context_range,
15099 column: point.column,
15100 extra_variables: runnable.extra_captures,
15101 },
15102 ));
15103 }
15104 runnable_rows
15105 })
15106 }
15107
15108 fn templates_with_tags(
15109 project: &Entity<Project>,
15110 runnable: &mut Runnable,
15111 cx: &mut App,
15112 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15113 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15114 let (worktree_id, file) = project
15115 .buffer_for_id(runnable.buffer, cx)
15116 .and_then(|buffer| buffer.read(cx).file())
15117 .map(|file| (file.worktree_id(cx), file.clone()))
15118 .unzip();
15119
15120 (
15121 project.task_store().read(cx).task_inventory().cloned(),
15122 worktree_id,
15123 file,
15124 )
15125 });
15126
15127 let tags = mem::take(&mut runnable.tags);
15128 let language = runnable.language.clone();
15129 cx.spawn(async move |cx| {
15130 let mut templates_with_tags = Vec::new();
15131 if let Some(inventory) = inventory {
15132 for RunnableTag(tag) in tags {
15133 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15134 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15135 }) else {
15136 return templates_with_tags;
15137 };
15138 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15139 move |(_, template)| {
15140 template.tags.iter().any(|source_tag| source_tag == &tag)
15141 },
15142 ));
15143 }
15144 }
15145 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15146
15147 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15148 // Strongest source wins; if we have worktree tag binding, prefer that to
15149 // global and language bindings;
15150 // if we have a global binding, prefer that to language binding.
15151 let first_mismatch = templates_with_tags
15152 .iter()
15153 .position(|(tag_source, _)| tag_source != leading_tag_source);
15154 if let Some(index) = first_mismatch {
15155 templates_with_tags.truncate(index);
15156 }
15157 }
15158
15159 templates_with_tags
15160 })
15161 }
15162
15163 pub fn move_to_enclosing_bracket(
15164 &mut self,
15165 _: &MoveToEnclosingBracket,
15166 window: &mut Window,
15167 cx: &mut Context<Self>,
15168 ) {
15169 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15170 self.change_selections(Default::default(), window, cx, |s| {
15171 s.move_offsets_with(|snapshot, selection| {
15172 let Some(enclosing_bracket_ranges) =
15173 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15174 else {
15175 return;
15176 };
15177
15178 let mut best_length = usize::MAX;
15179 let mut best_inside = false;
15180 let mut best_in_bracket_range = false;
15181 let mut best_destination = None;
15182 for (open, close) in enclosing_bracket_ranges {
15183 let close = close.to_inclusive();
15184 let length = close.end() - open.start;
15185 let inside = selection.start >= open.end && selection.end <= *close.start();
15186 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15187 || close.contains(&selection.head());
15188
15189 // If best is next to a bracket and current isn't, skip
15190 if !in_bracket_range && best_in_bracket_range {
15191 continue;
15192 }
15193
15194 // Prefer smaller lengths unless best is inside and current isn't
15195 if length > best_length && (best_inside || !inside) {
15196 continue;
15197 }
15198
15199 best_length = length;
15200 best_inside = inside;
15201 best_in_bracket_range = in_bracket_range;
15202 best_destination = Some(
15203 if close.contains(&selection.start) && close.contains(&selection.end) {
15204 if inside { open.end } else { open.start }
15205 } else if inside {
15206 *close.start()
15207 } else {
15208 *close.end()
15209 },
15210 );
15211 }
15212
15213 if let Some(destination) = best_destination {
15214 selection.collapse_to(destination, SelectionGoal::None);
15215 }
15216 })
15217 });
15218 }
15219
15220 pub fn undo_selection(
15221 &mut self,
15222 _: &UndoSelection,
15223 window: &mut Window,
15224 cx: &mut Context<Self>,
15225 ) {
15226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15227 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15228 self.selection_history.mode = SelectionHistoryMode::Undoing;
15229 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15230 this.end_selection(window, cx);
15231 this.change_selections(
15232 SelectionEffects::scroll(Autoscroll::newest()),
15233 window,
15234 cx,
15235 |s| s.select_anchors(entry.selections.to_vec()),
15236 );
15237 });
15238 self.selection_history.mode = SelectionHistoryMode::Normal;
15239
15240 self.select_next_state = entry.select_next_state;
15241 self.select_prev_state = entry.select_prev_state;
15242 self.add_selections_state = entry.add_selections_state;
15243 }
15244 }
15245
15246 pub fn redo_selection(
15247 &mut self,
15248 _: &RedoSelection,
15249 window: &mut Window,
15250 cx: &mut Context<Self>,
15251 ) {
15252 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15253 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15254 self.selection_history.mode = SelectionHistoryMode::Redoing;
15255 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15256 this.end_selection(window, cx);
15257 this.change_selections(
15258 SelectionEffects::scroll(Autoscroll::newest()),
15259 window,
15260 cx,
15261 |s| s.select_anchors(entry.selections.to_vec()),
15262 );
15263 });
15264 self.selection_history.mode = SelectionHistoryMode::Normal;
15265
15266 self.select_next_state = entry.select_next_state;
15267 self.select_prev_state = entry.select_prev_state;
15268 self.add_selections_state = entry.add_selections_state;
15269 }
15270 }
15271
15272 pub fn expand_excerpts(
15273 &mut self,
15274 action: &ExpandExcerpts,
15275 _: &mut Window,
15276 cx: &mut Context<Self>,
15277 ) {
15278 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15279 }
15280
15281 pub fn expand_excerpts_down(
15282 &mut self,
15283 action: &ExpandExcerptsDown,
15284 _: &mut Window,
15285 cx: &mut Context<Self>,
15286 ) {
15287 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15288 }
15289
15290 pub fn expand_excerpts_up(
15291 &mut self,
15292 action: &ExpandExcerptsUp,
15293 _: &mut Window,
15294 cx: &mut Context<Self>,
15295 ) {
15296 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15297 }
15298
15299 pub fn expand_excerpts_for_direction(
15300 &mut self,
15301 lines: u32,
15302 direction: ExpandExcerptDirection,
15303
15304 cx: &mut Context<Self>,
15305 ) {
15306 let selections = self.selections.disjoint_anchors();
15307
15308 let lines = if lines == 0 {
15309 EditorSettings::get_global(cx).expand_excerpt_lines
15310 } else {
15311 lines
15312 };
15313
15314 self.buffer.update(cx, |buffer, cx| {
15315 let snapshot = buffer.snapshot(cx);
15316 let mut excerpt_ids = selections
15317 .iter()
15318 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15319 .collect::<Vec<_>>();
15320 excerpt_ids.sort();
15321 excerpt_ids.dedup();
15322 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15323 })
15324 }
15325
15326 pub fn expand_excerpt(
15327 &mut self,
15328 excerpt: ExcerptId,
15329 direction: ExpandExcerptDirection,
15330 window: &mut Window,
15331 cx: &mut Context<Self>,
15332 ) {
15333 let current_scroll_position = self.scroll_position(cx);
15334 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15335 let mut should_scroll_up = false;
15336
15337 if direction == ExpandExcerptDirection::Down {
15338 let multi_buffer = self.buffer.read(cx);
15339 let snapshot = multi_buffer.snapshot(cx);
15340 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15341 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15342 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15343 {
15344 let buffer_snapshot = buffer.read(cx).snapshot();
15345 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15346 let last_row = buffer_snapshot.max_point().row;
15347 let lines_below = last_row.saturating_sub(excerpt_end_row);
15348 should_scroll_up = lines_below >= lines_to_expand;
15349 }
15350 }
15351
15352 self.buffer.update(cx, |buffer, cx| {
15353 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15354 });
15355
15356 if should_scroll_up {
15357 let new_scroll_position =
15358 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15359 self.set_scroll_position(new_scroll_position, window, cx);
15360 }
15361 }
15362
15363 pub fn go_to_singleton_buffer_point(
15364 &mut self,
15365 point: Point,
15366 window: &mut Window,
15367 cx: &mut Context<Self>,
15368 ) {
15369 self.go_to_singleton_buffer_range(point..point, window, cx);
15370 }
15371
15372 pub fn go_to_singleton_buffer_range(
15373 &mut self,
15374 range: Range<Point>,
15375 window: &mut Window,
15376 cx: &mut Context<Self>,
15377 ) {
15378 let multibuffer = self.buffer().read(cx);
15379 let Some(buffer) = multibuffer.as_singleton() else {
15380 return;
15381 };
15382 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15383 return;
15384 };
15385 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15386 return;
15387 };
15388 self.change_selections(
15389 SelectionEffects::default().nav_history(true),
15390 window,
15391 cx,
15392 |s| s.select_anchor_ranges([start..end]),
15393 );
15394 }
15395
15396 pub fn go_to_diagnostic(
15397 &mut self,
15398 action: &GoToDiagnostic,
15399 window: &mut Window,
15400 cx: &mut Context<Self>,
15401 ) {
15402 if !self.diagnostics_enabled() {
15403 return;
15404 }
15405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15406 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15407 }
15408
15409 pub fn go_to_prev_diagnostic(
15410 &mut self,
15411 action: &GoToPreviousDiagnostic,
15412 window: &mut Window,
15413 cx: &mut Context<Self>,
15414 ) {
15415 if !self.diagnostics_enabled() {
15416 return;
15417 }
15418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15419 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15420 }
15421
15422 pub fn go_to_diagnostic_impl(
15423 &mut self,
15424 direction: Direction,
15425 severity: GoToDiagnosticSeverityFilter,
15426 window: &mut Window,
15427 cx: &mut Context<Self>,
15428 ) {
15429 let buffer = self.buffer.read(cx).snapshot(cx);
15430 let selection = self.selections.newest::<usize>(cx);
15431
15432 let mut active_group_id = None;
15433 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15434 && active_group.active_range.start.to_offset(&buffer) == selection.start
15435 {
15436 active_group_id = Some(active_group.group_id);
15437 }
15438
15439 fn filtered(
15440 snapshot: EditorSnapshot,
15441 severity: GoToDiagnosticSeverityFilter,
15442 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15443 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15444 diagnostics
15445 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15446 .filter(|entry| entry.range.start != entry.range.end)
15447 .filter(|entry| !entry.diagnostic.is_unnecessary)
15448 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15449 }
15450
15451 let snapshot = self.snapshot(window, cx);
15452 let before = filtered(
15453 snapshot.clone(),
15454 severity,
15455 buffer
15456 .diagnostics_in_range(0..selection.start)
15457 .filter(|entry| entry.range.start <= selection.start),
15458 );
15459 let after = filtered(
15460 snapshot,
15461 severity,
15462 buffer
15463 .diagnostics_in_range(selection.start..buffer.len())
15464 .filter(|entry| entry.range.start >= selection.start),
15465 );
15466
15467 let mut found: Option<DiagnosticEntry<usize>> = None;
15468 if direction == Direction::Prev {
15469 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15470 {
15471 for diagnostic in prev_diagnostics.into_iter().rev() {
15472 if diagnostic.range.start != selection.start
15473 || active_group_id
15474 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15475 {
15476 found = Some(diagnostic);
15477 break 'outer;
15478 }
15479 }
15480 }
15481 } else {
15482 for diagnostic in after.chain(before) {
15483 if diagnostic.range.start != selection.start
15484 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15485 {
15486 found = Some(diagnostic);
15487 break;
15488 }
15489 }
15490 }
15491 let Some(next_diagnostic) = found else {
15492 return;
15493 };
15494
15495 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15496 return;
15497 };
15498 self.change_selections(Default::default(), window, cx, |s| {
15499 s.select_ranges(vec![
15500 next_diagnostic.range.start..next_diagnostic.range.start,
15501 ])
15502 });
15503 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15504 self.refresh_edit_prediction(false, true, window, cx);
15505 }
15506
15507 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15508 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15509 let snapshot = self.snapshot(window, cx);
15510 let selection = self.selections.newest::<Point>(cx);
15511 self.go_to_hunk_before_or_after_position(
15512 &snapshot,
15513 selection.head(),
15514 Direction::Next,
15515 window,
15516 cx,
15517 );
15518 }
15519
15520 pub fn go_to_hunk_before_or_after_position(
15521 &mut self,
15522 snapshot: &EditorSnapshot,
15523 position: Point,
15524 direction: Direction,
15525 window: &mut Window,
15526 cx: &mut Context<Editor>,
15527 ) {
15528 let row = if direction == Direction::Next {
15529 self.hunk_after_position(snapshot, position)
15530 .map(|hunk| hunk.row_range.start)
15531 } else {
15532 self.hunk_before_position(snapshot, position)
15533 };
15534
15535 if let Some(row) = row {
15536 let destination = Point::new(row.0, 0);
15537 let autoscroll = Autoscroll::center();
15538
15539 self.unfold_ranges(&[destination..destination], false, false, cx);
15540 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15541 s.select_ranges([destination..destination]);
15542 });
15543 }
15544 }
15545
15546 fn hunk_after_position(
15547 &mut self,
15548 snapshot: &EditorSnapshot,
15549 position: Point,
15550 ) -> Option<MultiBufferDiffHunk> {
15551 snapshot
15552 .buffer_snapshot
15553 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15554 .find(|hunk| hunk.row_range.start.0 > position.row)
15555 .or_else(|| {
15556 snapshot
15557 .buffer_snapshot
15558 .diff_hunks_in_range(Point::zero()..position)
15559 .find(|hunk| hunk.row_range.end.0 < position.row)
15560 })
15561 }
15562
15563 fn go_to_prev_hunk(
15564 &mut self,
15565 _: &GoToPreviousHunk,
15566 window: &mut Window,
15567 cx: &mut Context<Self>,
15568 ) {
15569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15570 let snapshot = self.snapshot(window, cx);
15571 let selection = self.selections.newest::<Point>(cx);
15572 self.go_to_hunk_before_or_after_position(
15573 &snapshot,
15574 selection.head(),
15575 Direction::Prev,
15576 window,
15577 cx,
15578 );
15579 }
15580
15581 fn hunk_before_position(
15582 &mut self,
15583 snapshot: &EditorSnapshot,
15584 position: Point,
15585 ) -> Option<MultiBufferRow> {
15586 snapshot
15587 .buffer_snapshot
15588 .diff_hunk_before(position)
15589 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15590 }
15591
15592 fn go_to_next_change(
15593 &mut self,
15594 _: &GoToNextChange,
15595 window: &mut Window,
15596 cx: &mut Context<Self>,
15597 ) {
15598 if let Some(selections) = self
15599 .change_list
15600 .next_change(1, Direction::Next)
15601 .map(|s| s.to_vec())
15602 {
15603 self.change_selections(Default::default(), window, cx, |s| {
15604 let map = s.display_map();
15605 s.select_display_ranges(selections.iter().map(|a| {
15606 let point = a.to_display_point(&map);
15607 point..point
15608 }))
15609 })
15610 }
15611 }
15612
15613 fn go_to_previous_change(
15614 &mut self,
15615 _: &GoToPreviousChange,
15616 window: &mut Window,
15617 cx: &mut Context<Self>,
15618 ) {
15619 if let Some(selections) = self
15620 .change_list
15621 .next_change(1, Direction::Prev)
15622 .map(|s| s.to_vec())
15623 {
15624 self.change_selections(Default::default(), window, cx, |s| {
15625 let map = s.display_map();
15626 s.select_display_ranges(selections.iter().map(|a| {
15627 let point = a.to_display_point(&map);
15628 point..point
15629 }))
15630 })
15631 }
15632 }
15633
15634 fn go_to_line<T: 'static>(
15635 &mut self,
15636 position: Anchor,
15637 highlight_color: Option<Hsla>,
15638 window: &mut Window,
15639 cx: &mut Context<Self>,
15640 ) {
15641 let snapshot = self.snapshot(window, cx).display_snapshot;
15642 let position = position.to_point(&snapshot.buffer_snapshot);
15643 let start = snapshot
15644 .buffer_snapshot
15645 .clip_point(Point::new(position.row, 0), Bias::Left);
15646 let end = start + Point::new(1, 0);
15647 let start = snapshot.buffer_snapshot.anchor_before(start);
15648 let end = snapshot.buffer_snapshot.anchor_before(end);
15649
15650 self.highlight_rows::<T>(
15651 start..end,
15652 highlight_color
15653 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15654 Default::default(),
15655 cx,
15656 );
15657
15658 if self.buffer.read(cx).is_singleton() {
15659 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15660 }
15661 }
15662
15663 pub fn go_to_definition(
15664 &mut self,
15665 _: &GoToDefinition,
15666 window: &mut Window,
15667 cx: &mut Context<Self>,
15668 ) -> Task<Result<Navigated>> {
15669 let definition =
15670 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15671 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15672 cx.spawn_in(window, async move |editor, cx| {
15673 if definition.await? == Navigated::Yes {
15674 return Ok(Navigated::Yes);
15675 }
15676 match fallback_strategy {
15677 GoToDefinitionFallback::None => Ok(Navigated::No),
15678 GoToDefinitionFallback::FindAllReferences => {
15679 match editor.update_in(cx, |editor, window, cx| {
15680 editor.find_all_references(&FindAllReferences, window, cx)
15681 })? {
15682 Some(references) => references.await,
15683 None => Ok(Navigated::No),
15684 }
15685 }
15686 }
15687 })
15688 }
15689
15690 pub fn go_to_declaration(
15691 &mut self,
15692 _: &GoToDeclaration,
15693 window: &mut Window,
15694 cx: &mut Context<Self>,
15695 ) -> Task<Result<Navigated>> {
15696 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15697 }
15698
15699 pub fn go_to_declaration_split(
15700 &mut self,
15701 _: &GoToDeclaration,
15702 window: &mut Window,
15703 cx: &mut Context<Self>,
15704 ) -> Task<Result<Navigated>> {
15705 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15706 }
15707
15708 pub fn go_to_implementation(
15709 &mut self,
15710 _: &GoToImplementation,
15711 window: &mut Window,
15712 cx: &mut Context<Self>,
15713 ) -> Task<Result<Navigated>> {
15714 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15715 }
15716
15717 pub fn go_to_implementation_split(
15718 &mut self,
15719 _: &GoToImplementationSplit,
15720 window: &mut Window,
15721 cx: &mut Context<Self>,
15722 ) -> Task<Result<Navigated>> {
15723 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15724 }
15725
15726 pub fn go_to_type_definition(
15727 &mut self,
15728 _: &GoToTypeDefinition,
15729 window: &mut Window,
15730 cx: &mut Context<Self>,
15731 ) -> Task<Result<Navigated>> {
15732 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15733 }
15734
15735 pub fn go_to_definition_split(
15736 &mut self,
15737 _: &GoToDefinitionSplit,
15738 window: &mut Window,
15739 cx: &mut Context<Self>,
15740 ) -> Task<Result<Navigated>> {
15741 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15742 }
15743
15744 pub fn go_to_type_definition_split(
15745 &mut self,
15746 _: &GoToTypeDefinitionSplit,
15747 window: &mut Window,
15748 cx: &mut Context<Self>,
15749 ) -> Task<Result<Navigated>> {
15750 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15751 }
15752
15753 fn go_to_definition_of_kind(
15754 &mut self,
15755 kind: GotoDefinitionKind,
15756 split: bool,
15757 window: &mut Window,
15758 cx: &mut Context<Self>,
15759 ) -> Task<Result<Navigated>> {
15760 let Some(provider) = self.semantics_provider.clone() else {
15761 return Task::ready(Ok(Navigated::No));
15762 };
15763 let head = self.selections.newest::<usize>(cx).head();
15764 let buffer = self.buffer.read(cx);
15765 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15766 return Task::ready(Ok(Navigated::No));
15767 };
15768 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15769 return Task::ready(Ok(Navigated::No));
15770 };
15771
15772 cx.spawn_in(window, async move |editor, cx| {
15773 let definitions = definitions.await?;
15774 let navigated = editor
15775 .update_in(cx, |editor, window, cx| {
15776 editor.navigate_to_hover_links(
15777 Some(kind),
15778 definitions
15779 .into_iter()
15780 .filter(|location| {
15781 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15782 })
15783 .map(HoverLink::Text)
15784 .collect::<Vec<_>>(),
15785 split,
15786 window,
15787 cx,
15788 )
15789 })?
15790 .await?;
15791 anyhow::Ok(navigated)
15792 })
15793 }
15794
15795 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15796 let selection = self.selections.newest_anchor();
15797 let head = selection.head();
15798 let tail = selection.tail();
15799
15800 let Some((buffer, start_position)) =
15801 self.buffer.read(cx).text_anchor_for_position(head, cx)
15802 else {
15803 return;
15804 };
15805
15806 let end_position = if head != tail {
15807 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15808 return;
15809 };
15810 Some(pos)
15811 } else {
15812 None
15813 };
15814
15815 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15816 let url = if let Some(end_pos) = end_position {
15817 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15818 } else {
15819 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15820 };
15821
15822 if let Some(url) = url {
15823 editor.update(cx, |_, cx| {
15824 cx.open_url(&url);
15825 })
15826 } else {
15827 Ok(())
15828 }
15829 });
15830
15831 url_finder.detach();
15832 }
15833
15834 pub fn open_selected_filename(
15835 &mut self,
15836 _: &OpenSelectedFilename,
15837 window: &mut Window,
15838 cx: &mut Context<Self>,
15839 ) {
15840 let Some(workspace) = self.workspace() else {
15841 return;
15842 };
15843
15844 let position = self.selections.newest_anchor().head();
15845
15846 let Some((buffer, buffer_position)) =
15847 self.buffer.read(cx).text_anchor_for_position(position, cx)
15848 else {
15849 return;
15850 };
15851
15852 let project = self.project.clone();
15853
15854 cx.spawn_in(window, async move |_, cx| {
15855 let result = find_file(&buffer, project, buffer_position, cx).await;
15856
15857 if let Some((_, path)) = result {
15858 workspace
15859 .update_in(cx, |workspace, window, cx| {
15860 workspace.open_resolved_path(path, window, cx)
15861 })?
15862 .await?;
15863 }
15864 anyhow::Ok(())
15865 })
15866 .detach();
15867 }
15868
15869 pub(crate) fn navigate_to_hover_links(
15870 &mut self,
15871 kind: Option<GotoDefinitionKind>,
15872 definitions: Vec<HoverLink>,
15873 split: bool,
15874 window: &mut Window,
15875 cx: &mut Context<Editor>,
15876 ) -> Task<Result<Navigated>> {
15877 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15878 let mut first_url_or_file = None;
15879 let definitions: Vec<_> = definitions
15880 .into_iter()
15881 .filter_map(|def| match def {
15882 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15883 HoverLink::InlayHint(lsp_location, server_id) => {
15884 let computation =
15885 self.compute_target_location(lsp_location, server_id, window, cx);
15886 Some(cx.background_spawn(computation))
15887 }
15888 HoverLink::Url(url) => {
15889 first_url_or_file = Some(Either::Left(url));
15890 None
15891 }
15892 HoverLink::File(path) => {
15893 first_url_or_file = Some(Either::Right(path));
15894 None
15895 }
15896 })
15897 .collect();
15898
15899 let workspace = self.workspace();
15900
15901 cx.spawn_in(window, async move |editor, acx| {
15902 let mut locations: Vec<Location> = future::join_all(definitions)
15903 .await
15904 .into_iter()
15905 .filter_map(|location| location.transpose())
15906 .collect::<Result<_>>()
15907 .context("location tasks")?;
15908
15909 if locations.len() > 1 {
15910 let Some(workspace) = workspace else {
15911 return Ok(Navigated::No);
15912 };
15913
15914 let tab_kind = match kind {
15915 Some(GotoDefinitionKind::Implementation) => "Implementations",
15916 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15917 Some(GotoDefinitionKind::Declaration) => "Declarations",
15918 Some(GotoDefinitionKind::Type) => "Types",
15919 };
15920 let title = editor
15921 .update_in(acx, |_, _, cx| {
15922 let target = locations
15923 .iter()
15924 .map(|location| {
15925 location
15926 .buffer
15927 .read(cx)
15928 .text_for_range(location.range.clone())
15929 .collect::<String>()
15930 })
15931 .filter(|text| !text.contains('\n'))
15932 .unique()
15933 .take(3)
15934 .join(", ");
15935 if target.is_empty() {
15936 tab_kind.to_owned()
15937 } else {
15938 format!("{tab_kind} for {target}")
15939 }
15940 })
15941 .context("buffer title")?;
15942
15943 let opened = workspace
15944 .update_in(acx, |workspace, window, cx| {
15945 Self::open_locations_in_multibuffer(
15946 workspace,
15947 locations,
15948 title,
15949 split,
15950 MultibufferSelectionMode::First,
15951 window,
15952 cx,
15953 )
15954 })
15955 .is_ok();
15956
15957 anyhow::Ok(Navigated::from_bool(opened))
15958 } else if locations.is_empty() {
15959 // If there is one definition, just open it directly
15960 match first_url_or_file {
15961 Some(Either::Left(url)) => {
15962 acx.update(|_, cx| cx.open_url(&url))?;
15963 Ok(Navigated::Yes)
15964 }
15965 Some(Either::Right(path)) => {
15966 let Some(workspace) = workspace else {
15967 return Ok(Navigated::No);
15968 };
15969
15970 workspace
15971 .update_in(acx, |workspace, window, cx| {
15972 workspace.open_resolved_path(path, window, cx)
15973 })?
15974 .await?;
15975 Ok(Navigated::Yes)
15976 }
15977 None => Ok(Navigated::No),
15978 }
15979 } else {
15980 let Some(workspace) = workspace else {
15981 return Ok(Navigated::No);
15982 };
15983
15984 let target = locations.pop().unwrap();
15985 editor.update_in(acx, |editor, window, cx| {
15986 let pane = workspace.read(cx).active_pane().clone();
15987
15988 let range = target.range.to_point(target.buffer.read(cx));
15989 let range = editor.range_for_match(&range);
15990 let range = collapse_multiline_range(range);
15991
15992 if !split
15993 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15994 {
15995 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15996 } else {
15997 window.defer(cx, move |window, cx| {
15998 let target_editor: Entity<Self> =
15999 workspace.update(cx, |workspace, cx| {
16000 let pane = if split {
16001 workspace.adjacent_pane(window, cx)
16002 } else {
16003 workspace.active_pane().clone()
16004 };
16005
16006 workspace.open_project_item(
16007 pane,
16008 target.buffer.clone(),
16009 true,
16010 true,
16011 window,
16012 cx,
16013 )
16014 });
16015 target_editor.update(cx, |target_editor, cx| {
16016 // When selecting a definition in a different buffer, disable the nav history
16017 // to avoid creating a history entry at the previous cursor location.
16018 pane.update(cx, |pane, _| pane.disable_history());
16019 target_editor.go_to_singleton_buffer_range(range, window, cx);
16020 pane.update(cx, |pane, _| pane.enable_history());
16021 });
16022 });
16023 }
16024 Navigated::Yes
16025 })
16026 }
16027 })
16028 }
16029
16030 fn compute_target_location(
16031 &self,
16032 lsp_location: lsp::Location,
16033 server_id: LanguageServerId,
16034 window: &mut Window,
16035 cx: &mut Context<Self>,
16036 ) -> Task<anyhow::Result<Option<Location>>> {
16037 let Some(project) = self.project.clone() else {
16038 return Task::ready(Ok(None));
16039 };
16040
16041 cx.spawn_in(window, async move |editor, cx| {
16042 let location_task = editor.update(cx, |_, cx| {
16043 project.update(cx, |project, cx| {
16044 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16045 })
16046 })?;
16047 let location = Some({
16048 let target_buffer_handle = location_task.await.context("open local buffer")?;
16049 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16050 let target_start = target_buffer
16051 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16052 let target_end = target_buffer
16053 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16054 target_buffer.anchor_after(target_start)
16055 ..target_buffer.anchor_before(target_end)
16056 })?;
16057 Location {
16058 buffer: target_buffer_handle,
16059 range,
16060 }
16061 });
16062 Ok(location)
16063 })
16064 }
16065
16066 pub fn find_all_references(
16067 &mut self,
16068 _: &FindAllReferences,
16069 window: &mut Window,
16070 cx: &mut Context<Self>,
16071 ) -> Option<Task<Result<Navigated>>> {
16072 let selection = self.selections.newest::<usize>(cx);
16073 let multi_buffer = self.buffer.read(cx);
16074 let head = selection.head();
16075
16076 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16077 let head_anchor = multi_buffer_snapshot.anchor_at(
16078 head,
16079 if head < selection.tail() {
16080 Bias::Right
16081 } else {
16082 Bias::Left
16083 },
16084 );
16085
16086 match self
16087 .find_all_references_task_sources
16088 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16089 {
16090 Ok(_) => {
16091 log::info!(
16092 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16093 );
16094 return None;
16095 }
16096 Err(i) => {
16097 self.find_all_references_task_sources.insert(i, head_anchor);
16098 }
16099 }
16100
16101 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16102 let workspace = self.workspace()?;
16103 let project = workspace.read(cx).project().clone();
16104 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16105 Some(cx.spawn_in(window, async move |editor, cx| {
16106 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16107 if let Ok(i) = editor
16108 .find_all_references_task_sources
16109 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16110 {
16111 editor.find_all_references_task_sources.remove(i);
16112 }
16113 });
16114
16115 let locations = references.await?;
16116 if locations.is_empty() {
16117 return anyhow::Ok(Navigated::No);
16118 }
16119
16120 workspace.update_in(cx, |workspace, window, cx| {
16121 let target = locations
16122 .iter()
16123 .map(|location| {
16124 location
16125 .buffer
16126 .read(cx)
16127 .text_for_range(location.range.clone())
16128 .collect::<String>()
16129 })
16130 .filter(|text| !text.contains('\n'))
16131 .unique()
16132 .take(3)
16133 .join(", ");
16134 let title = if target.is_empty() {
16135 "References".to_owned()
16136 } else {
16137 format!("References to {target}")
16138 };
16139 Self::open_locations_in_multibuffer(
16140 workspace,
16141 locations,
16142 title,
16143 false,
16144 MultibufferSelectionMode::First,
16145 window,
16146 cx,
16147 );
16148 Navigated::Yes
16149 })
16150 }))
16151 }
16152
16153 /// Opens a multibuffer with the given project locations in it
16154 pub fn open_locations_in_multibuffer(
16155 workspace: &mut Workspace,
16156 mut locations: Vec<Location>,
16157 title: String,
16158 split: bool,
16159 multibuffer_selection_mode: MultibufferSelectionMode,
16160 window: &mut Window,
16161 cx: &mut Context<Workspace>,
16162 ) {
16163 if locations.is_empty() {
16164 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16165 return;
16166 }
16167
16168 // If there are multiple definitions, open them in a multibuffer
16169 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16170 let mut locations = locations.into_iter().peekable();
16171 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16172 let capability = workspace.project().read(cx).capability();
16173
16174 let excerpt_buffer = cx.new(|cx| {
16175 let mut multibuffer = MultiBuffer::new(capability);
16176 while let Some(location) = locations.next() {
16177 let buffer = location.buffer.read(cx);
16178 let mut ranges_for_buffer = Vec::new();
16179 let range = location.range.to_point(buffer);
16180 ranges_for_buffer.push(range.clone());
16181
16182 while let Some(next_location) = locations.peek() {
16183 if next_location.buffer == location.buffer {
16184 ranges_for_buffer.push(next_location.range.to_point(buffer));
16185 locations.next();
16186 } else {
16187 break;
16188 }
16189 }
16190
16191 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16192 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16193 PathKey::for_buffer(&location.buffer, cx),
16194 location.buffer.clone(),
16195 ranges_for_buffer,
16196 DEFAULT_MULTIBUFFER_CONTEXT,
16197 cx,
16198 );
16199 ranges.extend(new_ranges)
16200 }
16201
16202 multibuffer.with_title(title)
16203 });
16204
16205 let editor = cx.new(|cx| {
16206 Editor::for_multibuffer(
16207 excerpt_buffer,
16208 Some(workspace.project().clone()),
16209 window,
16210 cx,
16211 )
16212 });
16213 editor.update(cx, |editor, cx| {
16214 match multibuffer_selection_mode {
16215 MultibufferSelectionMode::First => {
16216 if let Some(first_range) = ranges.first() {
16217 editor.change_selections(
16218 SelectionEffects::no_scroll(),
16219 window,
16220 cx,
16221 |selections| {
16222 selections.clear_disjoint();
16223 selections
16224 .select_anchor_ranges(std::iter::once(first_range.clone()));
16225 },
16226 );
16227 }
16228 editor.highlight_background::<Self>(
16229 &ranges,
16230 |theme| theme.colors().editor_highlighted_line_background,
16231 cx,
16232 );
16233 }
16234 MultibufferSelectionMode::All => {
16235 editor.change_selections(
16236 SelectionEffects::no_scroll(),
16237 window,
16238 cx,
16239 |selections| {
16240 selections.clear_disjoint();
16241 selections.select_anchor_ranges(ranges);
16242 },
16243 );
16244 }
16245 }
16246 editor.register_buffers_with_language_servers(cx);
16247 });
16248
16249 let item = Box::new(editor);
16250 let item_id = item.item_id();
16251
16252 if split {
16253 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16254 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16255 let (preview_item_id, preview_item_idx) =
16256 workspace.active_pane().read_with(cx, |pane, _| {
16257 (pane.preview_item_id(), pane.preview_item_idx())
16258 });
16259
16260 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16261
16262 if let Some(preview_item_id) = preview_item_id {
16263 workspace.active_pane().update(cx, |pane, cx| {
16264 pane.remove_item(preview_item_id, false, false, window, cx);
16265 });
16266 }
16267 } else {
16268 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16269 }
16270 workspace.active_pane().update(cx, |pane, cx| {
16271 pane.set_preview_item_id(Some(item_id), cx);
16272 });
16273 }
16274
16275 pub fn rename(
16276 &mut self,
16277 _: &Rename,
16278 window: &mut Window,
16279 cx: &mut Context<Self>,
16280 ) -> Option<Task<Result<()>>> {
16281 use language::ToOffset as _;
16282
16283 let provider = self.semantics_provider.clone()?;
16284 let selection = self.selections.newest_anchor().clone();
16285 let (cursor_buffer, cursor_buffer_position) = self
16286 .buffer
16287 .read(cx)
16288 .text_anchor_for_position(selection.head(), cx)?;
16289 let (tail_buffer, cursor_buffer_position_end) = self
16290 .buffer
16291 .read(cx)
16292 .text_anchor_for_position(selection.tail(), cx)?;
16293 if tail_buffer != cursor_buffer {
16294 return None;
16295 }
16296
16297 let snapshot = cursor_buffer.read(cx).snapshot();
16298 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16299 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16300 let prepare_rename = provider
16301 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16302 .unwrap_or_else(|| Task::ready(Ok(None)));
16303 drop(snapshot);
16304
16305 Some(cx.spawn_in(window, async move |this, cx| {
16306 let rename_range = if let Some(range) = prepare_rename.await? {
16307 Some(range)
16308 } else {
16309 this.update(cx, |this, cx| {
16310 let buffer = this.buffer.read(cx).snapshot(cx);
16311 let mut buffer_highlights = this
16312 .document_highlights_for_position(selection.head(), &buffer)
16313 .filter(|highlight| {
16314 highlight.start.excerpt_id == selection.head().excerpt_id
16315 && highlight.end.excerpt_id == selection.head().excerpt_id
16316 });
16317 buffer_highlights
16318 .next()
16319 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16320 })?
16321 };
16322 if let Some(rename_range) = rename_range {
16323 this.update_in(cx, |this, window, cx| {
16324 let snapshot = cursor_buffer.read(cx).snapshot();
16325 let rename_buffer_range = rename_range.to_offset(&snapshot);
16326 let cursor_offset_in_rename_range =
16327 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16328 let cursor_offset_in_rename_range_end =
16329 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16330
16331 this.take_rename(false, window, cx);
16332 let buffer = this.buffer.read(cx).read(cx);
16333 let cursor_offset = selection.head().to_offset(&buffer);
16334 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16335 let rename_end = rename_start + rename_buffer_range.len();
16336 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16337 let mut old_highlight_id = None;
16338 let old_name: Arc<str> = buffer
16339 .chunks(rename_start..rename_end, true)
16340 .map(|chunk| {
16341 if old_highlight_id.is_none() {
16342 old_highlight_id = chunk.syntax_highlight_id;
16343 }
16344 chunk.text
16345 })
16346 .collect::<String>()
16347 .into();
16348
16349 drop(buffer);
16350
16351 // Position the selection in the rename editor so that it matches the current selection.
16352 this.show_local_selections = false;
16353 let rename_editor = cx.new(|cx| {
16354 let mut editor = Editor::single_line(window, cx);
16355 editor.buffer.update(cx, |buffer, cx| {
16356 buffer.edit([(0..0, old_name.clone())], None, cx)
16357 });
16358 let rename_selection_range = match cursor_offset_in_rename_range
16359 .cmp(&cursor_offset_in_rename_range_end)
16360 {
16361 Ordering::Equal => {
16362 editor.select_all(&SelectAll, window, cx);
16363 return editor;
16364 }
16365 Ordering::Less => {
16366 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16367 }
16368 Ordering::Greater => {
16369 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16370 }
16371 };
16372 if rename_selection_range.end > old_name.len() {
16373 editor.select_all(&SelectAll, window, cx);
16374 } else {
16375 editor.change_selections(Default::default(), window, cx, |s| {
16376 s.select_ranges([rename_selection_range]);
16377 });
16378 }
16379 editor
16380 });
16381 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16382 if e == &EditorEvent::Focused {
16383 cx.emit(EditorEvent::FocusedIn)
16384 }
16385 })
16386 .detach();
16387
16388 let write_highlights =
16389 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16390 let read_highlights =
16391 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16392 let ranges = write_highlights
16393 .iter()
16394 .flat_map(|(_, ranges)| ranges.iter())
16395 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16396 .cloned()
16397 .collect();
16398
16399 this.highlight_text::<Rename>(
16400 ranges,
16401 HighlightStyle {
16402 fade_out: Some(0.6),
16403 ..Default::default()
16404 },
16405 cx,
16406 );
16407 let rename_focus_handle = rename_editor.focus_handle(cx);
16408 window.focus(&rename_focus_handle);
16409 let block_id = this.insert_blocks(
16410 [BlockProperties {
16411 style: BlockStyle::Flex,
16412 placement: BlockPlacement::Below(range.start),
16413 height: Some(1),
16414 render: Arc::new({
16415 let rename_editor = rename_editor.clone();
16416 move |cx: &mut BlockContext| {
16417 let mut text_style = cx.editor_style.text.clone();
16418 if let Some(highlight_style) = old_highlight_id
16419 .and_then(|h| h.style(&cx.editor_style.syntax))
16420 {
16421 text_style = text_style.highlight(highlight_style);
16422 }
16423 div()
16424 .block_mouse_except_scroll()
16425 .pl(cx.anchor_x)
16426 .child(EditorElement::new(
16427 &rename_editor,
16428 EditorStyle {
16429 background: cx.theme().system().transparent,
16430 local_player: cx.editor_style.local_player,
16431 text: text_style,
16432 scrollbar_width: cx.editor_style.scrollbar_width,
16433 syntax: cx.editor_style.syntax.clone(),
16434 status: cx.editor_style.status.clone(),
16435 inlay_hints_style: HighlightStyle {
16436 font_weight: Some(FontWeight::BOLD),
16437 ..make_inlay_hints_style(cx.app)
16438 },
16439 edit_prediction_styles: make_suggestion_styles(
16440 cx.app,
16441 ),
16442 ..EditorStyle::default()
16443 },
16444 ))
16445 .into_any_element()
16446 }
16447 }),
16448 priority: 0,
16449 }],
16450 Some(Autoscroll::fit()),
16451 cx,
16452 )[0];
16453 this.pending_rename = Some(RenameState {
16454 range,
16455 old_name,
16456 editor: rename_editor,
16457 block_id,
16458 });
16459 })?;
16460 }
16461
16462 Ok(())
16463 }))
16464 }
16465
16466 pub fn confirm_rename(
16467 &mut self,
16468 _: &ConfirmRename,
16469 window: &mut Window,
16470 cx: &mut Context<Self>,
16471 ) -> Option<Task<Result<()>>> {
16472 let rename = self.take_rename(false, window, cx)?;
16473 let workspace = self.workspace()?.downgrade();
16474 let (buffer, start) = self
16475 .buffer
16476 .read(cx)
16477 .text_anchor_for_position(rename.range.start, cx)?;
16478 let (end_buffer, _) = self
16479 .buffer
16480 .read(cx)
16481 .text_anchor_for_position(rename.range.end, cx)?;
16482 if buffer != end_buffer {
16483 return None;
16484 }
16485
16486 let old_name = rename.old_name;
16487 let new_name = rename.editor.read(cx).text(cx);
16488
16489 let rename = self.semantics_provider.as_ref()?.perform_rename(
16490 &buffer,
16491 start,
16492 new_name.clone(),
16493 cx,
16494 )?;
16495
16496 Some(cx.spawn_in(window, async move |editor, cx| {
16497 let project_transaction = rename.await?;
16498 Self::open_project_transaction(
16499 &editor,
16500 workspace,
16501 project_transaction,
16502 format!("Rename: {} → {}", old_name, new_name),
16503 cx,
16504 )
16505 .await?;
16506
16507 editor.update(cx, |editor, cx| {
16508 editor.refresh_document_highlights(cx);
16509 })?;
16510 Ok(())
16511 }))
16512 }
16513
16514 fn take_rename(
16515 &mut self,
16516 moving_cursor: bool,
16517 window: &mut Window,
16518 cx: &mut Context<Self>,
16519 ) -> Option<RenameState> {
16520 let rename = self.pending_rename.take()?;
16521 if rename.editor.focus_handle(cx).is_focused(window) {
16522 window.focus(&self.focus_handle);
16523 }
16524
16525 self.remove_blocks(
16526 [rename.block_id].into_iter().collect(),
16527 Some(Autoscroll::fit()),
16528 cx,
16529 );
16530 self.clear_highlights::<Rename>(cx);
16531 self.show_local_selections = true;
16532
16533 if moving_cursor {
16534 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16535 editor.selections.newest::<usize>(cx).head()
16536 });
16537
16538 // Update the selection to match the position of the selection inside
16539 // the rename editor.
16540 let snapshot = self.buffer.read(cx).read(cx);
16541 let rename_range = rename.range.to_offset(&snapshot);
16542 let cursor_in_editor = snapshot
16543 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16544 .min(rename_range.end);
16545 drop(snapshot);
16546
16547 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16548 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16549 });
16550 } else {
16551 self.refresh_document_highlights(cx);
16552 }
16553
16554 Some(rename)
16555 }
16556
16557 pub fn pending_rename(&self) -> Option<&RenameState> {
16558 self.pending_rename.as_ref()
16559 }
16560
16561 fn format(
16562 &mut self,
16563 _: &Format,
16564 window: &mut Window,
16565 cx: &mut Context<Self>,
16566 ) -> Option<Task<Result<()>>> {
16567 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16568
16569 let project = match &self.project {
16570 Some(project) => project.clone(),
16571 None => return None,
16572 };
16573
16574 Some(self.perform_format(
16575 project,
16576 FormatTrigger::Manual,
16577 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16578 window,
16579 cx,
16580 ))
16581 }
16582
16583 fn format_selections(
16584 &mut self,
16585 _: &FormatSelections,
16586 window: &mut Window,
16587 cx: &mut Context<Self>,
16588 ) -> Option<Task<Result<()>>> {
16589 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16590
16591 let project = match &self.project {
16592 Some(project) => project.clone(),
16593 None => return None,
16594 };
16595
16596 let ranges = self
16597 .selections
16598 .all_adjusted(cx)
16599 .into_iter()
16600 .map(|selection| selection.range())
16601 .collect_vec();
16602
16603 Some(self.perform_format(
16604 project,
16605 FormatTrigger::Manual,
16606 FormatTarget::Ranges(ranges),
16607 window,
16608 cx,
16609 ))
16610 }
16611
16612 fn perform_format(
16613 &mut self,
16614 project: Entity<Project>,
16615 trigger: FormatTrigger,
16616 target: FormatTarget,
16617 window: &mut Window,
16618 cx: &mut Context<Self>,
16619 ) -> Task<Result<()>> {
16620 let buffer = self.buffer.clone();
16621 let (buffers, target) = match target {
16622 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16623 FormatTarget::Ranges(selection_ranges) => {
16624 let multi_buffer = buffer.read(cx);
16625 let snapshot = multi_buffer.read(cx);
16626 let mut buffers = HashSet::default();
16627 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16628 BTreeMap::new();
16629 for selection_range in selection_ranges {
16630 for (buffer, buffer_range, _) in
16631 snapshot.range_to_buffer_ranges(selection_range)
16632 {
16633 let buffer_id = buffer.remote_id();
16634 let start = buffer.anchor_before(buffer_range.start);
16635 let end = buffer.anchor_after(buffer_range.end);
16636 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16637 buffer_id_to_ranges
16638 .entry(buffer_id)
16639 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16640 .or_insert_with(|| vec![start..end]);
16641 }
16642 }
16643 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16644 }
16645 };
16646
16647 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16648 let selections_prev = transaction_id_prev
16649 .and_then(|transaction_id_prev| {
16650 // default to selections as they were after the last edit, if we have them,
16651 // instead of how they are now.
16652 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16653 // will take you back to where you made the last edit, instead of staying where you scrolled
16654 self.selection_history
16655 .transaction(transaction_id_prev)
16656 .map(|t| t.0.clone())
16657 })
16658 .unwrap_or_else(|| {
16659 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16660 self.selections.disjoint_anchors()
16661 });
16662
16663 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16664 let format = project.update(cx, |project, cx| {
16665 project.format(buffers, target, true, trigger, cx)
16666 });
16667
16668 cx.spawn_in(window, async move |editor, cx| {
16669 let transaction = futures::select_biased! {
16670 transaction = format.log_err().fuse() => transaction,
16671 () = timeout => {
16672 log::warn!("timed out waiting for formatting");
16673 None
16674 }
16675 };
16676
16677 buffer
16678 .update(cx, |buffer, cx| {
16679 if let Some(transaction) = transaction
16680 && !buffer.is_singleton()
16681 {
16682 buffer.push_transaction(&transaction.0, cx);
16683 }
16684 cx.notify();
16685 })
16686 .ok();
16687
16688 if let Some(transaction_id_now) =
16689 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16690 {
16691 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16692 if has_new_transaction {
16693 _ = editor.update(cx, |editor, _| {
16694 editor
16695 .selection_history
16696 .insert_transaction(transaction_id_now, selections_prev);
16697 });
16698 }
16699 }
16700
16701 Ok(())
16702 })
16703 }
16704
16705 fn organize_imports(
16706 &mut self,
16707 _: &OrganizeImports,
16708 window: &mut Window,
16709 cx: &mut Context<Self>,
16710 ) -> Option<Task<Result<()>>> {
16711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16712 let project = match &self.project {
16713 Some(project) => project.clone(),
16714 None => return None,
16715 };
16716 Some(self.perform_code_action_kind(
16717 project,
16718 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16719 window,
16720 cx,
16721 ))
16722 }
16723
16724 fn perform_code_action_kind(
16725 &mut self,
16726 project: Entity<Project>,
16727 kind: CodeActionKind,
16728 window: &mut Window,
16729 cx: &mut Context<Self>,
16730 ) -> Task<Result<()>> {
16731 let buffer = self.buffer.clone();
16732 let buffers = buffer.read(cx).all_buffers();
16733 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16734 let apply_action = project.update(cx, |project, cx| {
16735 project.apply_code_action_kind(buffers, kind, true, cx)
16736 });
16737 cx.spawn_in(window, async move |_, cx| {
16738 let transaction = futures::select_biased! {
16739 () = timeout => {
16740 log::warn!("timed out waiting for executing code action");
16741 None
16742 }
16743 transaction = apply_action.log_err().fuse() => transaction,
16744 };
16745 buffer
16746 .update(cx, |buffer, cx| {
16747 // check if we need this
16748 if let Some(transaction) = transaction
16749 && !buffer.is_singleton()
16750 {
16751 buffer.push_transaction(&transaction.0, cx);
16752 }
16753 cx.notify();
16754 })
16755 .ok();
16756 Ok(())
16757 })
16758 }
16759
16760 pub fn restart_language_server(
16761 &mut self,
16762 _: &RestartLanguageServer,
16763 _: &mut Window,
16764 cx: &mut Context<Self>,
16765 ) {
16766 if let Some(project) = self.project.clone() {
16767 self.buffer.update(cx, |multi_buffer, cx| {
16768 project.update(cx, |project, cx| {
16769 project.restart_language_servers_for_buffers(
16770 multi_buffer.all_buffers().into_iter().collect(),
16771 HashSet::default(),
16772 cx,
16773 );
16774 });
16775 })
16776 }
16777 }
16778
16779 pub fn stop_language_server(
16780 &mut self,
16781 _: &StopLanguageServer,
16782 _: &mut Window,
16783 cx: &mut Context<Self>,
16784 ) {
16785 if let Some(project) = self.project.clone() {
16786 self.buffer.update(cx, |multi_buffer, cx| {
16787 project.update(cx, |project, cx| {
16788 project.stop_language_servers_for_buffers(
16789 multi_buffer.all_buffers().into_iter().collect(),
16790 HashSet::default(),
16791 cx,
16792 );
16793 cx.emit(project::Event::RefreshInlayHints);
16794 });
16795 });
16796 }
16797 }
16798
16799 fn cancel_language_server_work(
16800 workspace: &mut Workspace,
16801 _: &actions::CancelLanguageServerWork,
16802 _: &mut Window,
16803 cx: &mut Context<Workspace>,
16804 ) {
16805 let project = workspace.project();
16806 let buffers = workspace
16807 .active_item(cx)
16808 .and_then(|item| item.act_as::<Editor>(cx))
16809 .map_or(HashSet::default(), |editor| {
16810 editor.read(cx).buffer.read(cx).all_buffers()
16811 });
16812 project.update(cx, |project, cx| {
16813 project.cancel_language_server_work_for_buffers(buffers, cx);
16814 });
16815 }
16816
16817 fn show_character_palette(
16818 &mut self,
16819 _: &ShowCharacterPalette,
16820 window: &mut Window,
16821 _: &mut Context<Self>,
16822 ) {
16823 window.show_character_palette();
16824 }
16825
16826 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16827 if !self.diagnostics_enabled() {
16828 return;
16829 }
16830
16831 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16832 let buffer = self.buffer.read(cx).snapshot(cx);
16833 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16834 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16835 let is_valid = buffer
16836 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16837 .any(|entry| {
16838 entry.diagnostic.is_primary
16839 && !entry.range.is_empty()
16840 && entry.range.start == primary_range_start
16841 && entry.diagnostic.message == active_diagnostics.active_message
16842 });
16843
16844 if !is_valid {
16845 self.dismiss_diagnostics(cx);
16846 }
16847 }
16848 }
16849
16850 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16851 match &self.active_diagnostics {
16852 ActiveDiagnostic::Group(group) => Some(group),
16853 _ => None,
16854 }
16855 }
16856
16857 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16858 if !self.diagnostics_enabled() {
16859 return;
16860 }
16861 self.dismiss_diagnostics(cx);
16862 self.active_diagnostics = ActiveDiagnostic::All;
16863 }
16864
16865 fn activate_diagnostics(
16866 &mut self,
16867 buffer_id: BufferId,
16868 diagnostic: DiagnosticEntry<usize>,
16869 window: &mut Window,
16870 cx: &mut Context<Self>,
16871 ) {
16872 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16873 return;
16874 }
16875 self.dismiss_diagnostics(cx);
16876 let snapshot = self.snapshot(window, cx);
16877 let buffer = self.buffer.read(cx).snapshot(cx);
16878 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16879 return;
16880 };
16881
16882 let diagnostic_group = buffer
16883 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16884 .collect::<Vec<_>>();
16885
16886 let blocks =
16887 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16888
16889 let blocks = self.display_map.update(cx, |display_map, cx| {
16890 display_map.insert_blocks(blocks, cx).into_iter().collect()
16891 });
16892 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16893 active_range: buffer.anchor_before(diagnostic.range.start)
16894 ..buffer.anchor_after(diagnostic.range.end),
16895 active_message: diagnostic.diagnostic.message.clone(),
16896 group_id: diagnostic.diagnostic.group_id,
16897 blocks,
16898 });
16899 cx.notify();
16900 }
16901
16902 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16903 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16904 return;
16905 };
16906
16907 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16908 if let ActiveDiagnostic::Group(group) = prev {
16909 self.display_map.update(cx, |display_map, cx| {
16910 display_map.remove_blocks(group.blocks, cx);
16911 });
16912 cx.notify();
16913 }
16914 }
16915
16916 /// Disable inline diagnostics rendering for this editor.
16917 pub fn disable_inline_diagnostics(&mut self) {
16918 self.inline_diagnostics_enabled = false;
16919 self.inline_diagnostics_update = Task::ready(());
16920 self.inline_diagnostics.clear();
16921 }
16922
16923 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16924 self.diagnostics_enabled = false;
16925 self.dismiss_diagnostics(cx);
16926 self.inline_diagnostics_update = Task::ready(());
16927 self.inline_diagnostics.clear();
16928 }
16929
16930 pub fn diagnostics_enabled(&self) -> bool {
16931 self.diagnostics_enabled && self.mode.is_full()
16932 }
16933
16934 pub fn inline_diagnostics_enabled(&self) -> bool {
16935 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16936 }
16937
16938 pub fn show_inline_diagnostics(&self) -> bool {
16939 self.show_inline_diagnostics
16940 }
16941
16942 pub fn toggle_inline_diagnostics(
16943 &mut self,
16944 _: &ToggleInlineDiagnostics,
16945 window: &mut Window,
16946 cx: &mut Context<Editor>,
16947 ) {
16948 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16949 self.refresh_inline_diagnostics(false, window, cx);
16950 }
16951
16952 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16953 self.diagnostics_max_severity = severity;
16954 self.display_map.update(cx, |display_map, _| {
16955 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16956 });
16957 }
16958
16959 pub fn toggle_diagnostics(
16960 &mut self,
16961 _: &ToggleDiagnostics,
16962 window: &mut Window,
16963 cx: &mut Context<Editor>,
16964 ) {
16965 if !self.diagnostics_enabled() {
16966 return;
16967 }
16968
16969 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16970 EditorSettings::get_global(cx)
16971 .diagnostics_max_severity
16972 .filter(|severity| severity != &DiagnosticSeverity::Off)
16973 .unwrap_or(DiagnosticSeverity::Hint)
16974 } else {
16975 DiagnosticSeverity::Off
16976 };
16977 self.set_max_diagnostics_severity(new_severity, cx);
16978 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16979 self.active_diagnostics = ActiveDiagnostic::None;
16980 self.inline_diagnostics_update = Task::ready(());
16981 self.inline_diagnostics.clear();
16982 } else {
16983 self.refresh_inline_diagnostics(false, window, cx);
16984 }
16985
16986 cx.notify();
16987 }
16988
16989 pub fn toggle_minimap(
16990 &mut self,
16991 _: &ToggleMinimap,
16992 window: &mut Window,
16993 cx: &mut Context<Editor>,
16994 ) {
16995 if self.supports_minimap(cx) {
16996 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16997 }
16998 }
16999
17000 fn refresh_inline_diagnostics(
17001 &mut self,
17002 debounce: bool,
17003 window: &mut Window,
17004 cx: &mut Context<Self>,
17005 ) {
17006 let max_severity = ProjectSettings::get_global(cx)
17007 .diagnostics
17008 .inline
17009 .max_severity
17010 .unwrap_or(self.diagnostics_max_severity);
17011
17012 if !self.inline_diagnostics_enabled()
17013 || !self.show_inline_diagnostics
17014 || max_severity == DiagnosticSeverity::Off
17015 {
17016 self.inline_diagnostics_update = Task::ready(());
17017 self.inline_diagnostics.clear();
17018 return;
17019 }
17020
17021 let debounce_ms = ProjectSettings::get_global(cx)
17022 .diagnostics
17023 .inline
17024 .update_debounce_ms;
17025 let debounce = if debounce && debounce_ms > 0 {
17026 Some(Duration::from_millis(debounce_ms))
17027 } else {
17028 None
17029 };
17030 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17031 if let Some(debounce) = debounce {
17032 cx.background_executor().timer(debounce).await;
17033 }
17034 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17035 editor
17036 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17037 .ok()
17038 }) else {
17039 return;
17040 };
17041
17042 let new_inline_diagnostics = cx
17043 .background_spawn(async move {
17044 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17045 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17046 let message = diagnostic_entry
17047 .diagnostic
17048 .message
17049 .split_once('\n')
17050 .map(|(line, _)| line)
17051 .map(SharedString::new)
17052 .unwrap_or_else(|| {
17053 SharedString::from(diagnostic_entry.diagnostic.message)
17054 });
17055 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17056 let (Ok(i) | Err(i)) = inline_diagnostics
17057 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17058 inline_diagnostics.insert(
17059 i,
17060 (
17061 start_anchor,
17062 InlineDiagnostic {
17063 message,
17064 group_id: diagnostic_entry.diagnostic.group_id,
17065 start: diagnostic_entry.range.start.to_point(&snapshot),
17066 is_primary: diagnostic_entry.diagnostic.is_primary,
17067 severity: diagnostic_entry.diagnostic.severity,
17068 },
17069 ),
17070 );
17071 }
17072 inline_diagnostics
17073 })
17074 .await;
17075
17076 editor
17077 .update(cx, |editor, cx| {
17078 editor.inline_diagnostics = new_inline_diagnostics;
17079 cx.notify();
17080 })
17081 .ok();
17082 });
17083 }
17084
17085 fn pull_diagnostics(
17086 &mut self,
17087 buffer_id: Option<BufferId>,
17088 window: &Window,
17089 cx: &mut Context<Self>,
17090 ) -> Option<()> {
17091 if !self.mode().is_full() {
17092 return None;
17093 }
17094 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17095 .diagnostics
17096 .lsp_pull_diagnostics;
17097 if !pull_diagnostics_settings.enabled {
17098 return None;
17099 }
17100 let project = self.project()?.downgrade();
17101 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17102 let mut buffers = self.buffer.read(cx).all_buffers();
17103 if let Some(buffer_id) = buffer_id {
17104 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17105 }
17106
17107 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17108 cx.background_executor().timer(debounce).await;
17109
17110 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17111 buffers
17112 .into_iter()
17113 .filter_map(|buffer| {
17114 project
17115 .update(cx, |project, cx| {
17116 project.lsp_store().update(cx, |lsp_store, cx| {
17117 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17118 })
17119 })
17120 .ok()
17121 })
17122 .collect::<FuturesUnordered<_>>()
17123 }) else {
17124 return;
17125 };
17126
17127 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17128 match pull_task {
17129 Ok(()) => {
17130 if editor
17131 .update_in(cx, |editor, window, cx| {
17132 editor.update_diagnostics_state(window, cx);
17133 })
17134 .is_err()
17135 {
17136 return;
17137 }
17138 }
17139 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17140 }
17141 }
17142 });
17143
17144 Some(())
17145 }
17146
17147 pub fn set_selections_from_remote(
17148 &mut self,
17149 selections: Vec<Selection<Anchor>>,
17150 pending_selection: Option<Selection<Anchor>>,
17151 window: &mut Window,
17152 cx: &mut Context<Self>,
17153 ) {
17154 let old_cursor_position = self.selections.newest_anchor().head();
17155 self.selections.change_with(cx, |s| {
17156 s.select_anchors(selections);
17157 if let Some(pending_selection) = pending_selection {
17158 s.set_pending(pending_selection, SelectMode::Character);
17159 } else {
17160 s.clear_pending();
17161 }
17162 });
17163 self.selections_did_change(
17164 false,
17165 &old_cursor_position,
17166 SelectionEffects::default(),
17167 window,
17168 cx,
17169 );
17170 }
17171
17172 pub fn transact(
17173 &mut self,
17174 window: &mut Window,
17175 cx: &mut Context<Self>,
17176 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17177 ) -> Option<TransactionId> {
17178 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17179 this.start_transaction_at(Instant::now(), window, cx);
17180 update(this, window, cx);
17181 this.end_transaction_at(Instant::now(), cx)
17182 })
17183 }
17184
17185 pub fn start_transaction_at(
17186 &mut self,
17187 now: Instant,
17188 window: &mut Window,
17189 cx: &mut Context<Self>,
17190 ) -> Option<TransactionId> {
17191 self.end_selection(window, cx);
17192 if let Some(tx_id) = self
17193 .buffer
17194 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17195 {
17196 self.selection_history
17197 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17198 cx.emit(EditorEvent::TransactionBegun {
17199 transaction_id: tx_id,
17200 });
17201 Some(tx_id)
17202 } else {
17203 None
17204 }
17205 }
17206
17207 pub fn end_transaction_at(
17208 &mut self,
17209 now: Instant,
17210 cx: &mut Context<Self>,
17211 ) -> Option<TransactionId> {
17212 if let Some(transaction_id) = self
17213 .buffer
17214 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17215 {
17216 if let Some((_, end_selections)) =
17217 self.selection_history.transaction_mut(transaction_id)
17218 {
17219 *end_selections = Some(self.selections.disjoint_anchors());
17220 } else {
17221 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17222 }
17223
17224 cx.emit(EditorEvent::Edited { transaction_id });
17225 Some(transaction_id)
17226 } else {
17227 None
17228 }
17229 }
17230
17231 pub fn modify_transaction_selection_history(
17232 &mut self,
17233 transaction_id: TransactionId,
17234 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17235 ) -> bool {
17236 self.selection_history
17237 .transaction_mut(transaction_id)
17238 .map(modify)
17239 .is_some()
17240 }
17241
17242 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17243 if self.selection_mark_mode {
17244 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17245 s.move_with(|_, sel| {
17246 sel.collapse_to(sel.head(), SelectionGoal::None);
17247 });
17248 })
17249 }
17250 self.selection_mark_mode = true;
17251 cx.notify();
17252 }
17253
17254 pub fn swap_selection_ends(
17255 &mut self,
17256 _: &actions::SwapSelectionEnds,
17257 window: &mut Window,
17258 cx: &mut Context<Self>,
17259 ) {
17260 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17261 s.move_with(|_, sel| {
17262 if sel.start != sel.end {
17263 sel.reversed = !sel.reversed
17264 }
17265 });
17266 });
17267 self.request_autoscroll(Autoscroll::newest(), cx);
17268 cx.notify();
17269 }
17270
17271 pub fn toggle_focus(
17272 workspace: &mut Workspace,
17273 _: &actions::ToggleFocus,
17274 window: &mut Window,
17275 cx: &mut Context<Workspace>,
17276 ) {
17277 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17278 return;
17279 };
17280 workspace.activate_item(&item, true, true, window, cx);
17281 }
17282
17283 pub fn toggle_fold(
17284 &mut self,
17285 _: &actions::ToggleFold,
17286 window: &mut Window,
17287 cx: &mut Context<Self>,
17288 ) {
17289 if self.is_singleton(cx) {
17290 let selection = self.selections.newest::<Point>(cx);
17291
17292 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17293 let range = if selection.is_empty() {
17294 let point = selection.head().to_display_point(&display_map);
17295 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17296 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17297 .to_point(&display_map);
17298 start..end
17299 } else {
17300 selection.range()
17301 };
17302 if display_map.folds_in_range(range).next().is_some() {
17303 self.unfold_lines(&Default::default(), window, cx)
17304 } else {
17305 self.fold(&Default::default(), window, cx)
17306 }
17307 } else {
17308 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17309 let buffer_ids: HashSet<_> = self
17310 .selections
17311 .disjoint_anchor_ranges()
17312 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17313 .collect();
17314
17315 let should_unfold = buffer_ids
17316 .iter()
17317 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17318
17319 for buffer_id in buffer_ids {
17320 if should_unfold {
17321 self.unfold_buffer(buffer_id, cx);
17322 } else {
17323 self.fold_buffer(buffer_id, cx);
17324 }
17325 }
17326 }
17327 }
17328
17329 pub fn toggle_fold_recursive(
17330 &mut self,
17331 _: &actions::ToggleFoldRecursive,
17332 window: &mut Window,
17333 cx: &mut Context<Self>,
17334 ) {
17335 let selection = self.selections.newest::<Point>(cx);
17336
17337 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17338 let range = if selection.is_empty() {
17339 let point = selection.head().to_display_point(&display_map);
17340 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17341 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17342 .to_point(&display_map);
17343 start..end
17344 } else {
17345 selection.range()
17346 };
17347 if display_map.folds_in_range(range).next().is_some() {
17348 self.unfold_recursive(&Default::default(), window, cx)
17349 } else {
17350 self.fold_recursive(&Default::default(), window, cx)
17351 }
17352 }
17353
17354 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17355 if self.is_singleton(cx) {
17356 let mut to_fold = Vec::new();
17357 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17358 let selections = self.selections.all_adjusted(cx);
17359
17360 for selection in selections {
17361 let range = selection.range().sorted();
17362 let buffer_start_row = range.start.row;
17363
17364 if range.start.row != range.end.row {
17365 let mut found = false;
17366 let mut row = range.start.row;
17367 while row <= range.end.row {
17368 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17369 {
17370 found = true;
17371 row = crease.range().end.row + 1;
17372 to_fold.push(crease);
17373 } else {
17374 row += 1
17375 }
17376 }
17377 if found {
17378 continue;
17379 }
17380 }
17381
17382 for row in (0..=range.start.row).rev() {
17383 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17384 && crease.range().end.row >= buffer_start_row
17385 {
17386 to_fold.push(crease);
17387 if row <= range.start.row {
17388 break;
17389 }
17390 }
17391 }
17392 }
17393
17394 self.fold_creases(to_fold, true, window, cx);
17395 } else {
17396 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17397 let buffer_ids = self
17398 .selections
17399 .disjoint_anchor_ranges()
17400 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17401 .collect::<HashSet<_>>();
17402 for buffer_id in buffer_ids {
17403 self.fold_buffer(buffer_id, cx);
17404 }
17405 }
17406 }
17407
17408 pub fn toggle_fold_all(
17409 &mut self,
17410 _: &actions::ToggleFoldAll,
17411 window: &mut Window,
17412 cx: &mut Context<Self>,
17413 ) {
17414 if self.buffer.read(cx).is_singleton() {
17415 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17416 let has_folds = display_map
17417 .folds_in_range(0..display_map.buffer_snapshot.len())
17418 .next()
17419 .is_some();
17420
17421 if has_folds {
17422 self.unfold_all(&actions::UnfoldAll, window, cx);
17423 } else {
17424 self.fold_all(&actions::FoldAll, window, cx);
17425 }
17426 } else {
17427 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17428 let should_unfold = buffer_ids
17429 .iter()
17430 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17431
17432 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17433 editor
17434 .update_in(cx, |editor, _, cx| {
17435 for buffer_id in buffer_ids {
17436 if should_unfold {
17437 editor.unfold_buffer(buffer_id, cx);
17438 } else {
17439 editor.fold_buffer(buffer_id, cx);
17440 }
17441 }
17442 })
17443 .ok();
17444 });
17445 }
17446 }
17447
17448 fn fold_at_level(
17449 &mut self,
17450 fold_at: &FoldAtLevel,
17451 window: &mut Window,
17452 cx: &mut Context<Self>,
17453 ) {
17454 if !self.buffer.read(cx).is_singleton() {
17455 return;
17456 }
17457
17458 let fold_at_level = fold_at.0;
17459 let snapshot = self.buffer.read(cx).snapshot(cx);
17460 let mut to_fold = Vec::new();
17461 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17462
17463 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17464 while start_row < end_row {
17465 match self
17466 .snapshot(window, cx)
17467 .crease_for_buffer_row(MultiBufferRow(start_row))
17468 {
17469 Some(crease) => {
17470 let nested_start_row = crease.range().start.row + 1;
17471 let nested_end_row = crease.range().end.row;
17472
17473 if current_level < fold_at_level {
17474 stack.push((nested_start_row, nested_end_row, current_level + 1));
17475 } else if current_level == fold_at_level {
17476 to_fold.push(crease);
17477 }
17478
17479 start_row = nested_end_row + 1;
17480 }
17481 None => start_row += 1,
17482 }
17483 }
17484 }
17485
17486 self.fold_creases(to_fold, true, window, cx);
17487 }
17488
17489 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17490 if self.buffer.read(cx).is_singleton() {
17491 let mut fold_ranges = Vec::new();
17492 let snapshot = self.buffer.read(cx).snapshot(cx);
17493
17494 for row in 0..snapshot.max_row().0 {
17495 if let Some(foldable_range) = self
17496 .snapshot(window, cx)
17497 .crease_for_buffer_row(MultiBufferRow(row))
17498 {
17499 fold_ranges.push(foldable_range);
17500 }
17501 }
17502
17503 self.fold_creases(fold_ranges, true, window, cx);
17504 } else {
17505 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17506 editor
17507 .update_in(cx, |editor, _, cx| {
17508 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17509 editor.fold_buffer(buffer_id, cx);
17510 }
17511 })
17512 .ok();
17513 });
17514 }
17515 }
17516
17517 pub fn fold_function_bodies(
17518 &mut self,
17519 _: &actions::FoldFunctionBodies,
17520 window: &mut Window,
17521 cx: &mut Context<Self>,
17522 ) {
17523 let snapshot = self.buffer.read(cx).snapshot(cx);
17524
17525 let ranges = snapshot
17526 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17527 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17528 .collect::<Vec<_>>();
17529
17530 let creases = ranges
17531 .into_iter()
17532 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17533 .collect();
17534
17535 self.fold_creases(creases, true, window, cx);
17536 }
17537
17538 pub fn fold_recursive(
17539 &mut self,
17540 _: &actions::FoldRecursive,
17541 window: &mut Window,
17542 cx: &mut Context<Self>,
17543 ) {
17544 let mut to_fold = Vec::new();
17545 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17546 let selections = self.selections.all_adjusted(cx);
17547
17548 for selection in selections {
17549 let range = selection.range().sorted();
17550 let buffer_start_row = range.start.row;
17551
17552 if range.start.row != range.end.row {
17553 let mut found = false;
17554 for row in range.start.row..=range.end.row {
17555 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17556 found = true;
17557 to_fold.push(crease);
17558 }
17559 }
17560 if found {
17561 continue;
17562 }
17563 }
17564
17565 for row in (0..=range.start.row).rev() {
17566 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17567 if crease.range().end.row >= buffer_start_row {
17568 to_fold.push(crease);
17569 } else {
17570 break;
17571 }
17572 }
17573 }
17574 }
17575
17576 self.fold_creases(to_fold, true, window, cx);
17577 }
17578
17579 pub fn fold_at(
17580 &mut self,
17581 buffer_row: MultiBufferRow,
17582 window: &mut Window,
17583 cx: &mut Context<Self>,
17584 ) {
17585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17586
17587 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17588 let autoscroll = self
17589 .selections
17590 .all::<Point>(cx)
17591 .iter()
17592 .any(|selection| crease.range().overlaps(&selection.range()));
17593
17594 self.fold_creases(vec![crease], autoscroll, window, cx);
17595 }
17596 }
17597
17598 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17599 if self.is_singleton(cx) {
17600 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17601 let buffer = &display_map.buffer_snapshot;
17602 let selections = self.selections.all::<Point>(cx);
17603 let ranges = selections
17604 .iter()
17605 .map(|s| {
17606 let range = s.display_range(&display_map).sorted();
17607 let mut start = range.start.to_point(&display_map);
17608 let mut end = range.end.to_point(&display_map);
17609 start.column = 0;
17610 end.column = buffer.line_len(MultiBufferRow(end.row));
17611 start..end
17612 })
17613 .collect::<Vec<_>>();
17614
17615 self.unfold_ranges(&ranges, true, true, cx);
17616 } else {
17617 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17618 let buffer_ids = self
17619 .selections
17620 .disjoint_anchor_ranges()
17621 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17622 .collect::<HashSet<_>>();
17623 for buffer_id in buffer_ids {
17624 self.unfold_buffer(buffer_id, cx);
17625 }
17626 }
17627 }
17628
17629 pub fn unfold_recursive(
17630 &mut self,
17631 _: &UnfoldRecursive,
17632 _window: &mut Window,
17633 cx: &mut Context<Self>,
17634 ) {
17635 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17636 let selections = self.selections.all::<Point>(cx);
17637 let ranges = selections
17638 .iter()
17639 .map(|s| {
17640 let mut range = s.display_range(&display_map).sorted();
17641 *range.start.column_mut() = 0;
17642 *range.end.column_mut() = display_map.line_len(range.end.row());
17643 let start = range.start.to_point(&display_map);
17644 let end = range.end.to_point(&display_map);
17645 start..end
17646 })
17647 .collect::<Vec<_>>();
17648
17649 self.unfold_ranges(&ranges, true, true, cx);
17650 }
17651
17652 pub fn unfold_at(
17653 &mut self,
17654 buffer_row: MultiBufferRow,
17655 _window: &mut Window,
17656 cx: &mut Context<Self>,
17657 ) {
17658 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17659
17660 let intersection_range = Point::new(buffer_row.0, 0)
17661 ..Point::new(
17662 buffer_row.0,
17663 display_map.buffer_snapshot.line_len(buffer_row),
17664 );
17665
17666 let autoscroll = self
17667 .selections
17668 .all::<Point>(cx)
17669 .iter()
17670 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17671
17672 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17673 }
17674
17675 pub fn unfold_all(
17676 &mut self,
17677 _: &actions::UnfoldAll,
17678 _window: &mut Window,
17679 cx: &mut Context<Self>,
17680 ) {
17681 if self.buffer.read(cx).is_singleton() {
17682 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17683 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17684 } else {
17685 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17686 editor
17687 .update(cx, |editor, cx| {
17688 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17689 editor.unfold_buffer(buffer_id, cx);
17690 }
17691 })
17692 .ok();
17693 });
17694 }
17695 }
17696
17697 pub fn fold_selected_ranges(
17698 &mut self,
17699 _: &FoldSelectedRanges,
17700 window: &mut Window,
17701 cx: &mut Context<Self>,
17702 ) {
17703 let selections = self.selections.all_adjusted(cx);
17704 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17705 let ranges = selections
17706 .into_iter()
17707 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17708 .collect::<Vec<_>>();
17709 self.fold_creases(ranges, true, window, cx);
17710 }
17711
17712 pub fn fold_ranges<T: ToOffset + Clone>(
17713 &mut self,
17714 ranges: Vec<Range<T>>,
17715 auto_scroll: bool,
17716 window: &mut Window,
17717 cx: &mut Context<Self>,
17718 ) {
17719 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17720 let ranges = ranges
17721 .into_iter()
17722 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17723 .collect::<Vec<_>>();
17724 self.fold_creases(ranges, auto_scroll, window, cx);
17725 }
17726
17727 pub fn fold_creases<T: ToOffset + Clone>(
17728 &mut self,
17729 creases: Vec<Crease<T>>,
17730 auto_scroll: bool,
17731 _window: &mut Window,
17732 cx: &mut Context<Self>,
17733 ) {
17734 if creases.is_empty() {
17735 return;
17736 }
17737
17738 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17739
17740 if auto_scroll {
17741 self.request_autoscroll(Autoscroll::fit(), cx);
17742 }
17743
17744 cx.notify();
17745
17746 self.scrollbar_marker_state.dirty = true;
17747 self.folds_did_change(cx);
17748 }
17749
17750 /// Removes any folds whose ranges intersect any of the given ranges.
17751 pub fn unfold_ranges<T: ToOffset + Clone>(
17752 &mut self,
17753 ranges: &[Range<T>],
17754 inclusive: bool,
17755 auto_scroll: bool,
17756 cx: &mut Context<Self>,
17757 ) {
17758 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17759 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17760 });
17761 self.folds_did_change(cx);
17762 }
17763
17764 pub fn fold_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 folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17769 self.display_map.update(cx, |display_map, cx| {
17770 display_map.fold_buffers([buffer_id], cx)
17771 });
17772 cx.emit(EditorEvent::BufferFoldToggled {
17773 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17774 folded: true,
17775 });
17776 cx.notify();
17777 }
17778
17779 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17780 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17781 return;
17782 }
17783 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17784 self.display_map.update(cx, |display_map, cx| {
17785 display_map.unfold_buffers([buffer_id], cx);
17786 });
17787 cx.emit(EditorEvent::BufferFoldToggled {
17788 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17789 folded: false,
17790 });
17791 cx.notify();
17792 }
17793
17794 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17795 self.display_map.read(cx).is_buffer_folded(buffer)
17796 }
17797
17798 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17799 self.display_map.read(cx).folded_buffers()
17800 }
17801
17802 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17803 self.display_map.update(cx, |display_map, cx| {
17804 display_map.disable_header_for_buffer(buffer_id, cx);
17805 });
17806 cx.notify();
17807 }
17808
17809 /// Removes any folds with the given ranges.
17810 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17811 &mut self,
17812 ranges: &[Range<T>],
17813 type_id: TypeId,
17814 auto_scroll: bool,
17815 cx: &mut Context<Self>,
17816 ) {
17817 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17818 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17819 });
17820 self.folds_did_change(cx);
17821 }
17822
17823 fn remove_folds_with<T: ToOffset + Clone>(
17824 &mut self,
17825 ranges: &[Range<T>],
17826 auto_scroll: bool,
17827 cx: &mut Context<Self>,
17828 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17829 ) {
17830 if ranges.is_empty() {
17831 return;
17832 }
17833
17834 let mut buffers_affected = HashSet::default();
17835 let multi_buffer = self.buffer().read(cx);
17836 for range in ranges {
17837 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17838 buffers_affected.insert(buffer.read(cx).remote_id());
17839 };
17840 }
17841
17842 self.display_map.update(cx, update);
17843
17844 if auto_scroll {
17845 self.request_autoscroll(Autoscroll::fit(), cx);
17846 }
17847
17848 cx.notify();
17849 self.scrollbar_marker_state.dirty = true;
17850 self.active_indent_guides_state.dirty = true;
17851 }
17852
17853 pub fn update_renderer_widths(
17854 &mut self,
17855 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17856 cx: &mut Context<Self>,
17857 ) -> bool {
17858 self.display_map
17859 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17860 }
17861
17862 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17863 self.display_map.read(cx).fold_placeholder.clone()
17864 }
17865
17866 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17867 self.buffer.update(cx, |buffer, cx| {
17868 buffer.set_all_diff_hunks_expanded(cx);
17869 });
17870 }
17871
17872 pub fn expand_all_diff_hunks(
17873 &mut self,
17874 _: &ExpandAllDiffHunks,
17875 _window: &mut Window,
17876 cx: &mut Context<Self>,
17877 ) {
17878 self.buffer.update(cx, |buffer, cx| {
17879 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17880 });
17881 }
17882
17883 pub fn toggle_selected_diff_hunks(
17884 &mut self,
17885 _: &ToggleSelectedDiffHunks,
17886 _window: &mut Window,
17887 cx: &mut Context<Self>,
17888 ) {
17889 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17890 self.toggle_diff_hunks_in_ranges(ranges, cx);
17891 }
17892
17893 pub fn diff_hunks_in_ranges<'a>(
17894 &'a self,
17895 ranges: &'a [Range<Anchor>],
17896 buffer: &'a MultiBufferSnapshot,
17897 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17898 ranges.iter().flat_map(move |range| {
17899 let end_excerpt_id = range.end.excerpt_id;
17900 let range = range.to_point(buffer);
17901 let mut peek_end = range.end;
17902 if range.end.row < buffer.max_row().0 {
17903 peek_end = Point::new(range.end.row + 1, 0);
17904 }
17905 buffer
17906 .diff_hunks_in_range(range.start..peek_end)
17907 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17908 })
17909 }
17910
17911 pub fn has_stageable_diff_hunks_in_ranges(
17912 &self,
17913 ranges: &[Range<Anchor>],
17914 snapshot: &MultiBufferSnapshot,
17915 ) -> bool {
17916 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
17917 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17918 }
17919
17920 pub fn toggle_staged_selected_diff_hunks(
17921 &mut self,
17922 _: &::git::ToggleStaged,
17923 _: &mut Window,
17924 cx: &mut Context<Self>,
17925 ) {
17926 let snapshot = self.buffer.read(cx).snapshot(cx);
17927 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17928 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17929 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17930 }
17931
17932 pub fn set_render_diff_hunk_controls(
17933 &mut self,
17934 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17935 cx: &mut Context<Self>,
17936 ) {
17937 self.render_diff_hunk_controls = render_diff_hunk_controls;
17938 cx.notify();
17939 }
17940
17941 pub fn stage_and_next(
17942 &mut self,
17943 _: &::git::StageAndNext,
17944 window: &mut Window,
17945 cx: &mut Context<Self>,
17946 ) {
17947 self.do_stage_or_unstage_and_next(true, window, cx);
17948 }
17949
17950 pub fn unstage_and_next(
17951 &mut self,
17952 _: &::git::UnstageAndNext,
17953 window: &mut Window,
17954 cx: &mut Context<Self>,
17955 ) {
17956 self.do_stage_or_unstage_and_next(false, window, cx);
17957 }
17958
17959 pub fn stage_or_unstage_diff_hunks(
17960 &mut self,
17961 stage: bool,
17962 ranges: Vec<Range<Anchor>>,
17963 cx: &mut Context<Self>,
17964 ) {
17965 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17966 cx.spawn(async move |this, cx| {
17967 task.await?;
17968 this.update(cx, |this, cx| {
17969 let snapshot = this.buffer.read(cx).snapshot(cx);
17970 let chunk_by = this
17971 .diff_hunks_in_ranges(&ranges, &snapshot)
17972 .chunk_by(|hunk| hunk.buffer_id);
17973 for (buffer_id, hunks) in &chunk_by {
17974 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17975 }
17976 })
17977 })
17978 .detach_and_log_err(cx);
17979 }
17980
17981 fn save_buffers_for_ranges_if_needed(
17982 &mut self,
17983 ranges: &[Range<Anchor>],
17984 cx: &mut Context<Editor>,
17985 ) -> Task<Result<()>> {
17986 let multibuffer = self.buffer.read(cx);
17987 let snapshot = multibuffer.read(cx);
17988 let buffer_ids: HashSet<_> = ranges
17989 .iter()
17990 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17991 .collect();
17992 drop(snapshot);
17993
17994 let mut buffers = HashSet::default();
17995 for buffer_id in buffer_ids {
17996 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17997 let buffer = buffer_entity.read(cx);
17998 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17999 {
18000 buffers.insert(buffer_entity);
18001 }
18002 }
18003 }
18004
18005 if let Some(project) = &self.project {
18006 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18007 } else {
18008 Task::ready(Ok(()))
18009 }
18010 }
18011
18012 fn do_stage_or_unstage_and_next(
18013 &mut self,
18014 stage: bool,
18015 window: &mut Window,
18016 cx: &mut Context<Self>,
18017 ) {
18018 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18019
18020 if ranges.iter().any(|range| range.start != range.end) {
18021 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18022 return;
18023 }
18024
18025 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18026 let snapshot = self.snapshot(window, cx);
18027 let position = self.selections.newest::<Point>(cx).head();
18028 let mut row = snapshot
18029 .buffer_snapshot
18030 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18031 .find(|hunk| hunk.row_range.start.0 > position.row)
18032 .map(|hunk| hunk.row_range.start);
18033
18034 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18035 // Outside of the project diff editor, wrap around to the beginning.
18036 if !all_diff_hunks_expanded {
18037 row = row.or_else(|| {
18038 snapshot
18039 .buffer_snapshot
18040 .diff_hunks_in_range(Point::zero()..position)
18041 .find(|hunk| hunk.row_range.end.0 < position.row)
18042 .map(|hunk| hunk.row_range.start)
18043 });
18044 }
18045
18046 if let Some(row) = row {
18047 let destination = Point::new(row.0, 0);
18048 let autoscroll = Autoscroll::center();
18049
18050 self.unfold_ranges(&[destination..destination], false, false, cx);
18051 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18052 s.select_ranges([destination..destination]);
18053 });
18054 }
18055 }
18056
18057 fn do_stage_or_unstage(
18058 &self,
18059 stage: bool,
18060 buffer_id: BufferId,
18061 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18062 cx: &mut App,
18063 ) -> Option<()> {
18064 let project = self.project()?;
18065 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18066 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18067 let buffer_snapshot = buffer.read(cx).snapshot();
18068 let file_exists = buffer_snapshot
18069 .file()
18070 .is_some_and(|file| file.disk_state().exists());
18071 diff.update(cx, |diff, cx| {
18072 diff.stage_or_unstage_hunks(
18073 stage,
18074 &hunks
18075 .map(|hunk| buffer_diff::DiffHunk {
18076 buffer_range: hunk.buffer_range,
18077 diff_base_byte_range: hunk.diff_base_byte_range,
18078 secondary_status: hunk.secondary_status,
18079 range: Point::zero()..Point::zero(), // unused
18080 })
18081 .collect::<Vec<_>>(),
18082 &buffer_snapshot,
18083 file_exists,
18084 cx,
18085 )
18086 });
18087 None
18088 }
18089
18090 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18091 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18092 self.buffer
18093 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18094 }
18095
18096 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18097 self.buffer.update(cx, |buffer, cx| {
18098 let ranges = vec![Anchor::min()..Anchor::max()];
18099 if !buffer.all_diff_hunks_expanded()
18100 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18101 {
18102 buffer.collapse_diff_hunks(ranges, cx);
18103 true
18104 } else {
18105 false
18106 }
18107 })
18108 }
18109
18110 fn toggle_diff_hunks_in_ranges(
18111 &mut self,
18112 ranges: Vec<Range<Anchor>>,
18113 cx: &mut Context<Editor>,
18114 ) {
18115 self.buffer.update(cx, |buffer, cx| {
18116 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18117 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18118 })
18119 }
18120
18121 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18122 self.buffer.update(cx, |buffer, cx| {
18123 let snapshot = buffer.snapshot(cx);
18124 let excerpt_id = range.end.excerpt_id;
18125 let point_range = range.to_point(&snapshot);
18126 let expand = !buffer.single_hunk_is_expanded(range, cx);
18127 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18128 })
18129 }
18130
18131 pub(crate) fn apply_all_diff_hunks(
18132 &mut self,
18133 _: &ApplyAllDiffHunks,
18134 window: &mut Window,
18135 cx: &mut Context<Self>,
18136 ) {
18137 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18138
18139 let buffers = self.buffer.read(cx).all_buffers();
18140 for branch_buffer in buffers {
18141 branch_buffer.update(cx, |branch_buffer, cx| {
18142 branch_buffer.merge_into_base(Vec::new(), cx);
18143 });
18144 }
18145
18146 if let Some(project) = self.project.clone() {
18147 self.save(
18148 SaveOptions {
18149 format: true,
18150 autosave: false,
18151 },
18152 project,
18153 window,
18154 cx,
18155 )
18156 .detach_and_log_err(cx);
18157 }
18158 }
18159
18160 pub(crate) fn apply_selected_diff_hunks(
18161 &mut self,
18162 _: &ApplyDiffHunk,
18163 window: &mut Window,
18164 cx: &mut Context<Self>,
18165 ) {
18166 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18167 let snapshot = self.snapshot(window, cx);
18168 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18169 let mut ranges_by_buffer = HashMap::default();
18170 self.transact(window, cx, |editor, _window, cx| {
18171 for hunk in hunks {
18172 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18173 ranges_by_buffer
18174 .entry(buffer.clone())
18175 .or_insert_with(Vec::new)
18176 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18177 }
18178 }
18179
18180 for (buffer, ranges) in ranges_by_buffer {
18181 buffer.update(cx, |buffer, cx| {
18182 buffer.merge_into_base(ranges, cx);
18183 });
18184 }
18185 });
18186
18187 if let Some(project) = self.project.clone() {
18188 self.save(
18189 SaveOptions {
18190 format: true,
18191 autosave: false,
18192 },
18193 project,
18194 window,
18195 cx,
18196 )
18197 .detach_and_log_err(cx);
18198 }
18199 }
18200
18201 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18202 if hovered != self.gutter_hovered {
18203 self.gutter_hovered = hovered;
18204 cx.notify();
18205 }
18206 }
18207
18208 pub fn insert_blocks(
18209 &mut self,
18210 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18211 autoscroll: Option<Autoscroll>,
18212 cx: &mut Context<Self>,
18213 ) -> Vec<CustomBlockId> {
18214 let blocks = self
18215 .display_map
18216 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18217 if let Some(autoscroll) = autoscroll {
18218 self.request_autoscroll(autoscroll, cx);
18219 }
18220 cx.notify();
18221 blocks
18222 }
18223
18224 pub fn resize_blocks(
18225 &mut self,
18226 heights: HashMap<CustomBlockId, u32>,
18227 autoscroll: Option<Autoscroll>,
18228 cx: &mut Context<Self>,
18229 ) {
18230 self.display_map
18231 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18232 if let Some(autoscroll) = autoscroll {
18233 self.request_autoscroll(autoscroll, cx);
18234 }
18235 cx.notify();
18236 }
18237
18238 pub fn replace_blocks(
18239 &mut self,
18240 renderers: HashMap<CustomBlockId, RenderBlock>,
18241 autoscroll: Option<Autoscroll>,
18242 cx: &mut Context<Self>,
18243 ) {
18244 self.display_map
18245 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18246 if let Some(autoscroll) = autoscroll {
18247 self.request_autoscroll(autoscroll, cx);
18248 }
18249 cx.notify();
18250 }
18251
18252 pub fn remove_blocks(
18253 &mut self,
18254 block_ids: HashSet<CustomBlockId>,
18255 autoscroll: Option<Autoscroll>,
18256 cx: &mut Context<Self>,
18257 ) {
18258 self.display_map.update(cx, |display_map, cx| {
18259 display_map.remove_blocks(block_ids, cx)
18260 });
18261 if let Some(autoscroll) = autoscroll {
18262 self.request_autoscroll(autoscroll, cx);
18263 }
18264 cx.notify();
18265 }
18266
18267 pub fn row_for_block(
18268 &self,
18269 block_id: CustomBlockId,
18270 cx: &mut Context<Self>,
18271 ) -> Option<DisplayRow> {
18272 self.display_map
18273 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18274 }
18275
18276 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18277 self.focused_block = Some(focused_block);
18278 }
18279
18280 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18281 self.focused_block.take()
18282 }
18283
18284 pub fn insert_creases(
18285 &mut self,
18286 creases: impl IntoIterator<Item = Crease<Anchor>>,
18287 cx: &mut Context<Self>,
18288 ) -> Vec<CreaseId> {
18289 self.display_map
18290 .update(cx, |map, cx| map.insert_creases(creases, cx))
18291 }
18292
18293 pub fn remove_creases(
18294 &mut self,
18295 ids: impl IntoIterator<Item = CreaseId>,
18296 cx: &mut Context<Self>,
18297 ) -> Vec<(CreaseId, Range<Anchor>)> {
18298 self.display_map
18299 .update(cx, |map, cx| map.remove_creases(ids, cx))
18300 }
18301
18302 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18303 self.display_map
18304 .update(cx, |map, cx| map.snapshot(cx))
18305 .longest_row()
18306 }
18307
18308 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18309 self.display_map
18310 .update(cx, |map, cx| map.snapshot(cx))
18311 .max_point()
18312 }
18313
18314 pub fn text(&self, cx: &App) -> String {
18315 self.buffer.read(cx).read(cx).text()
18316 }
18317
18318 pub fn is_empty(&self, cx: &App) -> bool {
18319 self.buffer.read(cx).read(cx).is_empty()
18320 }
18321
18322 pub fn text_option(&self, cx: &App) -> Option<String> {
18323 let text = self.text(cx);
18324 let text = text.trim();
18325
18326 if text.is_empty() {
18327 return None;
18328 }
18329
18330 Some(text.to_string())
18331 }
18332
18333 pub fn set_text(
18334 &mut self,
18335 text: impl Into<Arc<str>>,
18336 window: &mut Window,
18337 cx: &mut Context<Self>,
18338 ) {
18339 self.transact(window, cx, |this, _, cx| {
18340 this.buffer
18341 .read(cx)
18342 .as_singleton()
18343 .expect("you can only call set_text on editors for singleton buffers")
18344 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18345 });
18346 }
18347
18348 pub fn display_text(&self, cx: &mut App) -> String {
18349 self.display_map
18350 .update(cx, |map, cx| map.snapshot(cx))
18351 .text()
18352 }
18353
18354 fn create_minimap(
18355 &self,
18356 minimap_settings: MinimapSettings,
18357 window: &mut Window,
18358 cx: &mut Context<Self>,
18359 ) -> Option<Entity<Self>> {
18360 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18361 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18362 }
18363
18364 fn initialize_new_minimap(
18365 &self,
18366 minimap_settings: MinimapSettings,
18367 window: &mut Window,
18368 cx: &mut Context<Self>,
18369 ) -> Entity<Self> {
18370 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18371
18372 let mut minimap = Editor::new_internal(
18373 EditorMode::Minimap {
18374 parent: cx.weak_entity(),
18375 },
18376 self.buffer.clone(),
18377 None,
18378 Some(self.display_map.clone()),
18379 window,
18380 cx,
18381 );
18382 minimap.scroll_manager.clone_state(&self.scroll_manager);
18383 minimap.set_text_style_refinement(TextStyleRefinement {
18384 font_size: Some(MINIMAP_FONT_SIZE),
18385 font_weight: Some(MINIMAP_FONT_WEIGHT),
18386 ..Default::default()
18387 });
18388 minimap.update_minimap_configuration(minimap_settings, cx);
18389 cx.new(|_| minimap)
18390 }
18391
18392 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18393 let current_line_highlight = minimap_settings
18394 .current_line_highlight
18395 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18396 self.set_current_line_highlight(Some(current_line_highlight));
18397 }
18398
18399 pub fn minimap(&self) -> Option<&Entity<Self>> {
18400 self.minimap
18401 .as_ref()
18402 .filter(|_| self.minimap_visibility.visible())
18403 }
18404
18405 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18406 let mut wrap_guides = smallvec![];
18407
18408 if self.show_wrap_guides == Some(false) {
18409 return wrap_guides;
18410 }
18411
18412 let settings = self.buffer.read(cx).language_settings(cx);
18413 if settings.show_wrap_guides {
18414 match self.soft_wrap_mode(cx) {
18415 SoftWrap::Column(soft_wrap) => {
18416 wrap_guides.push((soft_wrap as usize, true));
18417 }
18418 SoftWrap::Bounded(soft_wrap) => {
18419 wrap_guides.push((soft_wrap as usize, true));
18420 }
18421 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18422 }
18423 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18424 }
18425
18426 wrap_guides
18427 }
18428
18429 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18430 let settings = self.buffer.read(cx).language_settings(cx);
18431 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18432 match mode {
18433 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18434 SoftWrap::None
18435 }
18436 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18437 language_settings::SoftWrap::PreferredLineLength => {
18438 SoftWrap::Column(settings.preferred_line_length)
18439 }
18440 language_settings::SoftWrap::Bounded => {
18441 SoftWrap::Bounded(settings.preferred_line_length)
18442 }
18443 }
18444 }
18445
18446 pub fn set_soft_wrap_mode(
18447 &mut self,
18448 mode: language_settings::SoftWrap,
18449
18450 cx: &mut Context<Self>,
18451 ) {
18452 self.soft_wrap_mode_override = Some(mode);
18453 cx.notify();
18454 }
18455
18456 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18457 self.hard_wrap = hard_wrap;
18458 cx.notify();
18459 }
18460
18461 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18462 self.text_style_refinement = Some(style);
18463 }
18464
18465 /// called by the Element so we know what style we were most recently rendered with.
18466 pub(crate) fn set_style(
18467 &mut self,
18468 style: EditorStyle,
18469 window: &mut Window,
18470 cx: &mut Context<Self>,
18471 ) {
18472 // We intentionally do not inform the display map about the minimap style
18473 // so that wrapping is not recalculated and stays consistent for the editor
18474 // and its linked minimap.
18475 if !self.mode.is_minimap() {
18476 let rem_size = window.rem_size();
18477 self.display_map.update(cx, |map, cx| {
18478 map.set_font(
18479 style.text.font(),
18480 style.text.font_size.to_pixels(rem_size),
18481 cx,
18482 )
18483 });
18484 }
18485 self.style = Some(style);
18486 }
18487
18488 pub fn style(&self) -> Option<&EditorStyle> {
18489 self.style.as_ref()
18490 }
18491
18492 // Called by the element. This method is not designed to be called outside of the editor
18493 // element's layout code because it does not notify when rewrapping is computed synchronously.
18494 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18495 self.display_map
18496 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18497 }
18498
18499 pub fn set_soft_wrap(&mut self) {
18500 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18501 }
18502
18503 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18504 if self.soft_wrap_mode_override.is_some() {
18505 self.soft_wrap_mode_override.take();
18506 } else {
18507 let soft_wrap = match self.soft_wrap_mode(cx) {
18508 SoftWrap::GitDiff => return,
18509 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18510 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18511 language_settings::SoftWrap::None
18512 }
18513 };
18514 self.soft_wrap_mode_override = Some(soft_wrap);
18515 }
18516 cx.notify();
18517 }
18518
18519 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18520 let Some(workspace) = self.workspace() else {
18521 return;
18522 };
18523 let fs = workspace.read(cx).app_state().fs.clone();
18524 let current_show = TabBarSettings::get_global(cx).show;
18525 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18526 setting.show = Some(!current_show);
18527 });
18528 }
18529
18530 pub fn toggle_indent_guides(
18531 &mut self,
18532 _: &ToggleIndentGuides,
18533 _: &mut Window,
18534 cx: &mut Context<Self>,
18535 ) {
18536 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18537 self.buffer
18538 .read(cx)
18539 .language_settings(cx)
18540 .indent_guides
18541 .enabled
18542 });
18543 self.show_indent_guides = Some(!currently_enabled);
18544 cx.notify();
18545 }
18546
18547 fn should_show_indent_guides(&self) -> Option<bool> {
18548 self.show_indent_guides
18549 }
18550
18551 pub fn toggle_line_numbers(
18552 &mut self,
18553 _: &ToggleLineNumbers,
18554 _: &mut Window,
18555 cx: &mut Context<Self>,
18556 ) {
18557 let mut editor_settings = EditorSettings::get_global(cx).clone();
18558 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18559 EditorSettings::override_global(editor_settings, cx);
18560 }
18561
18562 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18563 if let Some(show_line_numbers) = self.show_line_numbers {
18564 return show_line_numbers;
18565 }
18566 EditorSettings::get_global(cx).gutter.line_numbers
18567 }
18568
18569 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18570 self.use_relative_line_numbers
18571 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18572 }
18573
18574 pub fn toggle_relative_line_numbers(
18575 &mut self,
18576 _: &ToggleRelativeLineNumbers,
18577 _: &mut Window,
18578 cx: &mut Context<Self>,
18579 ) {
18580 let is_relative = self.should_use_relative_line_numbers(cx);
18581 self.set_relative_line_number(Some(!is_relative), cx)
18582 }
18583
18584 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18585 self.use_relative_line_numbers = is_relative;
18586 cx.notify();
18587 }
18588
18589 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18590 self.show_gutter = show_gutter;
18591 cx.notify();
18592 }
18593
18594 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18595 self.show_scrollbars = ScrollbarAxes {
18596 horizontal: show,
18597 vertical: show,
18598 };
18599 cx.notify();
18600 }
18601
18602 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18603 self.show_scrollbars.vertical = show;
18604 cx.notify();
18605 }
18606
18607 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18608 self.show_scrollbars.horizontal = show;
18609 cx.notify();
18610 }
18611
18612 pub fn set_minimap_visibility(
18613 &mut self,
18614 minimap_visibility: MinimapVisibility,
18615 window: &mut Window,
18616 cx: &mut Context<Self>,
18617 ) {
18618 if self.minimap_visibility != minimap_visibility {
18619 if minimap_visibility.visible() && self.minimap.is_none() {
18620 let minimap_settings = EditorSettings::get_global(cx).minimap;
18621 self.minimap =
18622 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18623 }
18624 self.minimap_visibility = minimap_visibility;
18625 cx.notify();
18626 }
18627 }
18628
18629 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18630 self.set_show_scrollbars(false, cx);
18631 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18632 }
18633
18634 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18635 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18636 }
18637
18638 /// Normally the text in full mode and auto height editors is padded on the
18639 /// left side by roughly half a character width for improved hit testing.
18640 ///
18641 /// Use this method to disable this for cases where this is not wanted (e.g.
18642 /// if you want to align the editor text with some other text above or below)
18643 /// or if you want to add this padding to single-line editors.
18644 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18645 self.offset_content = offset_content;
18646 cx.notify();
18647 }
18648
18649 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18650 self.show_line_numbers = Some(show_line_numbers);
18651 cx.notify();
18652 }
18653
18654 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18655 self.disable_expand_excerpt_buttons = true;
18656 cx.notify();
18657 }
18658
18659 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18660 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18661 cx.notify();
18662 }
18663
18664 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18665 self.show_code_actions = Some(show_code_actions);
18666 cx.notify();
18667 }
18668
18669 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18670 self.show_runnables = Some(show_runnables);
18671 cx.notify();
18672 }
18673
18674 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18675 self.show_breakpoints = Some(show_breakpoints);
18676 cx.notify();
18677 }
18678
18679 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18680 if self.display_map.read(cx).masked != masked {
18681 self.display_map.update(cx, |map, _| map.masked = masked);
18682 }
18683 cx.notify()
18684 }
18685
18686 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18687 self.show_wrap_guides = Some(show_wrap_guides);
18688 cx.notify();
18689 }
18690
18691 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18692 self.show_indent_guides = Some(show_indent_guides);
18693 cx.notify();
18694 }
18695
18696 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18697 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18698 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18699 && let Some(dir) = file.abs_path(cx).parent()
18700 {
18701 return Some(dir.to_owned());
18702 }
18703
18704 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18705 return Some(project_path.path.to_path_buf());
18706 }
18707 }
18708
18709 None
18710 }
18711
18712 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18713 self.active_excerpt(cx)?
18714 .1
18715 .read(cx)
18716 .file()
18717 .and_then(|f| f.as_local())
18718 }
18719
18720 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18721 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18722 let buffer = buffer.read(cx);
18723 if let Some(project_path) = buffer.project_path(cx) {
18724 let project = self.project()?.read(cx);
18725 project.absolute_path(&project_path, cx)
18726 } else {
18727 buffer
18728 .file()
18729 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18730 }
18731 })
18732 }
18733
18734 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18735 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18736 let project_path = buffer.read(cx).project_path(cx)?;
18737 let project = self.project()?.read(cx);
18738 let entry = project.entry_for_path(&project_path, cx)?;
18739 let path = entry.path.to_path_buf();
18740 Some(path)
18741 })
18742 }
18743
18744 pub fn reveal_in_finder(
18745 &mut self,
18746 _: &RevealInFileManager,
18747 _window: &mut Window,
18748 cx: &mut Context<Self>,
18749 ) {
18750 if let Some(target) = self.target_file(cx) {
18751 cx.reveal_path(&target.abs_path(cx));
18752 }
18753 }
18754
18755 pub fn copy_path(
18756 &mut self,
18757 _: &zed_actions::workspace::CopyPath,
18758 _window: &mut Window,
18759 cx: &mut Context<Self>,
18760 ) {
18761 if let Some(path) = self.target_file_abs_path(cx)
18762 && let Some(path) = path.to_str()
18763 {
18764 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18765 }
18766 }
18767
18768 pub fn copy_relative_path(
18769 &mut self,
18770 _: &zed_actions::workspace::CopyRelativePath,
18771 _window: &mut Window,
18772 cx: &mut Context<Self>,
18773 ) {
18774 if let Some(path) = self.target_file_path(cx)
18775 && let Some(path) = path.to_str()
18776 {
18777 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18778 }
18779 }
18780
18781 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18782 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18783 buffer.read(cx).project_path(cx)
18784 } else {
18785 None
18786 }
18787 }
18788
18789 // Returns true if the editor handled a go-to-line request
18790 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18791 maybe!({
18792 let breakpoint_store = self.breakpoint_store.as_ref()?;
18793
18794 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18795 else {
18796 self.clear_row_highlights::<ActiveDebugLine>();
18797 return None;
18798 };
18799
18800 let position = active_stack_frame.position;
18801 let buffer_id = position.buffer_id?;
18802 let snapshot = self
18803 .project
18804 .as_ref()?
18805 .read(cx)
18806 .buffer_for_id(buffer_id, cx)?
18807 .read(cx)
18808 .snapshot();
18809
18810 let mut handled = false;
18811 for (id, ExcerptRange { context, .. }) in
18812 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18813 {
18814 if context.start.cmp(&position, &snapshot).is_ge()
18815 || context.end.cmp(&position, &snapshot).is_lt()
18816 {
18817 continue;
18818 }
18819 let snapshot = self.buffer.read(cx).snapshot(cx);
18820 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18821
18822 handled = true;
18823 self.clear_row_highlights::<ActiveDebugLine>();
18824
18825 self.go_to_line::<ActiveDebugLine>(
18826 multibuffer_anchor,
18827 Some(cx.theme().colors().editor_debugger_active_line_background),
18828 window,
18829 cx,
18830 );
18831
18832 cx.notify();
18833 }
18834
18835 handled.then_some(())
18836 })
18837 .is_some()
18838 }
18839
18840 pub fn copy_file_name_without_extension(
18841 &mut self,
18842 _: &CopyFileNameWithoutExtension,
18843 _: &mut Window,
18844 cx: &mut Context<Self>,
18845 ) {
18846 if let Some(file) = self.target_file(cx)
18847 && let Some(file_stem) = file.path().file_stem()
18848 && let Some(name) = file_stem.to_str()
18849 {
18850 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18851 }
18852 }
18853
18854 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18855 if let Some(file) = self.target_file(cx)
18856 && let Some(file_name) = file.path().file_name()
18857 && let Some(name) = file_name.to_str()
18858 {
18859 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18860 }
18861 }
18862
18863 pub fn toggle_git_blame(
18864 &mut self,
18865 _: &::git::Blame,
18866 window: &mut Window,
18867 cx: &mut Context<Self>,
18868 ) {
18869 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18870
18871 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18872 self.start_git_blame(true, window, cx);
18873 }
18874
18875 cx.notify();
18876 }
18877
18878 pub fn toggle_git_blame_inline(
18879 &mut self,
18880 _: &ToggleGitBlameInline,
18881 window: &mut Window,
18882 cx: &mut Context<Self>,
18883 ) {
18884 self.toggle_git_blame_inline_internal(true, window, cx);
18885 cx.notify();
18886 }
18887
18888 pub fn open_git_blame_commit(
18889 &mut self,
18890 _: &OpenGitBlameCommit,
18891 window: &mut Window,
18892 cx: &mut Context<Self>,
18893 ) {
18894 self.open_git_blame_commit_internal(window, cx);
18895 }
18896
18897 fn open_git_blame_commit_internal(
18898 &mut self,
18899 window: &mut Window,
18900 cx: &mut Context<Self>,
18901 ) -> Option<()> {
18902 let blame = self.blame.as_ref()?;
18903 let snapshot = self.snapshot(window, cx);
18904 let cursor = self.selections.newest::<Point>(cx).head();
18905 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18906 let blame_entry = blame
18907 .update(cx, |blame, cx| {
18908 blame
18909 .blame_for_rows(
18910 &[RowInfo {
18911 buffer_id: Some(buffer.remote_id()),
18912 buffer_row: Some(point.row),
18913 ..Default::default()
18914 }],
18915 cx,
18916 )
18917 .next()
18918 })
18919 .flatten()?;
18920 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18921 let repo = blame.read(cx).repository(cx)?;
18922 let workspace = self.workspace()?.downgrade();
18923 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18924 None
18925 }
18926
18927 pub fn git_blame_inline_enabled(&self) -> bool {
18928 self.git_blame_inline_enabled
18929 }
18930
18931 pub fn toggle_selection_menu(
18932 &mut self,
18933 _: &ToggleSelectionMenu,
18934 _: &mut Window,
18935 cx: &mut Context<Self>,
18936 ) {
18937 self.show_selection_menu = self
18938 .show_selection_menu
18939 .map(|show_selections_menu| !show_selections_menu)
18940 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18941
18942 cx.notify();
18943 }
18944
18945 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18946 self.show_selection_menu
18947 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18948 }
18949
18950 fn start_git_blame(
18951 &mut self,
18952 user_triggered: bool,
18953 window: &mut Window,
18954 cx: &mut Context<Self>,
18955 ) {
18956 if let Some(project) = self.project() {
18957 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18958 return;
18959 };
18960
18961 if buffer.read(cx).file().is_none() {
18962 return;
18963 }
18964
18965 let focused = self.focus_handle(cx).contains_focused(window, cx);
18966
18967 let project = project.clone();
18968 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18969 self.blame_subscription =
18970 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18971 self.blame = Some(blame);
18972 }
18973 }
18974
18975 fn toggle_git_blame_inline_internal(
18976 &mut self,
18977 user_triggered: bool,
18978 window: &mut Window,
18979 cx: &mut Context<Self>,
18980 ) {
18981 if self.git_blame_inline_enabled {
18982 self.git_blame_inline_enabled = false;
18983 self.show_git_blame_inline = false;
18984 self.show_git_blame_inline_delay_task.take();
18985 } else {
18986 self.git_blame_inline_enabled = true;
18987 self.start_git_blame_inline(user_triggered, window, cx);
18988 }
18989
18990 cx.notify();
18991 }
18992
18993 fn start_git_blame_inline(
18994 &mut self,
18995 user_triggered: bool,
18996 window: &mut Window,
18997 cx: &mut Context<Self>,
18998 ) {
18999 self.start_git_blame(user_triggered, window, cx);
19000
19001 if ProjectSettings::get_global(cx)
19002 .git
19003 .inline_blame_delay()
19004 .is_some()
19005 {
19006 self.start_inline_blame_timer(window, cx);
19007 } else {
19008 self.show_git_blame_inline = true
19009 }
19010 }
19011
19012 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19013 self.blame.as_ref()
19014 }
19015
19016 pub fn show_git_blame_gutter(&self) -> bool {
19017 self.show_git_blame_gutter
19018 }
19019
19020 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19021 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19022 }
19023
19024 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19025 self.show_git_blame_inline
19026 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19027 && !self.newest_selection_head_on_empty_line(cx)
19028 && self.has_blame_entries(cx)
19029 }
19030
19031 fn has_blame_entries(&self, cx: &App) -> bool {
19032 self.blame()
19033 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19034 }
19035
19036 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19037 let cursor_anchor = self.selections.newest_anchor().head();
19038
19039 let snapshot = self.buffer.read(cx).snapshot(cx);
19040 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19041
19042 snapshot.line_len(buffer_row) == 0
19043 }
19044
19045 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19046 let buffer_and_selection = maybe!({
19047 let selection = self.selections.newest::<Point>(cx);
19048 let selection_range = selection.range();
19049
19050 let multi_buffer = self.buffer().read(cx);
19051 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19052 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19053
19054 let (buffer, range, _) = if selection.reversed {
19055 buffer_ranges.first()
19056 } else {
19057 buffer_ranges.last()
19058 }?;
19059
19060 let selection = text::ToPoint::to_point(&range.start, buffer).row
19061 ..text::ToPoint::to_point(&range.end, buffer).row;
19062 Some((
19063 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
19064 selection,
19065 ))
19066 });
19067
19068 let Some((buffer, selection)) = buffer_and_selection else {
19069 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19070 };
19071
19072 let Some(project) = self.project() else {
19073 return Task::ready(Err(anyhow!("editor does not have project")));
19074 };
19075
19076 project.update(cx, |project, cx| {
19077 project.get_permalink_to_line(&buffer, selection, cx)
19078 })
19079 }
19080
19081 pub fn copy_permalink_to_line(
19082 &mut self,
19083 _: &CopyPermalinkToLine,
19084 window: &mut Window,
19085 cx: &mut Context<Self>,
19086 ) {
19087 let permalink_task = self.get_permalink_to_line(cx);
19088 let workspace = self.workspace();
19089
19090 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19091 Ok(permalink) => {
19092 cx.update(|_, cx| {
19093 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19094 })
19095 .ok();
19096 }
19097 Err(err) => {
19098 let message = format!("Failed to copy permalink: {err}");
19099
19100 anyhow::Result::<()>::Err(err).log_err();
19101
19102 if let Some(workspace) = workspace {
19103 workspace
19104 .update_in(cx, |workspace, _, cx| {
19105 struct CopyPermalinkToLine;
19106
19107 workspace.show_toast(
19108 Toast::new(
19109 NotificationId::unique::<CopyPermalinkToLine>(),
19110 message,
19111 ),
19112 cx,
19113 )
19114 })
19115 .ok();
19116 }
19117 }
19118 })
19119 .detach();
19120 }
19121
19122 pub fn copy_file_location(
19123 &mut self,
19124 _: &CopyFileLocation,
19125 _: &mut Window,
19126 cx: &mut Context<Self>,
19127 ) {
19128 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19129 if let Some(file) = self.target_file(cx)
19130 && let Some(path) = file.path().to_str()
19131 {
19132 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19133 }
19134 }
19135
19136 pub fn open_permalink_to_line(
19137 &mut self,
19138 _: &OpenPermalinkToLine,
19139 window: &mut Window,
19140 cx: &mut Context<Self>,
19141 ) {
19142 let permalink_task = self.get_permalink_to_line(cx);
19143 let workspace = self.workspace();
19144
19145 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19146 Ok(permalink) => {
19147 cx.update(|_, cx| {
19148 cx.open_url(permalink.as_ref());
19149 })
19150 .ok();
19151 }
19152 Err(err) => {
19153 let message = format!("Failed to open permalink: {err}");
19154
19155 anyhow::Result::<()>::Err(err).log_err();
19156
19157 if let Some(workspace) = workspace {
19158 workspace
19159 .update(cx, |workspace, cx| {
19160 struct OpenPermalinkToLine;
19161
19162 workspace.show_toast(
19163 Toast::new(
19164 NotificationId::unique::<OpenPermalinkToLine>(),
19165 message,
19166 ),
19167 cx,
19168 )
19169 })
19170 .ok();
19171 }
19172 }
19173 })
19174 .detach();
19175 }
19176
19177 pub fn insert_uuid_v4(
19178 &mut self,
19179 _: &InsertUuidV4,
19180 window: &mut Window,
19181 cx: &mut Context<Self>,
19182 ) {
19183 self.insert_uuid(UuidVersion::V4, window, cx);
19184 }
19185
19186 pub fn insert_uuid_v7(
19187 &mut self,
19188 _: &InsertUuidV7,
19189 window: &mut Window,
19190 cx: &mut Context<Self>,
19191 ) {
19192 self.insert_uuid(UuidVersion::V7, window, cx);
19193 }
19194
19195 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19196 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19197 self.transact(window, cx, |this, window, cx| {
19198 let edits = this
19199 .selections
19200 .all::<Point>(cx)
19201 .into_iter()
19202 .map(|selection| {
19203 let uuid = match version {
19204 UuidVersion::V4 => uuid::Uuid::new_v4(),
19205 UuidVersion::V7 => uuid::Uuid::now_v7(),
19206 };
19207
19208 (selection.range(), uuid.to_string())
19209 });
19210 this.edit(edits, cx);
19211 this.refresh_edit_prediction(true, false, window, cx);
19212 });
19213 }
19214
19215 pub fn open_selections_in_multibuffer(
19216 &mut self,
19217 _: &OpenSelectionsInMultibuffer,
19218 window: &mut Window,
19219 cx: &mut Context<Self>,
19220 ) {
19221 let multibuffer = self.buffer.read(cx);
19222
19223 let Some(buffer) = multibuffer.as_singleton() else {
19224 return;
19225 };
19226
19227 let Some(workspace) = self.workspace() else {
19228 return;
19229 };
19230
19231 let title = multibuffer.title(cx).to_string();
19232
19233 let locations = self
19234 .selections
19235 .all_anchors(cx)
19236 .into_iter()
19237 .map(|selection| Location {
19238 buffer: buffer.clone(),
19239 range: selection.start.text_anchor..selection.end.text_anchor,
19240 })
19241 .collect::<Vec<_>>();
19242
19243 cx.spawn_in(window, async move |_, cx| {
19244 workspace.update_in(cx, |workspace, window, cx| {
19245 Self::open_locations_in_multibuffer(
19246 workspace,
19247 locations,
19248 format!("Selections for '{title}'"),
19249 false,
19250 MultibufferSelectionMode::All,
19251 window,
19252 cx,
19253 );
19254 })
19255 })
19256 .detach();
19257 }
19258
19259 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19260 /// last highlight added will be used.
19261 ///
19262 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19263 pub fn highlight_rows<T: 'static>(
19264 &mut self,
19265 range: Range<Anchor>,
19266 color: Hsla,
19267 options: RowHighlightOptions,
19268 cx: &mut Context<Self>,
19269 ) {
19270 let snapshot = self.buffer().read(cx).snapshot(cx);
19271 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19272 let ix = row_highlights.binary_search_by(|highlight| {
19273 Ordering::Equal
19274 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19275 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19276 });
19277
19278 if let Err(mut ix) = ix {
19279 let index = post_inc(&mut self.highlight_order);
19280
19281 // If this range intersects with the preceding highlight, then merge it with
19282 // the preceding highlight. Otherwise insert a new highlight.
19283 let mut merged = false;
19284 if ix > 0 {
19285 let prev_highlight = &mut row_highlights[ix - 1];
19286 if prev_highlight
19287 .range
19288 .end
19289 .cmp(&range.start, &snapshot)
19290 .is_ge()
19291 {
19292 ix -= 1;
19293 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19294 prev_highlight.range.end = range.end;
19295 }
19296 merged = true;
19297 prev_highlight.index = index;
19298 prev_highlight.color = color;
19299 prev_highlight.options = options;
19300 }
19301 }
19302
19303 if !merged {
19304 row_highlights.insert(
19305 ix,
19306 RowHighlight {
19307 range: range.clone(),
19308 index,
19309 color,
19310 options,
19311 type_id: TypeId::of::<T>(),
19312 },
19313 );
19314 }
19315
19316 // If any of the following highlights intersect with this one, merge them.
19317 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19318 let highlight = &row_highlights[ix];
19319 if next_highlight
19320 .range
19321 .start
19322 .cmp(&highlight.range.end, &snapshot)
19323 .is_le()
19324 {
19325 if next_highlight
19326 .range
19327 .end
19328 .cmp(&highlight.range.end, &snapshot)
19329 .is_gt()
19330 {
19331 row_highlights[ix].range.end = next_highlight.range.end;
19332 }
19333 row_highlights.remove(ix + 1);
19334 } else {
19335 break;
19336 }
19337 }
19338 }
19339 }
19340
19341 /// Remove any highlighted row ranges of the given type that intersect the
19342 /// given ranges.
19343 pub fn remove_highlighted_rows<T: 'static>(
19344 &mut self,
19345 ranges_to_remove: Vec<Range<Anchor>>,
19346 cx: &mut Context<Self>,
19347 ) {
19348 let snapshot = self.buffer().read(cx).snapshot(cx);
19349 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19350 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19351 row_highlights.retain(|highlight| {
19352 while let Some(range_to_remove) = ranges_to_remove.peek() {
19353 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19354 Ordering::Less | Ordering::Equal => {
19355 ranges_to_remove.next();
19356 }
19357 Ordering::Greater => {
19358 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19359 Ordering::Less | Ordering::Equal => {
19360 return false;
19361 }
19362 Ordering::Greater => break,
19363 }
19364 }
19365 }
19366 }
19367
19368 true
19369 })
19370 }
19371
19372 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19373 pub fn clear_row_highlights<T: 'static>(&mut self) {
19374 self.highlighted_rows.remove(&TypeId::of::<T>());
19375 }
19376
19377 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19378 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19379 self.highlighted_rows
19380 .get(&TypeId::of::<T>())
19381 .map_or(&[] as &[_], |vec| vec.as_slice())
19382 .iter()
19383 .map(|highlight| (highlight.range.clone(), highlight.color))
19384 }
19385
19386 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19387 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19388 /// Allows to ignore certain kinds of highlights.
19389 pub fn highlighted_display_rows(
19390 &self,
19391 window: &mut Window,
19392 cx: &mut App,
19393 ) -> BTreeMap<DisplayRow, LineHighlight> {
19394 let snapshot = self.snapshot(window, cx);
19395 let mut used_highlight_orders = HashMap::default();
19396 self.highlighted_rows
19397 .iter()
19398 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19399 .fold(
19400 BTreeMap::<DisplayRow, LineHighlight>::new(),
19401 |mut unique_rows, highlight| {
19402 let start = highlight.range.start.to_display_point(&snapshot);
19403 let end = highlight.range.end.to_display_point(&snapshot);
19404 let start_row = start.row().0;
19405 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19406 && end.column() == 0
19407 {
19408 end.row().0.saturating_sub(1)
19409 } else {
19410 end.row().0
19411 };
19412 for row in start_row..=end_row {
19413 let used_index =
19414 used_highlight_orders.entry(row).or_insert(highlight.index);
19415 if highlight.index >= *used_index {
19416 *used_index = highlight.index;
19417 unique_rows.insert(
19418 DisplayRow(row),
19419 LineHighlight {
19420 include_gutter: highlight.options.include_gutter,
19421 border: None,
19422 background: highlight.color.into(),
19423 type_id: Some(highlight.type_id),
19424 },
19425 );
19426 }
19427 }
19428 unique_rows
19429 },
19430 )
19431 }
19432
19433 pub fn highlighted_display_row_for_autoscroll(
19434 &self,
19435 snapshot: &DisplaySnapshot,
19436 ) -> Option<DisplayRow> {
19437 self.highlighted_rows
19438 .values()
19439 .flat_map(|highlighted_rows| highlighted_rows.iter())
19440 .filter_map(|highlight| {
19441 if highlight.options.autoscroll {
19442 Some(highlight.range.start.to_display_point(snapshot).row())
19443 } else {
19444 None
19445 }
19446 })
19447 .min()
19448 }
19449
19450 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19451 self.highlight_background::<SearchWithinRange>(
19452 ranges,
19453 |colors| colors.colors().editor_document_highlight_read_background,
19454 cx,
19455 )
19456 }
19457
19458 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19459 self.breadcrumb_header = Some(new_header);
19460 }
19461
19462 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19463 self.clear_background_highlights::<SearchWithinRange>(cx);
19464 }
19465
19466 pub fn highlight_background<T: 'static>(
19467 &mut self,
19468 ranges: &[Range<Anchor>],
19469 color_fetcher: fn(&Theme) -> Hsla,
19470 cx: &mut Context<Self>,
19471 ) {
19472 self.background_highlights.insert(
19473 HighlightKey::Type(TypeId::of::<T>()),
19474 (color_fetcher, Arc::from(ranges)),
19475 );
19476 self.scrollbar_marker_state.dirty = true;
19477 cx.notify();
19478 }
19479
19480 pub fn highlight_background_key<T: 'static>(
19481 &mut self,
19482 key: usize,
19483 ranges: &[Range<Anchor>],
19484 color_fetcher: fn(&Theme) -> Hsla,
19485 cx: &mut Context<Self>,
19486 ) {
19487 self.background_highlights.insert(
19488 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19489 (color_fetcher, Arc::from(ranges)),
19490 );
19491 self.scrollbar_marker_state.dirty = true;
19492 cx.notify();
19493 }
19494
19495 pub fn clear_background_highlights<T: 'static>(
19496 &mut self,
19497 cx: &mut Context<Self>,
19498 ) -> Option<BackgroundHighlight> {
19499 let text_highlights = self
19500 .background_highlights
19501 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19502 if !text_highlights.1.is_empty() {
19503 self.scrollbar_marker_state.dirty = true;
19504 cx.notify();
19505 }
19506 Some(text_highlights)
19507 }
19508
19509 pub fn highlight_gutter<T: 'static>(
19510 &mut self,
19511 ranges: impl Into<Vec<Range<Anchor>>>,
19512 color_fetcher: fn(&App) -> Hsla,
19513 cx: &mut Context<Self>,
19514 ) {
19515 self.gutter_highlights
19516 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19517 cx.notify();
19518 }
19519
19520 pub fn clear_gutter_highlights<T: 'static>(
19521 &mut self,
19522 cx: &mut Context<Self>,
19523 ) -> Option<GutterHighlight> {
19524 cx.notify();
19525 self.gutter_highlights.remove(&TypeId::of::<T>())
19526 }
19527
19528 pub fn insert_gutter_highlight<T: 'static>(
19529 &mut self,
19530 range: Range<Anchor>,
19531 color_fetcher: fn(&App) -> Hsla,
19532 cx: &mut Context<Self>,
19533 ) {
19534 let snapshot = self.buffer().read(cx).snapshot(cx);
19535 let mut highlights = self
19536 .gutter_highlights
19537 .remove(&TypeId::of::<T>())
19538 .map(|(_, highlights)| highlights)
19539 .unwrap_or_default();
19540 let ix = highlights.binary_search_by(|highlight| {
19541 Ordering::Equal
19542 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19543 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19544 });
19545 if let Err(ix) = ix {
19546 highlights.insert(ix, range);
19547 }
19548 self.gutter_highlights
19549 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19550 }
19551
19552 pub fn remove_gutter_highlights<T: 'static>(
19553 &mut self,
19554 ranges_to_remove: Vec<Range<Anchor>>,
19555 cx: &mut Context<Self>,
19556 ) {
19557 let snapshot = self.buffer().read(cx).snapshot(cx);
19558 let Some((color_fetcher, mut gutter_highlights)) =
19559 self.gutter_highlights.remove(&TypeId::of::<T>())
19560 else {
19561 return;
19562 };
19563 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19564 gutter_highlights.retain(|highlight| {
19565 while let Some(range_to_remove) = ranges_to_remove.peek() {
19566 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19567 Ordering::Less | Ordering::Equal => {
19568 ranges_to_remove.next();
19569 }
19570 Ordering::Greater => {
19571 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19572 Ordering::Less | Ordering::Equal => {
19573 return false;
19574 }
19575 Ordering::Greater => break,
19576 }
19577 }
19578 }
19579 }
19580
19581 true
19582 });
19583 self.gutter_highlights
19584 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19585 }
19586
19587 #[cfg(feature = "test-support")]
19588 pub fn all_text_highlights(
19589 &self,
19590 window: &mut Window,
19591 cx: &mut Context<Self>,
19592 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19593 let snapshot = self.snapshot(window, cx);
19594 self.display_map.update(cx, |display_map, _| {
19595 display_map
19596 .all_text_highlights()
19597 .map(|highlight| {
19598 let (style, ranges) = highlight.as_ref();
19599 (
19600 *style,
19601 ranges
19602 .iter()
19603 .map(|range| range.clone().to_display_points(&snapshot))
19604 .collect(),
19605 )
19606 })
19607 .collect()
19608 })
19609 }
19610
19611 #[cfg(feature = "test-support")]
19612 pub fn all_text_background_highlights(
19613 &self,
19614 window: &mut Window,
19615 cx: &mut Context<Self>,
19616 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19617 let snapshot = self.snapshot(window, cx);
19618 let buffer = &snapshot.buffer_snapshot;
19619 let start = buffer.anchor_before(0);
19620 let end = buffer.anchor_after(buffer.len());
19621 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19622 }
19623
19624 #[cfg(feature = "test-support")]
19625 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19626 let snapshot = self.buffer().read(cx).snapshot(cx);
19627
19628 let highlights = self
19629 .background_highlights
19630 .get(&HighlightKey::Type(TypeId::of::<
19631 items::BufferSearchHighlights,
19632 >()));
19633
19634 if let Some((_color, ranges)) = highlights {
19635 ranges
19636 .iter()
19637 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19638 .collect_vec()
19639 } else {
19640 vec![]
19641 }
19642 }
19643
19644 fn document_highlights_for_position<'a>(
19645 &'a self,
19646 position: Anchor,
19647 buffer: &'a MultiBufferSnapshot,
19648 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19649 let read_highlights = self
19650 .background_highlights
19651 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19652 .map(|h| &h.1);
19653 let write_highlights = self
19654 .background_highlights
19655 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19656 .map(|h| &h.1);
19657 let left_position = position.bias_left(buffer);
19658 let right_position = position.bias_right(buffer);
19659 read_highlights
19660 .into_iter()
19661 .chain(write_highlights)
19662 .flat_map(move |ranges| {
19663 let start_ix = match ranges.binary_search_by(|probe| {
19664 let cmp = probe.end.cmp(&left_position, buffer);
19665 if cmp.is_ge() {
19666 Ordering::Greater
19667 } else {
19668 Ordering::Less
19669 }
19670 }) {
19671 Ok(i) | Err(i) => i,
19672 };
19673
19674 ranges[start_ix..]
19675 .iter()
19676 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19677 })
19678 }
19679
19680 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19681 self.background_highlights
19682 .get(&HighlightKey::Type(TypeId::of::<T>()))
19683 .is_some_and(|(_, highlights)| !highlights.is_empty())
19684 }
19685
19686 pub fn background_highlights_in_range(
19687 &self,
19688 search_range: Range<Anchor>,
19689 display_snapshot: &DisplaySnapshot,
19690 theme: &Theme,
19691 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19692 let mut results = Vec::new();
19693 for (color_fetcher, ranges) in self.background_highlights.values() {
19694 let color = color_fetcher(theme);
19695 let start_ix = match ranges.binary_search_by(|probe| {
19696 let cmp = probe
19697 .end
19698 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19699 if cmp.is_gt() {
19700 Ordering::Greater
19701 } else {
19702 Ordering::Less
19703 }
19704 }) {
19705 Ok(i) | Err(i) => i,
19706 };
19707 for range in &ranges[start_ix..] {
19708 if range
19709 .start
19710 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19711 .is_ge()
19712 {
19713 break;
19714 }
19715
19716 let start = range.start.to_display_point(display_snapshot);
19717 let end = range.end.to_display_point(display_snapshot);
19718 results.push((start..end, color))
19719 }
19720 }
19721 results
19722 }
19723
19724 pub fn background_highlight_row_ranges<T: 'static>(
19725 &self,
19726 search_range: Range<Anchor>,
19727 display_snapshot: &DisplaySnapshot,
19728 count: usize,
19729 ) -> Vec<RangeInclusive<DisplayPoint>> {
19730 let mut results = Vec::new();
19731 let Some((_, ranges)) = self
19732 .background_highlights
19733 .get(&HighlightKey::Type(TypeId::of::<T>()))
19734 else {
19735 return vec![];
19736 };
19737
19738 let start_ix = match ranges.binary_search_by(|probe| {
19739 let cmp = probe
19740 .end
19741 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19742 if cmp.is_gt() {
19743 Ordering::Greater
19744 } else {
19745 Ordering::Less
19746 }
19747 }) {
19748 Ok(i) | Err(i) => i,
19749 };
19750 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19751 if let (Some(start_display), Some(end_display)) = (start, end) {
19752 results.push(
19753 start_display.to_display_point(display_snapshot)
19754 ..=end_display.to_display_point(display_snapshot),
19755 );
19756 }
19757 };
19758 let mut start_row: Option<Point> = None;
19759 let mut end_row: Option<Point> = None;
19760 if ranges.len() > count {
19761 return Vec::new();
19762 }
19763 for range in &ranges[start_ix..] {
19764 if range
19765 .start
19766 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19767 .is_ge()
19768 {
19769 break;
19770 }
19771 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19772 if let Some(current_row) = &end_row
19773 && end.row == current_row.row
19774 {
19775 continue;
19776 }
19777 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19778 if start_row.is_none() {
19779 assert_eq!(end_row, None);
19780 start_row = Some(start);
19781 end_row = Some(end);
19782 continue;
19783 }
19784 if let Some(current_end) = end_row.as_mut() {
19785 if start.row > current_end.row + 1 {
19786 push_region(start_row, end_row);
19787 start_row = Some(start);
19788 end_row = Some(end);
19789 } else {
19790 // Merge two hunks.
19791 *current_end = end;
19792 }
19793 } else {
19794 unreachable!();
19795 }
19796 }
19797 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19798 push_region(start_row, end_row);
19799 results
19800 }
19801
19802 pub fn gutter_highlights_in_range(
19803 &self,
19804 search_range: Range<Anchor>,
19805 display_snapshot: &DisplaySnapshot,
19806 cx: &App,
19807 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19808 let mut results = Vec::new();
19809 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19810 let color = color_fetcher(cx);
19811 let start_ix = match ranges.binary_search_by(|probe| {
19812 let cmp = probe
19813 .end
19814 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19815 if cmp.is_gt() {
19816 Ordering::Greater
19817 } else {
19818 Ordering::Less
19819 }
19820 }) {
19821 Ok(i) | Err(i) => i,
19822 };
19823 for range in &ranges[start_ix..] {
19824 if range
19825 .start
19826 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19827 .is_ge()
19828 {
19829 break;
19830 }
19831
19832 let start = range.start.to_display_point(display_snapshot);
19833 let end = range.end.to_display_point(display_snapshot);
19834 results.push((start..end, color))
19835 }
19836 }
19837 results
19838 }
19839
19840 /// Get the text ranges corresponding to the redaction query
19841 pub fn redacted_ranges(
19842 &self,
19843 search_range: Range<Anchor>,
19844 display_snapshot: &DisplaySnapshot,
19845 cx: &App,
19846 ) -> Vec<Range<DisplayPoint>> {
19847 display_snapshot
19848 .buffer_snapshot
19849 .redacted_ranges(search_range, |file| {
19850 if let Some(file) = file {
19851 file.is_private()
19852 && EditorSettings::get(
19853 Some(SettingsLocation {
19854 worktree_id: file.worktree_id(cx),
19855 path: file.path().as_ref(),
19856 }),
19857 cx,
19858 )
19859 .redact_private_values
19860 } else {
19861 false
19862 }
19863 })
19864 .map(|range| {
19865 range.start.to_display_point(display_snapshot)
19866 ..range.end.to_display_point(display_snapshot)
19867 })
19868 .collect()
19869 }
19870
19871 pub fn highlight_text_key<T: 'static>(
19872 &mut self,
19873 key: usize,
19874 ranges: Vec<Range<Anchor>>,
19875 style: HighlightStyle,
19876 cx: &mut Context<Self>,
19877 ) {
19878 self.display_map.update(cx, |map, _| {
19879 map.highlight_text(
19880 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19881 ranges,
19882 style,
19883 );
19884 });
19885 cx.notify();
19886 }
19887
19888 pub fn highlight_text<T: 'static>(
19889 &mut self,
19890 ranges: Vec<Range<Anchor>>,
19891 style: HighlightStyle,
19892 cx: &mut Context<Self>,
19893 ) {
19894 self.display_map.update(cx, |map, _| {
19895 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19896 });
19897 cx.notify();
19898 }
19899
19900 pub(crate) fn highlight_inlays<T: 'static>(
19901 &mut self,
19902 highlights: Vec<InlayHighlight>,
19903 style: HighlightStyle,
19904 cx: &mut Context<Self>,
19905 ) {
19906 self.display_map.update(cx, |map, _| {
19907 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19908 });
19909 cx.notify();
19910 }
19911
19912 pub fn text_highlights<'a, T: 'static>(
19913 &'a self,
19914 cx: &'a App,
19915 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19916 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19917 }
19918
19919 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19920 let cleared = self
19921 .display_map
19922 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19923 if cleared {
19924 cx.notify();
19925 }
19926 }
19927
19928 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19929 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19930 && self.focus_handle.is_focused(window)
19931 }
19932
19933 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19934 self.show_cursor_when_unfocused = is_enabled;
19935 cx.notify();
19936 }
19937
19938 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19939 cx.notify();
19940 }
19941
19942 fn on_debug_session_event(
19943 &mut self,
19944 _session: Entity<Session>,
19945 event: &SessionEvent,
19946 cx: &mut Context<Self>,
19947 ) {
19948 match event {
19949 SessionEvent::InvalidateInlineValue => {
19950 self.refresh_inline_values(cx);
19951 }
19952 _ => {}
19953 }
19954 }
19955
19956 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19957 let Some(project) = self.project.clone() else {
19958 return;
19959 };
19960
19961 if !self.inline_value_cache.enabled {
19962 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19963 self.splice_inlays(&inlays, Vec::new(), cx);
19964 return;
19965 }
19966
19967 let current_execution_position = self
19968 .highlighted_rows
19969 .get(&TypeId::of::<ActiveDebugLine>())
19970 .and_then(|lines| lines.last().map(|line| line.range.end));
19971
19972 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19973 let inline_values = editor
19974 .update(cx, |editor, cx| {
19975 let Some(current_execution_position) = current_execution_position else {
19976 return Some(Task::ready(Ok(Vec::new())));
19977 };
19978
19979 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19980 let snapshot = buffer.snapshot(cx);
19981
19982 let excerpt = snapshot.excerpt_containing(
19983 current_execution_position..current_execution_position,
19984 )?;
19985
19986 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19987 })?;
19988
19989 let range =
19990 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19991
19992 project.inline_values(buffer, range, cx)
19993 })
19994 .ok()
19995 .flatten()?
19996 .await
19997 .context("refreshing debugger inlays")
19998 .log_err()?;
19999
20000 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20001
20002 for (buffer_id, inline_value) in inline_values
20003 .into_iter()
20004 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20005 {
20006 buffer_inline_values
20007 .entry(buffer_id)
20008 .or_default()
20009 .push(inline_value);
20010 }
20011
20012 editor
20013 .update(cx, |editor, cx| {
20014 let snapshot = editor.buffer.read(cx).snapshot(cx);
20015 let mut new_inlays = Vec::default();
20016
20017 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20018 let buffer_id = buffer_snapshot.remote_id();
20019 buffer_inline_values
20020 .get(&buffer_id)
20021 .into_iter()
20022 .flatten()
20023 .for_each(|hint| {
20024 let inlay = Inlay::debugger(
20025 post_inc(&mut editor.next_inlay_id),
20026 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20027 hint.text(),
20028 );
20029 if !inlay.text.chars().contains(&'\n') {
20030 new_inlays.push(inlay);
20031 }
20032 });
20033 }
20034
20035 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20036 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20037
20038 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20039 })
20040 .ok()?;
20041 Some(())
20042 });
20043 }
20044
20045 fn on_buffer_event(
20046 &mut self,
20047 multibuffer: &Entity<MultiBuffer>,
20048 event: &multi_buffer::Event,
20049 window: &mut Window,
20050 cx: &mut Context<Self>,
20051 ) {
20052 match event {
20053 multi_buffer::Event::Edited {
20054 singleton_buffer_edited,
20055 edited_buffer,
20056 } => {
20057 self.scrollbar_marker_state.dirty = true;
20058 self.active_indent_guides_state.dirty = true;
20059 self.refresh_active_diagnostics(cx);
20060 self.refresh_code_actions(window, cx);
20061 self.refresh_selected_text_highlights(true, window, cx);
20062 self.refresh_single_line_folds(window, cx);
20063 refresh_matching_bracket_highlights(self, window, cx);
20064 if self.has_active_edit_prediction() {
20065 self.update_visible_edit_prediction(window, cx);
20066 }
20067 if let Some(project) = self.project.as_ref()
20068 && let Some(edited_buffer) = edited_buffer
20069 {
20070 project.update(cx, |project, cx| {
20071 self.registered_buffers
20072 .entry(edited_buffer.read(cx).remote_id())
20073 .or_insert_with(|| {
20074 project.register_buffer_with_language_servers(edited_buffer, cx)
20075 });
20076 });
20077 }
20078 cx.emit(EditorEvent::BufferEdited);
20079 cx.emit(SearchEvent::MatchesInvalidated);
20080
20081 if let Some(buffer) = edited_buffer {
20082 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20083 }
20084
20085 if *singleton_buffer_edited {
20086 if let Some(buffer) = edited_buffer
20087 && buffer.read(cx).file().is_none()
20088 {
20089 cx.emit(EditorEvent::TitleChanged);
20090 }
20091 if let Some(project) = &self.project {
20092 #[allow(clippy::mutable_key_type)]
20093 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20094 multibuffer
20095 .all_buffers()
20096 .into_iter()
20097 .filter_map(|buffer| {
20098 buffer.update(cx, |buffer, cx| {
20099 let language = buffer.language()?;
20100 let should_discard = project.update(cx, |project, cx| {
20101 project.is_local()
20102 && !project.has_language_servers_for(buffer, cx)
20103 });
20104 should_discard.not().then_some(language.clone())
20105 })
20106 })
20107 .collect::<HashSet<_>>()
20108 });
20109 if !languages_affected.is_empty() {
20110 self.refresh_inlay_hints(
20111 InlayHintRefreshReason::BufferEdited(languages_affected),
20112 cx,
20113 );
20114 }
20115 }
20116 }
20117
20118 let Some(project) = &self.project else { return };
20119 let (telemetry, is_via_ssh) = {
20120 let project = project.read(cx);
20121 let telemetry = project.client().telemetry().clone();
20122 let is_via_ssh = project.is_via_ssh();
20123 (telemetry, is_via_ssh)
20124 };
20125 refresh_linked_ranges(self, window, cx);
20126 telemetry.log_edit_event("editor", is_via_ssh);
20127 }
20128 multi_buffer::Event::ExcerptsAdded {
20129 buffer,
20130 predecessor,
20131 excerpts,
20132 } => {
20133 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20134 let buffer_id = buffer.read(cx).remote_id();
20135 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20136 && let Some(project) = &self.project
20137 {
20138 update_uncommitted_diff_for_buffer(
20139 cx.entity(),
20140 project,
20141 [buffer.clone()],
20142 self.buffer.clone(),
20143 cx,
20144 )
20145 .detach();
20146 }
20147 self.update_lsp_data(false, Some(buffer_id), window, cx);
20148 cx.emit(EditorEvent::ExcerptsAdded {
20149 buffer: buffer.clone(),
20150 predecessor: *predecessor,
20151 excerpts: excerpts.clone(),
20152 });
20153 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20154 }
20155 multi_buffer::Event::ExcerptsRemoved {
20156 ids,
20157 removed_buffer_ids,
20158 } => {
20159 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20160 let buffer = self.buffer.read(cx);
20161 self.registered_buffers
20162 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20163 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20164 cx.emit(EditorEvent::ExcerptsRemoved {
20165 ids: ids.clone(),
20166 removed_buffer_ids: removed_buffer_ids.clone(),
20167 });
20168 }
20169 multi_buffer::Event::ExcerptsEdited {
20170 excerpt_ids,
20171 buffer_ids,
20172 } => {
20173 self.display_map.update(cx, |map, cx| {
20174 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20175 });
20176 cx.emit(EditorEvent::ExcerptsEdited {
20177 ids: excerpt_ids.clone(),
20178 });
20179 }
20180 multi_buffer::Event::ExcerptsExpanded { ids } => {
20181 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20182 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20183 }
20184 multi_buffer::Event::Reparsed(buffer_id) => {
20185 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20186 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20187
20188 cx.emit(EditorEvent::Reparsed(*buffer_id));
20189 }
20190 multi_buffer::Event::DiffHunksToggled => {
20191 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20192 }
20193 multi_buffer::Event::LanguageChanged(buffer_id) => {
20194 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20195 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20196 cx.emit(EditorEvent::Reparsed(*buffer_id));
20197 cx.notify();
20198 }
20199 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20200 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20201 multi_buffer::Event::FileHandleChanged
20202 | multi_buffer::Event::Reloaded
20203 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20204 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20205 multi_buffer::Event::DiagnosticsUpdated => {
20206 self.update_diagnostics_state(window, cx);
20207 }
20208 _ => {}
20209 };
20210 }
20211
20212 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20213 if !self.diagnostics_enabled() {
20214 return;
20215 }
20216 self.refresh_active_diagnostics(cx);
20217 self.refresh_inline_diagnostics(true, window, cx);
20218 self.scrollbar_marker_state.dirty = true;
20219 cx.notify();
20220 }
20221
20222 pub fn start_temporary_diff_override(&mut self) {
20223 self.load_diff_task.take();
20224 self.temporary_diff_override = true;
20225 }
20226
20227 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20228 self.temporary_diff_override = false;
20229 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20230 self.buffer.update(cx, |buffer, cx| {
20231 buffer.set_all_diff_hunks_collapsed(cx);
20232 });
20233
20234 if let Some(project) = self.project.clone() {
20235 self.load_diff_task = Some(
20236 update_uncommitted_diff_for_buffer(
20237 cx.entity(),
20238 &project,
20239 self.buffer.read(cx).all_buffers(),
20240 self.buffer.clone(),
20241 cx,
20242 )
20243 .shared(),
20244 );
20245 }
20246 }
20247
20248 fn on_display_map_changed(
20249 &mut self,
20250 _: Entity<DisplayMap>,
20251 _: &mut Window,
20252 cx: &mut Context<Self>,
20253 ) {
20254 cx.notify();
20255 }
20256
20257 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20258 if self.diagnostics_enabled() {
20259 let new_severity = EditorSettings::get_global(cx)
20260 .diagnostics_max_severity
20261 .unwrap_or(DiagnosticSeverity::Hint);
20262 self.set_max_diagnostics_severity(new_severity, cx);
20263 }
20264 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20265 self.update_edit_prediction_settings(cx);
20266 self.refresh_edit_prediction(true, false, window, cx);
20267 self.refresh_inline_values(cx);
20268 self.refresh_inlay_hints(
20269 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20270 self.selections.newest_anchor().head(),
20271 &self.buffer.read(cx).snapshot(cx),
20272 cx,
20273 )),
20274 cx,
20275 );
20276
20277 let old_cursor_shape = self.cursor_shape;
20278 let old_show_breadcrumbs = self.show_breadcrumbs;
20279
20280 {
20281 let editor_settings = EditorSettings::get_global(cx);
20282 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20283 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20284 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20285 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20286 }
20287
20288 if old_cursor_shape != self.cursor_shape {
20289 cx.emit(EditorEvent::CursorShapeChanged);
20290 }
20291
20292 if old_show_breadcrumbs != self.show_breadcrumbs {
20293 cx.emit(EditorEvent::BreadcrumbsChanged);
20294 }
20295
20296 let project_settings = ProjectSettings::get_global(cx);
20297 self.serialize_dirty_buffers =
20298 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20299
20300 if self.mode.is_full() {
20301 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20302 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20303 if self.show_inline_diagnostics != show_inline_diagnostics {
20304 self.show_inline_diagnostics = show_inline_diagnostics;
20305 self.refresh_inline_diagnostics(false, window, cx);
20306 }
20307
20308 if self.git_blame_inline_enabled != inline_blame_enabled {
20309 self.toggle_git_blame_inline_internal(false, window, cx);
20310 }
20311
20312 let minimap_settings = EditorSettings::get_global(cx).minimap;
20313 if self.minimap_visibility != MinimapVisibility::Disabled {
20314 if self.minimap_visibility.settings_visibility()
20315 != minimap_settings.minimap_enabled()
20316 {
20317 self.set_minimap_visibility(
20318 MinimapVisibility::for_mode(self.mode(), cx),
20319 window,
20320 cx,
20321 );
20322 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20323 minimap_entity.update(cx, |minimap_editor, cx| {
20324 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20325 })
20326 }
20327 }
20328 }
20329
20330 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20331 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20332 }) {
20333 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20334 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20335 }
20336 self.refresh_colors(false, None, window, cx);
20337 }
20338
20339 cx.notify();
20340 }
20341
20342 pub fn set_searchable(&mut self, searchable: bool) {
20343 self.searchable = searchable;
20344 }
20345
20346 pub fn searchable(&self) -> bool {
20347 self.searchable
20348 }
20349
20350 fn open_proposed_changes_editor(
20351 &mut self,
20352 _: &OpenProposedChangesEditor,
20353 window: &mut Window,
20354 cx: &mut Context<Self>,
20355 ) {
20356 let Some(workspace) = self.workspace() else {
20357 cx.propagate();
20358 return;
20359 };
20360
20361 let selections = self.selections.all::<usize>(cx);
20362 let multi_buffer = self.buffer.read(cx);
20363 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20364 let mut new_selections_by_buffer = HashMap::default();
20365 for selection in selections {
20366 for (buffer, range, _) in
20367 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20368 {
20369 let mut range = range.to_point(buffer);
20370 range.start.column = 0;
20371 range.end.column = buffer.line_len(range.end.row);
20372 new_selections_by_buffer
20373 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20374 .or_insert(Vec::new())
20375 .push(range)
20376 }
20377 }
20378
20379 let proposed_changes_buffers = new_selections_by_buffer
20380 .into_iter()
20381 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20382 .collect::<Vec<_>>();
20383 let proposed_changes_editor = cx.new(|cx| {
20384 ProposedChangesEditor::new(
20385 "Proposed changes",
20386 proposed_changes_buffers,
20387 self.project.clone(),
20388 window,
20389 cx,
20390 )
20391 });
20392
20393 window.defer(cx, move |window, cx| {
20394 workspace.update(cx, |workspace, cx| {
20395 workspace.active_pane().update(cx, |pane, cx| {
20396 pane.add_item(
20397 Box::new(proposed_changes_editor),
20398 true,
20399 true,
20400 None,
20401 window,
20402 cx,
20403 );
20404 });
20405 });
20406 });
20407 }
20408
20409 pub fn open_excerpts_in_split(
20410 &mut self,
20411 _: &OpenExcerptsSplit,
20412 window: &mut Window,
20413 cx: &mut Context<Self>,
20414 ) {
20415 self.open_excerpts_common(None, true, window, cx)
20416 }
20417
20418 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20419 self.open_excerpts_common(None, false, window, cx)
20420 }
20421
20422 fn open_excerpts_common(
20423 &mut self,
20424 jump_data: Option<JumpData>,
20425 split: bool,
20426 window: &mut Window,
20427 cx: &mut Context<Self>,
20428 ) {
20429 let Some(workspace) = self.workspace() else {
20430 cx.propagate();
20431 return;
20432 };
20433
20434 if self.buffer.read(cx).is_singleton() {
20435 cx.propagate();
20436 return;
20437 }
20438
20439 let mut new_selections_by_buffer = HashMap::default();
20440 match &jump_data {
20441 Some(JumpData::MultiBufferPoint {
20442 excerpt_id,
20443 position,
20444 anchor,
20445 line_offset_from_top,
20446 }) => {
20447 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20448 if let Some(buffer) = multi_buffer_snapshot
20449 .buffer_id_for_excerpt(*excerpt_id)
20450 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20451 {
20452 let buffer_snapshot = buffer.read(cx).snapshot();
20453 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20454 language::ToPoint::to_point(anchor, &buffer_snapshot)
20455 } else {
20456 buffer_snapshot.clip_point(*position, Bias::Left)
20457 };
20458 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20459 new_selections_by_buffer.insert(
20460 buffer,
20461 (
20462 vec![jump_to_offset..jump_to_offset],
20463 Some(*line_offset_from_top),
20464 ),
20465 );
20466 }
20467 }
20468 Some(JumpData::MultiBufferRow {
20469 row,
20470 line_offset_from_top,
20471 }) => {
20472 let point = MultiBufferPoint::new(row.0, 0);
20473 if let Some((buffer, buffer_point, _)) =
20474 self.buffer.read(cx).point_to_buffer_point(point, cx)
20475 {
20476 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20477 new_selections_by_buffer
20478 .entry(buffer)
20479 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20480 .0
20481 .push(buffer_offset..buffer_offset)
20482 }
20483 }
20484 None => {
20485 let selections = self.selections.all::<usize>(cx);
20486 let multi_buffer = self.buffer.read(cx);
20487 for selection in selections {
20488 for (snapshot, range, _, anchor) in multi_buffer
20489 .snapshot(cx)
20490 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20491 {
20492 if let Some(anchor) = anchor {
20493 // selection is in a deleted hunk
20494 let Some(buffer_id) = anchor.buffer_id else {
20495 continue;
20496 };
20497 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20498 continue;
20499 };
20500 let offset = text::ToOffset::to_offset(
20501 &anchor.text_anchor,
20502 &buffer_handle.read(cx).snapshot(),
20503 );
20504 let range = offset..offset;
20505 new_selections_by_buffer
20506 .entry(buffer_handle)
20507 .or_insert((Vec::new(), None))
20508 .0
20509 .push(range)
20510 } else {
20511 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20512 else {
20513 continue;
20514 };
20515 new_selections_by_buffer
20516 .entry(buffer_handle)
20517 .or_insert((Vec::new(), None))
20518 .0
20519 .push(range)
20520 }
20521 }
20522 }
20523 }
20524 }
20525
20526 new_selections_by_buffer
20527 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20528
20529 if new_selections_by_buffer.is_empty() {
20530 return;
20531 }
20532
20533 // We defer the pane interaction because we ourselves are a workspace item
20534 // and activating a new item causes the pane to call a method on us reentrantly,
20535 // which panics if we're on the stack.
20536 window.defer(cx, move |window, cx| {
20537 workspace.update(cx, |workspace, cx| {
20538 let pane = if split {
20539 workspace.adjacent_pane(window, cx)
20540 } else {
20541 workspace.active_pane().clone()
20542 };
20543
20544 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20545 let editor = buffer
20546 .read(cx)
20547 .file()
20548 .is_none()
20549 .then(|| {
20550 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20551 // so `workspace.open_project_item` will never find them, always opening a new editor.
20552 // Instead, we try to activate the existing editor in the pane first.
20553 let (editor, pane_item_index) =
20554 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20555 let editor = item.downcast::<Editor>()?;
20556 let singleton_buffer =
20557 editor.read(cx).buffer().read(cx).as_singleton()?;
20558 if singleton_buffer == buffer {
20559 Some((editor, i))
20560 } else {
20561 None
20562 }
20563 })?;
20564 pane.update(cx, |pane, cx| {
20565 pane.activate_item(pane_item_index, true, true, window, cx)
20566 });
20567 Some(editor)
20568 })
20569 .flatten()
20570 .unwrap_or_else(|| {
20571 workspace.open_project_item::<Self>(
20572 pane.clone(),
20573 buffer,
20574 true,
20575 true,
20576 window,
20577 cx,
20578 )
20579 });
20580
20581 editor.update(cx, |editor, cx| {
20582 let autoscroll = match scroll_offset {
20583 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20584 None => Autoscroll::newest(),
20585 };
20586 let nav_history = editor.nav_history.take();
20587 editor.change_selections(
20588 SelectionEffects::scroll(autoscroll),
20589 window,
20590 cx,
20591 |s| {
20592 s.select_ranges(ranges);
20593 },
20594 );
20595 editor.nav_history = nav_history;
20596 });
20597 }
20598 })
20599 });
20600 }
20601
20602 // For now, don't allow opening excerpts in buffers that aren't backed by
20603 // regular project files.
20604 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20605 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20606 }
20607
20608 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20609 let snapshot = self.buffer.read(cx).read(cx);
20610 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20611 Some(
20612 ranges
20613 .iter()
20614 .map(move |range| {
20615 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20616 })
20617 .collect(),
20618 )
20619 }
20620
20621 fn selection_replacement_ranges(
20622 &self,
20623 range: Range<OffsetUtf16>,
20624 cx: &mut App,
20625 ) -> Vec<Range<OffsetUtf16>> {
20626 let selections = self.selections.all::<OffsetUtf16>(cx);
20627 let newest_selection = selections
20628 .iter()
20629 .max_by_key(|selection| selection.id)
20630 .unwrap();
20631 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20632 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20633 let snapshot = self.buffer.read(cx).read(cx);
20634 selections
20635 .into_iter()
20636 .map(|mut selection| {
20637 selection.start.0 =
20638 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20639 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20640 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20641 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20642 })
20643 .collect()
20644 }
20645
20646 fn report_editor_event(
20647 &self,
20648 reported_event: ReportEditorEvent,
20649 file_extension: Option<String>,
20650 cx: &App,
20651 ) {
20652 if cfg!(any(test, feature = "test-support")) {
20653 return;
20654 }
20655
20656 let Some(project) = &self.project else { return };
20657
20658 // If None, we are in a file without an extension
20659 let file = self
20660 .buffer
20661 .read(cx)
20662 .as_singleton()
20663 .and_then(|b| b.read(cx).file());
20664 let file_extension = file_extension.or(file
20665 .as_ref()
20666 .and_then(|file| Path::new(file.file_name(cx)).extension())
20667 .and_then(|e| e.to_str())
20668 .map(|a| a.to_string()));
20669
20670 let vim_mode = vim_enabled(cx);
20671
20672 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20673 let copilot_enabled = edit_predictions_provider
20674 == language::language_settings::EditPredictionProvider::Copilot;
20675 let copilot_enabled_for_language = self
20676 .buffer
20677 .read(cx)
20678 .language_settings(cx)
20679 .show_edit_predictions;
20680
20681 let project = project.read(cx);
20682 let event_type = reported_event.event_type();
20683
20684 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20685 telemetry::event!(
20686 event_type,
20687 type = if auto_saved {"autosave"} else {"manual"},
20688 file_extension,
20689 vim_mode,
20690 copilot_enabled,
20691 copilot_enabled_for_language,
20692 edit_predictions_provider,
20693 is_via_ssh = project.is_via_ssh(),
20694 );
20695 } else {
20696 telemetry::event!(
20697 event_type,
20698 file_extension,
20699 vim_mode,
20700 copilot_enabled,
20701 copilot_enabled_for_language,
20702 edit_predictions_provider,
20703 is_via_ssh = project.is_via_ssh(),
20704 );
20705 };
20706 }
20707
20708 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20709 /// with each line being an array of {text, highlight} objects.
20710 fn copy_highlight_json(
20711 &mut self,
20712 _: &CopyHighlightJson,
20713 window: &mut Window,
20714 cx: &mut Context<Self>,
20715 ) {
20716 #[derive(Serialize)]
20717 struct Chunk<'a> {
20718 text: String,
20719 highlight: Option<&'a str>,
20720 }
20721
20722 let snapshot = self.buffer.read(cx).snapshot(cx);
20723 let range = self
20724 .selected_text_range(false, window, cx)
20725 .and_then(|selection| {
20726 if selection.range.is_empty() {
20727 None
20728 } else {
20729 Some(selection.range)
20730 }
20731 })
20732 .unwrap_or_else(|| 0..snapshot.len());
20733
20734 let chunks = snapshot.chunks(range, true);
20735 let mut lines = Vec::new();
20736 let mut line: VecDeque<Chunk> = VecDeque::new();
20737
20738 let Some(style) = self.style.as_ref() else {
20739 return;
20740 };
20741
20742 for chunk in chunks {
20743 let highlight = chunk
20744 .syntax_highlight_id
20745 .and_then(|id| id.name(&style.syntax));
20746 let mut chunk_lines = chunk.text.split('\n').peekable();
20747 while let Some(text) = chunk_lines.next() {
20748 let mut merged_with_last_token = false;
20749 if let Some(last_token) = line.back_mut()
20750 && last_token.highlight == highlight
20751 {
20752 last_token.text.push_str(text);
20753 merged_with_last_token = true;
20754 }
20755
20756 if !merged_with_last_token {
20757 line.push_back(Chunk {
20758 text: text.into(),
20759 highlight,
20760 });
20761 }
20762
20763 if chunk_lines.peek().is_some() {
20764 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20765 line.pop_front();
20766 }
20767 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20768 line.pop_back();
20769 }
20770
20771 lines.push(mem::take(&mut line));
20772 }
20773 }
20774 }
20775
20776 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20777 return;
20778 };
20779 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20780 }
20781
20782 pub fn open_context_menu(
20783 &mut self,
20784 _: &OpenContextMenu,
20785 window: &mut Window,
20786 cx: &mut Context<Self>,
20787 ) {
20788 self.request_autoscroll(Autoscroll::newest(), cx);
20789 let position = self.selections.newest_display(cx).start;
20790 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20791 }
20792
20793 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20794 &self.inlay_hint_cache
20795 }
20796
20797 pub fn replay_insert_event(
20798 &mut self,
20799 text: &str,
20800 relative_utf16_range: Option<Range<isize>>,
20801 window: &mut Window,
20802 cx: &mut Context<Self>,
20803 ) {
20804 if !self.input_enabled {
20805 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20806 return;
20807 }
20808 if let Some(relative_utf16_range) = relative_utf16_range {
20809 let selections = self.selections.all::<OffsetUtf16>(cx);
20810 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20811 let new_ranges = selections.into_iter().map(|range| {
20812 let start = OffsetUtf16(
20813 range
20814 .head()
20815 .0
20816 .saturating_add_signed(relative_utf16_range.start),
20817 );
20818 let end = OffsetUtf16(
20819 range
20820 .head()
20821 .0
20822 .saturating_add_signed(relative_utf16_range.end),
20823 );
20824 start..end
20825 });
20826 s.select_ranges(new_ranges);
20827 });
20828 }
20829
20830 self.handle_input(text, window, cx);
20831 }
20832
20833 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20834 let Some(provider) = self.semantics_provider.as_ref() else {
20835 return false;
20836 };
20837
20838 let mut supports = false;
20839 self.buffer().update(cx, |this, cx| {
20840 this.for_each_buffer(|buffer| {
20841 supports |= provider.supports_inlay_hints(buffer, cx);
20842 });
20843 });
20844
20845 supports
20846 }
20847
20848 pub fn is_focused(&self, window: &Window) -> bool {
20849 self.focus_handle.is_focused(window)
20850 }
20851
20852 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20853 cx.emit(EditorEvent::Focused);
20854
20855 if let Some(descendant) = self
20856 .last_focused_descendant
20857 .take()
20858 .and_then(|descendant| descendant.upgrade())
20859 {
20860 window.focus(&descendant);
20861 } else {
20862 if let Some(blame) = self.blame.as_ref() {
20863 blame.update(cx, GitBlame::focus)
20864 }
20865
20866 self.blink_manager.update(cx, BlinkManager::enable);
20867 self.show_cursor_names(window, cx);
20868 self.buffer.update(cx, |buffer, cx| {
20869 buffer.finalize_last_transaction(cx);
20870 if self.leader_id.is_none() {
20871 buffer.set_active_selections(
20872 &self.selections.disjoint_anchors(),
20873 self.selections.line_mode,
20874 self.cursor_shape,
20875 cx,
20876 );
20877 }
20878 });
20879 }
20880 }
20881
20882 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20883 cx.emit(EditorEvent::FocusedIn)
20884 }
20885
20886 fn handle_focus_out(
20887 &mut self,
20888 event: FocusOutEvent,
20889 _window: &mut Window,
20890 cx: &mut Context<Self>,
20891 ) {
20892 if event.blurred != self.focus_handle {
20893 self.last_focused_descendant = Some(event.blurred);
20894 }
20895 self.selection_drag_state = SelectionDragState::None;
20896 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20897 }
20898
20899 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20900 self.blink_manager.update(cx, BlinkManager::disable);
20901 self.buffer
20902 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20903
20904 if let Some(blame) = self.blame.as_ref() {
20905 blame.update(cx, GitBlame::blur)
20906 }
20907 if !self.hover_state.focused(window, cx) {
20908 hide_hover(self, cx);
20909 }
20910 if !self
20911 .context_menu
20912 .borrow()
20913 .as_ref()
20914 .is_some_and(|context_menu| context_menu.focused(window, cx))
20915 {
20916 self.hide_context_menu(window, cx);
20917 }
20918 self.discard_edit_prediction(false, cx);
20919 cx.emit(EditorEvent::Blurred);
20920 cx.notify();
20921 }
20922
20923 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20924 let mut pending: String = window
20925 .pending_input_keystrokes()
20926 .into_iter()
20927 .flatten()
20928 .filter_map(|keystroke| {
20929 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20930 keystroke.key_char.clone()
20931 } else {
20932 None
20933 }
20934 })
20935 .collect();
20936
20937 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20938 pending = "".to_string();
20939 }
20940
20941 let existing_pending = self
20942 .text_highlights::<PendingInput>(cx)
20943 .map(|(_, ranges)| ranges.to_vec());
20944 if existing_pending.is_none() && pending.is_empty() {
20945 return;
20946 }
20947 let transaction =
20948 self.transact(window, cx, |this, window, cx| {
20949 let selections = this.selections.all::<usize>(cx);
20950 let edits = selections
20951 .iter()
20952 .map(|selection| (selection.end..selection.end, pending.clone()));
20953 this.edit(edits, cx);
20954 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20955 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20956 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20957 }));
20958 });
20959 if let Some(existing_ranges) = existing_pending {
20960 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20961 this.edit(edits, cx);
20962 }
20963 });
20964
20965 let snapshot = self.snapshot(window, cx);
20966 let ranges = self
20967 .selections
20968 .all::<usize>(cx)
20969 .into_iter()
20970 .map(|selection| {
20971 snapshot.buffer_snapshot.anchor_after(selection.end)
20972 ..snapshot
20973 .buffer_snapshot
20974 .anchor_before(selection.end + pending.len())
20975 })
20976 .collect();
20977
20978 if pending.is_empty() {
20979 self.clear_highlights::<PendingInput>(cx);
20980 } else {
20981 self.highlight_text::<PendingInput>(
20982 ranges,
20983 HighlightStyle {
20984 underline: Some(UnderlineStyle {
20985 thickness: px(1.),
20986 color: None,
20987 wavy: false,
20988 }),
20989 ..Default::default()
20990 },
20991 cx,
20992 );
20993 }
20994
20995 self.ime_transaction = self.ime_transaction.or(transaction);
20996 if let Some(transaction) = self.ime_transaction {
20997 self.buffer.update(cx, |buffer, cx| {
20998 buffer.group_until_transaction(transaction, cx);
20999 });
21000 }
21001
21002 if self.text_highlights::<PendingInput>(cx).is_none() {
21003 self.ime_transaction.take();
21004 }
21005 }
21006
21007 pub fn register_action_renderer(
21008 &mut self,
21009 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21010 ) -> Subscription {
21011 let id = self.next_editor_action_id.post_inc();
21012 self.editor_actions
21013 .borrow_mut()
21014 .insert(id, Box::new(listener));
21015
21016 let editor_actions = self.editor_actions.clone();
21017 Subscription::new(move || {
21018 editor_actions.borrow_mut().remove(&id);
21019 })
21020 }
21021
21022 pub fn register_action<A: Action>(
21023 &mut self,
21024 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21025 ) -> Subscription {
21026 let id = self.next_editor_action_id.post_inc();
21027 let listener = Arc::new(listener);
21028 self.editor_actions.borrow_mut().insert(
21029 id,
21030 Box::new(move |_, window, _| {
21031 let listener = listener.clone();
21032 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21033 let action = action.downcast_ref().unwrap();
21034 if phase == DispatchPhase::Bubble {
21035 listener(action, window, cx)
21036 }
21037 })
21038 }),
21039 );
21040
21041 let editor_actions = self.editor_actions.clone();
21042 Subscription::new(move || {
21043 editor_actions.borrow_mut().remove(&id);
21044 })
21045 }
21046
21047 pub fn file_header_size(&self) -> u32 {
21048 FILE_HEADER_HEIGHT
21049 }
21050
21051 pub fn restore(
21052 &mut self,
21053 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21054 window: &mut Window,
21055 cx: &mut Context<Self>,
21056 ) {
21057 let workspace = self.workspace();
21058 let project = self.project();
21059 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21060 let mut tasks = Vec::new();
21061 for (buffer_id, changes) in revert_changes {
21062 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21063 buffer.update(cx, |buffer, cx| {
21064 buffer.edit(
21065 changes
21066 .into_iter()
21067 .map(|(range, text)| (range, text.to_string())),
21068 None,
21069 cx,
21070 );
21071 });
21072
21073 if let Some(project) =
21074 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21075 {
21076 project.update(cx, |project, cx| {
21077 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21078 })
21079 }
21080 }
21081 }
21082 tasks
21083 });
21084 cx.spawn_in(window, async move |_, cx| {
21085 for (buffer, task) in save_tasks {
21086 let result = task.await;
21087 if result.is_err() {
21088 let Some(path) = buffer
21089 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21090 .ok()
21091 else {
21092 continue;
21093 };
21094 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21095 let Some(task) = cx
21096 .update_window_entity(workspace, |workspace, window, cx| {
21097 workspace
21098 .open_path_preview(path, None, false, false, false, window, cx)
21099 })
21100 .ok()
21101 else {
21102 continue;
21103 };
21104 task.await.log_err();
21105 }
21106 }
21107 }
21108 })
21109 .detach();
21110 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21111 selections.refresh()
21112 });
21113 }
21114
21115 pub fn to_pixel_point(
21116 &self,
21117 source: multi_buffer::Anchor,
21118 editor_snapshot: &EditorSnapshot,
21119 window: &mut Window,
21120 ) -> Option<gpui::Point<Pixels>> {
21121 let source_point = source.to_display_point(editor_snapshot);
21122 self.display_to_pixel_point(source_point, editor_snapshot, window)
21123 }
21124
21125 pub fn display_to_pixel_point(
21126 &self,
21127 source: DisplayPoint,
21128 editor_snapshot: &EditorSnapshot,
21129 window: &mut Window,
21130 ) -> Option<gpui::Point<Pixels>> {
21131 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21132 let text_layout_details = self.text_layout_details(window);
21133 let scroll_top = text_layout_details
21134 .scroll_anchor
21135 .scroll_position(editor_snapshot)
21136 .y;
21137
21138 if source.row().as_f32() < scroll_top.floor() {
21139 return None;
21140 }
21141 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21142 let source_y = line_height * (source.row().as_f32() - scroll_top);
21143 Some(gpui::Point::new(source_x, source_y))
21144 }
21145
21146 pub fn has_visible_completions_menu(&self) -> bool {
21147 !self.edit_prediction_preview_is_active()
21148 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21149 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21150 })
21151 }
21152
21153 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21154 if self.mode.is_minimap() {
21155 return;
21156 }
21157 self.addons
21158 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21159 }
21160
21161 pub fn unregister_addon<T: Addon>(&mut self) {
21162 self.addons.remove(&std::any::TypeId::of::<T>());
21163 }
21164
21165 pub fn addon<T: Addon>(&self) -> Option<&T> {
21166 let type_id = std::any::TypeId::of::<T>();
21167 self.addons
21168 .get(&type_id)
21169 .and_then(|item| item.to_any().downcast_ref::<T>())
21170 }
21171
21172 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21173 let type_id = std::any::TypeId::of::<T>();
21174 self.addons
21175 .get_mut(&type_id)
21176 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21177 }
21178
21179 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21180 let text_layout_details = self.text_layout_details(window);
21181 let style = &text_layout_details.editor_style;
21182 let font_id = window.text_system().resolve_font(&style.text.font());
21183 let font_size = style.text.font_size.to_pixels(window.rem_size());
21184 let line_height = style.text.line_height_in_pixels(window.rem_size());
21185 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21186 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21187
21188 CharacterDimensions {
21189 em_width,
21190 em_advance,
21191 line_height,
21192 }
21193 }
21194
21195 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21196 self.load_diff_task.clone()
21197 }
21198
21199 fn read_metadata_from_db(
21200 &mut self,
21201 item_id: u64,
21202 workspace_id: WorkspaceId,
21203 window: &mut Window,
21204 cx: &mut Context<Editor>,
21205 ) {
21206 if self.is_singleton(cx)
21207 && !self.mode.is_minimap()
21208 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21209 {
21210 let buffer_snapshot = OnceCell::new();
21211
21212 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21213 && !folds.is_empty()
21214 {
21215 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21216 self.fold_ranges(
21217 folds
21218 .into_iter()
21219 .map(|(start, end)| {
21220 snapshot.clip_offset(start, Bias::Left)
21221 ..snapshot.clip_offset(end, Bias::Right)
21222 })
21223 .collect(),
21224 false,
21225 window,
21226 cx,
21227 );
21228 }
21229
21230 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21231 && !selections.is_empty()
21232 {
21233 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21234 // skip adding the initial selection to selection history
21235 self.selection_history.mode = SelectionHistoryMode::Skipping;
21236 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21237 s.select_ranges(selections.into_iter().map(|(start, end)| {
21238 snapshot.clip_offset(start, Bias::Left)
21239 ..snapshot.clip_offset(end, Bias::Right)
21240 }));
21241 });
21242 self.selection_history.mode = SelectionHistoryMode::Normal;
21243 };
21244 }
21245
21246 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21247 }
21248
21249 fn update_lsp_data(
21250 &mut self,
21251 ignore_cache: bool,
21252 for_buffer: Option<BufferId>,
21253 window: &mut Window,
21254 cx: &mut Context<'_, Self>,
21255 ) {
21256 self.pull_diagnostics(for_buffer, window, cx);
21257 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21258 }
21259}
21260
21261fn vim_enabled(cx: &App) -> bool {
21262 cx.global::<SettingsStore>()
21263 .raw_user_settings()
21264 .get("vim_mode")
21265 == Some(&serde_json::Value::Bool(true))
21266}
21267
21268fn process_completion_for_edit(
21269 completion: &Completion,
21270 intent: CompletionIntent,
21271 buffer: &Entity<Buffer>,
21272 cursor_position: &text::Anchor,
21273 cx: &mut Context<Editor>,
21274) -> CompletionEdit {
21275 let buffer = buffer.read(cx);
21276 let buffer_snapshot = buffer.snapshot();
21277 let (snippet, new_text) = if completion.is_snippet() {
21278 // Workaround for typescript language server issues so that methods don't expand within
21279 // strings and functions with type expressions. The previous point is used because the query
21280 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21281 let mut snippet_source = completion.new_text.clone();
21282 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21283 previous_point.column = previous_point.column.saturating_sub(1);
21284 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21285 && scope.prefers_label_for_snippet_in_completion()
21286 && let Some(label) = completion.label()
21287 && matches!(
21288 completion.kind(),
21289 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21290 )
21291 {
21292 snippet_source = label;
21293 }
21294 match Snippet::parse(&snippet_source).log_err() {
21295 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21296 None => (None, completion.new_text.clone()),
21297 }
21298 } else {
21299 (None, completion.new_text.clone())
21300 };
21301
21302 let mut range_to_replace = {
21303 let replace_range = &completion.replace_range;
21304 if let CompletionSource::Lsp {
21305 insert_range: Some(insert_range),
21306 ..
21307 } = &completion.source
21308 {
21309 debug_assert_eq!(
21310 insert_range.start, replace_range.start,
21311 "insert_range and replace_range should start at the same position"
21312 );
21313 debug_assert!(
21314 insert_range
21315 .start
21316 .cmp(cursor_position, &buffer_snapshot)
21317 .is_le(),
21318 "insert_range should start before or at cursor position"
21319 );
21320 debug_assert!(
21321 replace_range
21322 .start
21323 .cmp(cursor_position, &buffer_snapshot)
21324 .is_le(),
21325 "replace_range should start before or at cursor position"
21326 );
21327
21328 let should_replace = match intent {
21329 CompletionIntent::CompleteWithInsert => false,
21330 CompletionIntent::CompleteWithReplace => true,
21331 CompletionIntent::Complete | CompletionIntent::Compose => {
21332 let insert_mode =
21333 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21334 .completions
21335 .lsp_insert_mode;
21336 match insert_mode {
21337 LspInsertMode::Insert => false,
21338 LspInsertMode::Replace => true,
21339 LspInsertMode::ReplaceSubsequence => {
21340 let mut text_to_replace = buffer.chars_for_range(
21341 buffer.anchor_before(replace_range.start)
21342 ..buffer.anchor_after(replace_range.end),
21343 );
21344 let mut current_needle = text_to_replace.next();
21345 for haystack_ch in completion.label.text.chars() {
21346 if let Some(needle_ch) = current_needle
21347 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21348 {
21349 current_needle = text_to_replace.next();
21350 }
21351 }
21352 current_needle.is_none()
21353 }
21354 LspInsertMode::ReplaceSuffix => {
21355 if replace_range
21356 .end
21357 .cmp(cursor_position, &buffer_snapshot)
21358 .is_gt()
21359 {
21360 let range_after_cursor = *cursor_position..replace_range.end;
21361 let text_after_cursor = buffer
21362 .text_for_range(
21363 buffer.anchor_before(range_after_cursor.start)
21364 ..buffer.anchor_after(range_after_cursor.end),
21365 )
21366 .collect::<String>()
21367 .to_ascii_lowercase();
21368 completion
21369 .label
21370 .text
21371 .to_ascii_lowercase()
21372 .ends_with(&text_after_cursor)
21373 } else {
21374 true
21375 }
21376 }
21377 }
21378 }
21379 };
21380
21381 if should_replace {
21382 replace_range.clone()
21383 } else {
21384 insert_range.clone()
21385 }
21386 } else {
21387 replace_range.clone()
21388 }
21389 };
21390
21391 if range_to_replace
21392 .end
21393 .cmp(cursor_position, &buffer_snapshot)
21394 .is_lt()
21395 {
21396 range_to_replace.end = *cursor_position;
21397 }
21398
21399 CompletionEdit {
21400 new_text,
21401 replace_range: range_to_replace.to_offset(buffer),
21402 snippet,
21403 }
21404}
21405
21406struct CompletionEdit {
21407 new_text: String,
21408 replace_range: Range<usize>,
21409 snippet: Option<Snippet>,
21410}
21411
21412fn insert_extra_newline_brackets(
21413 buffer: &MultiBufferSnapshot,
21414 range: Range<usize>,
21415 language: &language::LanguageScope,
21416) -> bool {
21417 let leading_whitespace_len = buffer
21418 .reversed_chars_at(range.start)
21419 .take_while(|c| c.is_whitespace() && *c != '\n')
21420 .map(|c| c.len_utf8())
21421 .sum::<usize>();
21422 let trailing_whitespace_len = buffer
21423 .chars_at(range.end)
21424 .take_while(|c| c.is_whitespace() && *c != '\n')
21425 .map(|c| c.len_utf8())
21426 .sum::<usize>();
21427 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21428
21429 language.brackets().any(|(pair, enabled)| {
21430 let pair_start = pair.start.trim_end();
21431 let pair_end = pair.end.trim_start();
21432
21433 enabled
21434 && pair.newline
21435 && buffer.contains_str_at(range.end, pair_end)
21436 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21437 })
21438}
21439
21440fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21441 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21442 [(buffer, range, _)] => (*buffer, range.clone()),
21443 _ => return false,
21444 };
21445 let pair = {
21446 let mut result: Option<BracketMatch> = None;
21447
21448 for pair in buffer
21449 .all_bracket_ranges(range.clone())
21450 .filter(move |pair| {
21451 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21452 })
21453 {
21454 let len = pair.close_range.end - pair.open_range.start;
21455
21456 if let Some(existing) = &result {
21457 let existing_len = existing.close_range.end - existing.open_range.start;
21458 if len > existing_len {
21459 continue;
21460 }
21461 }
21462
21463 result = Some(pair);
21464 }
21465
21466 result
21467 };
21468 let Some(pair) = pair else {
21469 return false;
21470 };
21471 pair.newline_only
21472 && buffer
21473 .chars_for_range(pair.open_range.end..range.start)
21474 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21475 .all(|c| c.is_whitespace() && c != '\n')
21476}
21477
21478fn update_uncommitted_diff_for_buffer(
21479 editor: Entity<Editor>,
21480 project: &Entity<Project>,
21481 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21482 buffer: Entity<MultiBuffer>,
21483 cx: &mut App,
21484) -> Task<()> {
21485 let mut tasks = Vec::new();
21486 project.update(cx, |project, cx| {
21487 for buffer in buffers {
21488 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21489 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21490 }
21491 }
21492 });
21493 cx.spawn(async move |cx| {
21494 let diffs = future::join_all(tasks).await;
21495 if editor
21496 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21497 .unwrap_or(false)
21498 {
21499 return;
21500 }
21501
21502 buffer
21503 .update(cx, |buffer, cx| {
21504 for diff in diffs.into_iter().flatten() {
21505 buffer.add_diff(diff, cx);
21506 }
21507 })
21508 .ok();
21509 })
21510}
21511
21512fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21513 let tab_size = tab_size.get() as usize;
21514 let mut width = offset;
21515
21516 for ch in text.chars() {
21517 width += if ch == '\t' {
21518 tab_size - (width % tab_size)
21519 } else {
21520 1
21521 };
21522 }
21523
21524 width - offset
21525}
21526
21527#[cfg(test)]
21528mod tests {
21529 use super::*;
21530
21531 #[test]
21532 fn test_string_size_with_expanded_tabs() {
21533 let nz = |val| NonZeroU32::new(val).unwrap();
21534 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21535 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21536 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21537 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21538 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21539 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21540 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21541 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21542 }
21543}
21544
21545/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21546struct WordBreakingTokenizer<'a> {
21547 input: &'a str,
21548}
21549
21550impl<'a> WordBreakingTokenizer<'a> {
21551 fn new(input: &'a str) -> Self {
21552 Self { input }
21553 }
21554}
21555
21556fn is_char_ideographic(ch: char) -> bool {
21557 use unicode_script::Script::*;
21558 use unicode_script::UnicodeScript;
21559 matches!(ch.script(), Han | Tangut | Yi)
21560}
21561
21562fn is_grapheme_ideographic(text: &str) -> bool {
21563 text.chars().any(is_char_ideographic)
21564}
21565
21566fn is_grapheme_whitespace(text: &str) -> bool {
21567 text.chars().any(|x| x.is_whitespace())
21568}
21569
21570fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21571 text.chars()
21572 .next()
21573 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21574}
21575
21576#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21577enum WordBreakToken<'a> {
21578 Word { token: &'a str, grapheme_len: usize },
21579 InlineWhitespace { token: &'a str, grapheme_len: usize },
21580 Newline,
21581}
21582
21583impl<'a> Iterator for WordBreakingTokenizer<'a> {
21584 /// Yields a span, the count of graphemes in the token, and whether it was
21585 /// whitespace. Note that it also breaks at word boundaries.
21586 type Item = WordBreakToken<'a>;
21587
21588 fn next(&mut self) -> Option<Self::Item> {
21589 use unicode_segmentation::UnicodeSegmentation;
21590 if self.input.is_empty() {
21591 return None;
21592 }
21593
21594 let mut iter = self.input.graphemes(true).peekable();
21595 let mut offset = 0;
21596 let mut grapheme_len = 0;
21597 if let Some(first_grapheme) = iter.next() {
21598 let is_newline = first_grapheme == "\n";
21599 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21600 offset += first_grapheme.len();
21601 grapheme_len += 1;
21602 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21603 if let Some(grapheme) = iter.peek().copied()
21604 && should_stay_with_preceding_ideograph(grapheme)
21605 {
21606 offset += grapheme.len();
21607 grapheme_len += 1;
21608 }
21609 } else {
21610 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21611 let mut next_word_bound = words.peek().copied();
21612 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21613 next_word_bound = words.next();
21614 }
21615 while let Some(grapheme) = iter.peek().copied() {
21616 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21617 break;
21618 };
21619 if is_grapheme_whitespace(grapheme) != is_whitespace
21620 || (grapheme == "\n") != is_newline
21621 {
21622 break;
21623 };
21624 offset += grapheme.len();
21625 grapheme_len += 1;
21626 iter.next();
21627 }
21628 }
21629 let token = &self.input[..offset];
21630 self.input = &self.input[offset..];
21631 if token == "\n" {
21632 Some(WordBreakToken::Newline)
21633 } else if is_whitespace {
21634 Some(WordBreakToken::InlineWhitespace {
21635 token,
21636 grapheme_len,
21637 })
21638 } else {
21639 Some(WordBreakToken::Word {
21640 token,
21641 grapheme_len,
21642 })
21643 }
21644 } else {
21645 None
21646 }
21647 }
21648}
21649
21650#[test]
21651fn test_word_breaking_tokenizer() {
21652 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21653 ("", &[]),
21654 (" ", &[whitespace(" ", 2)]),
21655 ("Ʒ", &[word("Ʒ", 1)]),
21656 ("Ǽ", &[word("Ǽ", 1)]),
21657 ("⋑", &[word("⋑", 1)]),
21658 ("⋑⋑", &[word("⋑⋑", 2)]),
21659 (
21660 "原理,进而",
21661 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21662 ),
21663 (
21664 "hello world",
21665 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21666 ),
21667 (
21668 "hello, world",
21669 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21670 ),
21671 (
21672 " hello world",
21673 &[
21674 whitespace(" ", 2),
21675 word("hello", 5),
21676 whitespace(" ", 1),
21677 word("world", 5),
21678 ],
21679 ),
21680 (
21681 "这是什么 \n 钢笔",
21682 &[
21683 word("这", 1),
21684 word("是", 1),
21685 word("什", 1),
21686 word("么", 1),
21687 whitespace(" ", 1),
21688 newline(),
21689 whitespace(" ", 1),
21690 word("钢", 1),
21691 word("笔", 1),
21692 ],
21693 ),
21694 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21695 ];
21696
21697 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21698 WordBreakToken::Word {
21699 token,
21700 grapheme_len,
21701 }
21702 }
21703
21704 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21705 WordBreakToken::InlineWhitespace {
21706 token,
21707 grapheme_len,
21708 }
21709 }
21710
21711 fn newline() -> WordBreakToken<'static> {
21712 WordBreakToken::Newline
21713 }
21714
21715 for (input, result) in tests {
21716 assert_eq!(
21717 WordBreakingTokenizer::new(input)
21718 .collect::<Vec<_>>()
21719 .as_slice(),
21720 *result,
21721 );
21722 }
21723}
21724
21725fn wrap_with_prefix(
21726 first_line_prefix: String,
21727 subsequent_lines_prefix: String,
21728 unwrapped_text: String,
21729 wrap_column: usize,
21730 tab_size: NonZeroU32,
21731 preserve_existing_whitespace: bool,
21732) -> String {
21733 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21734 let subsequent_lines_prefix_len =
21735 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21736 let mut wrapped_text = String::new();
21737 let mut current_line = first_line_prefix.clone();
21738 let mut is_first_line = true;
21739
21740 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21741 let mut current_line_len = first_line_prefix_len;
21742 let mut in_whitespace = false;
21743 for token in tokenizer {
21744 let have_preceding_whitespace = in_whitespace;
21745 match token {
21746 WordBreakToken::Word {
21747 token,
21748 grapheme_len,
21749 } => {
21750 in_whitespace = false;
21751 let current_prefix_len = if is_first_line {
21752 first_line_prefix_len
21753 } else {
21754 subsequent_lines_prefix_len
21755 };
21756 if current_line_len + grapheme_len > wrap_column
21757 && current_line_len != current_prefix_len
21758 {
21759 wrapped_text.push_str(current_line.trim_end());
21760 wrapped_text.push('\n');
21761 is_first_line = false;
21762 current_line = subsequent_lines_prefix.clone();
21763 current_line_len = subsequent_lines_prefix_len;
21764 }
21765 current_line.push_str(token);
21766 current_line_len += grapheme_len;
21767 }
21768 WordBreakToken::InlineWhitespace {
21769 mut token,
21770 mut grapheme_len,
21771 } => {
21772 in_whitespace = true;
21773 if have_preceding_whitespace && !preserve_existing_whitespace {
21774 continue;
21775 }
21776 if !preserve_existing_whitespace {
21777 token = " ";
21778 grapheme_len = 1;
21779 }
21780 let current_prefix_len = if is_first_line {
21781 first_line_prefix_len
21782 } else {
21783 subsequent_lines_prefix_len
21784 };
21785 if current_line_len + grapheme_len > wrap_column {
21786 wrapped_text.push_str(current_line.trim_end());
21787 wrapped_text.push('\n');
21788 is_first_line = false;
21789 current_line = subsequent_lines_prefix.clone();
21790 current_line_len = subsequent_lines_prefix_len;
21791 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21792 current_line.push_str(token);
21793 current_line_len += grapheme_len;
21794 }
21795 }
21796 WordBreakToken::Newline => {
21797 in_whitespace = true;
21798 let current_prefix_len = if is_first_line {
21799 first_line_prefix_len
21800 } else {
21801 subsequent_lines_prefix_len
21802 };
21803 if preserve_existing_whitespace {
21804 wrapped_text.push_str(current_line.trim_end());
21805 wrapped_text.push('\n');
21806 is_first_line = false;
21807 current_line = subsequent_lines_prefix.clone();
21808 current_line_len = subsequent_lines_prefix_len;
21809 } else if have_preceding_whitespace {
21810 continue;
21811 } else if current_line_len + 1 > wrap_column
21812 && current_line_len != current_prefix_len
21813 {
21814 wrapped_text.push_str(current_line.trim_end());
21815 wrapped_text.push('\n');
21816 is_first_line = false;
21817 current_line = subsequent_lines_prefix.clone();
21818 current_line_len = subsequent_lines_prefix_len;
21819 } else if current_line_len != current_prefix_len {
21820 current_line.push(' ');
21821 current_line_len += 1;
21822 }
21823 }
21824 }
21825 }
21826
21827 if !current_line.is_empty() {
21828 wrapped_text.push_str(¤t_line);
21829 }
21830 wrapped_text
21831}
21832
21833#[test]
21834fn test_wrap_with_prefix() {
21835 assert_eq!(
21836 wrap_with_prefix(
21837 "# ".to_string(),
21838 "# ".to_string(),
21839 "abcdefg".to_string(),
21840 4,
21841 NonZeroU32::new(4).unwrap(),
21842 false,
21843 ),
21844 "# abcdefg"
21845 );
21846 assert_eq!(
21847 wrap_with_prefix(
21848 "".to_string(),
21849 "".to_string(),
21850 "\thello world".to_string(),
21851 8,
21852 NonZeroU32::new(4).unwrap(),
21853 false,
21854 ),
21855 "hello\nworld"
21856 );
21857 assert_eq!(
21858 wrap_with_prefix(
21859 "// ".to_string(),
21860 "// ".to_string(),
21861 "xx \nyy zz aa bb cc".to_string(),
21862 12,
21863 NonZeroU32::new(4).unwrap(),
21864 false,
21865 ),
21866 "// xx yy zz\n// aa bb cc"
21867 );
21868 assert_eq!(
21869 wrap_with_prefix(
21870 String::new(),
21871 String::new(),
21872 "这是什么 \n 钢笔".to_string(),
21873 3,
21874 NonZeroU32::new(4).unwrap(),
21875 false,
21876 ),
21877 "这是什\n么 钢\n笔"
21878 );
21879}
21880
21881pub trait CollaborationHub {
21882 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21883 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21884 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21885}
21886
21887impl CollaborationHub for Entity<Project> {
21888 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21889 self.read(cx).collaborators()
21890 }
21891
21892 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21893 self.read(cx).user_store().read(cx).participant_indices()
21894 }
21895
21896 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21897 let this = self.read(cx);
21898 let user_ids = this.collaborators().values().map(|c| c.user_id);
21899 this.user_store().read(cx).participant_names(user_ids, cx)
21900 }
21901}
21902
21903pub trait SemanticsProvider {
21904 fn hover(
21905 &self,
21906 buffer: &Entity<Buffer>,
21907 position: text::Anchor,
21908 cx: &mut App,
21909 ) -> Option<Task<Vec<project::Hover>>>;
21910
21911 fn inline_values(
21912 &self,
21913 buffer_handle: Entity<Buffer>,
21914 range: Range<text::Anchor>,
21915 cx: &mut App,
21916 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21917
21918 fn inlay_hints(
21919 &self,
21920 buffer_handle: Entity<Buffer>,
21921 range: Range<text::Anchor>,
21922 cx: &mut App,
21923 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21924
21925 fn resolve_inlay_hint(
21926 &self,
21927 hint: InlayHint,
21928 buffer_handle: Entity<Buffer>,
21929 server_id: LanguageServerId,
21930 cx: &mut App,
21931 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21932
21933 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21934
21935 fn document_highlights(
21936 &self,
21937 buffer: &Entity<Buffer>,
21938 position: text::Anchor,
21939 cx: &mut App,
21940 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21941
21942 fn definitions(
21943 &self,
21944 buffer: &Entity<Buffer>,
21945 position: text::Anchor,
21946 kind: GotoDefinitionKind,
21947 cx: &mut App,
21948 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21949
21950 fn range_for_rename(
21951 &self,
21952 buffer: &Entity<Buffer>,
21953 position: text::Anchor,
21954 cx: &mut App,
21955 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21956
21957 fn perform_rename(
21958 &self,
21959 buffer: &Entity<Buffer>,
21960 position: text::Anchor,
21961 new_name: String,
21962 cx: &mut App,
21963 ) -> Option<Task<Result<ProjectTransaction>>>;
21964}
21965
21966pub trait CompletionProvider {
21967 fn completions(
21968 &self,
21969 excerpt_id: ExcerptId,
21970 buffer: &Entity<Buffer>,
21971 buffer_position: text::Anchor,
21972 trigger: CompletionContext,
21973 window: &mut Window,
21974 cx: &mut Context<Editor>,
21975 ) -> Task<Result<Vec<CompletionResponse>>>;
21976
21977 fn resolve_completions(
21978 &self,
21979 _buffer: Entity<Buffer>,
21980 _completion_indices: Vec<usize>,
21981 _completions: Rc<RefCell<Box<[Completion]>>>,
21982 _cx: &mut Context<Editor>,
21983 ) -> Task<Result<bool>> {
21984 Task::ready(Ok(false))
21985 }
21986
21987 fn apply_additional_edits_for_completion(
21988 &self,
21989 _buffer: Entity<Buffer>,
21990 _completions: Rc<RefCell<Box<[Completion]>>>,
21991 _completion_index: usize,
21992 _push_to_history: bool,
21993 _cx: &mut Context<Editor>,
21994 ) -> Task<Result<Option<language::Transaction>>> {
21995 Task::ready(Ok(None))
21996 }
21997
21998 fn is_completion_trigger(
21999 &self,
22000 buffer: &Entity<Buffer>,
22001 position: language::Anchor,
22002 text: &str,
22003 trigger_in_words: bool,
22004 menu_is_open: bool,
22005 cx: &mut Context<Editor>,
22006 ) -> bool;
22007
22008 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22009
22010 fn sort_completions(&self) -> bool {
22011 true
22012 }
22013
22014 fn filter_completions(&self) -> bool {
22015 true
22016 }
22017}
22018
22019pub trait CodeActionProvider {
22020 fn id(&self) -> Arc<str>;
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
22030 fn apply_code_action(
22031 &self,
22032 buffer_handle: Entity<Buffer>,
22033 action: CodeAction,
22034 excerpt_id: ExcerptId,
22035 push_to_history: bool,
22036 window: &mut Window,
22037 cx: &mut App,
22038 ) -> Task<Result<ProjectTransaction>>;
22039}
22040
22041impl CodeActionProvider for Entity<Project> {
22042 fn id(&self) -> Arc<str> {
22043 "project".into()
22044 }
22045
22046 fn code_actions(
22047 &self,
22048 buffer: &Entity<Buffer>,
22049 range: Range<text::Anchor>,
22050 _window: &mut Window,
22051 cx: &mut App,
22052 ) -> Task<Result<Vec<CodeAction>>> {
22053 self.update(cx, |project, cx| {
22054 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22055 let code_actions = project.code_actions(buffer, range, None, cx);
22056 cx.background_spawn(async move {
22057 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22058 Ok(code_lens_actions
22059 .context("code lens fetch")?
22060 .into_iter()
22061 .chain(code_actions.context("code action fetch")?)
22062 .collect())
22063 })
22064 })
22065 }
22066
22067 fn apply_code_action(
22068 &self,
22069 buffer_handle: Entity<Buffer>,
22070 action: CodeAction,
22071 _excerpt_id: ExcerptId,
22072 push_to_history: bool,
22073 _window: &mut Window,
22074 cx: &mut App,
22075 ) -> Task<Result<ProjectTransaction>> {
22076 self.update(cx, |project, cx| {
22077 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22078 })
22079 }
22080}
22081
22082fn snippet_completions(
22083 project: &Project,
22084 buffer: &Entity<Buffer>,
22085 buffer_position: text::Anchor,
22086 cx: &mut App,
22087) -> Task<Result<CompletionResponse>> {
22088 let languages = buffer.read(cx).languages_at(buffer_position);
22089 let snippet_store = project.snippets().read(cx);
22090
22091 let scopes: Vec<_> = languages
22092 .iter()
22093 .filter_map(|language| {
22094 let language_name = language.lsp_id();
22095 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22096
22097 if snippets.is_empty() {
22098 None
22099 } else {
22100 Some((language.default_scope(), snippets))
22101 }
22102 })
22103 .collect();
22104
22105 if scopes.is_empty() {
22106 return Task::ready(Ok(CompletionResponse {
22107 completions: vec![],
22108 is_incomplete: false,
22109 }));
22110 }
22111
22112 let snapshot = buffer.read(cx).text_snapshot();
22113 let chars: String = snapshot
22114 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22115 .collect();
22116 let executor = cx.background_executor().clone();
22117
22118 cx.background_spawn(async move {
22119 let mut is_incomplete = false;
22120 let mut completions: Vec<Completion> = Vec::new();
22121 for (scope, snippets) in scopes.into_iter() {
22122 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22123 let mut last_word = chars
22124 .chars()
22125 .take_while(|c| classifier.is_word(*c))
22126 .collect::<String>();
22127 last_word = last_word.chars().rev().collect();
22128
22129 if last_word.is_empty() {
22130 return Ok(CompletionResponse {
22131 completions: vec![],
22132 is_incomplete: true,
22133 });
22134 }
22135
22136 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22137 let to_lsp = |point: &text::Anchor| {
22138 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22139 point_to_lsp(end)
22140 };
22141 let lsp_end = to_lsp(&buffer_position);
22142
22143 let candidates = snippets
22144 .iter()
22145 .enumerate()
22146 .flat_map(|(ix, snippet)| {
22147 snippet
22148 .prefix
22149 .iter()
22150 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22151 })
22152 .collect::<Vec<StringMatchCandidate>>();
22153
22154 const MAX_RESULTS: usize = 100;
22155 let mut matches = fuzzy::match_strings(
22156 &candidates,
22157 &last_word,
22158 last_word.chars().any(|c| c.is_uppercase()),
22159 true,
22160 MAX_RESULTS,
22161 &Default::default(),
22162 executor.clone(),
22163 )
22164 .await;
22165
22166 if matches.len() >= MAX_RESULTS {
22167 is_incomplete = true;
22168 }
22169
22170 // Remove all candidates where the query's start does not match the start of any word in the candidate
22171 if let Some(query_start) = last_word.chars().next() {
22172 matches.retain(|string_match| {
22173 split_words(&string_match.string).any(|word| {
22174 // Check that the first codepoint of the word as lowercase matches the first
22175 // codepoint of the query as lowercase
22176 word.chars()
22177 .flat_map(|codepoint| codepoint.to_lowercase())
22178 .zip(query_start.to_lowercase())
22179 .all(|(word_cp, query_cp)| word_cp == query_cp)
22180 })
22181 });
22182 }
22183
22184 let matched_strings = matches
22185 .into_iter()
22186 .map(|m| m.string)
22187 .collect::<HashSet<_>>();
22188
22189 completions.extend(snippets.iter().filter_map(|snippet| {
22190 let matching_prefix = snippet
22191 .prefix
22192 .iter()
22193 .find(|prefix| matched_strings.contains(*prefix))?;
22194 let start = as_offset - last_word.len();
22195 let start = snapshot.anchor_before(start);
22196 let range = start..buffer_position;
22197 let lsp_start = to_lsp(&start);
22198 let lsp_range = lsp::Range {
22199 start: lsp_start,
22200 end: lsp_end,
22201 };
22202 Some(Completion {
22203 replace_range: range,
22204 new_text: snippet.body.clone(),
22205 source: CompletionSource::Lsp {
22206 insert_range: None,
22207 server_id: LanguageServerId(usize::MAX),
22208 resolved: true,
22209 lsp_completion: Box::new(lsp::CompletionItem {
22210 label: snippet.prefix.first().unwrap().clone(),
22211 kind: Some(CompletionItemKind::SNIPPET),
22212 label_details: snippet.description.as_ref().map(|description| {
22213 lsp::CompletionItemLabelDetails {
22214 detail: Some(description.clone()),
22215 description: None,
22216 }
22217 }),
22218 insert_text_format: Some(InsertTextFormat::SNIPPET),
22219 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22220 lsp::InsertReplaceEdit {
22221 new_text: snippet.body.clone(),
22222 insert: lsp_range,
22223 replace: lsp_range,
22224 },
22225 )),
22226 filter_text: Some(snippet.body.clone()),
22227 sort_text: Some(char::MAX.to_string()),
22228 ..lsp::CompletionItem::default()
22229 }),
22230 lsp_defaults: None,
22231 },
22232 label: CodeLabel {
22233 text: matching_prefix.clone(),
22234 runs: Vec::new(),
22235 filter_range: 0..matching_prefix.len(),
22236 },
22237 icon_path: None,
22238 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22239 single_line: snippet.name.clone().into(),
22240 plain_text: snippet
22241 .description
22242 .clone()
22243 .map(|description| description.into()),
22244 }),
22245 insert_text_mode: None,
22246 confirm: None,
22247 })
22248 }))
22249 }
22250
22251 Ok(CompletionResponse {
22252 completions,
22253 is_incomplete,
22254 })
22255 })
22256}
22257
22258impl CompletionProvider for Entity<Project> {
22259 fn completions(
22260 &self,
22261 _excerpt_id: ExcerptId,
22262 buffer: &Entity<Buffer>,
22263 buffer_position: text::Anchor,
22264 options: CompletionContext,
22265 _window: &mut Window,
22266 cx: &mut Context<Editor>,
22267 ) -> Task<Result<Vec<CompletionResponse>>> {
22268 self.update(cx, |project, cx| {
22269 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22270 let project_completions = project.completions(buffer, buffer_position, options, cx);
22271 cx.background_spawn(async move {
22272 let mut responses = project_completions.await?;
22273 let snippets = snippets.await?;
22274 if !snippets.completions.is_empty() {
22275 responses.push(snippets);
22276 }
22277 Ok(responses)
22278 })
22279 })
22280 }
22281
22282 fn resolve_completions(
22283 &self,
22284 buffer: Entity<Buffer>,
22285 completion_indices: Vec<usize>,
22286 completions: Rc<RefCell<Box<[Completion]>>>,
22287 cx: &mut Context<Editor>,
22288 ) -> Task<Result<bool>> {
22289 self.update(cx, |project, cx| {
22290 project.lsp_store().update(cx, |lsp_store, cx| {
22291 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22292 })
22293 })
22294 }
22295
22296 fn apply_additional_edits_for_completion(
22297 &self,
22298 buffer: Entity<Buffer>,
22299 completions: Rc<RefCell<Box<[Completion]>>>,
22300 completion_index: usize,
22301 push_to_history: bool,
22302 cx: &mut Context<Editor>,
22303 ) -> Task<Result<Option<language::Transaction>>> {
22304 self.update(cx, |project, cx| {
22305 project.lsp_store().update(cx, |lsp_store, cx| {
22306 lsp_store.apply_additional_edits_for_completion(
22307 buffer,
22308 completions,
22309 completion_index,
22310 push_to_history,
22311 cx,
22312 )
22313 })
22314 })
22315 }
22316
22317 fn is_completion_trigger(
22318 &self,
22319 buffer: &Entity<Buffer>,
22320 position: language::Anchor,
22321 text: &str,
22322 trigger_in_words: bool,
22323 menu_is_open: bool,
22324 cx: &mut Context<Editor>,
22325 ) -> bool {
22326 let mut chars = text.chars();
22327 let char = if let Some(char) = chars.next() {
22328 char
22329 } else {
22330 return false;
22331 };
22332 if chars.next().is_some() {
22333 return false;
22334 }
22335
22336 let buffer = buffer.read(cx);
22337 let snapshot = buffer.snapshot();
22338 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22339 return false;
22340 }
22341 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22342 if trigger_in_words && classifier.is_word(char) {
22343 return true;
22344 }
22345
22346 buffer.completion_triggers().contains(text)
22347 }
22348}
22349
22350impl SemanticsProvider for Entity<Project> {
22351 fn hover(
22352 &self,
22353 buffer: &Entity<Buffer>,
22354 position: text::Anchor,
22355 cx: &mut App,
22356 ) -> Option<Task<Vec<project::Hover>>> {
22357 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22358 }
22359
22360 fn document_highlights(
22361 &self,
22362 buffer: &Entity<Buffer>,
22363 position: text::Anchor,
22364 cx: &mut App,
22365 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22366 Some(self.update(cx, |project, cx| {
22367 project.document_highlights(buffer, position, cx)
22368 }))
22369 }
22370
22371 fn definitions(
22372 &self,
22373 buffer: &Entity<Buffer>,
22374 position: text::Anchor,
22375 kind: GotoDefinitionKind,
22376 cx: &mut App,
22377 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22378 Some(self.update(cx, |project, cx| match kind {
22379 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22380 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22381 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22382 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22383 }))
22384 }
22385
22386 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22387 self.update(cx, |project, cx| {
22388 if project
22389 .active_debug_session(cx)
22390 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22391 {
22392 return true;
22393 }
22394
22395 buffer.update(cx, |buffer, cx| {
22396 project.any_language_server_supports_inlay_hints(buffer, cx)
22397 })
22398 })
22399 }
22400
22401 fn inline_values(
22402 &self,
22403 buffer_handle: Entity<Buffer>,
22404 range: Range<text::Anchor>,
22405 cx: &mut App,
22406 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22407 self.update(cx, |project, cx| {
22408 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22409
22410 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22411 })
22412 }
22413
22414 fn inlay_hints(
22415 &self,
22416 buffer_handle: Entity<Buffer>,
22417 range: Range<text::Anchor>,
22418 cx: &mut App,
22419 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22420 Some(self.update(cx, |project, cx| {
22421 project.inlay_hints(buffer_handle, range, cx)
22422 }))
22423 }
22424
22425 fn resolve_inlay_hint(
22426 &self,
22427 hint: InlayHint,
22428 buffer_handle: Entity<Buffer>,
22429 server_id: LanguageServerId,
22430 cx: &mut App,
22431 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22432 Some(self.update(cx, |project, cx| {
22433 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22434 }))
22435 }
22436
22437 fn range_for_rename(
22438 &self,
22439 buffer: &Entity<Buffer>,
22440 position: text::Anchor,
22441 cx: &mut App,
22442 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22443 Some(self.update(cx, |project, cx| {
22444 let buffer = buffer.clone();
22445 let task = project.prepare_rename(buffer.clone(), position, cx);
22446 cx.spawn(async move |_, cx| {
22447 Ok(match task.await? {
22448 PrepareRenameResponse::Success(range) => Some(range),
22449 PrepareRenameResponse::InvalidPosition => None,
22450 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22451 // Fallback on using TreeSitter info to determine identifier range
22452 buffer.read_with(cx, |buffer, _| {
22453 let snapshot = buffer.snapshot();
22454 let (range, kind) = snapshot.surrounding_word(position, false);
22455 if kind != Some(CharKind::Word) {
22456 return None;
22457 }
22458 Some(
22459 snapshot.anchor_before(range.start)
22460 ..snapshot.anchor_after(range.end),
22461 )
22462 })?
22463 }
22464 })
22465 })
22466 }))
22467 }
22468
22469 fn perform_rename(
22470 &self,
22471 buffer: &Entity<Buffer>,
22472 position: text::Anchor,
22473 new_name: String,
22474 cx: &mut App,
22475 ) -> Option<Task<Result<ProjectTransaction>>> {
22476 Some(self.update(cx, |project, cx| {
22477 project.perform_rename(buffer.clone(), position, new_name, cx)
22478 }))
22479 }
22480}
22481
22482fn inlay_hint_settings(
22483 location: Anchor,
22484 snapshot: &MultiBufferSnapshot,
22485 cx: &mut Context<Editor>,
22486) -> InlayHintSettings {
22487 let file = snapshot.file_at(location);
22488 let language = snapshot.language_at(location).map(|l| l.name());
22489 language_settings(language, file, cx).inlay_hints
22490}
22491
22492fn consume_contiguous_rows(
22493 contiguous_row_selections: &mut Vec<Selection<Point>>,
22494 selection: &Selection<Point>,
22495 display_map: &DisplaySnapshot,
22496 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22497) -> (MultiBufferRow, MultiBufferRow) {
22498 contiguous_row_selections.push(selection.clone());
22499 let start_row = starting_row(selection, display_map);
22500 let mut end_row = ending_row(selection, display_map);
22501
22502 while let Some(next_selection) = selections.peek() {
22503 if next_selection.start.row <= end_row.0 {
22504 end_row = ending_row(next_selection, display_map);
22505 contiguous_row_selections.push(selections.next().unwrap().clone());
22506 } else {
22507 break;
22508 }
22509 }
22510 (start_row, end_row)
22511}
22512
22513fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22514 if selection.start.column > 0 {
22515 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22516 } else {
22517 MultiBufferRow(selection.start.row)
22518 }
22519}
22520
22521fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22522 if next_selection.end.column > 0 || next_selection.is_empty() {
22523 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22524 } else {
22525 MultiBufferRow(next_selection.end.row)
22526 }
22527}
22528
22529impl EditorSnapshot {
22530 pub fn remote_selections_in_range<'a>(
22531 &'a self,
22532 range: &'a Range<Anchor>,
22533 collaboration_hub: &dyn CollaborationHub,
22534 cx: &'a App,
22535 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22536 let participant_names = collaboration_hub.user_names(cx);
22537 let participant_indices = collaboration_hub.user_participant_indices(cx);
22538 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22539 let collaborators_by_replica_id = collaborators_by_peer_id
22540 .values()
22541 .map(|collaborator| (collaborator.replica_id, collaborator))
22542 .collect::<HashMap<_, _>>();
22543 self.buffer_snapshot
22544 .selections_in_range(range, false)
22545 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22546 if replica_id == AGENT_REPLICA_ID {
22547 Some(RemoteSelection {
22548 replica_id,
22549 selection,
22550 cursor_shape,
22551 line_mode,
22552 collaborator_id: CollaboratorId::Agent,
22553 user_name: Some("Agent".into()),
22554 color: cx.theme().players().agent(),
22555 })
22556 } else {
22557 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22558 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22559 let user_name = participant_names.get(&collaborator.user_id).cloned();
22560 Some(RemoteSelection {
22561 replica_id,
22562 selection,
22563 cursor_shape,
22564 line_mode,
22565 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22566 user_name,
22567 color: if let Some(index) = participant_index {
22568 cx.theme().players().color_for_participant(index.0)
22569 } else {
22570 cx.theme().players().absent()
22571 },
22572 })
22573 }
22574 })
22575 }
22576
22577 pub fn hunks_for_ranges(
22578 &self,
22579 ranges: impl IntoIterator<Item = Range<Point>>,
22580 ) -> Vec<MultiBufferDiffHunk> {
22581 let mut hunks = Vec::new();
22582 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22583 HashMap::default();
22584 for query_range in ranges {
22585 let query_rows =
22586 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22587 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22588 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22589 ) {
22590 // Include deleted hunks that are adjacent to the query range, because
22591 // otherwise they would be missed.
22592 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22593 if hunk.status().is_deleted() {
22594 intersects_range |= hunk.row_range.start == query_rows.end;
22595 intersects_range |= hunk.row_range.end == query_rows.start;
22596 }
22597 if intersects_range {
22598 if !processed_buffer_rows
22599 .entry(hunk.buffer_id)
22600 .or_default()
22601 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22602 {
22603 continue;
22604 }
22605 hunks.push(hunk);
22606 }
22607 }
22608 }
22609
22610 hunks
22611 }
22612
22613 fn display_diff_hunks_for_rows<'a>(
22614 &'a self,
22615 display_rows: Range<DisplayRow>,
22616 folded_buffers: &'a HashSet<BufferId>,
22617 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22618 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22619 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22620
22621 self.buffer_snapshot
22622 .diff_hunks_in_range(buffer_start..buffer_end)
22623 .filter_map(|hunk| {
22624 if folded_buffers.contains(&hunk.buffer_id) {
22625 return None;
22626 }
22627
22628 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22629 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22630
22631 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22632 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22633
22634 let display_hunk = if hunk_display_start.column() != 0 {
22635 DisplayDiffHunk::Folded {
22636 display_row: hunk_display_start.row(),
22637 }
22638 } else {
22639 let mut end_row = hunk_display_end.row();
22640 if hunk_display_end.column() > 0 {
22641 end_row.0 += 1;
22642 }
22643 let is_created_file = hunk.is_created_file();
22644 DisplayDiffHunk::Unfolded {
22645 status: hunk.status(),
22646 diff_base_byte_range: hunk.diff_base_byte_range,
22647 display_row_range: hunk_display_start.row()..end_row,
22648 multi_buffer_range: Anchor::range_in_buffer(
22649 hunk.excerpt_id,
22650 hunk.buffer_id,
22651 hunk.buffer_range,
22652 ),
22653 is_created_file,
22654 }
22655 };
22656
22657 Some(display_hunk)
22658 })
22659 }
22660
22661 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22662 self.display_snapshot.buffer_snapshot.language_at(position)
22663 }
22664
22665 pub fn is_focused(&self) -> bool {
22666 self.is_focused
22667 }
22668
22669 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22670 self.placeholder_text.as_ref()
22671 }
22672
22673 pub fn scroll_position(&self) -> gpui::Point<f32> {
22674 self.scroll_anchor.scroll_position(&self.display_snapshot)
22675 }
22676
22677 fn gutter_dimensions(
22678 &self,
22679 font_id: FontId,
22680 font_size: Pixels,
22681 max_line_number_width: Pixels,
22682 cx: &App,
22683 ) -> Option<GutterDimensions> {
22684 if !self.show_gutter {
22685 return None;
22686 }
22687
22688 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22689 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22690
22691 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22692 matches!(
22693 ProjectSettings::get_global(cx).git.git_gutter,
22694 Some(GitGutterSetting::TrackedFiles)
22695 )
22696 });
22697 let gutter_settings = EditorSettings::get_global(cx).gutter;
22698 let show_line_numbers = self
22699 .show_line_numbers
22700 .unwrap_or(gutter_settings.line_numbers);
22701 let line_gutter_width = if show_line_numbers {
22702 // Avoid flicker-like gutter resizes when the line number gains another digit by
22703 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22704 let min_width_for_number_on_gutter =
22705 ch_advance * gutter_settings.min_line_number_digits as f32;
22706 max_line_number_width.max(min_width_for_number_on_gutter)
22707 } else {
22708 0.0.into()
22709 };
22710
22711 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22712 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22713
22714 let git_blame_entries_width =
22715 self.git_blame_gutter_max_author_length
22716 .map(|max_author_length| {
22717 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22718 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22719
22720 /// The number of characters to dedicate to gaps and margins.
22721 const SPACING_WIDTH: usize = 4;
22722
22723 let max_char_count = max_author_length.min(renderer.max_author_length())
22724 + ::git::SHORT_SHA_LENGTH
22725 + MAX_RELATIVE_TIMESTAMP.len()
22726 + SPACING_WIDTH;
22727
22728 ch_advance * max_char_count
22729 });
22730
22731 let is_singleton = self.buffer_snapshot.is_singleton();
22732
22733 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22734 left_padding += if !is_singleton {
22735 ch_width * 4.0
22736 } else if show_runnables || show_breakpoints {
22737 ch_width * 3.0
22738 } else if show_git_gutter && show_line_numbers {
22739 ch_width * 2.0
22740 } else if show_git_gutter || show_line_numbers {
22741 ch_width
22742 } else {
22743 px(0.)
22744 };
22745
22746 let shows_folds = is_singleton && gutter_settings.folds;
22747
22748 let right_padding = if shows_folds && show_line_numbers {
22749 ch_width * 4.0
22750 } else if shows_folds || (!is_singleton && show_line_numbers) {
22751 ch_width * 3.0
22752 } else if show_line_numbers {
22753 ch_width
22754 } else {
22755 px(0.)
22756 };
22757
22758 Some(GutterDimensions {
22759 left_padding,
22760 right_padding,
22761 width: line_gutter_width + left_padding + right_padding,
22762 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22763 git_blame_entries_width,
22764 })
22765 }
22766
22767 pub fn render_crease_toggle(
22768 &self,
22769 buffer_row: MultiBufferRow,
22770 row_contains_cursor: bool,
22771 editor: Entity<Editor>,
22772 window: &mut Window,
22773 cx: &mut App,
22774 ) -> Option<AnyElement> {
22775 let folded = self.is_line_folded(buffer_row);
22776 let mut is_foldable = false;
22777
22778 if let Some(crease) = self
22779 .crease_snapshot
22780 .query_row(buffer_row, &self.buffer_snapshot)
22781 {
22782 is_foldable = true;
22783 match crease {
22784 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22785 if let Some(render_toggle) = render_toggle {
22786 let toggle_callback =
22787 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22788 if folded {
22789 editor.update(cx, |editor, cx| {
22790 editor.fold_at(buffer_row, window, cx)
22791 });
22792 } else {
22793 editor.update(cx, |editor, cx| {
22794 editor.unfold_at(buffer_row, window, cx)
22795 });
22796 }
22797 });
22798 return Some((render_toggle)(
22799 buffer_row,
22800 folded,
22801 toggle_callback,
22802 window,
22803 cx,
22804 ));
22805 }
22806 }
22807 }
22808 }
22809
22810 is_foldable |= self.starts_indent(buffer_row);
22811
22812 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22813 Some(
22814 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22815 .toggle_state(folded)
22816 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22817 if folded {
22818 this.unfold_at(buffer_row, window, cx);
22819 } else {
22820 this.fold_at(buffer_row, window, cx);
22821 }
22822 }))
22823 .into_any_element(),
22824 )
22825 } else {
22826 None
22827 }
22828 }
22829
22830 pub fn render_crease_trailer(
22831 &self,
22832 buffer_row: MultiBufferRow,
22833 window: &mut Window,
22834 cx: &mut App,
22835 ) -> Option<AnyElement> {
22836 let folded = self.is_line_folded(buffer_row);
22837 if let Crease::Inline { render_trailer, .. } = self
22838 .crease_snapshot
22839 .query_row(buffer_row, &self.buffer_snapshot)?
22840 {
22841 let render_trailer = render_trailer.as_ref()?;
22842 Some(render_trailer(buffer_row, folded, window, cx))
22843 } else {
22844 None
22845 }
22846 }
22847}
22848
22849impl Deref for EditorSnapshot {
22850 type Target = DisplaySnapshot;
22851
22852 fn deref(&self) -> &Self::Target {
22853 &self.display_snapshot
22854 }
22855}
22856
22857#[derive(Clone, Debug, PartialEq, Eq)]
22858pub enum EditorEvent {
22859 InputIgnored {
22860 text: Arc<str>,
22861 },
22862 InputHandled {
22863 utf16_range_to_replace: Option<Range<isize>>,
22864 text: Arc<str>,
22865 },
22866 ExcerptsAdded {
22867 buffer: Entity<Buffer>,
22868 predecessor: ExcerptId,
22869 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22870 },
22871 ExcerptsRemoved {
22872 ids: Vec<ExcerptId>,
22873 removed_buffer_ids: Vec<BufferId>,
22874 },
22875 BufferFoldToggled {
22876 ids: Vec<ExcerptId>,
22877 folded: bool,
22878 },
22879 ExcerptsEdited {
22880 ids: Vec<ExcerptId>,
22881 },
22882 ExcerptsExpanded {
22883 ids: Vec<ExcerptId>,
22884 },
22885 BufferEdited,
22886 Edited {
22887 transaction_id: clock::Lamport,
22888 },
22889 Reparsed(BufferId),
22890 Focused,
22891 FocusedIn,
22892 Blurred,
22893 DirtyChanged,
22894 Saved,
22895 TitleChanged,
22896 DiffBaseChanged,
22897 SelectionsChanged {
22898 local: bool,
22899 },
22900 ScrollPositionChanged {
22901 local: bool,
22902 autoscroll: bool,
22903 },
22904 Closed,
22905 TransactionUndone {
22906 transaction_id: clock::Lamport,
22907 },
22908 TransactionBegun {
22909 transaction_id: clock::Lamport,
22910 },
22911 Reloaded,
22912 CursorShapeChanged,
22913 BreadcrumbsChanged,
22914 PushedToNavHistory {
22915 anchor: Anchor,
22916 is_deactivate: bool,
22917 },
22918}
22919
22920impl EventEmitter<EditorEvent> for Editor {}
22921
22922impl Focusable for Editor {
22923 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22924 self.focus_handle.clone()
22925 }
22926}
22927
22928impl Render for Editor {
22929 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22930 let settings = ThemeSettings::get_global(cx);
22931
22932 let mut text_style = match self.mode {
22933 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22934 color: cx.theme().colors().editor_foreground,
22935 font_family: settings.ui_font.family.clone(),
22936 font_features: settings.ui_font.features.clone(),
22937 font_fallbacks: settings.ui_font.fallbacks.clone(),
22938 font_size: rems(0.875).into(),
22939 font_weight: settings.ui_font.weight,
22940 line_height: relative(settings.buffer_line_height.value()),
22941 ..Default::default()
22942 },
22943 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22944 color: cx.theme().colors().editor_foreground,
22945 font_family: settings.buffer_font.family.clone(),
22946 font_features: settings.buffer_font.features.clone(),
22947 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22948 font_size: settings.buffer_font_size(cx).into(),
22949 font_weight: settings.buffer_font.weight,
22950 line_height: relative(settings.buffer_line_height.value()),
22951 ..Default::default()
22952 },
22953 };
22954 if let Some(text_style_refinement) = &self.text_style_refinement {
22955 text_style.refine(text_style_refinement)
22956 }
22957
22958 let background = match self.mode {
22959 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22960 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22961 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22962 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22963 };
22964
22965 EditorElement::new(
22966 &cx.entity(),
22967 EditorStyle {
22968 background,
22969 border: cx.theme().colors().border,
22970 local_player: cx.theme().players().local(),
22971 text: text_style,
22972 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22973 syntax: cx.theme().syntax().clone(),
22974 status: cx.theme().status().clone(),
22975 inlay_hints_style: make_inlay_hints_style(cx),
22976 edit_prediction_styles: make_suggestion_styles(cx),
22977 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22978 show_underlines: self.diagnostics_enabled(),
22979 },
22980 )
22981 }
22982}
22983
22984impl EntityInputHandler for Editor {
22985 fn text_for_range(
22986 &mut self,
22987 range_utf16: Range<usize>,
22988 adjusted_range: &mut Option<Range<usize>>,
22989 _: &mut Window,
22990 cx: &mut Context<Self>,
22991 ) -> Option<String> {
22992 let snapshot = self.buffer.read(cx).read(cx);
22993 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22994 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22995 if (start.0..end.0) != range_utf16 {
22996 adjusted_range.replace(start.0..end.0);
22997 }
22998 Some(snapshot.text_for_range(start..end).collect())
22999 }
23000
23001 fn selected_text_range(
23002 &mut self,
23003 ignore_disabled_input: bool,
23004 _: &mut Window,
23005 cx: &mut Context<Self>,
23006 ) -> Option<UTF16Selection> {
23007 // Prevent the IME menu from appearing when holding down an alphabetic key
23008 // while input is disabled.
23009 if !ignore_disabled_input && !self.input_enabled {
23010 return None;
23011 }
23012
23013 let selection = self.selections.newest::<OffsetUtf16>(cx);
23014 let range = selection.range();
23015
23016 Some(UTF16Selection {
23017 range: range.start.0..range.end.0,
23018 reversed: selection.reversed,
23019 })
23020 }
23021
23022 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23023 let snapshot = self.buffer.read(cx).read(cx);
23024 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23025 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23026 }
23027
23028 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23029 self.clear_highlights::<InputComposition>(cx);
23030 self.ime_transaction.take();
23031 }
23032
23033 fn replace_text_in_range(
23034 &mut self,
23035 range_utf16: Option<Range<usize>>,
23036 text: &str,
23037 window: &mut Window,
23038 cx: &mut Context<Self>,
23039 ) {
23040 if !self.input_enabled {
23041 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23042 return;
23043 }
23044
23045 self.transact(window, cx, |this, window, cx| {
23046 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23047 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23048 Some(this.selection_replacement_ranges(range_utf16, cx))
23049 } else {
23050 this.marked_text_ranges(cx)
23051 };
23052
23053 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23054 let newest_selection_id = this.selections.newest_anchor().id;
23055 this.selections
23056 .all::<OffsetUtf16>(cx)
23057 .iter()
23058 .zip(ranges_to_replace.iter())
23059 .find_map(|(selection, range)| {
23060 if selection.id == newest_selection_id {
23061 Some(
23062 (range.start.0 as isize - selection.head().0 as isize)
23063 ..(range.end.0 as isize - selection.head().0 as isize),
23064 )
23065 } else {
23066 None
23067 }
23068 })
23069 });
23070
23071 cx.emit(EditorEvent::InputHandled {
23072 utf16_range_to_replace: range_to_replace,
23073 text: text.into(),
23074 });
23075
23076 if let Some(new_selected_ranges) = new_selected_ranges {
23077 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23078 selections.select_ranges(new_selected_ranges)
23079 });
23080 this.backspace(&Default::default(), window, cx);
23081 }
23082
23083 this.handle_input(text, window, cx);
23084 });
23085
23086 if let Some(transaction) = self.ime_transaction {
23087 self.buffer.update(cx, |buffer, cx| {
23088 buffer.group_until_transaction(transaction, cx);
23089 });
23090 }
23091
23092 self.unmark_text(window, cx);
23093 }
23094
23095 fn replace_and_mark_text_in_range(
23096 &mut self,
23097 range_utf16: Option<Range<usize>>,
23098 text: &str,
23099 new_selected_range_utf16: Option<Range<usize>>,
23100 window: &mut Window,
23101 cx: &mut Context<Self>,
23102 ) {
23103 if !self.input_enabled {
23104 return;
23105 }
23106
23107 let transaction = self.transact(window, cx, |this, window, cx| {
23108 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23109 let snapshot = this.buffer.read(cx).read(cx);
23110 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23111 for marked_range in &mut marked_ranges {
23112 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23113 marked_range.start.0 += relative_range_utf16.start;
23114 marked_range.start =
23115 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23116 marked_range.end =
23117 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23118 }
23119 }
23120 Some(marked_ranges)
23121 } else if let Some(range_utf16) = range_utf16 {
23122 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23123 Some(this.selection_replacement_ranges(range_utf16, cx))
23124 } else {
23125 None
23126 };
23127
23128 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23129 let newest_selection_id = this.selections.newest_anchor().id;
23130 this.selections
23131 .all::<OffsetUtf16>(cx)
23132 .iter()
23133 .zip(ranges_to_replace.iter())
23134 .find_map(|(selection, range)| {
23135 if selection.id == newest_selection_id {
23136 Some(
23137 (range.start.0 as isize - selection.head().0 as isize)
23138 ..(range.end.0 as isize - selection.head().0 as isize),
23139 )
23140 } else {
23141 None
23142 }
23143 })
23144 });
23145
23146 cx.emit(EditorEvent::InputHandled {
23147 utf16_range_to_replace: range_to_replace,
23148 text: text.into(),
23149 });
23150
23151 if let Some(ranges) = ranges_to_replace {
23152 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23153 s.select_ranges(ranges)
23154 });
23155 }
23156
23157 let marked_ranges = {
23158 let snapshot = this.buffer.read(cx).read(cx);
23159 this.selections
23160 .disjoint_anchors()
23161 .iter()
23162 .map(|selection| {
23163 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23164 })
23165 .collect::<Vec<_>>()
23166 };
23167
23168 if text.is_empty() {
23169 this.unmark_text(window, cx);
23170 } else {
23171 this.highlight_text::<InputComposition>(
23172 marked_ranges.clone(),
23173 HighlightStyle {
23174 underline: Some(UnderlineStyle {
23175 thickness: px(1.),
23176 color: None,
23177 wavy: false,
23178 }),
23179 ..Default::default()
23180 },
23181 cx,
23182 );
23183 }
23184
23185 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23186 let use_autoclose = this.use_autoclose;
23187 let use_auto_surround = this.use_auto_surround;
23188 this.set_use_autoclose(false);
23189 this.set_use_auto_surround(false);
23190 this.handle_input(text, window, cx);
23191 this.set_use_autoclose(use_autoclose);
23192 this.set_use_auto_surround(use_auto_surround);
23193
23194 if let Some(new_selected_range) = new_selected_range_utf16 {
23195 let snapshot = this.buffer.read(cx).read(cx);
23196 let new_selected_ranges = marked_ranges
23197 .into_iter()
23198 .map(|marked_range| {
23199 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23200 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23201 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23202 snapshot.clip_offset_utf16(new_start, Bias::Left)
23203 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23204 })
23205 .collect::<Vec<_>>();
23206
23207 drop(snapshot);
23208 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23209 selections.select_ranges(new_selected_ranges)
23210 });
23211 }
23212 });
23213
23214 self.ime_transaction = self.ime_transaction.or(transaction);
23215 if let Some(transaction) = self.ime_transaction {
23216 self.buffer.update(cx, |buffer, cx| {
23217 buffer.group_until_transaction(transaction, cx);
23218 });
23219 }
23220
23221 if self.text_highlights::<InputComposition>(cx).is_none() {
23222 self.ime_transaction.take();
23223 }
23224 }
23225
23226 fn bounds_for_range(
23227 &mut self,
23228 range_utf16: Range<usize>,
23229 element_bounds: gpui::Bounds<Pixels>,
23230 window: &mut Window,
23231 cx: &mut Context<Self>,
23232 ) -> Option<gpui::Bounds<Pixels>> {
23233 let text_layout_details = self.text_layout_details(window);
23234 let CharacterDimensions {
23235 em_width,
23236 em_advance,
23237 line_height,
23238 } = self.character_dimensions(window);
23239
23240 let snapshot = self.snapshot(window, cx);
23241 let scroll_position = snapshot.scroll_position();
23242 let scroll_left = scroll_position.x * em_advance;
23243
23244 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23245 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23246 + self.gutter_dimensions.full_width();
23247 let y = line_height * (start.row().as_f32() - scroll_position.y);
23248
23249 Some(Bounds {
23250 origin: element_bounds.origin + point(x, y),
23251 size: size(em_width, line_height),
23252 })
23253 }
23254
23255 fn character_index_for_point(
23256 &mut self,
23257 point: gpui::Point<Pixels>,
23258 _window: &mut Window,
23259 _cx: &mut Context<Self>,
23260 ) -> Option<usize> {
23261 let position_map = self.last_position_map.as_ref()?;
23262 if !position_map.text_hitbox.contains(&point) {
23263 return None;
23264 }
23265 let display_point = position_map.point_for_position(point).previous_valid;
23266 let anchor = position_map
23267 .snapshot
23268 .display_point_to_anchor(display_point, Bias::Left);
23269 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23270 Some(utf16_offset.0)
23271 }
23272}
23273
23274trait SelectionExt {
23275 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23276 fn spanned_rows(
23277 &self,
23278 include_end_if_at_line_start: bool,
23279 map: &DisplaySnapshot,
23280 ) -> Range<MultiBufferRow>;
23281}
23282
23283impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23284 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23285 let start = self
23286 .start
23287 .to_point(&map.buffer_snapshot)
23288 .to_display_point(map);
23289 let end = self
23290 .end
23291 .to_point(&map.buffer_snapshot)
23292 .to_display_point(map);
23293 if self.reversed {
23294 end..start
23295 } else {
23296 start..end
23297 }
23298 }
23299
23300 fn spanned_rows(
23301 &self,
23302 include_end_if_at_line_start: bool,
23303 map: &DisplaySnapshot,
23304 ) -> Range<MultiBufferRow> {
23305 let start = self.start.to_point(&map.buffer_snapshot);
23306 let mut end = self.end.to_point(&map.buffer_snapshot);
23307 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23308 end.row -= 1;
23309 }
23310
23311 let buffer_start = map.prev_line_boundary(start).0;
23312 let buffer_end = map.next_line_boundary(end).0;
23313 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23314 }
23315}
23316
23317impl<T: InvalidationRegion> InvalidationStack<T> {
23318 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23319 where
23320 S: Clone + ToOffset,
23321 {
23322 while let Some(region) = self.last() {
23323 let all_selections_inside_invalidation_ranges =
23324 if selections.len() == region.ranges().len() {
23325 selections
23326 .iter()
23327 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23328 .all(|(selection, invalidation_range)| {
23329 let head = selection.head().to_offset(buffer);
23330 invalidation_range.start <= head && invalidation_range.end >= head
23331 })
23332 } else {
23333 false
23334 };
23335
23336 if all_selections_inside_invalidation_ranges {
23337 break;
23338 } else {
23339 self.pop();
23340 }
23341 }
23342 }
23343}
23344
23345impl<T> Default for InvalidationStack<T> {
23346 fn default() -> Self {
23347 Self(Default::default())
23348 }
23349}
23350
23351impl<T> Deref for InvalidationStack<T> {
23352 type Target = Vec<T>;
23353
23354 fn deref(&self) -> &Self::Target {
23355 &self.0
23356 }
23357}
23358
23359impl<T> DerefMut for InvalidationStack<T> {
23360 fn deref_mut(&mut self) -> &mut Self::Target {
23361 &mut self.0
23362 }
23363}
23364
23365impl InvalidationRegion for SnippetState {
23366 fn ranges(&self) -> &[Range<Anchor>] {
23367 &self.ranges[self.active_index]
23368 }
23369}
23370
23371fn edit_prediction_edit_text(
23372 current_snapshot: &BufferSnapshot,
23373 edits: &[(Range<Anchor>, String)],
23374 edit_preview: &EditPreview,
23375 include_deletions: bool,
23376 cx: &App,
23377) -> HighlightedText {
23378 let edits = edits
23379 .iter()
23380 .map(|(anchor, text)| {
23381 (
23382 anchor.start.text_anchor..anchor.end.text_anchor,
23383 text.clone(),
23384 )
23385 })
23386 .collect::<Vec<_>>();
23387
23388 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23389}
23390
23391fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23392 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23393 // Just show the raw edit text with basic styling
23394 let mut text = String::new();
23395 let mut highlights = Vec::new();
23396
23397 let insertion_highlight_style = HighlightStyle {
23398 color: Some(cx.theme().colors().text),
23399 ..Default::default()
23400 };
23401
23402 for (_, edit_text) in edits {
23403 let start_offset = text.len();
23404 text.push_str(edit_text);
23405 let end_offset = text.len();
23406
23407 if start_offset < end_offset {
23408 highlights.push((start_offset..end_offset, insertion_highlight_style));
23409 }
23410 }
23411
23412 HighlightedText {
23413 text: text.into(),
23414 highlights,
23415 }
23416}
23417
23418pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23419 match severity {
23420 lsp::DiagnosticSeverity::ERROR => colors.error,
23421 lsp::DiagnosticSeverity::WARNING => colors.warning,
23422 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23423 lsp::DiagnosticSeverity::HINT => colors.info,
23424 _ => colors.ignored,
23425 }
23426}
23427
23428pub fn styled_runs_for_code_label<'a>(
23429 label: &'a CodeLabel,
23430 syntax_theme: &'a theme::SyntaxTheme,
23431) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23432 let fade_out = HighlightStyle {
23433 fade_out: Some(0.35),
23434 ..Default::default()
23435 };
23436
23437 let mut prev_end = label.filter_range.end;
23438 label
23439 .runs
23440 .iter()
23441 .enumerate()
23442 .flat_map(move |(ix, (range, highlight_id))| {
23443 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23444 style
23445 } else {
23446 return Default::default();
23447 };
23448 let mut muted_style = style;
23449 muted_style.highlight(fade_out);
23450
23451 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23452 if range.start >= label.filter_range.end {
23453 if range.start > prev_end {
23454 runs.push((prev_end..range.start, fade_out));
23455 }
23456 runs.push((range.clone(), muted_style));
23457 } else if range.end <= label.filter_range.end {
23458 runs.push((range.clone(), style));
23459 } else {
23460 runs.push((range.start..label.filter_range.end, style));
23461 runs.push((label.filter_range.end..range.end, muted_style));
23462 }
23463 prev_end = cmp::max(prev_end, range.end);
23464
23465 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23466 runs.push((prev_end..label.text.len(), fade_out));
23467 }
23468
23469 runs
23470 })
23471}
23472
23473pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23474 let mut prev_index = 0;
23475 let mut prev_codepoint: Option<char> = None;
23476 text.char_indices()
23477 .chain([(text.len(), '\0')])
23478 .filter_map(move |(index, codepoint)| {
23479 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23480 let is_boundary = index == text.len()
23481 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23482 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23483 if is_boundary {
23484 let chunk = &text[prev_index..index];
23485 prev_index = index;
23486 Some(chunk)
23487 } else {
23488 None
23489 }
23490 })
23491}
23492
23493pub trait RangeToAnchorExt: Sized {
23494 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23495
23496 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23497 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23498 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23499 }
23500}
23501
23502impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23503 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23504 let start_offset = self.start.to_offset(snapshot);
23505 let end_offset = self.end.to_offset(snapshot);
23506 if start_offset == end_offset {
23507 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23508 } else {
23509 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23510 }
23511 }
23512}
23513
23514pub trait RowExt {
23515 fn as_f32(&self) -> f32;
23516
23517 fn next_row(&self) -> Self;
23518
23519 fn previous_row(&self) -> Self;
23520
23521 fn minus(&self, other: Self) -> u32;
23522}
23523
23524impl RowExt for DisplayRow {
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
23542impl RowExt for MultiBufferRow {
23543 fn as_f32(&self) -> f32 {
23544 self.0 as f32
23545 }
23546
23547 fn next_row(&self) -> Self {
23548 Self(self.0 + 1)
23549 }
23550
23551 fn previous_row(&self) -> Self {
23552 Self(self.0.saturating_sub(1))
23553 }
23554
23555 fn minus(&self, other: Self) -> u32 {
23556 self.0 - other.0
23557 }
23558}
23559
23560trait RowRangeExt {
23561 type Row;
23562
23563 fn len(&self) -> usize;
23564
23565 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23566}
23567
23568impl RowRangeExt for Range<MultiBufferRow> {
23569 type Row = MultiBufferRow;
23570
23571 fn len(&self) -> usize {
23572 (self.end.0 - self.start.0) as usize
23573 }
23574
23575 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23576 (self.start.0..self.end.0).map(MultiBufferRow)
23577 }
23578}
23579
23580impl RowRangeExt for Range<DisplayRow> {
23581 type Row = DisplayRow;
23582
23583 fn len(&self) -> usize {
23584 (self.end.0 - self.start.0) as usize
23585 }
23586
23587 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23588 (self.start.0..self.end.0).map(DisplayRow)
23589 }
23590}
23591
23592/// If select range has more than one line, we
23593/// just point the cursor to range.start.
23594fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23595 if range.start.row == range.end.row {
23596 range
23597 } else {
23598 range.start..range.start
23599 }
23600}
23601pub struct KillRing(ClipboardItem);
23602impl Global for KillRing {}
23603
23604const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23605
23606enum BreakpointPromptEditAction {
23607 Log,
23608 Condition,
23609 HitCondition,
23610}
23611
23612struct BreakpointPromptEditor {
23613 pub(crate) prompt: Entity<Editor>,
23614 editor: WeakEntity<Editor>,
23615 breakpoint_anchor: Anchor,
23616 breakpoint: Breakpoint,
23617 edit_action: BreakpointPromptEditAction,
23618 block_ids: HashSet<CustomBlockId>,
23619 editor_margins: Arc<Mutex<EditorMargins>>,
23620 _subscriptions: Vec<Subscription>,
23621}
23622
23623impl BreakpointPromptEditor {
23624 const MAX_LINES: u8 = 4;
23625
23626 fn new(
23627 editor: WeakEntity<Editor>,
23628 breakpoint_anchor: Anchor,
23629 breakpoint: Breakpoint,
23630 edit_action: BreakpointPromptEditAction,
23631 window: &mut Window,
23632 cx: &mut Context<Self>,
23633 ) -> Self {
23634 let base_text = match edit_action {
23635 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23636 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23637 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23638 }
23639 .map(|msg| msg.to_string())
23640 .unwrap_or_default();
23641
23642 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23643 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23644
23645 let prompt = cx.new(|cx| {
23646 let mut prompt = Editor::new(
23647 EditorMode::AutoHeight {
23648 min_lines: 1,
23649 max_lines: Some(Self::MAX_LINES as usize),
23650 },
23651 buffer,
23652 None,
23653 window,
23654 cx,
23655 );
23656 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23657 prompt.set_show_cursor_when_unfocused(false, cx);
23658 prompt.set_placeholder_text(
23659 match edit_action {
23660 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23661 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23662 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23663 },
23664 cx,
23665 );
23666
23667 prompt
23668 });
23669
23670 Self {
23671 prompt,
23672 editor,
23673 breakpoint_anchor,
23674 breakpoint,
23675 edit_action,
23676 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23677 block_ids: Default::default(),
23678 _subscriptions: vec![],
23679 }
23680 }
23681
23682 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23683 self.block_ids.extend(block_ids)
23684 }
23685
23686 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23687 if let Some(editor) = self.editor.upgrade() {
23688 let message = self
23689 .prompt
23690 .read(cx)
23691 .buffer
23692 .read(cx)
23693 .as_singleton()
23694 .expect("A multi buffer in breakpoint prompt isn't possible")
23695 .read(cx)
23696 .as_rope()
23697 .to_string();
23698
23699 editor.update(cx, |editor, cx| {
23700 editor.edit_breakpoint_at_anchor(
23701 self.breakpoint_anchor,
23702 self.breakpoint.clone(),
23703 match self.edit_action {
23704 BreakpointPromptEditAction::Log => {
23705 BreakpointEditAction::EditLogMessage(message.into())
23706 }
23707 BreakpointPromptEditAction::Condition => {
23708 BreakpointEditAction::EditCondition(message.into())
23709 }
23710 BreakpointPromptEditAction::HitCondition => {
23711 BreakpointEditAction::EditHitCondition(message.into())
23712 }
23713 },
23714 cx,
23715 );
23716
23717 editor.remove_blocks(self.block_ids.clone(), None, cx);
23718 cx.focus_self(window);
23719 });
23720 }
23721 }
23722
23723 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23724 self.editor
23725 .update(cx, |editor, cx| {
23726 editor.remove_blocks(self.block_ids.clone(), None, cx);
23727 window.focus(&editor.focus_handle);
23728 })
23729 .log_err();
23730 }
23731
23732 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23733 let settings = ThemeSettings::get_global(cx);
23734 let text_style = TextStyle {
23735 color: if self.prompt.read(cx).read_only(cx) {
23736 cx.theme().colors().text_disabled
23737 } else {
23738 cx.theme().colors().text
23739 },
23740 font_family: settings.buffer_font.family.clone(),
23741 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23742 font_size: settings.buffer_font_size(cx).into(),
23743 font_weight: settings.buffer_font.weight,
23744 line_height: relative(settings.buffer_line_height.value()),
23745 ..Default::default()
23746 };
23747 EditorElement::new(
23748 &self.prompt,
23749 EditorStyle {
23750 background: cx.theme().colors().editor_background,
23751 local_player: cx.theme().players().local(),
23752 text: text_style,
23753 ..Default::default()
23754 },
23755 )
23756 }
23757}
23758
23759impl Render for BreakpointPromptEditor {
23760 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23761 let editor_margins = *self.editor_margins.lock();
23762 let gutter_dimensions = editor_margins.gutter;
23763 h_flex()
23764 .key_context("Editor")
23765 .bg(cx.theme().colors().editor_background)
23766 .border_y_1()
23767 .border_color(cx.theme().status().info_border)
23768 .size_full()
23769 .py(window.line_height() / 2.5)
23770 .on_action(cx.listener(Self::confirm))
23771 .on_action(cx.listener(Self::cancel))
23772 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23773 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23774 }
23775}
23776
23777impl Focusable for BreakpointPromptEditor {
23778 fn focus_handle(&self, cx: &App) -> FocusHandle {
23779 self.prompt.focus_handle(cx)
23780 }
23781}
23782
23783fn all_edits_insertions_or_deletions(
23784 edits: &Vec<(Range<Anchor>, String)>,
23785 snapshot: &MultiBufferSnapshot,
23786) -> bool {
23787 let mut all_insertions = true;
23788 let mut all_deletions = true;
23789
23790 for (range, new_text) in edits.iter() {
23791 let range_is_empty = range.to_offset(snapshot).is_empty();
23792 let text_is_empty = new_text.is_empty();
23793
23794 if range_is_empty != text_is_empty {
23795 if range_is_empty {
23796 all_deletions = false;
23797 } else {
23798 all_insertions = false;
23799 }
23800 } else {
23801 return false;
23802 }
23803
23804 if !all_insertions && !all_deletions {
23805 return false;
23806 }
23807 }
23808 all_insertions || all_deletions
23809}
23810
23811struct MissingEditPredictionKeybindingTooltip;
23812
23813impl Render for MissingEditPredictionKeybindingTooltip {
23814 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23815 ui::tooltip_container(window, cx, |container, _, cx| {
23816 container
23817 .flex_shrink_0()
23818 .max_w_80()
23819 .min_h(rems_from_px(124.))
23820 .justify_between()
23821 .child(
23822 v_flex()
23823 .flex_1()
23824 .text_ui_sm(cx)
23825 .child(Label::new("Conflict with Accept Keybinding"))
23826 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23827 )
23828 .child(
23829 h_flex()
23830 .pb_1()
23831 .gap_1()
23832 .items_end()
23833 .w_full()
23834 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23835 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23836 }))
23837 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23838 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23839 })),
23840 )
23841 })
23842 }
23843}
23844
23845#[derive(Debug, Clone, Copy, PartialEq)]
23846pub struct LineHighlight {
23847 pub background: Background,
23848 pub border: Option<gpui::Hsla>,
23849 pub include_gutter: bool,
23850 pub type_id: Option<TypeId>,
23851}
23852
23853struct LineManipulationResult {
23854 pub new_text: String,
23855 pub line_count_before: usize,
23856 pub line_count_after: usize,
23857}
23858
23859fn render_diff_hunk_controls(
23860 row: u32,
23861 status: &DiffHunkStatus,
23862 hunk_range: Range<Anchor>,
23863 is_created_file: bool,
23864 line_height: Pixels,
23865 editor: &Entity<Editor>,
23866 _window: &mut Window,
23867 cx: &mut App,
23868) -> AnyElement {
23869 h_flex()
23870 .h(line_height)
23871 .mr_1()
23872 .gap_1()
23873 .px_0p5()
23874 .pb_1()
23875 .border_x_1()
23876 .border_b_1()
23877 .border_color(cx.theme().colors().border_variant)
23878 .rounded_b_lg()
23879 .bg(cx.theme().colors().editor_background)
23880 .gap_1()
23881 .block_mouse_except_scroll()
23882 .shadow_md()
23883 .child(if status.has_secondary_hunk() {
23884 Button::new(("stage", row as u64), "Stage")
23885 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23886 .tooltip({
23887 let focus_handle = editor.focus_handle(cx);
23888 move |window, cx| {
23889 Tooltip::for_action_in(
23890 "Stage Hunk",
23891 &::git::ToggleStaged,
23892 &focus_handle,
23893 window,
23894 cx,
23895 )
23896 }
23897 })
23898 .on_click({
23899 let editor = editor.clone();
23900 move |_event, _window, cx| {
23901 editor.update(cx, |editor, cx| {
23902 editor.stage_or_unstage_diff_hunks(
23903 true,
23904 vec![hunk_range.start..hunk_range.start],
23905 cx,
23906 );
23907 });
23908 }
23909 })
23910 } else {
23911 Button::new(("unstage", row as u64), "Unstage")
23912 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23913 .tooltip({
23914 let focus_handle = editor.focus_handle(cx);
23915 move |window, cx| {
23916 Tooltip::for_action_in(
23917 "Unstage Hunk",
23918 &::git::ToggleStaged,
23919 &focus_handle,
23920 window,
23921 cx,
23922 )
23923 }
23924 })
23925 .on_click({
23926 let editor = editor.clone();
23927 move |_event, _window, cx| {
23928 editor.update(cx, |editor, cx| {
23929 editor.stage_or_unstage_diff_hunks(
23930 false,
23931 vec![hunk_range.start..hunk_range.start],
23932 cx,
23933 );
23934 });
23935 }
23936 })
23937 })
23938 .child(
23939 Button::new(("restore", row as u64), "Restore")
23940 .tooltip({
23941 let focus_handle = editor.focus_handle(cx);
23942 move |window, cx| {
23943 Tooltip::for_action_in(
23944 "Restore Hunk",
23945 &::git::Restore,
23946 &focus_handle,
23947 window,
23948 cx,
23949 )
23950 }
23951 })
23952 .on_click({
23953 let editor = editor.clone();
23954 move |_event, window, cx| {
23955 editor.update(cx, |editor, cx| {
23956 let snapshot = editor.snapshot(window, cx);
23957 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23958 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23959 });
23960 }
23961 })
23962 .disabled(is_created_file),
23963 )
23964 .when(
23965 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23966 |el| {
23967 el.child(
23968 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23969 .shape(IconButtonShape::Square)
23970 .icon_size(IconSize::Small)
23971 // .disabled(!has_multiple_hunks)
23972 .tooltip({
23973 let focus_handle = editor.focus_handle(cx);
23974 move |window, cx| {
23975 Tooltip::for_action_in(
23976 "Next Hunk",
23977 &GoToHunk,
23978 &focus_handle,
23979 window,
23980 cx,
23981 )
23982 }
23983 })
23984 .on_click({
23985 let editor = editor.clone();
23986 move |_event, window, cx| {
23987 editor.update(cx, |editor, cx| {
23988 let snapshot = editor.snapshot(window, cx);
23989 let position =
23990 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23991 editor.go_to_hunk_before_or_after_position(
23992 &snapshot,
23993 position,
23994 Direction::Next,
23995 window,
23996 cx,
23997 );
23998 editor.expand_selected_diff_hunks(cx);
23999 });
24000 }
24001 }),
24002 )
24003 .child(
24004 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24005 .shape(IconButtonShape::Square)
24006 .icon_size(IconSize::Small)
24007 // .disabled(!has_multiple_hunks)
24008 .tooltip({
24009 let focus_handle = editor.focus_handle(cx);
24010 move |window, cx| {
24011 Tooltip::for_action_in(
24012 "Previous Hunk",
24013 &GoToPreviousHunk,
24014 &focus_handle,
24015 window,
24016 cx,
24017 )
24018 }
24019 })
24020 .on_click({
24021 let editor = editor.clone();
24022 move |_event, window, cx| {
24023 editor.update(cx, |editor, cx| {
24024 let snapshot = editor.snapshot(window, cx);
24025 let point =
24026 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24027 editor.go_to_hunk_before_or_after_position(
24028 &snapshot,
24029 point,
24030 Direction::Prev,
24031 window,
24032 cx,
24033 );
24034 editor.expand_selected_diff_hunks(cx);
24035 });
24036 }
24037 }),
24038 )
24039 },
24040 )
24041 .into_any_element()
24042}