1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
125 DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
126 Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
127 TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionIntent, CompletionResponse,
151 CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, Location, LocationLink,
152 PrepareRenameResponse, Project, ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
153 debugger::breakpoint_store::Breakpoint,
154 debugger::{
155 breakpoint_store::{
156 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
157 BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
164 project_settings::{GitGutterSetting, ProjectSettings},
165};
166use rand::{seq::SliceRandom, thread_rng};
167use rpc::{ErrorCode, ErrorExt, proto::PeerId};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
169use selections_collection::{
170 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
171};
172use serde::{Deserialize, Serialize};
173use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::TypeId,
178 borrow::Cow,
179 cell::OnceCell,
180 cell::RefCell,
181 cmp::{self, Ordering, Reverse},
182 iter::Peekable,
183 mem,
184 num::NonZeroU32,
185 ops::Not,
186 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 sync::Arc,
190 time::{Duration, Instant},
191};
192use sum_tree::TreeMap;
193use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::{
214 code_context_menus::CompletionsMenuSource,
215 editor_settings::MultiCursorModifier,
216 hover_links::{find_url, find_url_from_range},
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
223const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
224const MAX_LINE_LEN: usize = 1024;
225const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
226const MAX_SELECTION_HISTORY_LEN: usize = 1024;
227pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
228#[doc(hidden)]
229pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
230const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
231
232pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
235
236pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
237pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
238pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
239
240pub type RenderDiffHunkControlsFn = Arc<
241 dyn Fn(
242 u32,
243 &DiffHunkStatus,
244 Range<Anchor>,
245 bool,
246 Pixels,
247 &Entity<Editor>,
248 &mut Window,
249 &mut App,
250 ) -> AnyElement,
251>;
252
253enum ReportEditorEvent {
254 Saved { auto_saved: bool },
255 EditorOpened,
256 ZetaTosClicked,
257 Closed,
258}
259
260impl ReportEditorEvent {
261 pub fn event_type(&self) -> &'static str {
262 match self {
263 Self::Saved { .. } => "Editor Saved",
264 Self::EditorOpened => "Editor Opened",
265 Self::ZetaTosClicked => "Edit Prediction Provider ToS Clicked",
266 Self::Closed => "Editor Closed",
267 }
268 }
269}
270
271struct InlineValueCache {
272 enabled: bool,
273 inlays: Vec<InlayId>,
274 refresh_task: Task<Option<()>>,
275}
276
277impl InlineValueCache {
278 fn new(enabled: bool) -> Self {
279 Self {
280 enabled,
281 inlays: Vec::new(),
282 refresh_task: Task::ready(None),
283 }
284 }
285}
286
287#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
288pub enum InlayId {
289 EditPrediction(usize),
290 DebuggerValue(usize),
291 // LSP
292 Hint(usize),
293 Color(usize),
294}
295
296impl InlayId {
297 fn id(&self) -> usize {
298 match self {
299 Self::EditPrediction(id) => *id,
300 Self::DebuggerValue(id) => *id,
301 Self::Hint(id) => *id,
302 Self::Color(id) => *id,
303 }
304 }
305}
306
307pub enum ActiveDebugLine {}
308pub enum DebugStackFrameLine {}
309enum DocumentHighlightRead {}
310enum DocumentHighlightWrite {}
311enum InputComposition {}
312pub enum PendingInput {}
313enum SelectedTextHighlight {}
314
315pub enum ConflictsOuter {}
316pub enum ConflictsOurs {}
317pub enum ConflictsTheirs {}
318pub enum ConflictsOursMarker {}
319pub enum ConflictsTheirsMarker {}
320
321#[derive(Debug, Copy, Clone, PartialEq, Eq)]
322pub enum Navigated {
323 Yes,
324 No,
325}
326
327impl Navigated {
328 pub fn from_bool(yes: bool) -> Navigated {
329 if yes { Navigated::Yes } else { Navigated::No }
330 }
331}
332
333#[derive(Debug, Clone, PartialEq, Eq)]
334enum DisplayDiffHunk {
335 Folded {
336 display_row: DisplayRow,
337 },
338 Unfolded {
339 is_created_file: bool,
340 diff_base_byte_range: Range<usize>,
341 display_row_range: Range<DisplayRow>,
342 multi_buffer_range: Range<Anchor>,
343 status: DiffHunkStatus,
344 },
345}
346
347pub enum HideMouseCursorOrigin {
348 TypingAction,
349 MovementAction,
350}
351
352pub fn init_settings(cx: &mut App) {
353 EditorSettings::register(cx);
354}
355
356pub fn init(cx: &mut App) {
357 init_settings(cx);
358
359 cx.set_global(GlobalBlameRenderer(Arc::new(())));
360
361 workspace::register_project_item::<Editor>(cx);
362 workspace::FollowableViewRegistry::register::<Editor>(cx);
363 workspace::register_serializable_item::<Editor>(cx);
364
365 cx.observe_new(
366 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
367 workspace.register_action(Editor::new_file);
368 workspace.register_action(Editor::new_file_vertical);
369 workspace.register_action(Editor::new_file_horizontal);
370 workspace.register_action(Editor::cancel_language_server_work);
371 workspace.register_action(Editor::toggle_focus);
372 },
373 )
374 .detach();
375
376 cx.on_action(move |_: &workspace::NewFile, cx| {
377 let app_state = workspace::AppState::global(cx);
378 if let Some(app_state) = app_state.upgrade() {
379 workspace::open_new(
380 Default::default(),
381 app_state,
382 cx,
383 |workspace, window, cx| {
384 Editor::new_file(workspace, &Default::default(), window, cx)
385 },
386 )
387 .detach();
388 }
389 });
390 cx.on_action(move |_: &workspace::NewWindow, cx| {
391 let app_state = workspace::AppState::global(cx);
392 if let Some(app_state) = app_state.upgrade() {
393 workspace::open_new(
394 Default::default(),
395 app_state,
396 cx,
397 |workspace, window, cx| {
398 cx.activate(true);
399 Editor::new_file(workspace, &Default::default(), window, cx)
400 },
401 )
402 .detach();
403 }
404 });
405}
406
407pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
408 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
409}
410
411pub trait DiagnosticRenderer {
412 fn render_group(
413 &self,
414 diagnostic_group: Vec<DiagnosticEntry<Point>>,
415 buffer_id: BufferId,
416 snapshot: EditorSnapshot,
417 editor: WeakEntity<Editor>,
418 cx: &mut App,
419 ) -> Vec<BlockProperties<Anchor>>;
420
421 fn render_hover(
422 &self,
423 diagnostic_group: Vec<DiagnosticEntry<Point>>,
424 range: Range<Point>,
425 buffer_id: BufferId,
426 cx: &mut App,
427 ) -> Option<Entity<markdown::Markdown>>;
428
429 fn open_link(
430 &self,
431 editor: &mut Editor,
432 link: SharedString,
433 window: &mut Window,
434 cx: &mut Context<Editor>,
435 );
436}
437
438pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
439
440impl GlobalDiagnosticRenderer {
441 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
442 cx.try_global::<Self>().map(|g| g.0.clone())
443 }
444}
445
446impl gpui::Global for GlobalDiagnosticRenderer {}
447pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
448 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
449}
450
451pub struct SearchWithinRange;
452
453trait InvalidationRegion {
454 fn ranges(&self) -> &[Range<Anchor>];
455}
456
457#[derive(Clone, Debug, PartialEq)]
458pub enum SelectPhase {
459 Begin {
460 position: DisplayPoint,
461 add: bool,
462 click_count: usize,
463 },
464 BeginColumnar {
465 position: DisplayPoint,
466 reset: bool,
467 mode: ColumnarMode,
468 goal_column: u32,
469 },
470 Extend {
471 position: DisplayPoint,
472 click_count: usize,
473 },
474 Update {
475 position: DisplayPoint,
476 goal_column: u32,
477 scroll_delta: gpui::Point<f32>,
478 },
479 End,
480}
481
482#[derive(Clone, Debug, PartialEq)]
483pub enum ColumnarMode {
484 FromMouse,
485 FromSelection,
486}
487
488#[derive(Clone, Debug)]
489pub enum SelectMode {
490 Character,
491 Word(Range<Anchor>),
492 Line(Range<Anchor>),
493 All,
494}
495
496#[derive(Clone, PartialEq, Eq, Debug)]
497pub enum EditorMode {
498 SingleLine,
499 AutoHeight {
500 min_lines: usize,
501 max_lines: Option<usize>,
502 },
503 Full {
504 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
505 scale_ui_elements_with_buffer_font_size: bool,
506 /// When set to `true`, the editor will render a background for the active line.
507 show_active_line_background: bool,
508 /// When set to `true`, the editor's height will be determined by its content.
509 sized_by_content: bool,
510 },
511 Minimap {
512 parent: WeakEntity<Editor>,
513 },
514}
515
516impl EditorMode {
517 pub fn full() -> Self {
518 Self::Full {
519 scale_ui_elements_with_buffer_font_size: true,
520 show_active_line_background: true,
521 sized_by_content: false,
522 }
523 }
524
525 #[inline]
526 pub fn is_full(&self) -> bool {
527 matches!(self, Self::Full { .. })
528 }
529
530 #[inline]
531 pub fn is_single_line(&self) -> bool {
532 matches!(self, Self::SingleLine { .. })
533 }
534
535 #[inline]
536 fn is_minimap(&self) -> bool {
537 matches!(self, Self::Minimap { .. })
538 }
539}
540
541#[derive(Copy, Clone, Debug)]
542pub enum SoftWrap {
543 /// Prefer not to wrap at all.
544 ///
545 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
546 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
547 GitDiff,
548 /// Prefer a single line generally, unless an overly long line is encountered.
549 None,
550 /// Soft wrap lines that exceed the editor width.
551 EditorWidth,
552 /// Soft wrap lines at the preferred line length.
553 Column(u32),
554 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
555 Bounded(u32),
556}
557
558#[derive(Clone)]
559pub struct EditorStyle {
560 pub background: Hsla,
561 pub border: Hsla,
562 pub local_player: PlayerColor,
563 pub text: TextStyle,
564 pub scrollbar_width: Pixels,
565 pub syntax: Arc<SyntaxTheme>,
566 pub status: StatusColors,
567 pub inlay_hints_style: HighlightStyle,
568 pub edit_prediction_styles: EditPredictionStyles,
569 pub unnecessary_code_fade: f32,
570 pub show_underlines: bool,
571}
572
573impl Default for EditorStyle {
574 fn default() -> Self {
575 Self {
576 background: Hsla::default(),
577 border: Hsla::default(),
578 local_player: PlayerColor::default(),
579 text: TextStyle::default(),
580 scrollbar_width: Pixels::default(),
581 syntax: Default::default(),
582 // HACK: Status colors don't have a real default.
583 // We should look into removing the status colors from the editor
584 // style and retrieve them directly from the theme.
585 status: StatusColors::dark(),
586 inlay_hints_style: HighlightStyle::default(),
587 edit_prediction_styles: EditPredictionStyles {
588 insertion: HighlightStyle::default(),
589 whitespace: HighlightStyle::default(),
590 },
591 unnecessary_code_fade: Default::default(),
592 show_underlines: true,
593 }
594 }
595}
596
597pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
598 let show_background = language_settings::language_settings(None, None, cx)
599 .inlay_hints
600 .show_background;
601
602 HighlightStyle {
603 color: Some(cx.theme().status().hint),
604 background_color: show_background.then(|| cx.theme().status().hint_background),
605 ..HighlightStyle::default()
606 }
607}
608
609pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
610 EditPredictionStyles {
611 insertion: HighlightStyle {
612 color: Some(cx.theme().status().predictive),
613 ..HighlightStyle::default()
614 },
615 whitespace: HighlightStyle {
616 background_color: Some(cx.theme().status().created_background),
617 ..HighlightStyle::default()
618 },
619 }
620}
621
622type CompletionId = usize;
623
624pub(crate) enum EditDisplayMode {
625 TabAccept,
626 DiffPopover,
627 Inline,
628}
629
630enum EditPrediction {
631 Edit {
632 edits: Vec<(Range<Anchor>, String)>,
633 edit_preview: Option<EditPreview>,
634 display_mode: EditDisplayMode,
635 snapshot: BufferSnapshot,
636 },
637 Move {
638 target: Anchor,
639 snapshot: BufferSnapshot,
640 },
641}
642
643struct EditPredictionState {
644 inlay_ids: Vec<InlayId>,
645 completion: EditPrediction,
646 completion_id: Option<SharedString>,
647 invalidation_range: Range<Anchor>,
648}
649
650enum EditPredictionSettings {
651 Disabled,
652 Enabled {
653 show_in_menu: bool,
654 preview_requires_modifier: bool,
655 },
656}
657
658enum EditPredictionHighlight {}
659
660#[derive(Debug, Clone)]
661struct InlineDiagnostic {
662 message: SharedString,
663 group_id: usize,
664 is_primary: bool,
665 start: Point,
666 severity: lsp::DiagnosticSeverity,
667}
668
669pub enum MenuEditPredictionsPolicy {
670 Never,
671 ByProvider,
672}
673
674pub enum EditPredictionPreview {
675 /// Modifier is not pressed
676 Inactive { released_too_fast: bool },
677 /// Modifier pressed
678 Active {
679 since: Instant,
680 previous_scroll_position: Option<ScrollAnchor>,
681 },
682}
683
684impl EditPredictionPreview {
685 pub fn released_too_fast(&self) -> bool {
686 match self {
687 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
688 EditPredictionPreview::Active { .. } => false,
689 }
690 }
691
692 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
693 if let EditPredictionPreview::Active {
694 previous_scroll_position,
695 ..
696 } = self
697 {
698 *previous_scroll_position = scroll_position;
699 }
700 }
701}
702
703pub struct ContextMenuOptions {
704 pub min_entries_visible: usize,
705 pub max_entries_visible: usize,
706 pub placement: Option<ContextMenuPlacement>,
707}
708
709#[derive(Debug, Clone, PartialEq, Eq)]
710pub enum ContextMenuPlacement {
711 Above,
712 Below,
713}
714
715#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
716struct EditorActionId(usize);
717
718impl EditorActionId {
719 pub fn post_inc(&mut self) -> Self {
720 let answer = self.0;
721
722 *self = Self(answer + 1);
723
724 Self(answer)
725 }
726}
727
728// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
729// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
730
731type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
732type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
733
734#[derive(Default)]
735struct ScrollbarMarkerState {
736 scrollbar_size: Size<Pixels>,
737 dirty: bool,
738 markers: Arc<[PaintQuad]>,
739 pending_refresh: Option<Task<Result<()>>>,
740}
741
742impl ScrollbarMarkerState {
743 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
744 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
745 }
746}
747
748#[derive(Clone, Copy, PartialEq, Eq)]
749pub enum MinimapVisibility {
750 Disabled,
751 Enabled {
752 /// The configuration currently present in the users settings.
753 setting_configuration: bool,
754 /// Whether to override the currently set visibility from the users setting.
755 toggle_override: bool,
756 },
757}
758
759impl MinimapVisibility {
760 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
761 if mode.is_full() {
762 Self::Enabled {
763 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
764 toggle_override: false,
765 }
766 } else {
767 Self::Disabled
768 }
769 }
770
771 fn hidden(&self) -> Self {
772 match *self {
773 Self::Enabled {
774 setting_configuration,
775 ..
776 } => Self::Enabled {
777 setting_configuration,
778 toggle_override: setting_configuration,
779 },
780 Self::Disabled => Self::Disabled,
781 }
782 }
783
784 fn disabled(&self) -> bool {
785 match *self {
786 Self::Disabled => true,
787 _ => false,
788 }
789 }
790
791 fn settings_visibility(&self) -> bool {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 ..
796 } => setting_configuration,
797 _ => false,
798 }
799 }
800
801 fn visible(&self) -> bool {
802 match *self {
803 Self::Enabled {
804 setting_configuration,
805 toggle_override,
806 } => setting_configuration ^ toggle_override,
807 _ => false,
808 }
809 }
810
811 fn toggle_visibility(&self) -> Self {
812 match *self {
813 Self::Enabled {
814 toggle_override,
815 setting_configuration,
816 } => Self::Enabled {
817 setting_configuration,
818 toggle_override: !toggle_override,
819 },
820 Self::Disabled => Self::Disabled,
821 }
822 }
823}
824
825#[derive(Clone, Debug)]
826struct RunnableTasks {
827 templates: Vec<(TaskSourceKind, TaskTemplate)>,
828 offset: multi_buffer::Anchor,
829 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
830 column: u32,
831 // Values of all named captures, including those starting with '_'
832 extra_variables: HashMap<String, String>,
833 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
834 context_range: Range<BufferOffset>,
835}
836
837impl RunnableTasks {
838 fn resolve<'a>(
839 &'a self,
840 cx: &'a task::TaskContext,
841 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
842 self.templates.iter().filter_map(|(kind, template)| {
843 template
844 .resolve_task(&kind.to_id_base(), cx)
845 .map(|task| (kind.clone(), task))
846 })
847 }
848}
849
850#[derive(Clone)]
851pub struct ResolvedTasks {
852 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
853 position: Anchor,
854}
855
856#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
857struct BufferOffset(usize);
858
859// Addons allow storing per-editor state in other crates (e.g. Vim)
860pub trait Addon: 'static {
861 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
862
863 fn render_buffer_header_controls(
864 &self,
865 _: &ExcerptInfo,
866 _: &Window,
867 _: &App,
868 ) -> Option<AnyElement> {
869 None
870 }
871
872 fn to_any(&self) -> &dyn std::any::Any;
873
874 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
875 None
876 }
877}
878
879struct ChangeLocation {
880 current: Option<Vec<Anchor>>,
881 original: Vec<Anchor>,
882}
883impl ChangeLocation {
884 fn locations(&self) -> &[Anchor] {
885 self.current.as_ref().unwrap_or(&self.original)
886 }
887}
888
889/// A set of caret positions, registered when the editor was edited.
890pub struct ChangeList {
891 changes: Vec<ChangeLocation>,
892 /// Currently "selected" change.
893 position: Option<usize>,
894}
895
896impl ChangeList {
897 pub fn new() -> Self {
898 Self {
899 changes: Vec::new(),
900 position: None,
901 }
902 }
903
904 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
905 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
906 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
907 if self.changes.is_empty() {
908 return None;
909 }
910
911 let prev = self.position.unwrap_or(self.changes.len());
912 let next = if direction == Direction::Prev {
913 prev.saturating_sub(count)
914 } else {
915 (prev + count).min(self.changes.len() - 1)
916 };
917 self.position = Some(next);
918 self.changes.get(next).map(|change| change.locations())
919 }
920
921 /// Adds a new change to the list, resetting the change list position.
922 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
923 self.position.take();
924 if let Some(last) = self.changes.last_mut()
925 && group
926 {
927 last.current = Some(new_positions)
928 } else {
929 self.changes.push(ChangeLocation {
930 original: new_positions,
931 current: None,
932 });
933 }
934 }
935
936 pub fn last(&self) -> Option<&[Anchor]> {
937 self.changes.last().map(|change| change.locations())
938 }
939
940 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
941 self.changes.last().map(|change| change.original.as_slice())
942 }
943
944 pub fn invert_last_group(&mut self) {
945 if let Some(last) = self.changes.last_mut() {
946 if let Some(current) = last.current.as_mut() {
947 mem::swap(&mut last.original, current);
948 }
949 }
950 }
951}
952
953#[derive(Clone)]
954struct InlineBlamePopoverState {
955 scroll_handle: ScrollHandle,
956 commit_message: Option<ParsedCommitMessage>,
957 markdown: Entity<Markdown>,
958}
959
960struct InlineBlamePopover {
961 position: gpui::Point<Pixels>,
962 hide_task: Option<Task<()>>,
963 popover_bounds: Option<Bounds<Pixels>>,
964 popover_state: InlineBlamePopoverState,
965 keyboard_grace: bool,
966}
967
968enum SelectionDragState {
969 /// State when no drag related activity is detected.
970 None,
971 /// State when the mouse is down on a selection that is about to be dragged.
972 ReadyToDrag {
973 selection: Selection<Anchor>,
974 click_position: gpui::Point<Pixels>,
975 mouse_down_time: Instant,
976 },
977 /// State when the mouse is dragging the selection in the editor.
978 Dragging {
979 selection: Selection<Anchor>,
980 drop_cursor: Selection<Anchor>,
981 hide_drop_cursor: bool,
982 },
983}
984
985enum ColumnarSelectionState {
986 FromMouse {
987 selection_tail: Anchor,
988 display_point: Option<DisplayPoint>,
989 },
990 FromSelection {
991 selection_tail: Anchor,
992 },
993}
994
995/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
996/// a breakpoint on them.
997#[derive(Clone, Copy, Debug, PartialEq, Eq)]
998struct PhantomBreakpointIndicator {
999 display_row: DisplayRow,
1000 /// There's a small debounce between hovering over the line and showing the indicator.
1001 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1002 is_active: bool,
1003 collides_with_existing_breakpoint: bool,
1004}
1005
1006/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1007///
1008/// See the [module level documentation](self) for more information.
1009pub struct Editor {
1010 focus_handle: FocusHandle,
1011 last_focused_descendant: Option<WeakFocusHandle>,
1012 /// The text buffer being edited
1013 buffer: Entity<MultiBuffer>,
1014 /// Map of how text in the buffer should be displayed.
1015 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1016 pub display_map: Entity<DisplayMap>,
1017 pub selections: SelectionsCollection,
1018 pub scroll_manager: ScrollManager,
1019 /// When inline assist editors are linked, they all render cursors because
1020 /// typing enters text into each of them, even the ones that aren't focused.
1021 pub(crate) show_cursor_when_unfocused: bool,
1022 columnar_selection_state: Option<ColumnarSelectionState>,
1023 add_selections_state: Option<AddSelectionsState>,
1024 select_next_state: Option<SelectNextState>,
1025 select_prev_state: Option<SelectNextState>,
1026 selection_history: SelectionHistory,
1027 defer_selection_effects: bool,
1028 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1029 autoclose_regions: Vec<AutocloseRegion>,
1030 snippet_stack: InvalidationStack<SnippetState>,
1031 select_syntax_node_history: SelectSyntaxNodeHistory,
1032 ime_transaction: Option<TransactionId>,
1033 pub diagnostics_max_severity: DiagnosticSeverity,
1034 active_diagnostics: ActiveDiagnostic,
1035 show_inline_diagnostics: bool,
1036 inline_diagnostics_update: Task<()>,
1037 inline_diagnostics_enabled: bool,
1038 diagnostics_enabled: bool,
1039 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1040 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1041 hard_wrap: Option<usize>,
1042
1043 // TODO: make this a access method
1044 pub project: Option<Entity<Project>>,
1045 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1046 completion_provider: Option<Rc<dyn CompletionProvider>>,
1047 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1048 blink_manager: Entity<BlinkManager>,
1049 show_cursor_names: bool,
1050 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1051 pub show_local_selections: bool,
1052 mode: EditorMode,
1053 show_breadcrumbs: bool,
1054 show_gutter: bool,
1055 show_scrollbars: ScrollbarAxes,
1056 minimap_visibility: MinimapVisibility,
1057 offset_content: bool,
1058 disable_expand_excerpt_buttons: bool,
1059 show_line_numbers: Option<bool>,
1060 use_relative_line_numbers: Option<bool>,
1061 show_git_diff_gutter: Option<bool>,
1062 show_code_actions: Option<bool>,
1063 show_runnables: Option<bool>,
1064 show_breakpoints: Option<bool>,
1065 show_wrap_guides: Option<bool>,
1066 show_indent_guides: Option<bool>,
1067 placeholder_text: Option<Arc<str>>,
1068 highlight_order: usize,
1069 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1070 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1071 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1072 scrollbar_marker_state: ScrollbarMarkerState,
1073 active_indent_guides_state: ActiveIndentGuidesState,
1074 nav_history: Option<ItemNavHistory>,
1075 context_menu: RefCell<Option<CodeContextMenu>>,
1076 context_menu_options: Option<ContextMenuOptions>,
1077 mouse_context_menu: Option<MouseContextMenu>,
1078 completion_tasks: Vec<(CompletionId, Task<()>)>,
1079 inline_blame_popover: Option<InlineBlamePopover>,
1080 inline_blame_popover_show_task: Option<Task<()>>,
1081 signature_help_state: SignatureHelpState,
1082 auto_signature_help: Option<bool>,
1083 find_all_references_task_sources: Vec<Anchor>,
1084 next_completion_id: CompletionId,
1085 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1086 code_actions_task: Option<Task<Result<()>>>,
1087 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1088 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1089 document_highlights_task: Option<Task<()>>,
1090 linked_editing_range_task: Option<Task<Option<()>>>,
1091 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1092 pending_rename: Option<RenameState>,
1093 searchable: bool,
1094 cursor_shape: CursorShape,
1095 current_line_highlight: Option<CurrentLineHighlight>,
1096 collapse_matches: bool,
1097 autoindent_mode: Option<AutoindentMode>,
1098 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1099 input_enabled: bool,
1100 use_modal_editing: bool,
1101 read_only: bool,
1102 leader_id: Option<CollaboratorId>,
1103 remote_id: Option<ViewId>,
1104 pub hover_state: HoverState,
1105 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1106 gutter_hovered: bool,
1107 hovered_link_state: Option<HoveredLinkState>,
1108 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1109 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1110 active_edit_prediction: Option<EditPredictionState>,
1111 /// Used to prevent flickering as the user types while the menu is open
1112 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1113 edit_prediction_settings: EditPredictionSettings,
1114 edit_predictions_hidden_for_vim_mode: bool,
1115 show_edit_predictions_override: Option<bool>,
1116 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1117 edit_prediction_preview: EditPredictionPreview,
1118 edit_prediction_indent_conflict: bool,
1119 edit_prediction_requires_modifier_in_indent_conflict: bool,
1120 inlay_hint_cache: InlayHintCache,
1121 next_inlay_id: usize,
1122 _subscriptions: Vec<Subscription>,
1123 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1124 gutter_dimensions: GutterDimensions,
1125 style: Option<EditorStyle>,
1126 text_style_refinement: Option<TextStyleRefinement>,
1127 next_editor_action_id: EditorActionId,
1128 editor_actions: Rc<
1129 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1130 >,
1131 use_autoclose: bool,
1132 use_auto_surround: bool,
1133 auto_replace_emoji_shortcode: bool,
1134 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1135 show_git_blame_gutter: bool,
1136 show_git_blame_inline: bool,
1137 show_git_blame_inline_delay_task: Option<Task<()>>,
1138 git_blame_inline_enabled: bool,
1139 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1140 serialize_dirty_buffers: bool,
1141 show_selection_menu: Option<bool>,
1142 blame: Option<Entity<GitBlame>>,
1143 blame_subscription: Option<Subscription>,
1144 custom_context_menu: Option<
1145 Box<
1146 dyn 'static
1147 + Fn(
1148 &mut Self,
1149 DisplayPoint,
1150 &mut Window,
1151 &mut Context<Self>,
1152 ) -> Option<Entity<ui::ContextMenu>>,
1153 >,
1154 >,
1155 last_bounds: Option<Bounds<Pixels>>,
1156 last_position_map: Option<Rc<PositionMap>>,
1157 expect_bounds_change: Option<Bounds<Pixels>>,
1158 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1159 tasks_update_task: Option<Task<()>>,
1160 breakpoint_store: Option<Entity<BreakpointStore>>,
1161 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1162 hovered_diff_hunk_row: Option<DisplayRow>,
1163 pull_diagnostics_task: Task<()>,
1164 in_project_search: bool,
1165 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1166 breadcrumb_header: Option<String>,
1167 focused_block: Option<FocusedBlock>,
1168 next_scroll_position: NextScrollCursorCenterTopBottom,
1169 addons: HashMap<TypeId, Box<dyn Addon>>,
1170 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1171 load_diff_task: Option<Shared<Task<()>>>,
1172 /// Whether we are temporarily displaying a diff other than git's
1173 temporary_diff_override: bool,
1174 selection_mark_mode: bool,
1175 toggle_fold_multiple_buffers: Task<()>,
1176 _scroll_cursor_center_top_bottom_task: Task<()>,
1177 serialize_selections: Task<()>,
1178 serialize_folds: Task<()>,
1179 mouse_cursor_hidden: bool,
1180 minimap: Option<Entity<Self>>,
1181 hide_mouse_mode: HideMouseMode,
1182 pub change_list: ChangeList,
1183 inline_value_cache: InlineValueCache,
1184 selection_drag_state: SelectionDragState,
1185 next_color_inlay_id: usize,
1186 colors: Option<LspColorData>,
1187 folding_newlines: Task<()>,
1188}
1189
1190#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1191enum NextScrollCursorCenterTopBottom {
1192 #[default]
1193 Center,
1194 Top,
1195 Bottom,
1196}
1197
1198impl NextScrollCursorCenterTopBottom {
1199 fn next(&self) -> Self {
1200 match self {
1201 Self::Center => Self::Top,
1202 Self::Top => Self::Bottom,
1203 Self::Bottom => Self::Center,
1204 }
1205 }
1206}
1207
1208#[derive(Clone)]
1209pub struct EditorSnapshot {
1210 pub mode: EditorMode,
1211 show_gutter: bool,
1212 show_line_numbers: Option<bool>,
1213 show_git_diff_gutter: Option<bool>,
1214 show_code_actions: Option<bool>,
1215 show_runnables: Option<bool>,
1216 show_breakpoints: Option<bool>,
1217 git_blame_gutter_max_author_length: Option<usize>,
1218 pub display_snapshot: DisplaySnapshot,
1219 pub placeholder_text: Option<Arc<str>>,
1220 is_focused: bool,
1221 scroll_anchor: ScrollAnchor,
1222 ongoing_scroll: OngoingScroll,
1223 current_line_highlight: CurrentLineHighlight,
1224 gutter_hovered: bool,
1225}
1226
1227#[derive(Default, Debug, Clone, Copy)]
1228pub struct GutterDimensions {
1229 pub left_padding: Pixels,
1230 pub right_padding: Pixels,
1231 pub width: Pixels,
1232 pub margin: Pixels,
1233 pub git_blame_entries_width: Option<Pixels>,
1234}
1235
1236impl GutterDimensions {
1237 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1238 Self {
1239 margin: Self::default_gutter_margin(font_id, font_size, cx),
1240 ..Default::default()
1241 }
1242 }
1243
1244 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1245 -cx.text_system().descent(font_id, font_size)
1246 }
1247 /// The full width of the space taken up by the gutter.
1248 pub fn full_width(&self) -> Pixels {
1249 self.margin + self.width
1250 }
1251
1252 /// The width of the space reserved for the fold indicators,
1253 /// use alongside 'justify_end' and `gutter_width` to
1254 /// right align content with the line numbers
1255 pub fn fold_area_width(&self) -> Pixels {
1256 self.margin + self.right_padding
1257 }
1258}
1259
1260struct CharacterDimensions {
1261 em_width: Pixels,
1262 em_advance: Pixels,
1263 line_height: Pixels,
1264}
1265
1266#[derive(Debug)]
1267pub struct RemoteSelection {
1268 pub replica_id: ReplicaId,
1269 pub selection: Selection<Anchor>,
1270 pub cursor_shape: CursorShape,
1271 pub collaborator_id: CollaboratorId,
1272 pub line_mode: bool,
1273 pub user_name: Option<SharedString>,
1274 pub color: PlayerColor,
1275}
1276
1277#[derive(Clone, Debug)]
1278struct SelectionHistoryEntry {
1279 selections: Arc<[Selection<Anchor>]>,
1280 select_next_state: Option<SelectNextState>,
1281 select_prev_state: Option<SelectNextState>,
1282 add_selections_state: Option<AddSelectionsState>,
1283}
1284
1285#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1286enum SelectionHistoryMode {
1287 Normal,
1288 Undoing,
1289 Redoing,
1290 Skipping,
1291}
1292
1293#[derive(Clone, PartialEq, Eq, Hash)]
1294struct HoveredCursor {
1295 replica_id: u16,
1296 selection_id: usize,
1297}
1298
1299impl Default for SelectionHistoryMode {
1300 fn default() -> Self {
1301 Self::Normal
1302 }
1303}
1304
1305#[derive(Debug)]
1306/// SelectionEffects controls the side-effects of updating the selection.
1307///
1308/// The default behaviour does "what you mostly want":
1309/// - it pushes to the nav history if the cursor moved by >10 lines
1310/// - it re-triggers completion requests
1311/// - it scrolls to fit
1312///
1313/// You might want to modify these behaviours. For example when doing a "jump"
1314/// like go to definition, we always want to add to nav history; but when scrolling
1315/// in vim mode we never do.
1316///
1317/// Similarly, you might want to disable scrolling if you don't want the viewport to
1318/// move.
1319#[derive(Clone)]
1320pub struct SelectionEffects {
1321 nav_history: Option<bool>,
1322 completions: bool,
1323 scroll: Option<Autoscroll>,
1324}
1325
1326impl Default for SelectionEffects {
1327 fn default() -> Self {
1328 Self {
1329 nav_history: None,
1330 completions: true,
1331 scroll: Some(Autoscroll::fit()),
1332 }
1333 }
1334}
1335impl SelectionEffects {
1336 pub fn scroll(scroll: Autoscroll) -> Self {
1337 Self {
1338 scroll: Some(scroll),
1339 ..Default::default()
1340 }
1341 }
1342
1343 pub fn no_scroll() -> Self {
1344 Self {
1345 scroll: None,
1346 ..Default::default()
1347 }
1348 }
1349
1350 pub fn completions(self, completions: bool) -> Self {
1351 Self {
1352 completions,
1353 ..self
1354 }
1355 }
1356
1357 pub fn nav_history(self, nav_history: bool) -> Self {
1358 Self {
1359 nav_history: Some(nav_history),
1360 ..self
1361 }
1362 }
1363}
1364
1365struct DeferredSelectionEffectsState {
1366 changed: bool,
1367 effects: SelectionEffects,
1368 old_cursor_position: Anchor,
1369 history_entry: SelectionHistoryEntry,
1370}
1371
1372#[derive(Default)]
1373struct SelectionHistory {
1374 #[allow(clippy::type_complexity)]
1375 selections_by_transaction:
1376 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1377 mode: SelectionHistoryMode,
1378 undo_stack: VecDeque<SelectionHistoryEntry>,
1379 redo_stack: VecDeque<SelectionHistoryEntry>,
1380}
1381
1382impl SelectionHistory {
1383 #[track_caller]
1384 fn insert_transaction(
1385 &mut self,
1386 transaction_id: TransactionId,
1387 selections: Arc<[Selection<Anchor>]>,
1388 ) {
1389 if selections.is_empty() {
1390 log::error!(
1391 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1392 std::panic::Location::caller()
1393 );
1394 return;
1395 }
1396 self.selections_by_transaction
1397 .insert(transaction_id, (selections, None));
1398 }
1399
1400 #[allow(clippy::type_complexity)]
1401 fn transaction(
1402 &self,
1403 transaction_id: TransactionId,
1404 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1405 self.selections_by_transaction.get(&transaction_id)
1406 }
1407
1408 #[allow(clippy::type_complexity)]
1409 fn transaction_mut(
1410 &mut self,
1411 transaction_id: TransactionId,
1412 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1413 self.selections_by_transaction.get_mut(&transaction_id)
1414 }
1415
1416 fn push(&mut self, entry: SelectionHistoryEntry) {
1417 if !entry.selections.is_empty() {
1418 match self.mode {
1419 SelectionHistoryMode::Normal => {
1420 self.push_undo(entry);
1421 self.redo_stack.clear();
1422 }
1423 SelectionHistoryMode::Undoing => self.push_redo(entry),
1424 SelectionHistoryMode::Redoing => self.push_undo(entry),
1425 SelectionHistoryMode::Skipping => {}
1426 }
1427 }
1428 }
1429
1430 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1431 if self
1432 .undo_stack
1433 .back()
1434 .map_or(true, |e| e.selections != entry.selections)
1435 {
1436 self.undo_stack.push_back(entry);
1437 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1438 self.undo_stack.pop_front();
1439 }
1440 }
1441 }
1442
1443 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1444 if self
1445 .redo_stack
1446 .back()
1447 .map_or(true, |e| e.selections != entry.selections)
1448 {
1449 self.redo_stack.push_back(entry);
1450 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1451 self.redo_stack.pop_front();
1452 }
1453 }
1454 }
1455}
1456
1457#[derive(Clone, Copy)]
1458pub struct RowHighlightOptions {
1459 pub autoscroll: bool,
1460 pub include_gutter: bool,
1461}
1462
1463impl Default for RowHighlightOptions {
1464 fn default() -> Self {
1465 Self {
1466 autoscroll: Default::default(),
1467 include_gutter: true,
1468 }
1469 }
1470}
1471
1472struct RowHighlight {
1473 index: usize,
1474 range: Range<Anchor>,
1475 color: Hsla,
1476 options: RowHighlightOptions,
1477 type_id: TypeId,
1478}
1479
1480#[derive(Clone, Debug)]
1481struct AddSelectionsState {
1482 groups: Vec<AddSelectionsGroup>,
1483}
1484
1485#[derive(Clone, Debug)]
1486struct AddSelectionsGroup {
1487 above: bool,
1488 stack: Vec<usize>,
1489}
1490
1491#[derive(Clone)]
1492struct SelectNextState {
1493 query: AhoCorasick,
1494 wordwise: bool,
1495 done: bool,
1496}
1497
1498impl std::fmt::Debug for SelectNextState {
1499 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1500 f.debug_struct(std::any::type_name::<Self>())
1501 .field("wordwise", &self.wordwise)
1502 .field("done", &self.done)
1503 .finish()
1504 }
1505}
1506
1507#[derive(Debug)]
1508struct AutocloseRegion {
1509 selection_id: usize,
1510 range: Range<Anchor>,
1511 pair: BracketPair,
1512}
1513
1514#[derive(Debug)]
1515struct SnippetState {
1516 ranges: Vec<Vec<Range<Anchor>>>,
1517 active_index: usize,
1518 choices: Vec<Option<Vec<String>>>,
1519}
1520
1521#[doc(hidden)]
1522pub struct RenameState {
1523 pub range: Range<Anchor>,
1524 pub old_name: Arc<str>,
1525 pub editor: Entity<Editor>,
1526 block_id: CustomBlockId,
1527}
1528
1529struct InvalidationStack<T>(Vec<T>);
1530
1531struct RegisteredEditPredictionProvider {
1532 provider: Arc<dyn EditPredictionProviderHandle>,
1533 _subscription: Subscription,
1534}
1535
1536#[derive(Debug, PartialEq, Eq)]
1537pub struct ActiveDiagnosticGroup {
1538 pub active_range: Range<Anchor>,
1539 pub active_message: String,
1540 pub group_id: usize,
1541 pub blocks: HashSet<CustomBlockId>,
1542}
1543
1544#[derive(Debug, PartialEq, Eq)]
1545
1546pub(crate) enum ActiveDiagnostic {
1547 None,
1548 All,
1549 Group(ActiveDiagnosticGroup),
1550}
1551
1552#[derive(Serialize, Deserialize, Clone, Debug)]
1553pub struct ClipboardSelection {
1554 /// The number of bytes in this selection.
1555 pub len: usize,
1556 /// Whether this was a full-line selection.
1557 pub is_entire_line: bool,
1558 /// The indentation of the first line when this content was originally copied.
1559 pub first_line_indent: u32,
1560}
1561
1562// selections, scroll behavior, was newest selection reversed
1563type SelectSyntaxNodeHistoryState = (
1564 Box<[Selection<usize>]>,
1565 SelectSyntaxNodeScrollBehavior,
1566 bool,
1567);
1568
1569#[derive(Default)]
1570struct SelectSyntaxNodeHistory {
1571 stack: Vec<SelectSyntaxNodeHistoryState>,
1572 // disable temporarily to allow changing selections without losing the stack
1573 pub disable_clearing: bool,
1574}
1575
1576impl SelectSyntaxNodeHistory {
1577 pub fn try_clear(&mut self) {
1578 if !self.disable_clearing {
1579 self.stack.clear();
1580 }
1581 }
1582
1583 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1584 self.stack.push(selection);
1585 }
1586
1587 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1588 self.stack.pop()
1589 }
1590}
1591
1592enum SelectSyntaxNodeScrollBehavior {
1593 CursorTop,
1594 FitSelection,
1595 CursorBottom,
1596}
1597
1598#[derive(Debug)]
1599pub(crate) struct NavigationData {
1600 cursor_anchor: Anchor,
1601 cursor_position: Point,
1602 scroll_anchor: ScrollAnchor,
1603 scroll_top_row: u32,
1604}
1605
1606#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1607pub enum GotoDefinitionKind {
1608 Symbol,
1609 Declaration,
1610 Type,
1611 Implementation,
1612}
1613
1614#[derive(Debug, Clone)]
1615enum InlayHintRefreshReason {
1616 ModifiersChanged(bool),
1617 Toggle(bool),
1618 SettingsChange(InlayHintSettings),
1619 NewLinesShown,
1620 BufferEdited(HashSet<Arc<Language>>),
1621 RefreshRequested,
1622 ExcerptsRemoved(Vec<ExcerptId>),
1623}
1624
1625impl InlayHintRefreshReason {
1626 fn description(&self) -> &'static str {
1627 match self {
1628 Self::ModifiersChanged(_) => "modifiers changed",
1629 Self::Toggle(_) => "toggle",
1630 Self::SettingsChange(_) => "settings change",
1631 Self::NewLinesShown => "new lines shown",
1632 Self::BufferEdited(_) => "buffer edited",
1633 Self::RefreshRequested => "refresh requested",
1634 Self::ExcerptsRemoved(_) => "excerpts removed",
1635 }
1636 }
1637}
1638
1639pub enum FormatTarget {
1640 Buffers(HashSet<Entity<Buffer>>),
1641 Ranges(Vec<Range<MultiBufferPoint>>),
1642}
1643
1644pub(crate) struct FocusedBlock {
1645 id: BlockId,
1646 focus_handle: WeakFocusHandle,
1647}
1648
1649#[derive(Clone)]
1650enum JumpData {
1651 MultiBufferRow {
1652 row: MultiBufferRow,
1653 line_offset_from_top: u32,
1654 },
1655 MultiBufferPoint {
1656 excerpt_id: ExcerptId,
1657 position: Point,
1658 anchor: text::Anchor,
1659 line_offset_from_top: u32,
1660 },
1661}
1662
1663pub enum MultibufferSelectionMode {
1664 First,
1665 All,
1666}
1667
1668#[derive(Clone, Copy, Debug, Default)]
1669pub struct RewrapOptions {
1670 pub override_language_settings: bool,
1671 pub preserve_existing_whitespace: bool,
1672}
1673
1674impl Editor {
1675 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1676 let buffer = cx.new(|cx| Buffer::local("", cx));
1677 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1678 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1679 }
1680
1681 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1682 let buffer = cx.new(|cx| Buffer::local("", cx));
1683 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1684 Self::new(EditorMode::full(), buffer, None, window, cx)
1685 }
1686
1687 pub fn auto_height(
1688 min_lines: usize,
1689 max_lines: usize,
1690 window: &mut Window,
1691 cx: &mut Context<Self>,
1692 ) -> Self {
1693 let buffer = cx.new(|cx| Buffer::local("", cx));
1694 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1695 Self::new(
1696 EditorMode::AutoHeight {
1697 min_lines,
1698 max_lines: Some(max_lines),
1699 },
1700 buffer,
1701 None,
1702 window,
1703 cx,
1704 )
1705 }
1706
1707 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1708 /// The editor grows as tall as needed to fit its content.
1709 pub fn auto_height_unbounded(
1710 min_lines: usize,
1711 window: &mut Window,
1712 cx: &mut Context<Self>,
1713 ) -> Self {
1714 let buffer = cx.new(|cx| Buffer::local("", cx));
1715 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1716 Self::new(
1717 EditorMode::AutoHeight {
1718 min_lines,
1719 max_lines: None,
1720 },
1721 buffer,
1722 None,
1723 window,
1724 cx,
1725 )
1726 }
1727
1728 pub fn for_buffer(
1729 buffer: Entity<Buffer>,
1730 project: Option<Entity<Project>>,
1731 window: &mut Window,
1732 cx: &mut Context<Self>,
1733 ) -> Self {
1734 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1735 Self::new(EditorMode::full(), buffer, project, window, cx)
1736 }
1737
1738 pub fn for_multibuffer(
1739 buffer: Entity<MultiBuffer>,
1740 project: Option<Entity<Project>>,
1741 window: &mut Window,
1742 cx: &mut Context<Self>,
1743 ) -> Self {
1744 Self::new(EditorMode::full(), buffer, project, window, cx)
1745 }
1746
1747 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1748 let mut clone = Self::new(
1749 self.mode.clone(),
1750 self.buffer.clone(),
1751 self.project.clone(),
1752 window,
1753 cx,
1754 );
1755 self.display_map.update(cx, |display_map, cx| {
1756 let snapshot = display_map.snapshot(cx);
1757 clone.display_map.update(cx, |display_map, cx| {
1758 display_map.set_state(&snapshot, cx);
1759 });
1760 });
1761 clone.folds_did_change(cx);
1762 clone.selections.clone_state(&self.selections);
1763 clone.scroll_manager.clone_state(&self.scroll_manager);
1764 clone.searchable = self.searchable;
1765 clone.read_only = self.read_only;
1766 clone
1767 }
1768
1769 pub fn new(
1770 mode: EditorMode,
1771 buffer: Entity<MultiBuffer>,
1772 project: Option<Entity<Project>>,
1773 window: &mut Window,
1774 cx: &mut Context<Self>,
1775 ) -> Self {
1776 Editor::new_internal(mode, buffer, project, None, window, cx)
1777 }
1778
1779 fn new_internal(
1780 mode: EditorMode,
1781 buffer: Entity<MultiBuffer>,
1782 project: Option<Entity<Project>>,
1783 display_map: Option<Entity<DisplayMap>>,
1784 window: &mut Window,
1785 cx: &mut Context<Self>,
1786 ) -> Self {
1787 debug_assert!(
1788 display_map.is_none() || mode.is_minimap(),
1789 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1790 );
1791
1792 let full_mode = mode.is_full();
1793 let is_minimap = mode.is_minimap();
1794 let diagnostics_max_severity = if full_mode {
1795 EditorSettings::get_global(cx)
1796 .diagnostics_max_severity
1797 .unwrap_or(DiagnosticSeverity::Hint)
1798 } else {
1799 DiagnosticSeverity::Off
1800 };
1801 let style = window.text_style();
1802 let font_size = style.font_size.to_pixels(window.rem_size());
1803 let editor = cx.entity().downgrade();
1804 let fold_placeholder = FoldPlaceholder {
1805 constrain_width: true,
1806 render: Arc::new(move |fold_id, fold_range, cx| {
1807 let editor = editor.clone();
1808 div()
1809 .id(fold_id)
1810 .bg(cx.theme().colors().ghost_element_background)
1811 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1812 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1813 .rounded_xs()
1814 .size_full()
1815 .cursor_pointer()
1816 .child("⋯")
1817 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1818 .on_click(move |_, _window, cx| {
1819 editor
1820 .update(cx, |editor, cx| {
1821 editor.unfold_ranges(
1822 &[fold_range.start..fold_range.end],
1823 true,
1824 false,
1825 cx,
1826 );
1827 cx.stop_propagation();
1828 })
1829 .ok();
1830 })
1831 .into_any()
1832 }),
1833 merge_adjacent: true,
1834 ..FoldPlaceholder::default()
1835 };
1836 let display_map = display_map.unwrap_or_else(|| {
1837 cx.new(|cx| {
1838 DisplayMap::new(
1839 buffer.clone(),
1840 style.font(),
1841 font_size,
1842 None,
1843 FILE_HEADER_HEIGHT,
1844 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1845 fold_placeholder,
1846 diagnostics_max_severity,
1847 cx,
1848 )
1849 })
1850 });
1851
1852 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1853
1854 let blink_manager = cx.new(|cx| {
1855 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1856 if is_minimap {
1857 blink_manager.disable(cx);
1858 }
1859 blink_manager
1860 });
1861
1862 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1863 .then(|| language_settings::SoftWrap::None);
1864
1865 let mut project_subscriptions = Vec::new();
1866 if full_mode {
1867 if let Some(project) = project.as_ref() {
1868 project_subscriptions.push(cx.subscribe_in(
1869 project,
1870 window,
1871 |editor, _, event, window, cx| match event {
1872 project::Event::RefreshCodeLens => {
1873 // we always query lens with actions, without storing them, always refreshing them
1874 }
1875 project::Event::RefreshInlayHints => {
1876 editor
1877 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1878 }
1879 project::Event::LanguageServerAdded(..)
1880 | project::Event::LanguageServerRemoved(..) => {
1881 if editor.tasks_update_task.is_none() {
1882 editor.tasks_update_task =
1883 Some(editor.refresh_runnables(window, cx));
1884 }
1885 }
1886 project::Event::SnippetEdit(id, snippet_edits) => {
1887 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1888 let focus_handle = editor.focus_handle(cx);
1889 if focus_handle.is_focused(window) {
1890 let snapshot = buffer.read(cx).snapshot();
1891 for (range, snippet) in snippet_edits {
1892 let editor_range =
1893 language::range_from_lsp(*range).to_offset(&snapshot);
1894 editor
1895 .insert_snippet(
1896 &[editor_range],
1897 snippet.clone(),
1898 window,
1899 cx,
1900 )
1901 .ok();
1902 }
1903 }
1904 }
1905 }
1906 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1907 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1908 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1909 }
1910 }
1911 _ => {}
1912 },
1913 ));
1914 if let Some(task_inventory) = project
1915 .read(cx)
1916 .task_store()
1917 .read(cx)
1918 .task_inventory()
1919 .cloned()
1920 {
1921 project_subscriptions.push(cx.observe_in(
1922 &task_inventory,
1923 window,
1924 |editor, _, window, cx| {
1925 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1926 },
1927 ));
1928 };
1929
1930 project_subscriptions.push(cx.subscribe_in(
1931 &project.read(cx).breakpoint_store(),
1932 window,
1933 |editor, _, event, window, cx| match event {
1934 BreakpointStoreEvent::ClearDebugLines => {
1935 editor.clear_row_highlights::<ActiveDebugLine>();
1936 editor.refresh_inline_values(cx);
1937 }
1938 BreakpointStoreEvent::SetDebugLine => {
1939 if editor.go_to_active_debug_line(window, cx) {
1940 cx.stop_propagation();
1941 }
1942
1943 editor.refresh_inline_values(cx);
1944 }
1945 _ => {}
1946 },
1947 ));
1948 let git_store = project.read(cx).git_store().clone();
1949 let project = project.clone();
1950 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1951 match event {
1952 GitStoreEvent::RepositoryUpdated(
1953 _,
1954 RepositoryEvent::Updated {
1955 new_instance: true, ..
1956 },
1957 _,
1958 ) => {
1959 this.load_diff_task = Some(
1960 update_uncommitted_diff_for_buffer(
1961 cx.entity(),
1962 &project,
1963 this.buffer.read(cx).all_buffers(),
1964 this.buffer.clone(),
1965 cx,
1966 )
1967 .shared(),
1968 );
1969 }
1970 _ => {}
1971 }
1972 }));
1973 }
1974 }
1975
1976 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1977
1978 let inlay_hint_settings =
1979 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1980 let focus_handle = cx.focus_handle();
1981 if !is_minimap {
1982 cx.on_focus(&focus_handle, window, Self::handle_focus)
1983 .detach();
1984 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1985 .detach();
1986 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1987 .detach();
1988 cx.on_blur(&focus_handle, window, Self::handle_blur)
1989 .detach();
1990 cx.observe_pending_input(window, Self::observe_pending_input)
1991 .detach();
1992 }
1993
1994 let show_indent_guides = if matches!(
1995 mode,
1996 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1997 ) {
1998 Some(false)
1999 } else {
2000 None
2001 };
2002
2003 let breakpoint_store = match (&mode, project.as_ref()) {
2004 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2005 _ => None,
2006 };
2007
2008 let mut code_action_providers = Vec::new();
2009 let mut load_uncommitted_diff = None;
2010 if let Some(project) = project.clone() {
2011 load_uncommitted_diff = Some(
2012 update_uncommitted_diff_for_buffer(
2013 cx.entity(),
2014 &project,
2015 buffer.read(cx).all_buffers(),
2016 buffer.clone(),
2017 cx,
2018 )
2019 .shared(),
2020 );
2021 code_action_providers.push(Rc::new(project) as Rc<_>);
2022 }
2023
2024 let mut editor = Self {
2025 focus_handle,
2026 show_cursor_when_unfocused: false,
2027 last_focused_descendant: None,
2028 buffer: buffer.clone(),
2029 display_map: display_map.clone(),
2030 selections,
2031 scroll_manager: ScrollManager::new(cx),
2032 columnar_selection_state: None,
2033 add_selections_state: None,
2034 select_next_state: None,
2035 select_prev_state: None,
2036 selection_history: SelectionHistory::default(),
2037 defer_selection_effects: false,
2038 deferred_selection_effects_state: None,
2039 autoclose_regions: Vec::new(),
2040 snippet_stack: InvalidationStack::default(),
2041 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2042 ime_transaction: None,
2043 active_diagnostics: ActiveDiagnostic::None,
2044 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2045 inline_diagnostics_update: Task::ready(()),
2046 inline_diagnostics: Vec::new(),
2047 soft_wrap_mode_override,
2048 diagnostics_max_severity,
2049 hard_wrap: None,
2050 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2051 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2052 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2053 project,
2054 blink_manager: blink_manager.clone(),
2055 show_local_selections: true,
2056 show_scrollbars: ScrollbarAxes {
2057 horizontal: full_mode,
2058 vertical: full_mode,
2059 },
2060 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2061 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2062 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2063 show_gutter: full_mode,
2064 show_line_numbers: (!full_mode).then_some(false),
2065 use_relative_line_numbers: None,
2066 disable_expand_excerpt_buttons: !full_mode,
2067 show_git_diff_gutter: None,
2068 show_code_actions: None,
2069 show_runnables: None,
2070 show_breakpoints: None,
2071 show_wrap_guides: None,
2072 show_indent_guides,
2073 placeholder_text: None,
2074 highlight_order: 0,
2075 highlighted_rows: HashMap::default(),
2076 background_highlights: TreeMap::default(),
2077 gutter_highlights: TreeMap::default(),
2078 scrollbar_marker_state: ScrollbarMarkerState::default(),
2079 active_indent_guides_state: ActiveIndentGuidesState::default(),
2080 nav_history: None,
2081 context_menu: RefCell::new(None),
2082 context_menu_options: None,
2083 mouse_context_menu: None,
2084 completion_tasks: Vec::new(),
2085 inline_blame_popover: None,
2086 inline_blame_popover_show_task: None,
2087 signature_help_state: SignatureHelpState::default(),
2088 auto_signature_help: None,
2089 find_all_references_task_sources: Vec::new(),
2090 next_completion_id: 0,
2091 next_inlay_id: 0,
2092 code_action_providers,
2093 available_code_actions: None,
2094 code_actions_task: None,
2095 quick_selection_highlight_task: None,
2096 debounced_selection_highlight_task: None,
2097 document_highlights_task: None,
2098 linked_editing_range_task: None,
2099 pending_rename: None,
2100 searchable: !is_minimap,
2101 cursor_shape: EditorSettings::get_global(cx)
2102 .cursor_shape
2103 .unwrap_or_default(),
2104 current_line_highlight: None,
2105 autoindent_mode: Some(AutoindentMode::EachLine),
2106 collapse_matches: false,
2107 workspace: None,
2108 input_enabled: !is_minimap,
2109 use_modal_editing: full_mode,
2110 read_only: is_minimap,
2111 use_autoclose: true,
2112 use_auto_surround: true,
2113 auto_replace_emoji_shortcode: false,
2114 jsx_tag_auto_close_enabled_in_any_buffer: false,
2115 leader_id: None,
2116 remote_id: None,
2117 hover_state: HoverState::default(),
2118 pending_mouse_down: None,
2119 hovered_link_state: None,
2120 edit_prediction_provider: None,
2121 active_edit_prediction: None,
2122 stale_edit_prediction_in_menu: None,
2123 edit_prediction_preview: EditPredictionPreview::Inactive {
2124 released_too_fast: false,
2125 },
2126 inline_diagnostics_enabled: full_mode,
2127 diagnostics_enabled: full_mode,
2128 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2129 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2130 gutter_hovered: false,
2131 pixel_position_of_newest_cursor: None,
2132 last_bounds: None,
2133 last_position_map: None,
2134 expect_bounds_change: None,
2135 gutter_dimensions: GutterDimensions::default(),
2136 style: None,
2137 show_cursor_names: false,
2138 hovered_cursors: HashMap::default(),
2139 next_editor_action_id: EditorActionId::default(),
2140 editor_actions: Rc::default(),
2141 edit_predictions_hidden_for_vim_mode: false,
2142 show_edit_predictions_override: None,
2143 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2144 edit_prediction_settings: EditPredictionSettings::Disabled,
2145 edit_prediction_indent_conflict: false,
2146 edit_prediction_requires_modifier_in_indent_conflict: true,
2147 custom_context_menu: None,
2148 show_git_blame_gutter: false,
2149 show_git_blame_inline: false,
2150 show_selection_menu: None,
2151 show_git_blame_inline_delay_task: None,
2152 git_blame_inline_enabled: full_mode
2153 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2154 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2155 serialize_dirty_buffers: !is_minimap
2156 && ProjectSettings::get_global(cx)
2157 .session
2158 .restore_unsaved_buffers,
2159 blame: None,
2160 blame_subscription: None,
2161 tasks: BTreeMap::default(),
2162
2163 breakpoint_store,
2164 gutter_breakpoint_indicator: (None, None),
2165 hovered_diff_hunk_row: None,
2166 _subscriptions: (!is_minimap)
2167 .then(|| {
2168 vec![
2169 cx.observe(&buffer, Self::on_buffer_changed),
2170 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2171 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2172 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2173 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2174 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2175 cx.observe_window_activation(window, |editor, window, cx| {
2176 let active = window.is_window_active();
2177 editor.blink_manager.update(cx, |blink_manager, cx| {
2178 if active {
2179 blink_manager.enable(cx);
2180 } else {
2181 blink_manager.disable(cx);
2182 }
2183 });
2184 if active {
2185 editor.show_mouse_cursor(cx);
2186 }
2187 }),
2188 ]
2189 })
2190 .unwrap_or_default(),
2191 tasks_update_task: None,
2192 pull_diagnostics_task: Task::ready(()),
2193 colors: None,
2194 next_color_inlay_id: 0,
2195 linked_edit_ranges: Default::default(),
2196 in_project_search: false,
2197 previous_search_ranges: None,
2198 breadcrumb_header: None,
2199 focused_block: None,
2200 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2201 addons: HashMap::default(),
2202 registered_buffers: HashMap::default(),
2203 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2204 selection_mark_mode: false,
2205 toggle_fold_multiple_buffers: Task::ready(()),
2206 serialize_selections: Task::ready(()),
2207 serialize_folds: Task::ready(()),
2208 text_style_refinement: None,
2209 load_diff_task: load_uncommitted_diff,
2210 temporary_diff_override: false,
2211 mouse_cursor_hidden: false,
2212 minimap: None,
2213 hide_mouse_mode: EditorSettings::get_global(cx)
2214 .hide_mouse
2215 .unwrap_or_default(),
2216 change_list: ChangeList::new(),
2217 mode,
2218 selection_drag_state: SelectionDragState::None,
2219 folding_newlines: Task::ready(()),
2220 };
2221
2222 if is_minimap {
2223 return editor;
2224 }
2225
2226 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2227 editor
2228 ._subscriptions
2229 .push(cx.observe(breakpoints, |_, _, cx| {
2230 cx.notify();
2231 }));
2232 }
2233 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2234 editor._subscriptions.extend(project_subscriptions);
2235
2236 editor._subscriptions.push(cx.subscribe_in(
2237 &cx.entity(),
2238 window,
2239 |editor, _, e: &EditorEvent, window, cx| match e {
2240 EditorEvent::ScrollPositionChanged { local, .. } => {
2241 if *local {
2242 let new_anchor = editor.scroll_manager.anchor();
2243 let snapshot = editor.snapshot(window, cx);
2244 editor.update_restoration_data(cx, move |data| {
2245 data.scroll_position = (
2246 new_anchor.top_row(&snapshot.buffer_snapshot),
2247 new_anchor.offset,
2248 );
2249 });
2250 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2251 editor.inline_blame_popover.take();
2252 }
2253 }
2254 EditorEvent::Edited { .. } => {
2255 if !vim_enabled(cx) {
2256 let (map, selections) = editor.selections.all_adjusted_display(cx);
2257 let pop_state = editor
2258 .change_list
2259 .last()
2260 .map(|previous| {
2261 previous.len() == selections.len()
2262 && previous.iter().enumerate().all(|(ix, p)| {
2263 p.to_display_point(&map).row()
2264 == selections[ix].head().row()
2265 })
2266 })
2267 .unwrap_or(false);
2268 let new_positions = selections
2269 .into_iter()
2270 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2271 .collect();
2272 editor
2273 .change_list
2274 .push_to_change_list(pop_state, new_positions);
2275 }
2276 }
2277 _ => (),
2278 },
2279 ));
2280
2281 if let Some(dap_store) = editor
2282 .project
2283 .as_ref()
2284 .map(|project| project.read(cx).dap_store())
2285 {
2286 let weak_editor = cx.weak_entity();
2287
2288 editor
2289 ._subscriptions
2290 .push(
2291 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2292 let session_entity = cx.entity();
2293 weak_editor
2294 .update(cx, |editor, cx| {
2295 editor._subscriptions.push(
2296 cx.subscribe(&session_entity, Self::on_debug_session_event),
2297 );
2298 })
2299 .ok();
2300 }),
2301 );
2302
2303 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2304 editor
2305 ._subscriptions
2306 .push(cx.subscribe(&session, Self::on_debug_session_event));
2307 }
2308 }
2309
2310 // skip adding the initial selection to selection history
2311 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2312 editor.end_selection(window, cx);
2313 editor.selection_history.mode = SelectionHistoryMode::Normal;
2314
2315 editor.scroll_manager.show_scrollbars(window, cx);
2316 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2317
2318 if full_mode {
2319 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2320 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2321
2322 if editor.git_blame_inline_enabled {
2323 editor.start_git_blame_inline(false, window, cx);
2324 }
2325
2326 editor.go_to_active_debug_line(window, cx);
2327
2328 if let Some(buffer) = buffer.read(cx).as_singleton() {
2329 if let Some(project) = editor.project.as_ref() {
2330 let handle = project.update(cx, |project, cx| {
2331 project.register_buffer_with_language_servers(&buffer, cx)
2332 });
2333 editor
2334 .registered_buffers
2335 .insert(buffer.read(cx).remote_id(), handle);
2336 }
2337 }
2338
2339 editor.minimap =
2340 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2341 editor.colors = Some(LspColorData::new(cx));
2342 editor.update_lsp_data(false, None, window, cx);
2343 }
2344
2345 if editor.mode.is_full() {
2346 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2347 }
2348
2349 editor
2350 }
2351
2352 pub fn deploy_mouse_context_menu(
2353 &mut self,
2354 position: gpui::Point<Pixels>,
2355 context_menu: Entity<ContextMenu>,
2356 window: &mut Window,
2357 cx: &mut Context<Self>,
2358 ) {
2359 self.mouse_context_menu = Some(MouseContextMenu::new(
2360 self,
2361 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2362 context_menu,
2363 window,
2364 cx,
2365 ));
2366 }
2367
2368 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2369 self.mouse_context_menu
2370 .as_ref()
2371 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2372 }
2373
2374 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2375 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2376 }
2377
2378 fn key_context_internal(
2379 &self,
2380 has_active_edit_prediction: bool,
2381 window: &Window,
2382 cx: &App,
2383 ) -> KeyContext {
2384 let mut key_context = KeyContext::new_with_defaults();
2385 key_context.add("Editor");
2386 let mode = match self.mode {
2387 EditorMode::SingleLine { .. } => "single_line",
2388 EditorMode::AutoHeight { .. } => "auto_height",
2389 EditorMode::Minimap { .. } => "minimap",
2390 EditorMode::Full { .. } => "full",
2391 };
2392
2393 if EditorSettings::jupyter_enabled(cx) {
2394 key_context.add("jupyter");
2395 }
2396
2397 key_context.set("mode", mode);
2398 if self.pending_rename.is_some() {
2399 key_context.add("renaming");
2400 }
2401
2402 match self.context_menu.borrow().as_ref() {
2403 Some(CodeContextMenu::Completions(menu)) => {
2404 if menu.visible() {
2405 key_context.add("menu");
2406 key_context.add("showing_completions");
2407 }
2408 }
2409 Some(CodeContextMenu::CodeActions(menu)) => {
2410 if menu.visible() {
2411 key_context.add("menu");
2412 key_context.add("showing_code_actions")
2413 }
2414 }
2415 None => {}
2416 }
2417
2418 if self.signature_help_state.has_multiple_signatures() {
2419 key_context.add("showing_signature_help");
2420 }
2421
2422 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2423 if !self.focus_handle(cx).contains_focused(window, cx)
2424 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2425 {
2426 for addon in self.addons.values() {
2427 addon.extend_key_context(&mut key_context, cx)
2428 }
2429 }
2430
2431 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2432 if let Some(extension) = singleton_buffer
2433 .read(cx)
2434 .file()
2435 .and_then(|file| file.path().extension()?.to_str())
2436 {
2437 key_context.set("extension", extension.to_string());
2438 }
2439 } else {
2440 key_context.add("multibuffer");
2441 }
2442
2443 if has_active_edit_prediction {
2444 if self.edit_prediction_in_conflict() {
2445 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2446 } else {
2447 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2448 key_context.add("copilot_suggestion");
2449 }
2450 }
2451
2452 if self.selection_mark_mode {
2453 key_context.add("selection_mode");
2454 }
2455
2456 key_context
2457 }
2458
2459 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2460 if self.mouse_cursor_hidden {
2461 self.mouse_cursor_hidden = false;
2462 cx.notify();
2463 }
2464 }
2465
2466 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2467 let hide_mouse_cursor = match origin {
2468 HideMouseCursorOrigin::TypingAction => {
2469 matches!(
2470 self.hide_mouse_mode,
2471 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2472 )
2473 }
2474 HideMouseCursorOrigin::MovementAction => {
2475 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2476 }
2477 };
2478 if self.mouse_cursor_hidden != hide_mouse_cursor {
2479 self.mouse_cursor_hidden = hide_mouse_cursor;
2480 cx.notify();
2481 }
2482 }
2483
2484 pub fn edit_prediction_in_conflict(&self) -> bool {
2485 if !self.show_edit_predictions_in_menu() {
2486 return false;
2487 }
2488
2489 let showing_completions = self
2490 .context_menu
2491 .borrow()
2492 .as_ref()
2493 .map_or(false, |context| {
2494 matches!(context, CodeContextMenu::Completions(_))
2495 });
2496
2497 showing_completions
2498 || self.edit_prediction_requires_modifier()
2499 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2500 // bindings to insert tab characters.
2501 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2502 }
2503
2504 pub fn accept_edit_prediction_keybind(
2505 &self,
2506 accept_partial: bool,
2507 window: &Window,
2508 cx: &App,
2509 ) -> AcceptEditPredictionBinding {
2510 let key_context = self.key_context_internal(true, window, cx);
2511 let in_conflict = self.edit_prediction_in_conflict();
2512
2513 let bindings = if accept_partial {
2514 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2515 } else {
2516 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2517 };
2518
2519 // TODO: if the binding contains multiple keystrokes, display all of them, not
2520 // just the first one.
2521 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2522 !in_conflict
2523 || binding
2524 .keystrokes()
2525 .first()
2526 .map_or(false, |keystroke| keystroke.modifiers.modified())
2527 }))
2528 }
2529
2530 pub fn new_file(
2531 workspace: &mut Workspace,
2532 _: &workspace::NewFile,
2533 window: &mut Window,
2534 cx: &mut Context<Workspace>,
2535 ) {
2536 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2537 "Failed to create buffer",
2538 window,
2539 cx,
2540 |e, _, _| match e.error_code() {
2541 ErrorCode::RemoteUpgradeRequired => Some(format!(
2542 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2543 e.error_tag("required").unwrap_or("the latest version")
2544 )),
2545 _ => None,
2546 },
2547 );
2548 }
2549
2550 pub fn new_in_workspace(
2551 workspace: &mut Workspace,
2552 window: &mut Window,
2553 cx: &mut Context<Workspace>,
2554 ) -> Task<Result<Entity<Editor>>> {
2555 let project = workspace.project().clone();
2556 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2557
2558 cx.spawn_in(window, async move |workspace, cx| {
2559 let buffer = create.await?;
2560 workspace.update_in(cx, |workspace, window, cx| {
2561 let editor =
2562 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2563 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2564 editor
2565 })
2566 })
2567 }
2568
2569 fn new_file_vertical(
2570 workspace: &mut Workspace,
2571 _: &workspace::NewFileSplitVertical,
2572 window: &mut Window,
2573 cx: &mut Context<Workspace>,
2574 ) {
2575 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2576 }
2577
2578 fn new_file_horizontal(
2579 workspace: &mut Workspace,
2580 _: &workspace::NewFileSplitHorizontal,
2581 window: &mut Window,
2582 cx: &mut Context<Workspace>,
2583 ) {
2584 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2585 }
2586
2587 fn new_file_in_direction(
2588 workspace: &mut Workspace,
2589 direction: SplitDirection,
2590 window: &mut Window,
2591 cx: &mut Context<Workspace>,
2592 ) {
2593 let project = workspace.project().clone();
2594 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2595
2596 cx.spawn_in(window, async move |workspace, cx| {
2597 let buffer = create.await?;
2598 workspace.update_in(cx, move |workspace, window, cx| {
2599 workspace.split_item(
2600 direction,
2601 Box::new(
2602 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2603 ),
2604 window,
2605 cx,
2606 )
2607 })?;
2608 anyhow::Ok(())
2609 })
2610 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2611 match e.error_code() {
2612 ErrorCode::RemoteUpgradeRequired => Some(format!(
2613 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2614 e.error_tag("required").unwrap_or("the latest version")
2615 )),
2616 _ => None,
2617 }
2618 });
2619 }
2620
2621 pub fn leader_id(&self) -> Option<CollaboratorId> {
2622 self.leader_id
2623 }
2624
2625 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2626 &self.buffer
2627 }
2628
2629 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2630 self.workspace.as_ref()?.0.upgrade()
2631 }
2632
2633 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2634 self.buffer().read(cx).title(cx)
2635 }
2636
2637 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2638 let git_blame_gutter_max_author_length = self
2639 .render_git_blame_gutter(cx)
2640 .then(|| {
2641 if let Some(blame) = self.blame.as_ref() {
2642 let max_author_length =
2643 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2644 Some(max_author_length)
2645 } else {
2646 None
2647 }
2648 })
2649 .flatten();
2650
2651 EditorSnapshot {
2652 mode: self.mode.clone(),
2653 show_gutter: self.show_gutter,
2654 show_line_numbers: self.show_line_numbers,
2655 show_git_diff_gutter: self.show_git_diff_gutter,
2656 show_code_actions: self.show_code_actions,
2657 show_runnables: self.show_runnables,
2658 show_breakpoints: self.show_breakpoints,
2659 git_blame_gutter_max_author_length,
2660 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2661 scroll_anchor: self.scroll_manager.anchor(),
2662 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2663 placeholder_text: self.placeholder_text.clone(),
2664 is_focused: self.focus_handle.is_focused(window),
2665 current_line_highlight: self
2666 .current_line_highlight
2667 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2668 gutter_hovered: self.gutter_hovered,
2669 }
2670 }
2671
2672 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2673 self.buffer.read(cx).language_at(point, cx)
2674 }
2675
2676 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2677 self.buffer.read(cx).read(cx).file_at(point).cloned()
2678 }
2679
2680 pub fn active_excerpt(
2681 &self,
2682 cx: &App,
2683 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2684 self.buffer
2685 .read(cx)
2686 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2687 }
2688
2689 pub fn mode(&self) -> &EditorMode {
2690 &self.mode
2691 }
2692
2693 pub fn set_mode(&mut self, mode: EditorMode) {
2694 self.mode = mode;
2695 }
2696
2697 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2698 self.collaboration_hub.as_deref()
2699 }
2700
2701 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2702 self.collaboration_hub = Some(hub);
2703 }
2704
2705 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2706 self.in_project_search = in_project_search;
2707 }
2708
2709 pub fn set_custom_context_menu(
2710 &mut self,
2711 f: impl 'static
2712 + Fn(
2713 &mut Self,
2714 DisplayPoint,
2715 &mut Window,
2716 &mut Context<Self>,
2717 ) -> Option<Entity<ui::ContextMenu>>,
2718 ) {
2719 self.custom_context_menu = Some(Box::new(f))
2720 }
2721
2722 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2723 self.completion_provider = provider;
2724 }
2725
2726 #[cfg(any(test, feature = "test-support"))]
2727 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2728 self.completion_provider.clone()
2729 }
2730
2731 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2732 self.semantics_provider.clone()
2733 }
2734
2735 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2736 self.semantics_provider = provider;
2737 }
2738
2739 pub fn set_edit_prediction_provider<T>(
2740 &mut self,
2741 provider: Option<Entity<T>>,
2742 window: &mut Window,
2743 cx: &mut Context<Self>,
2744 ) where
2745 T: EditPredictionProvider,
2746 {
2747 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2748 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2749 if this.focus_handle.is_focused(window) {
2750 this.update_visible_edit_prediction(window, cx);
2751 }
2752 }),
2753 provider: Arc::new(provider),
2754 });
2755 self.update_edit_prediction_settings(cx);
2756 self.refresh_edit_prediction(false, false, window, cx);
2757 }
2758
2759 pub fn placeholder_text(&self) -> Option<&str> {
2760 self.placeholder_text.as_deref()
2761 }
2762
2763 pub fn set_placeholder_text(
2764 &mut self,
2765 placeholder_text: impl Into<Arc<str>>,
2766 cx: &mut Context<Self>,
2767 ) {
2768 let placeholder_text = Some(placeholder_text.into());
2769 if self.placeholder_text != placeholder_text {
2770 self.placeholder_text = placeholder_text;
2771 cx.notify();
2772 }
2773 }
2774
2775 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2776 self.cursor_shape = cursor_shape;
2777
2778 // Disrupt blink for immediate user feedback that the cursor shape has changed
2779 self.blink_manager.update(cx, BlinkManager::show_cursor);
2780
2781 cx.notify();
2782 }
2783
2784 pub fn set_current_line_highlight(
2785 &mut self,
2786 current_line_highlight: Option<CurrentLineHighlight>,
2787 ) {
2788 self.current_line_highlight = current_line_highlight;
2789 }
2790
2791 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2792 self.collapse_matches = collapse_matches;
2793 }
2794
2795 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2796 let buffers = self.buffer.read(cx).all_buffers();
2797 let Some(project) = self.project.as_ref() else {
2798 return;
2799 };
2800 project.update(cx, |project, cx| {
2801 for buffer in buffers {
2802 self.registered_buffers
2803 .entry(buffer.read(cx).remote_id())
2804 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2805 }
2806 })
2807 }
2808
2809 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2810 if self.collapse_matches {
2811 return range.start..range.start;
2812 }
2813 range.clone()
2814 }
2815
2816 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2817 if self.display_map.read(cx).clip_at_line_ends != clip {
2818 self.display_map
2819 .update(cx, |map, _| map.clip_at_line_ends = clip);
2820 }
2821 }
2822
2823 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2824 self.input_enabled = input_enabled;
2825 }
2826
2827 pub fn set_edit_predictions_hidden_for_vim_mode(
2828 &mut self,
2829 hidden: bool,
2830 window: &mut Window,
2831 cx: &mut Context<Self>,
2832 ) {
2833 if hidden != self.edit_predictions_hidden_for_vim_mode {
2834 self.edit_predictions_hidden_for_vim_mode = hidden;
2835 if hidden {
2836 self.update_visible_edit_prediction(window, cx);
2837 } else {
2838 self.refresh_edit_prediction(true, false, window, cx);
2839 }
2840 }
2841 }
2842
2843 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2844 self.menu_edit_predictions_policy = value;
2845 }
2846
2847 pub fn set_autoindent(&mut self, autoindent: bool) {
2848 if autoindent {
2849 self.autoindent_mode = Some(AutoindentMode::EachLine);
2850 } else {
2851 self.autoindent_mode = None;
2852 }
2853 }
2854
2855 pub fn read_only(&self, cx: &App) -> bool {
2856 self.read_only || self.buffer.read(cx).read_only()
2857 }
2858
2859 pub fn set_read_only(&mut self, read_only: bool) {
2860 self.read_only = read_only;
2861 }
2862
2863 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2864 self.use_autoclose = autoclose;
2865 }
2866
2867 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2868 self.use_auto_surround = auto_surround;
2869 }
2870
2871 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2872 self.auto_replace_emoji_shortcode = auto_replace;
2873 }
2874
2875 pub fn toggle_edit_predictions(
2876 &mut self,
2877 _: &ToggleEditPrediction,
2878 window: &mut Window,
2879 cx: &mut Context<Self>,
2880 ) {
2881 if self.show_edit_predictions_override.is_some() {
2882 self.set_show_edit_predictions(None, window, cx);
2883 } else {
2884 let show_edit_predictions = !self.edit_predictions_enabled();
2885 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2886 }
2887 }
2888
2889 pub fn set_show_edit_predictions(
2890 &mut self,
2891 show_edit_predictions: Option<bool>,
2892 window: &mut Window,
2893 cx: &mut Context<Self>,
2894 ) {
2895 self.show_edit_predictions_override = show_edit_predictions;
2896 self.update_edit_prediction_settings(cx);
2897
2898 if let Some(false) = show_edit_predictions {
2899 self.discard_edit_prediction(false, cx);
2900 } else {
2901 self.refresh_edit_prediction(false, true, window, cx);
2902 }
2903 }
2904
2905 fn edit_predictions_disabled_in_scope(
2906 &self,
2907 buffer: &Entity<Buffer>,
2908 buffer_position: language::Anchor,
2909 cx: &App,
2910 ) -> bool {
2911 let snapshot = buffer.read(cx).snapshot();
2912 let settings = snapshot.settings_at(buffer_position, cx);
2913
2914 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2915 return false;
2916 };
2917
2918 scope.override_name().map_or(false, |scope_name| {
2919 settings
2920 .edit_predictions_disabled_in
2921 .iter()
2922 .any(|s| s == scope_name)
2923 })
2924 }
2925
2926 pub fn set_use_modal_editing(&mut self, to: bool) {
2927 self.use_modal_editing = to;
2928 }
2929
2930 pub fn use_modal_editing(&self) -> bool {
2931 self.use_modal_editing
2932 }
2933
2934 fn selections_did_change(
2935 &mut self,
2936 local: bool,
2937 old_cursor_position: &Anchor,
2938 effects: SelectionEffects,
2939 window: &mut Window,
2940 cx: &mut Context<Self>,
2941 ) {
2942 window.invalidate_character_coordinates();
2943
2944 // Copy selections to primary selection buffer
2945 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2946 if local {
2947 let selections = self.selections.all::<usize>(cx);
2948 let buffer_handle = self.buffer.read(cx).read(cx);
2949
2950 let mut text = String::new();
2951 for (index, selection) in selections.iter().enumerate() {
2952 let text_for_selection = buffer_handle
2953 .text_for_range(selection.start..selection.end)
2954 .collect::<String>();
2955
2956 text.push_str(&text_for_selection);
2957 if index != selections.len() - 1 {
2958 text.push('\n');
2959 }
2960 }
2961
2962 if !text.is_empty() {
2963 cx.write_to_primary(ClipboardItem::new_string(text));
2964 }
2965 }
2966
2967 let selection_anchors = self.selections.disjoint_anchors();
2968
2969 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2970 self.buffer.update(cx, |buffer, cx| {
2971 buffer.set_active_selections(
2972 &selection_anchors,
2973 self.selections.line_mode,
2974 self.cursor_shape,
2975 cx,
2976 )
2977 });
2978 }
2979 let display_map = self
2980 .display_map
2981 .update(cx, |display_map, cx| display_map.snapshot(cx));
2982 let buffer = &display_map.buffer_snapshot;
2983 if self.selections.count() == 1 {
2984 self.add_selections_state = None;
2985 }
2986 self.select_next_state = None;
2987 self.select_prev_state = None;
2988 self.select_syntax_node_history.try_clear();
2989 self.invalidate_autoclose_regions(&selection_anchors, buffer);
2990 self.snippet_stack.invalidate(&selection_anchors, buffer);
2991 self.take_rename(false, window, cx);
2992
2993 let newest_selection = self.selections.newest_anchor();
2994 let new_cursor_position = newest_selection.head();
2995 let selection_start = newest_selection.start;
2996
2997 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2998 self.push_to_nav_history(
2999 *old_cursor_position,
3000 Some(new_cursor_position.to_point(buffer)),
3001 false,
3002 effects.nav_history == Some(true),
3003 cx,
3004 );
3005 }
3006
3007 if local {
3008 if let Some(buffer_id) = new_cursor_position.buffer_id {
3009 if !self.registered_buffers.contains_key(&buffer_id) {
3010 if let Some(project) = self.project.as_ref() {
3011 project.update(cx, |project, cx| {
3012 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3013 return;
3014 };
3015 self.registered_buffers.insert(
3016 buffer_id,
3017 project.register_buffer_with_language_servers(&buffer, cx),
3018 );
3019 })
3020 }
3021 }
3022 }
3023
3024 let mut context_menu = self.context_menu.borrow_mut();
3025 let completion_menu = match context_menu.as_ref() {
3026 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3027 Some(CodeContextMenu::CodeActions(_)) => {
3028 *context_menu = None;
3029 None
3030 }
3031 None => None,
3032 };
3033 let completion_position = completion_menu.map(|menu| menu.initial_position);
3034 drop(context_menu);
3035
3036 if effects.completions {
3037 if let Some(completion_position) = completion_position {
3038 let start_offset = selection_start.to_offset(buffer);
3039 let position_matches = start_offset == completion_position.to_offset(buffer);
3040 let continue_showing = if position_matches {
3041 if self.snippet_stack.is_empty() {
3042 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3043 } else {
3044 // Snippet choices can be shown even when the cursor is in whitespace.
3045 // Dismissing the menu with actions like backspace is handled by
3046 // invalidation regions.
3047 true
3048 }
3049 } else {
3050 false
3051 };
3052
3053 if continue_showing {
3054 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3055 } else {
3056 self.hide_context_menu(window, cx);
3057 }
3058 }
3059 }
3060
3061 hide_hover(self, cx);
3062
3063 if old_cursor_position.to_display_point(&display_map).row()
3064 != new_cursor_position.to_display_point(&display_map).row()
3065 {
3066 self.available_code_actions.take();
3067 }
3068 self.refresh_code_actions(window, cx);
3069 self.refresh_document_highlights(cx);
3070 self.refresh_selected_text_highlights(false, window, cx);
3071 refresh_matching_bracket_highlights(self, window, cx);
3072 self.update_visible_edit_prediction(window, cx);
3073 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3074 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3075 self.inline_blame_popover.take();
3076 if self.git_blame_inline_enabled {
3077 self.start_inline_blame_timer(window, cx);
3078 }
3079 }
3080
3081 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3082 cx.emit(EditorEvent::SelectionsChanged { local });
3083
3084 let selections = &self.selections.disjoint;
3085 if selections.len() == 1 {
3086 cx.emit(SearchEvent::ActiveMatchChanged)
3087 }
3088 if local {
3089 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3090 let inmemory_selections = selections
3091 .iter()
3092 .map(|s| {
3093 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3094 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3095 })
3096 .collect();
3097 self.update_restoration_data(cx, |data| {
3098 data.selections = inmemory_selections;
3099 });
3100
3101 if WorkspaceSettings::get(None, cx).restore_on_startup
3102 != RestoreOnStartupBehavior::None
3103 {
3104 if let Some(workspace_id) =
3105 self.workspace.as_ref().and_then(|workspace| workspace.1)
3106 {
3107 let snapshot = self.buffer().read(cx).snapshot(cx);
3108 let selections = selections.clone();
3109 let background_executor = cx.background_executor().clone();
3110 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3111 self.serialize_selections = cx.background_spawn(async move {
3112 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3113 let db_selections = selections
3114 .iter()
3115 .map(|selection| {
3116 (
3117 selection.start.to_offset(&snapshot),
3118 selection.end.to_offset(&snapshot),
3119 )
3120 })
3121 .collect();
3122
3123 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3124 .await
3125 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3126 .log_err();
3127 });
3128 }
3129 }
3130 }
3131 }
3132
3133 cx.notify();
3134 }
3135
3136 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3137 use text::ToOffset as _;
3138 use text::ToPoint as _;
3139
3140 if self.mode.is_minimap()
3141 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3142 {
3143 return;
3144 }
3145
3146 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3147 return;
3148 };
3149
3150 let snapshot = singleton.read(cx).snapshot();
3151 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3152 let display_snapshot = display_map.snapshot(cx);
3153
3154 display_snapshot
3155 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3156 .map(|fold| {
3157 fold.range.start.text_anchor.to_point(&snapshot)
3158 ..fold.range.end.text_anchor.to_point(&snapshot)
3159 })
3160 .collect()
3161 });
3162 self.update_restoration_data(cx, |data| {
3163 data.folds = inmemory_folds;
3164 });
3165
3166 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3167 return;
3168 };
3169 let background_executor = cx.background_executor().clone();
3170 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3171 let db_folds = self.display_map.update(cx, |display_map, cx| {
3172 display_map
3173 .snapshot(cx)
3174 .folds_in_range(0..snapshot.len())
3175 .map(|fold| {
3176 (
3177 fold.range.start.text_anchor.to_offset(&snapshot),
3178 fold.range.end.text_anchor.to_offset(&snapshot),
3179 )
3180 })
3181 .collect()
3182 });
3183 self.serialize_folds = cx.background_spawn(async move {
3184 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3185 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3186 .await
3187 .with_context(|| {
3188 format!(
3189 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3190 )
3191 })
3192 .log_err();
3193 });
3194 }
3195
3196 pub fn sync_selections(
3197 &mut self,
3198 other: Entity<Editor>,
3199 cx: &mut Context<Self>,
3200 ) -> gpui::Subscription {
3201 let other_selections = other.read(cx).selections.disjoint.to_vec();
3202 self.selections.change_with(cx, |selections| {
3203 selections.select_anchors(other_selections);
3204 });
3205
3206 let other_subscription =
3207 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3208 EditorEvent::SelectionsChanged { local: true } => {
3209 let other_selections = other.read(cx).selections.disjoint.to_vec();
3210 if other_selections.is_empty() {
3211 return;
3212 }
3213 this.selections.change_with(cx, |selections| {
3214 selections.select_anchors(other_selections);
3215 });
3216 }
3217 _ => {}
3218 });
3219
3220 let this_subscription =
3221 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3222 EditorEvent::SelectionsChanged { local: true } => {
3223 let these_selections = this.selections.disjoint.to_vec();
3224 if these_selections.is_empty() {
3225 return;
3226 }
3227 other.update(cx, |other_editor, cx| {
3228 other_editor.selections.change_with(cx, |selections| {
3229 selections.select_anchors(these_selections);
3230 })
3231 });
3232 }
3233 _ => {}
3234 });
3235
3236 Subscription::join(other_subscription, this_subscription)
3237 }
3238
3239 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3240 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3241 /// effects of selection change occur at the end of the transaction.
3242 pub fn change_selections<R>(
3243 &mut self,
3244 effects: SelectionEffects,
3245 window: &mut Window,
3246 cx: &mut Context<Self>,
3247 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3248 ) -> R {
3249 if let Some(state) = &mut self.deferred_selection_effects_state {
3250 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3251 state.effects.completions = effects.completions;
3252 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3253 let (changed, result) = self.selections.change_with(cx, change);
3254 state.changed |= changed;
3255 return result;
3256 }
3257 let mut state = DeferredSelectionEffectsState {
3258 changed: false,
3259 effects,
3260 old_cursor_position: self.selections.newest_anchor().head(),
3261 history_entry: SelectionHistoryEntry {
3262 selections: self.selections.disjoint_anchors(),
3263 select_next_state: self.select_next_state.clone(),
3264 select_prev_state: self.select_prev_state.clone(),
3265 add_selections_state: self.add_selections_state.clone(),
3266 },
3267 };
3268 let (changed, result) = self.selections.change_with(cx, change);
3269 state.changed = state.changed || changed;
3270 if self.defer_selection_effects {
3271 self.deferred_selection_effects_state = Some(state);
3272 } else {
3273 self.apply_selection_effects(state, window, cx);
3274 }
3275 result
3276 }
3277
3278 /// Defers the effects of selection change, so that the effects of multiple calls to
3279 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3280 /// to selection history and the state of popovers based on selection position aren't
3281 /// erroneously updated.
3282 pub fn with_selection_effects_deferred<R>(
3283 &mut self,
3284 window: &mut Window,
3285 cx: &mut Context<Self>,
3286 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3287 ) -> R {
3288 let already_deferred = self.defer_selection_effects;
3289 self.defer_selection_effects = true;
3290 let result = update(self, window, cx);
3291 if !already_deferred {
3292 self.defer_selection_effects = false;
3293 if let Some(state) = self.deferred_selection_effects_state.take() {
3294 self.apply_selection_effects(state, window, cx);
3295 }
3296 }
3297 result
3298 }
3299
3300 fn apply_selection_effects(
3301 &mut self,
3302 state: DeferredSelectionEffectsState,
3303 window: &mut Window,
3304 cx: &mut Context<Self>,
3305 ) {
3306 if state.changed {
3307 self.selection_history.push(state.history_entry);
3308
3309 if let Some(autoscroll) = state.effects.scroll {
3310 self.request_autoscroll(autoscroll, cx);
3311 }
3312
3313 let old_cursor_position = &state.old_cursor_position;
3314
3315 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3316
3317 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3318 self.show_signature_help(&ShowSignatureHelp, window, cx);
3319 }
3320 }
3321 }
3322
3323 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3324 where
3325 I: IntoIterator<Item = (Range<S>, T)>,
3326 S: ToOffset,
3327 T: Into<Arc<str>>,
3328 {
3329 if self.read_only(cx) {
3330 return;
3331 }
3332
3333 self.buffer
3334 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3335 }
3336
3337 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3338 where
3339 I: IntoIterator<Item = (Range<S>, T)>,
3340 S: ToOffset,
3341 T: Into<Arc<str>>,
3342 {
3343 if self.read_only(cx) {
3344 return;
3345 }
3346
3347 self.buffer.update(cx, |buffer, cx| {
3348 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3349 });
3350 }
3351
3352 pub fn edit_with_block_indent<I, S, T>(
3353 &mut self,
3354 edits: I,
3355 original_indent_columns: Vec<Option<u32>>,
3356 cx: &mut Context<Self>,
3357 ) where
3358 I: IntoIterator<Item = (Range<S>, T)>,
3359 S: ToOffset,
3360 T: Into<Arc<str>>,
3361 {
3362 if self.read_only(cx) {
3363 return;
3364 }
3365
3366 self.buffer.update(cx, |buffer, cx| {
3367 buffer.edit(
3368 edits,
3369 Some(AutoindentMode::Block {
3370 original_indent_columns,
3371 }),
3372 cx,
3373 )
3374 });
3375 }
3376
3377 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3378 self.hide_context_menu(window, cx);
3379
3380 match phase {
3381 SelectPhase::Begin {
3382 position,
3383 add,
3384 click_count,
3385 } => self.begin_selection(position, add, click_count, window, cx),
3386 SelectPhase::BeginColumnar {
3387 position,
3388 goal_column,
3389 reset,
3390 mode,
3391 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3392 SelectPhase::Extend {
3393 position,
3394 click_count,
3395 } => self.extend_selection(position, click_count, window, cx),
3396 SelectPhase::Update {
3397 position,
3398 goal_column,
3399 scroll_delta,
3400 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3401 SelectPhase::End => self.end_selection(window, cx),
3402 }
3403 }
3404
3405 fn extend_selection(
3406 &mut self,
3407 position: DisplayPoint,
3408 click_count: usize,
3409 window: &mut Window,
3410 cx: &mut Context<Self>,
3411 ) {
3412 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3413 let tail = self.selections.newest::<usize>(cx).tail();
3414 self.begin_selection(position, false, click_count, window, cx);
3415
3416 let position = position.to_offset(&display_map, Bias::Left);
3417 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3418
3419 let mut pending_selection = self
3420 .selections
3421 .pending_anchor()
3422 .expect("extend_selection not called with pending selection");
3423 if position >= tail {
3424 pending_selection.start = tail_anchor;
3425 } else {
3426 pending_selection.end = tail_anchor;
3427 pending_selection.reversed = true;
3428 }
3429
3430 let mut pending_mode = self.selections.pending_mode().unwrap();
3431 match &mut pending_mode {
3432 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3433 _ => {}
3434 }
3435
3436 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3437 SelectionEffects::scroll(Autoscroll::fit())
3438 } else {
3439 SelectionEffects::no_scroll()
3440 };
3441
3442 self.change_selections(effects, window, cx, |s| {
3443 s.set_pending(pending_selection, pending_mode)
3444 });
3445 }
3446
3447 fn begin_selection(
3448 &mut self,
3449 position: DisplayPoint,
3450 add: bool,
3451 click_count: usize,
3452 window: &mut Window,
3453 cx: &mut Context<Self>,
3454 ) {
3455 if !self.focus_handle.is_focused(window) {
3456 self.last_focused_descendant = None;
3457 window.focus(&self.focus_handle);
3458 }
3459
3460 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3461 let buffer = &display_map.buffer_snapshot;
3462 let position = display_map.clip_point(position, Bias::Left);
3463
3464 let start;
3465 let end;
3466 let mode;
3467 let mut auto_scroll;
3468 match click_count {
3469 1 => {
3470 start = buffer.anchor_before(position.to_point(&display_map));
3471 end = start;
3472 mode = SelectMode::Character;
3473 auto_scroll = true;
3474 }
3475 2 => {
3476 let position = display_map
3477 .clip_point(position, Bias::Left)
3478 .to_offset(&display_map, Bias::Left);
3479 let (range, _) = buffer.surrounding_word(position, false);
3480 start = buffer.anchor_before(range.start);
3481 end = buffer.anchor_before(range.end);
3482 mode = SelectMode::Word(start..end);
3483 auto_scroll = true;
3484 }
3485 3 => {
3486 let position = display_map
3487 .clip_point(position, Bias::Left)
3488 .to_point(&display_map);
3489 let line_start = display_map.prev_line_boundary(position).0;
3490 let next_line_start = buffer.clip_point(
3491 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3492 Bias::Left,
3493 );
3494 start = buffer.anchor_before(line_start);
3495 end = buffer.anchor_before(next_line_start);
3496 mode = SelectMode::Line(start..end);
3497 auto_scroll = true;
3498 }
3499 _ => {
3500 start = buffer.anchor_before(0);
3501 end = buffer.anchor_before(buffer.len());
3502 mode = SelectMode::All;
3503 auto_scroll = false;
3504 }
3505 }
3506 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3507
3508 let point_to_delete: Option<usize> = {
3509 let selected_points: Vec<Selection<Point>> =
3510 self.selections.disjoint_in_range(start..end, cx);
3511
3512 if !add || click_count > 1 {
3513 None
3514 } else if !selected_points.is_empty() {
3515 Some(selected_points[0].id)
3516 } else {
3517 let clicked_point_already_selected =
3518 self.selections.disjoint.iter().find(|selection| {
3519 selection.start.to_point(buffer) == start.to_point(buffer)
3520 || selection.end.to_point(buffer) == end.to_point(buffer)
3521 });
3522
3523 clicked_point_already_selected.map(|selection| selection.id)
3524 }
3525 };
3526
3527 let selections_count = self.selections.count();
3528 let effects = if auto_scroll {
3529 SelectionEffects::default()
3530 } else {
3531 SelectionEffects::no_scroll()
3532 };
3533
3534 self.change_selections(effects, window, cx, |s| {
3535 if let Some(point_to_delete) = point_to_delete {
3536 s.delete(point_to_delete);
3537
3538 if selections_count == 1 {
3539 s.set_pending_anchor_range(start..end, mode);
3540 }
3541 } else {
3542 if !add {
3543 s.clear_disjoint();
3544 }
3545
3546 s.set_pending_anchor_range(start..end, mode);
3547 }
3548 });
3549 }
3550
3551 fn begin_columnar_selection(
3552 &mut self,
3553 position: DisplayPoint,
3554 goal_column: u32,
3555 reset: bool,
3556 mode: ColumnarMode,
3557 window: &mut Window,
3558 cx: &mut Context<Self>,
3559 ) {
3560 if !self.focus_handle.is_focused(window) {
3561 self.last_focused_descendant = None;
3562 window.focus(&self.focus_handle);
3563 }
3564
3565 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3566
3567 if reset {
3568 let pointer_position = display_map
3569 .buffer_snapshot
3570 .anchor_before(position.to_point(&display_map));
3571
3572 self.change_selections(
3573 SelectionEffects::scroll(Autoscroll::newest()),
3574 window,
3575 cx,
3576 |s| {
3577 s.clear_disjoint();
3578 s.set_pending_anchor_range(
3579 pointer_position..pointer_position,
3580 SelectMode::Character,
3581 );
3582 },
3583 );
3584 };
3585
3586 let tail = self.selections.newest::<Point>(cx).tail();
3587 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3588 self.columnar_selection_state = match mode {
3589 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3590 selection_tail: selection_anchor,
3591 display_point: if reset {
3592 if position.column() != goal_column {
3593 Some(DisplayPoint::new(position.row(), goal_column))
3594 } else {
3595 None
3596 }
3597 } else {
3598 None
3599 },
3600 }),
3601 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3602 selection_tail: selection_anchor,
3603 }),
3604 };
3605
3606 if !reset {
3607 self.select_columns(position, goal_column, &display_map, window, cx);
3608 }
3609 }
3610
3611 fn update_selection(
3612 &mut self,
3613 position: DisplayPoint,
3614 goal_column: u32,
3615 scroll_delta: gpui::Point<f32>,
3616 window: &mut Window,
3617 cx: &mut Context<Self>,
3618 ) {
3619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3620
3621 if self.columnar_selection_state.is_some() {
3622 self.select_columns(position, goal_column, &display_map, window, cx);
3623 } else if let Some(mut pending) = self.selections.pending_anchor() {
3624 let buffer = &display_map.buffer_snapshot;
3625 let head;
3626 let tail;
3627 let mode = self.selections.pending_mode().unwrap();
3628 match &mode {
3629 SelectMode::Character => {
3630 head = position.to_point(&display_map);
3631 tail = pending.tail().to_point(buffer);
3632 }
3633 SelectMode::Word(original_range) => {
3634 let offset = display_map
3635 .clip_point(position, Bias::Left)
3636 .to_offset(&display_map, Bias::Left);
3637 let original_range = original_range.to_offset(buffer);
3638
3639 let head_offset = if buffer.is_inside_word(offset, false)
3640 || original_range.contains(&offset)
3641 {
3642 let (word_range, _) = buffer.surrounding_word(offset, false);
3643 if word_range.start < original_range.start {
3644 word_range.start
3645 } else {
3646 word_range.end
3647 }
3648 } else {
3649 offset
3650 };
3651
3652 head = head_offset.to_point(buffer);
3653 if head_offset <= original_range.start {
3654 tail = original_range.end.to_point(buffer);
3655 } else {
3656 tail = original_range.start.to_point(buffer);
3657 }
3658 }
3659 SelectMode::Line(original_range) => {
3660 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3661
3662 let position = display_map
3663 .clip_point(position, Bias::Left)
3664 .to_point(&display_map);
3665 let line_start = display_map.prev_line_boundary(position).0;
3666 let next_line_start = buffer.clip_point(
3667 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3668 Bias::Left,
3669 );
3670
3671 if line_start < original_range.start {
3672 head = line_start
3673 } else {
3674 head = next_line_start
3675 }
3676
3677 if head <= original_range.start {
3678 tail = original_range.end;
3679 } else {
3680 tail = original_range.start;
3681 }
3682 }
3683 SelectMode::All => {
3684 return;
3685 }
3686 };
3687
3688 if head < tail {
3689 pending.start = buffer.anchor_before(head);
3690 pending.end = buffer.anchor_before(tail);
3691 pending.reversed = true;
3692 } else {
3693 pending.start = buffer.anchor_before(tail);
3694 pending.end = buffer.anchor_before(head);
3695 pending.reversed = false;
3696 }
3697
3698 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3699 s.set_pending(pending, mode);
3700 });
3701 } else {
3702 log::error!("update_selection dispatched with no pending selection");
3703 return;
3704 }
3705
3706 self.apply_scroll_delta(scroll_delta, window, cx);
3707 cx.notify();
3708 }
3709
3710 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3711 self.columnar_selection_state.take();
3712 if self.selections.pending_anchor().is_some() {
3713 let selections = self.selections.all::<usize>(cx);
3714 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3715 s.select(selections);
3716 s.clear_pending();
3717 });
3718 }
3719 }
3720
3721 fn select_columns(
3722 &mut self,
3723 head: DisplayPoint,
3724 goal_column: u32,
3725 display_map: &DisplaySnapshot,
3726 window: &mut Window,
3727 cx: &mut Context<Self>,
3728 ) {
3729 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3730 return;
3731 };
3732
3733 let tail = match columnar_state {
3734 ColumnarSelectionState::FromMouse {
3735 selection_tail,
3736 display_point,
3737 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3738 ColumnarSelectionState::FromSelection { selection_tail } => {
3739 selection_tail.to_display_point(&display_map)
3740 }
3741 };
3742
3743 let start_row = cmp::min(tail.row(), head.row());
3744 let end_row = cmp::max(tail.row(), head.row());
3745 let start_column = cmp::min(tail.column(), goal_column);
3746 let end_column = cmp::max(tail.column(), goal_column);
3747 let reversed = start_column < tail.column();
3748
3749 let selection_ranges = (start_row.0..=end_row.0)
3750 .map(DisplayRow)
3751 .filter_map(|row| {
3752 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3753 || start_column <= display_map.line_len(row))
3754 && !display_map.is_block_line(row)
3755 {
3756 let start = display_map
3757 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3758 .to_point(display_map);
3759 let end = display_map
3760 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3761 .to_point(display_map);
3762 if reversed {
3763 Some(end..start)
3764 } else {
3765 Some(start..end)
3766 }
3767 } else {
3768 None
3769 }
3770 })
3771 .collect::<Vec<_>>();
3772
3773 let ranges = match columnar_state {
3774 ColumnarSelectionState::FromMouse { .. } => {
3775 let mut non_empty_ranges = selection_ranges
3776 .iter()
3777 .filter(|selection_range| selection_range.start != selection_range.end)
3778 .peekable();
3779 if non_empty_ranges.peek().is_some() {
3780 non_empty_ranges.cloned().collect()
3781 } else {
3782 selection_ranges
3783 }
3784 }
3785 _ => selection_ranges,
3786 };
3787
3788 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3789 s.select_ranges(ranges);
3790 });
3791 cx.notify();
3792 }
3793
3794 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3795 self.selections
3796 .all_adjusted(cx)
3797 .iter()
3798 .any(|selection| !selection.is_empty())
3799 }
3800
3801 pub fn has_pending_nonempty_selection(&self) -> bool {
3802 let pending_nonempty_selection = match self.selections.pending_anchor() {
3803 Some(Selection { start, end, .. }) => start != end,
3804 None => false,
3805 };
3806
3807 pending_nonempty_selection
3808 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3809 }
3810
3811 pub fn has_pending_selection(&self) -> bool {
3812 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3813 }
3814
3815 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3816 self.selection_mark_mode = false;
3817 self.selection_drag_state = SelectionDragState::None;
3818
3819 if self.clear_expanded_diff_hunks(cx) {
3820 cx.notify();
3821 return;
3822 }
3823 if self.dismiss_menus_and_popups(true, window, cx) {
3824 return;
3825 }
3826
3827 if self.mode.is_full()
3828 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3829 {
3830 return;
3831 }
3832
3833 cx.propagate();
3834 }
3835
3836 pub fn dismiss_menus_and_popups(
3837 &mut self,
3838 is_user_requested: bool,
3839 window: &mut Window,
3840 cx: &mut Context<Self>,
3841 ) -> bool {
3842 if self.take_rename(false, window, cx).is_some() {
3843 return true;
3844 }
3845
3846 if hide_hover(self, cx) {
3847 return true;
3848 }
3849
3850 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3851 return true;
3852 }
3853
3854 if self.hide_context_menu(window, cx).is_some() {
3855 return true;
3856 }
3857
3858 if self.mouse_context_menu.take().is_some() {
3859 return true;
3860 }
3861
3862 if is_user_requested && self.discard_edit_prediction(true, cx) {
3863 return true;
3864 }
3865
3866 if self.snippet_stack.pop().is_some() {
3867 return true;
3868 }
3869
3870 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3871 self.dismiss_diagnostics(cx);
3872 return true;
3873 }
3874
3875 false
3876 }
3877
3878 fn linked_editing_ranges_for(
3879 &self,
3880 selection: Range<text::Anchor>,
3881 cx: &App,
3882 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3883 if self.linked_edit_ranges.is_empty() {
3884 return None;
3885 }
3886 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3887 selection.end.buffer_id.and_then(|end_buffer_id| {
3888 if selection.start.buffer_id != Some(end_buffer_id) {
3889 return None;
3890 }
3891 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3892 let snapshot = buffer.read(cx).snapshot();
3893 self.linked_edit_ranges
3894 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3895 .map(|ranges| (ranges, snapshot, buffer))
3896 })?;
3897 use text::ToOffset as TO;
3898 // find offset from the start of current range to current cursor position
3899 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3900
3901 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3902 let start_difference = start_offset - start_byte_offset;
3903 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3904 let end_difference = end_offset - start_byte_offset;
3905 // Current range has associated linked ranges.
3906 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3907 for range in linked_ranges.iter() {
3908 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3909 let end_offset = start_offset + end_difference;
3910 let start_offset = start_offset + start_difference;
3911 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3912 continue;
3913 }
3914 if self.selections.disjoint_anchor_ranges().any(|s| {
3915 if s.start.buffer_id != selection.start.buffer_id
3916 || s.end.buffer_id != selection.end.buffer_id
3917 {
3918 return false;
3919 }
3920 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3921 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3922 }) {
3923 continue;
3924 }
3925 let start = buffer_snapshot.anchor_after(start_offset);
3926 let end = buffer_snapshot.anchor_after(end_offset);
3927 linked_edits
3928 .entry(buffer.clone())
3929 .or_default()
3930 .push(start..end);
3931 }
3932 Some(linked_edits)
3933 }
3934
3935 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3936 let text: Arc<str> = text.into();
3937
3938 if self.read_only(cx) {
3939 return;
3940 }
3941
3942 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3943
3944 let selections = self.selections.all_adjusted(cx);
3945 let mut bracket_inserted = false;
3946 let mut edits = Vec::new();
3947 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3948 let mut new_selections = Vec::with_capacity(selections.len());
3949 let mut new_autoclose_regions = Vec::new();
3950 let snapshot = self.buffer.read(cx).read(cx);
3951 let mut clear_linked_edit_ranges = false;
3952
3953 for (selection, autoclose_region) in
3954 self.selections_with_autoclose_regions(selections, &snapshot)
3955 {
3956 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3957 // Determine if the inserted text matches the opening or closing
3958 // bracket of any of this language's bracket pairs.
3959 let mut bracket_pair = None;
3960 let mut is_bracket_pair_start = false;
3961 let mut is_bracket_pair_end = false;
3962 if !text.is_empty() {
3963 let mut bracket_pair_matching_end = None;
3964 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3965 // and they are removing the character that triggered IME popup.
3966 for (pair, enabled) in scope.brackets() {
3967 if !pair.close && !pair.surround {
3968 continue;
3969 }
3970
3971 if enabled && pair.start.ends_with(text.as_ref()) {
3972 let prefix_len = pair.start.len() - text.len();
3973 let preceding_text_matches_prefix = prefix_len == 0
3974 || (selection.start.column >= (prefix_len as u32)
3975 && snapshot.contains_str_at(
3976 Point::new(
3977 selection.start.row,
3978 selection.start.column - (prefix_len as u32),
3979 ),
3980 &pair.start[..prefix_len],
3981 ));
3982 if preceding_text_matches_prefix {
3983 bracket_pair = Some(pair.clone());
3984 is_bracket_pair_start = true;
3985 break;
3986 }
3987 }
3988 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3989 {
3990 // take first bracket pair matching end, but don't break in case a later bracket
3991 // pair matches start
3992 bracket_pair_matching_end = Some(pair.clone());
3993 }
3994 }
3995 if let Some(end) = bracket_pair_matching_end
3996 && bracket_pair.is_none()
3997 {
3998 bracket_pair = Some(end);
3999 is_bracket_pair_end = true;
4000 }
4001 }
4002
4003 if let Some(bracket_pair) = bracket_pair {
4004 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4005 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4006 let auto_surround =
4007 self.use_auto_surround && snapshot_settings.use_auto_surround;
4008 if selection.is_empty() {
4009 if is_bracket_pair_start {
4010 // If the inserted text is a suffix of an opening bracket and the
4011 // selection is preceded by the rest of the opening bracket, then
4012 // insert the closing bracket.
4013 let following_text_allows_autoclose = snapshot
4014 .chars_at(selection.start)
4015 .next()
4016 .map_or(true, |c| scope.should_autoclose_before(c));
4017
4018 let preceding_text_allows_autoclose = selection.start.column == 0
4019 || snapshot.reversed_chars_at(selection.start).next().map_or(
4020 true,
4021 |c| {
4022 bracket_pair.start != bracket_pair.end
4023 || !snapshot
4024 .char_classifier_at(selection.start)
4025 .is_word(c)
4026 },
4027 );
4028
4029 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4030 && bracket_pair.start.len() == 1
4031 {
4032 let target = bracket_pair.start.chars().next().unwrap();
4033 let current_line_count = snapshot
4034 .reversed_chars_at(selection.start)
4035 .take_while(|&c| c != '\n')
4036 .filter(|&c| c == target)
4037 .count();
4038 current_line_count % 2 == 1
4039 } else {
4040 false
4041 };
4042
4043 if autoclose
4044 && bracket_pair.close
4045 && following_text_allows_autoclose
4046 && preceding_text_allows_autoclose
4047 && !is_closing_quote
4048 {
4049 let anchor = snapshot.anchor_before(selection.end);
4050 new_selections.push((selection.map(|_| anchor), text.len()));
4051 new_autoclose_regions.push((
4052 anchor,
4053 text.len(),
4054 selection.id,
4055 bracket_pair.clone(),
4056 ));
4057 edits.push((
4058 selection.range(),
4059 format!("{}{}", text, bracket_pair.end).into(),
4060 ));
4061 bracket_inserted = true;
4062 continue;
4063 }
4064 }
4065
4066 if let Some(region) = autoclose_region {
4067 // If the selection is followed by an auto-inserted closing bracket,
4068 // then don't insert that closing bracket again; just move the selection
4069 // past the closing bracket.
4070 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4071 && text.as_ref() == region.pair.end.as_str()
4072 && snapshot.contains_str_at(region.range.end, text.as_ref());
4073 if should_skip {
4074 let anchor = snapshot.anchor_after(selection.end);
4075 new_selections
4076 .push((selection.map(|_| anchor), region.pair.end.len()));
4077 continue;
4078 }
4079 }
4080
4081 let always_treat_brackets_as_autoclosed = snapshot
4082 .language_settings_at(selection.start, cx)
4083 .always_treat_brackets_as_autoclosed;
4084 if always_treat_brackets_as_autoclosed
4085 && is_bracket_pair_end
4086 && snapshot.contains_str_at(selection.end, text.as_ref())
4087 {
4088 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4089 // and the inserted text is a closing bracket and the selection is followed
4090 // by the closing bracket then move the selection past the closing bracket.
4091 let anchor = snapshot.anchor_after(selection.end);
4092 new_selections.push((selection.map(|_| anchor), text.len()));
4093 continue;
4094 }
4095 }
4096 // If an opening bracket is 1 character long and is typed while
4097 // text is selected, then surround that text with the bracket pair.
4098 else if auto_surround
4099 && bracket_pair.surround
4100 && is_bracket_pair_start
4101 && bracket_pair.start.chars().count() == 1
4102 {
4103 edits.push((selection.start..selection.start, text.clone()));
4104 edits.push((
4105 selection.end..selection.end,
4106 bracket_pair.end.as_str().into(),
4107 ));
4108 bracket_inserted = true;
4109 new_selections.push((
4110 Selection {
4111 id: selection.id,
4112 start: snapshot.anchor_after(selection.start),
4113 end: snapshot.anchor_before(selection.end),
4114 reversed: selection.reversed,
4115 goal: selection.goal,
4116 },
4117 0,
4118 ));
4119 continue;
4120 }
4121 }
4122 }
4123
4124 if self.auto_replace_emoji_shortcode
4125 && selection.is_empty()
4126 && text.as_ref().ends_with(':')
4127 {
4128 if let Some(possible_emoji_short_code) =
4129 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4130 {
4131 if !possible_emoji_short_code.is_empty() {
4132 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4133 let emoji_shortcode_start = Point::new(
4134 selection.start.row,
4135 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4136 );
4137
4138 // Remove shortcode from buffer
4139 edits.push((
4140 emoji_shortcode_start..selection.start,
4141 "".to_string().into(),
4142 ));
4143 new_selections.push((
4144 Selection {
4145 id: selection.id,
4146 start: snapshot.anchor_after(emoji_shortcode_start),
4147 end: snapshot.anchor_before(selection.start),
4148 reversed: selection.reversed,
4149 goal: selection.goal,
4150 },
4151 0,
4152 ));
4153
4154 // Insert emoji
4155 let selection_start_anchor = snapshot.anchor_after(selection.start);
4156 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4157 edits.push((selection.start..selection.end, emoji.to_string().into()));
4158
4159 continue;
4160 }
4161 }
4162 }
4163 }
4164
4165 // If not handling any auto-close operation, then just replace the selected
4166 // text with the given input and move the selection to the end of the
4167 // newly inserted text.
4168 let anchor = snapshot.anchor_after(selection.end);
4169 if !self.linked_edit_ranges.is_empty() {
4170 let start_anchor = snapshot.anchor_before(selection.start);
4171
4172 let is_word_char = text.chars().next().map_or(true, |char| {
4173 let classifier = snapshot
4174 .char_classifier_at(start_anchor.to_offset(&snapshot))
4175 .ignore_punctuation(true);
4176 classifier.is_word(char)
4177 });
4178
4179 if is_word_char {
4180 if let Some(ranges) = self
4181 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4182 {
4183 for (buffer, edits) in ranges {
4184 linked_edits
4185 .entry(buffer.clone())
4186 .or_default()
4187 .extend(edits.into_iter().map(|range| (range, text.clone())));
4188 }
4189 }
4190 } else {
4191 clear_linked_edit_ranges = true;
4192 }
4193 }
4194
4195 new_selections.push((selection.map(|_| anchor), 0));
4196 edits.push((selection.start..selection.end, text.clone()));
4197 }
4198
4199 drop(snapshot);
4200
4201 self.transact(window, cx, |this, window, cx| {
4202 if clear_linked_edit_ranges {
4203 this.linked_edit_ranges.clear();
4204 }
4205 let initial_buffer_versions =
4206 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4207
4208 this.buffer.update(cx, |buffer, cx| {
4209 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4210 });
4211 for (buffer, edits) in linked_edits {
4212 buffer.update(cx, |buffer, cx| {
4213 let snapshot = buffer.snapshot();
4214 let edits = edits
4215 .into_iter()
4216 .map(|(range, text)| {
4217 use text::ToPoint as TP;
4218 let end_point = TP::to_point(&range.end, &snapshot);
4219 let start_point = TP::to_point(&range.start, &snapshot);
4220 (start_point..end_point, text)
4221 })
4222 .sorted_by_key(|(range, _)| range.start);
4223 buffer.edit(edits, None, cx);
4224 })
4225 }
4226 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4227 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4228 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4229 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4230 .zip(new_selection_deltas)
4231 .map(|(selection, delta)| Selection {
4232 id: selection.id,
4233 start: selection.start + delta,
4234 end: selection.end + delta,
4235 reversed: selection.reversed,
4236 goal: SelectionGoal::None,
4237 })
4238 .collect::<Vec<_>>();
4239
4240 let mut i = 0;
4241 for (position, delta, selection_id, pair) in new_autoclose_regions {
4242 let position = position.to_offset(&map.buffer_snapshot) + delta;
4243 let start = map.buffer_snapshot.anchor_before(position);
4244 let end = map.buffer_snapshot.anchor_after(position);
4245 while let Some(existing_state) = this.autoclose_regions.get(i) {
4246 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4247 Ordering::Less => i += 1,
4248 Ordering::Greater => break,
4249 Ordering::Equal => {
4250 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4251 Ordering::Less => i += 1,
4252 Ordering::Equal => break,
4253 Ordering::Greater => break,
4254 }
4255 }
4256 }
4257 }
4258 this.autoclose_regions.insert(
4259 i,
4260 AutocloseRegion {
4261 selection_id,
4262 range: start..end,
4263 pair,
4264 },
4265 );
4266 }
4267
4268 let had_active_edit_prediction = this.has_active_edit_prediction();
4269 this.change_selections(
4270 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4271 window,
4272 cx,
4273 |s| s.select(new_selections),
4274 );
4275
4276 if !bracket_inserted {
4277 if let Some(on_type_format_task) =
4278 this.trigger_on_type_formatting(text.to_string(), window, cx)
4279 {
4280 on_type_format_task.detach_and_log_err(cx);
4281 }
4282 }
4283
4284 let editor_settings = EditorSettings::get_global(cx);
4285 if bracket_inserted
4286 && (editor_settings.auto_signature_help
4287 || editor_settings.show_signature_help_after_edits)
4288 {
4289 this.show_signature_help(&ShowSignatureHelp, window, cx);
4290 }
4291
4292 let trigger_in_words =
4293 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4294 if this.hard_wrap.is_some() {
4295 let latest: Range<Point> = this.selections.newest(cx).range();
4296 if latest.is_empty()
4297 && this
4298 .buffer()
4299 .read(cx)
4300 .snapshot(cx)
4301 .line_len(MultiBufferRow(latest.start.row))
4302 == latest.start.column
4303 {
4304 this.rewrap_impl(
4305 RewrapOptions {
4306 override_language_settings: true,
4307 preserve_existing_whitespace: true,
4308 },
4309 cx,
4310 )
4311 }
4312 }
4313 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4314 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4315 this.refresh_edit_prediction(true, false, window, cx);
4316 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4317 });
4318 }
4319
4320 fn find_possible_emoji_shortcode_at_position(
4321 snapshot: &MultiBufferSnapshot,
4322 position: Point,
4323 ) -> Option<String> {
4324 let mut chars = Vec::new();
4325 let mut found_colon = false;
4326 for char in snapshot.reversed_chars_at(position).take(100) {
4327 // Found a possible emoji shortcode in the middle of the buffer
4328 if found_colon {
4329 if char.is_whitespace() {
4330 chars.reverse();
4331 return Some(chars.iter().collect());
4332 }
4333 // If the previous character is not a whitespace, we are in the middle of a word
4334 // and we only want to complete the shortcode if the word is made up of other emojis
4335 let mut containing_word = String::new();
4336 for ch in snapshot
4337 .reversed_chars_at(position)
4338 .skip(chars.len() + 1)
4339 .take(100)
4340 {
4341 if ch.is_whitespace() {
4342 break;
4343 }
4344 containing_word.push(ch);
4345 }
4346 let containing_word = containing_word.chars().rev().collect::<String>();
4347 if util::word_consists_of_emojis(containing_word.as_str()) {
4348 chars.reverse();
4349 return Some(chars.iter().collect());
4350 }
4351 }
4352
4353 if char.is_whitespace() || !char.is_ascii() {
4354 return None;
4355 }
4356 if char == ':' {
4357 found_colon = true;
4358 } else {
4359 chars.push(char);
4360 }
4361 }
4362 // Found a possible emoji shortcode at the beginning of the buffer
4363 chars.reverse();
4364 Some(chars.iter().collect())
4365 }
4366
4367 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4368 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4369 self.transact(window, cx, |this, window, cx| {
4370 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4371 let selections = this.selections.all::<usize>(cx);
4372 let multi_buffer = this.buffer.read(cx);
4373 let buffer = multi_buffer.snapshot(cx);
4374 selections
4375 .iter()
4376 .map(|selection| {
4377 let start_point = selection.start.to_point(&buffer);
4378 let mut existing_indent =
4379 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4380 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4381 let start = selection.start;
4382 let end = selection.end;
4383 let selection_is_empty = start == end;
4384 let language_scope = buffer.language_scope_at(start);
4385 let (
4386 comment_delimiter,
4387 doc_delimiter,
4388 insert_extra_newline,
4389 indent_on_newline,
4390 indent_on_extra_newline,
4391 ) = if let Some(language) = &language_scope {
4392 let mut insert_extra_newline =
4393 insert_extra_newline_brackets(&buffer, start..end, language)
4394 || insert_extra_newline_tree_sitter(&buffer, start..end);
4395
4396 // Comment extension on newline is allowed only for cursor selections
4397 let comment_delimiter = maybe!({
4398 if !selection_is_empty {
4399 return None;
4400 }
4401
4402 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4403 return None;
4404 }
4405
4406 let delimiters = language.line_comment_prefixes();
4407 let max_len_of_delimiter =
4408 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4409 let (snapshot, range) =
4410 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4411
4412 let num_of_whitespaces = snapshot
4413 .chars_for_range(range.clone())
4414 .take_while(|c| c.is_whitespace())
4415 .count();
4416 let comment_candidate = snapshot
4417 .chars_for_range(range.clone())
4418 .skip(num_of_whitespaces)
4419 .take(max_len_of_delimiter)
4420 .collect::<String>();
4421 let (delimiter, trimmed_len) = delimiters
4422 .iter()
4423 .filter_map(|delimiter| {
4424 let prefix = delimiter.trim_end();
4425 if comment_candidate.starts_with(prefix) {
4426 Some((delimiter, prefix.len()))
4427 } else {
4428 None
4429 }
4430 })
4431 .max_by_key(|(_, len)| *len)?;
4432
4433 if let Some(BlockCommentConfig {
4434 start: block_start, ..
4435 }) = language.block_comment()
4436 {
4437 let block_start_trimmed = block_start.trim_end();
4438 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4439 let line_content = snapshot
4440 .chars_for_range(range)
4441 .skip(num_of_whitespaces)
4442 .take(block_start_trimmed.len())
4443 .collect::<String>();
4444
4445 if line_content.starts_with(block_start_trimmed) {
4446 return None;
4447 }
4448 }
4449 }
4450
4451 let cursor_is_placed_after_comment_marker =
4452 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4453 if cursor_is_placed_after_comment_marker {
4454 Some(delimiter.clone())
4455 } else {
4456 None
4457 }
4458 });
4459
4460 let mut indent_on_newline = IndentSize::spaces(0);
4461 let mut indent_on_extra_newline = IndentSize::spaces(0);
4462
4463 let doc_delimiter = maybe!({
4464 if !selection_is_empty {
4465 return None;
4466 }
4467
4468 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4469 return None;
4470 }
4471
4472 let BlockCommentConfig {
4473 start: start_tag,
4474 end: end_tag,
4475 prefix: delimiter,
4476 tab_size: len,
4477 } = language.documentation_comment()?;
4478 let is_within_block_comment = buffer
4479 .language_scope_at(start_point)
4480 .is_some_and(|scope| scope.override_name() == Some("comment"));
4481 if !is_within_block_comment {
4482 return None;
4483 }
4484
4485 let (snapshot, range) =
4486 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4487
4488 let num_of_whitespaces = snapshot
4489 .chars_for_range(range.clone())
4490 .take_while(|c| c.is_whitespace())
4491 .count();
4492
4493 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4494 let column = start_point.column;
4495 let cursor_is_after_start_tag = {
4496 let start_tag_len = start_tag.len();
4497 let start_tag_line = snapshot
4498 .chars_for_range(range.clone())
4499 .skip(num_of_whitespaces)
4500 .take(start_tag_len)
4501 .collect::<String>();
4502 if start_tag_line.starts_with(start_tag.as_ref()) {
4503 num_of_whitespaces + start_tag_len <= column as usize
4504 } else {
4505 false
4506 }
4507 };
4508
4509 let cursor_is_after_delimiter = {
4510 let delimiter_trim = delimiter.trim_end();
4511 let delimiter_line = snapshot
4512 .chars_for_range(range.clone())
4513 .skip(num_of_whitespaces)
4514 .take(delimiter_trim.len())
4515 .collect::<String>();
4516 if delimiter_line.starts_with(delimiter_trim) {
4517 num_of_whitespaces + delimiter_trim.len() <= column as usize
4518 } else {
4519 false
4520 }
4521 };
4522
4523 let cursor_is_before_end_tag_if_exists = {
4524 let mut char_position = 0u32;
4525 let mut end_tag_offset = None;
4526
4527 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4528 if let Some(byte_pos) = chunk.find(&**end_tag) {
4529 let chars_before_match =
4530 chunk[..byte_pos].chars().count() as u32;
4531 end_tag_offset =
4532 Some(char_position + chars_before_match);
4533 break 'outer;
4534 }
4535 char_position += chunk.chars().count() as u32;
4536 }
4537
4538 if let Some(end_tag_offset) = end_tag_offset {
4539 let cursor_is_before_end_tag = column <= end_tag_offset;
4540 if cursor_is_after_start_tag {
4541 if cursor_is_before_end_tag {
4542 insert_extra_newline = true;
4543 }
4544 let cursor_is_at_start_of_end_tag =
4545 column == end_tag_offset;
4546 if cursor_is_at_start_of_end_tag {
4547 indent_on_extra_newline.len = *len;
4548 }
4549 }
4550 cursor_is_before_end_tag
4551 } else {
4552 true
4553 }
4554 };
4555
4556 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4557 && cursor_is_before_end_tag_if_exists
4558 {
4559 if cursor_is_after_start_tag {
4560 indent_on_newline.len = *len;
4561 }
4562 Some(delimiter.clone())
4563 } else {
4564 None
4565 }
4566 });
4567
4568 (
4569 comment_delimiter,
4570 doc_delimiter,
4571 insert_extra_newline,
4572 indent_on_newline,
4573 indent_on_extra_newline,
4574 )
4575 } else {
4576 (
4577 None,
4578 None,
4579 false,
4580 IndentSize::default(),
4581 IndentSize::default(),
4582 )
4583 };
4584
4585 let prevent_auto_indent = doc_delimiter.is_some();
4586 let delimiter = comment_delimiter.or(doc_delimiter);
4587
4588 let capacity_for_delimiter =
4589 delimiter.as_deref().map(str::len).unwrap_or_default();
4590 let mut new_text = String::with_capacity(
4591 1 + capacity_for_delimiter
4592 + existing_indent.len as usize
4593 + indent_on_newline.len as usize
4594 + indent_on_extra_newline.len as usize,
4595 );
4596 new_text.push('\n');
4597 new_text.extend(existing_indent.chars());
4598 new_text.extend(indent_on_newline.chars());
4599
4600 if let Some(delimiter) = &delimiter {
4601 new_text.push_str(delimiter);
4602 }
4603
4604 if insert_extra_newline {
4605 new_text.push('\n');
4606 new_text.extend(existing_indent.chars());
4607 new_text.extend(indent_on_extra_newline.chars());
4608 }
4609
4610 let anchor = buffer.anchor_after(end);
4611 let new_selection = selection.map(|_| anchor);
4612 (
4613 ((start..end, new_text), prevent_auto_indent),
4614 (insert_extra_newline, new_selection),
4615 )
4616 })
4617 .unzip()
4618 };
4619
4620 let mut auto_indent_edits = Vec::new();
4621 let mut edits = Vec::new();
4622 for (edit, prevent_auto_indent) in edits_with_flags {
4623 if prevent_auto_indent {
4624 edits.push(edit);
4625 } else {
4626 auto_indent_edits.push(edit);
4627 }
4628 }
4629 if !edits.is_empty() {
4630 this.edit(edits, cx);
4631 }
4632 if !auto_indent_edits.is_empty() {
4633 this.edit_with_autoindent(auto_indent_edits, cx);
4634 }
4635
4636 let buffer = this.buffer.read(cx).snapshot(cx);
4637 let new_selections = selection_info
4638 .into_iter()
4639 .map(|(extra_newline_inserted, new_selection)| {
4640 let mut cursor = new_selection.end.to_point(&buffer);
4641 if extra_newline_inserted {
4642 cursor.row -= 1;
4643 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4644 }
4645 new_selection.map(|_| cursor)
4646 })
4647 .collect();
4648
4649 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4650 this.refresh_edit_prediction(true, false, window, cx);
4651 });
4652 }
4653
4654 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4655 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4656
4657 let buffer = self.buffer.read(cx);
4658 let snapshot = buffer.snapshot(cx);
4659
4660 let mut edits = Vec::new();
4661 let mut rows = Vec::new();
4662
4663 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4664 let cursor = selection.head();
4665 let row = cursor.row;
4666
4667 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4668
4669 let newline = "\n".to_string();
4670 edits.push((start_of_line..start_of_line, newline));
4671
4672 rows.push(row + rows_inserted as u32);
4673 }
4674
4675 self.transact(window, cx, |editor, window, cx| {
4676 editor.edit(edits, cx);
4677
4678 editor.change_selections(Default::default(), window, cx, |s| {
4679 let mut index = 0;
4680 s.move_cursors_with(|map, _, _| {
4681 let row = rows[index];
4682 index += 1;
4683
4684 let point = Point::new(row, 0);
4685 let boundary = map.next_line_boundary(point).1;
4686 let clipped = map.clip_point(boundary, Bias::Left);
4687
4688 (clipped, SelectionGoal::None)
4689 });
4690 });
4691
4692 let mut indent_edits = Vec::new();
4693 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4694 for row in rows {
4695 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4696 for (row, indent) in indents {
4697 if indent.len == 0 {
4698 continue;
4699 }
4700
4701 let text = match indent.kind {
4702 IndentKind::Space => " ".repeat(indent.len as usize),
4703 IndentKind::Tab => "\t".repeat(indent.len as usize),
4704 };
4705 let point = Point::new(row.0, 0);
4706 indent_edits.push((point..point, text));
4707 }
4708 }
4709 editor.edit(indent_edits, cx);
4710 });
4711 }
4712
4713 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4714 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4715
4716 let buffer = self.buffer.read(cx);
4717 let snapshot = buffer.snapshot(cx);
4718
4719 let mut edits = Vec::new();
4720 let mut rows = Vec::new();
4721 let mut rows_inserted = 0;
4722
4723 for selection in self.selections.all_adjusted(cx) {
4724 let cursor = selection.head();
4725 let row = cursor.row;
4726
4727 let point = Point::new(row + 1, 0);
4728 let start_of_line = snapshot.clip_point(point, Bias::Left);
4729
4730 let newline = "\n".to_string();
4731 edits.push((start_of_line..start_of_line, newline));
4732
4733 rows_inserted += 1;
4734 rows.push(row + rows_inserted);
4735 }
4736
4737 self.transact(window, cx, |editor, window, cx| {
4738 editor.edit(edits, cx);
4739
4740 editor.change_selections(Default::default(), window, cx, |s| {
4741 let mut index = 0;
4742 s.move_cursors_with(|map, _, _| {
4743 let row = rows[index];
4744 index += 1;
4745
4746 let point = Point::new(row, 0);
4747 let boundary = map.next_line_boundary(point).1;
4748 let clipped = map.clip_point(boundary, Bias::Left);
4749
4750 (clipped, SelectionGoal::None)
4751 });
4752 });
4753
4754 let mut indent_edits = Vec::new();
4755 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4756 for row in rows {
4757 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4758 for (row, indent) in indents {
4759 if indent.len == 0 {
4760 continue;
4761 }
4762
4763 let text = match indent.kind {
4764 IndentKind::Space => " ".repeat(indent.len as usize),
4765 IndentKind::Tab => "\t".repeat(indent.len as usize),
4766 };
4767 let point = Point::new(row.0, 0);
4768 indent_edits.push((point..point, text));
4769 }
4770 }
4771 editor.edit(indent_edits, cx);
4772 });
4773 }
4774
4775 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4776 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4777 original_indent_columns: Vec::new(),
4778 });
4779 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4780 }
4781
4782 fn insert_with_autoindent_mode(
4783 &mut self,
4784 text: &str,
4785 autoindent_mode: Option<AutoindentMode>,
4786 window: &mut Window,
4787 cx: &mut Context<Self>,
4788 ) {
4789 if self.read_only(cx) {
4790 return;
4791 }
4792
4793 let text: Arc<str> = text.into();
4794 self.transact(window, cx, |this, window, cx| {
4795 let old_selections = this.selections.all_adjusted(cx);
4796 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4797 let anchors = {
4798 let snapshot = buffer.read(cx);
4799 old_selections
4800 .iter()
4801 .map(|s| {
4802 let anchor = snapshot.anchor_after(s.head());
4803 s.map(|_| anchor)
4804 })
4805 .collect::<Vec<_>>()
4806 };
4807 buffer.edit(
4808 old_selections
4809 .iter()
4810 .map(|s| (s.start..s.end, text.clone())),
4811 autoindent_mode,
4812 cx,
4813 );
4814 anchors
4815 });
4816
4817 this.change_selections(Default::default(), window, cx, |s| {
4818 s.select_anchors(selection_anchors);
4819 });
4820
4821 cx.notify();
4822 });
4823 }
4824
4825 fn trigger_completion_on_input(
4826 &mut self,
4827 text: &str,
4828 trigger_in_words: bool,
4829 window: &mut Window,
4830 cx: &mut Context<Self>,
4831 ) {
4832 let completions_source = self
4833 .context_menu
4834 .borrow()
4835 .as_ref()
4836 .and_then(|menu| match menu {
4837 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4838 CodeContextMenu::CodeActions(_) => None,
4839 });
4840
4841 match completions_source {
4842 Some(CompletionsMenuSource::Words) => {
4843 self.show_word_completions(&ShowWordCompletions, window, cx)
4844 }
4845 Some(CompletionsMenuSource::Normal)
4846 | Some(CompletionsMenuSource::SnippetChoices)
4847 | None
4848 if self.is_completion_trigger(
4849 text,
4850 trigger_in_words,
4851 completions_source.is_some(),
4852 cx,
4853 ) =>
4854 {
4855 self.show_completions(
4856 &ShowCompletions {
4857 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4858 },
4859 window,
4860 cx,
4861 )
4862 }
4863 _ => {
4864 self.hide_context_menu(window, cx);
4865 }
4866 }
4867 }
4868
4869 fn is_completion_trigger(
4870 &self,
4871 text: &str,
4872 trigger_in_words: bool,
4873 menu_is_open: bool,
4874 cx: &mut Context<Self>,
4875 ) -> bool {
4876 let position = self.selections.newest_anchor().head();
4877 let multibuffer = self.buffer.read(cx);
4878 let Some(buffer) = position
4879 .buffer_id
4880 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4881 else {
4882 return false;
4883 };
4884
4885 if let Some(completion_provider) = &self.completion_provider {
4886 completion_provider.is_completion_trigger(
4887 &buffer,
4888 position.text_anchor,
4889 text,
4890 trigger_in_words,
4891 menu_is_open,
4892 cx,
4893 )
4894 } else {
4895 false
4896 }
4897 }
4898
4899 /// If any empty selections is touching the start of its innermost containing autoclose
4900 /// region, expand it to select the brackets.
4901 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4902 let selections = self.selections.all::<usize>(cx);
4903 let buffer = self.buffer.read(cx).read(cx);
4904 let new_selections = self
4905 .selections_with_autoclose_regions(selections, &buffer)
4906 .map(|(mut selection, region)| {
4907 if !selection.is_empty() {
4908 return selection;
4909 }
4910
4911 if let Some(region) = region {
4912 let mut range = region.range.to_offset(&buffer);
4913 if selection.start == range.start && range.start >= region.pair.start.len() {
4914 range.start -= region.pair.start.len();
4915 if buffer.contains_str_at(range.start, ®ion.pair.start)
4916 && buffer.contains_str_at(range.end, ®ion.pair.end)
4917 {
4918 range.end += region.pair.end.len();
4919 selection.start = range.start;
4920 selection.end = range.end;
4921
4922 return selection;
4923 }
4924 }
4925 }
4926
4927 let always_treat_brackets_as_autoclosed = buffer
4928 .language_settings_at(selection.start, cx)
4929 .always_treat_brackets_as_autoclosed;
4930
4931 if !always_treat_brackets_as_autoclosed {
4932 return selection;
4933 }
4934
4935 if let Some(scope) = buffer.language_scope_at(selection.start) {
4936 for (pair, enabled) in scope.brackets() {
4937 if !enabled || !pair.close {
4938 continue;
4939 }
4940
4941 if buffer.contains_str_at(selection.start, &pair.end) {
4942 let pair_start_len = pair.start.len();
4943 if buffer.contains_str_at(
4944 selection.start.saturating_sub(pair_start_len),
4945 &pair.start,
4946 ) {
4947 selection.start -= pair_start_len;
4948 selection.end += pair.end.len();
4949
4950 return selection;
4951 }
4952 }
4953 }
4954 }
4955
4956 selection
4957 })
4958 .collect();
4959
4960 drop(buffer);
4961 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4962 selections.select(new_selections)
4963 });
4964 }
4965
4966 /// Iterate the given selections, and for each one, find the smallest surrounding
4967 /// autoclose region. This uses the ordering of the selections and the autoclose
4968 /// regions to avoid repeated comparisons.
4969 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4970 &'a self,
4971 selections: impl IntoIterator<Item = Selection<D>>,
4972 buffer: &'a MultiBufferSnapshot,
4973 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4974 let mut i = 0;
4975 let mut regions = self.autoclose_regions.as_slice();
4976 selections.into_iter().map(move |selection| {
4977 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4978
4979 let mut enclosing = None;
4980 while let Some(pair_state) = regions.get(i) {
4981 if pair_state.range.end.to_offset(buffer) < range.start {
4982 regions = ®ions[i + 1..];
4983 i = 0;
4984 } else if pair_state.range.start.to_offset(buffer) > range.end {
4985 break;
4986 } else {
4987 if pair_state.selection_id == selection.id {
4988 enclosing = Some(pair_state);
4989 }
4990 i += 1;
4991 }
4992 }
4993
4994 (selection, enclosing)
4995 })
4996 }
4997
4998 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4999 fn invalidate_autoclose_regions(
5000 &mut self,
5001 mut selections: &[Selection<Anchor>],
5002 buffer: &MultiBufferSnapshot,
5003 ) {
5004 self.autoclose_regions.retain(|state| {
5005 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5006 return false;
5007 }
5008
5009 let mut i = 0;
5010 while let Some(selection) = selections.get(i) {
5011 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5012 selections = &selections[1..];
5013 continue;
5014 }
5015 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5016 break;
5017 }
5018 if selection.id == state.selection_id {
5019 return true;
5020 } else {
5021 i += 1;
5022 }
5023 }
5024 false
5025 });
5026 }
5027
5028 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5029 let offset = position.to_offset(buffer);
5030 let (word_range, kind) = buffer.surrounding_word(offset, true);
5031 if offset > word_range.start && kind == Some(CharKind::Word) {
5032 Some(
5033 buffer
5034 .text_for_range(word_range.start..offset)
5035 .collect::<String>(),
5036 )
5037 } else {
5038 None
5039 }
5040 }
5041
5042 pub fn toggle_inline_values(
5043 &mut self,
5044 _: &ToggleInlineValues,
5045 _: &mut Window,
5046 cx: &mut Context<Self>,
5047 ) {
5048 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5049
5050 self.refresh_inline_values(cx);
5051 }
5052
5053 pub fn toggle_inlay_hints(
5054 &mut self,
5055 _: &ToggleInlayHints,
5056 _: &mut Window,
5057 cx: &mut Context<Self>,
5058 ) {
5059 self.refresh_inlay_hints(
5060 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5061 cx,
5062 );
5063 }
5064
5065 pub fn inlay_hints_enabled(&self) -> bool {
5066 self.inlay_hint_cache.enabled
5067 }
5068
5069 pub fn inline_values_enabled(&self) -> bool {
5070 self.inline_value_cache.enabled
5071 }
5072
5073 #[cfg(any(test, feature = "test-support"))]
5074 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5075 self.display_map
5076 .read(cx)
5077 .current_inlays()
5078 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5079 .cloned()
5080 .collect()
5081 }
5082
5083 #[cfg(any(test, feature = "test-support"))]
5084 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5085 self.display_map
5086 .read(cx)
5087 .current_inlays()
5088 .cloned()
5089 .collect()
5090 }
5091
5092 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5093 if self.semantics_provider.is_none() || !self.mode.is_full() {
5094 return;
5095 }
5096
5097 let reason_description = reason.description();
5098 let ignore_debounce = matches!(
5099 reason,
5100 InlayHintRefreshReason::SettingsChange(_)
5101 | InlayHintRefreshReason::Toggle(_)
5102 | InlayHintRefreshReason::ExcerptsRemoved(_)
5103 | InlayHintRefreshReason::ModifiersChanged(_)
5104 );
5105 let (invalidate_cache, required_languages) = match reason {
5106 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5107 match self.inlay_hint_cache.modifiers_override(enabled) {
5108 Some(enabled) => {
5109 if enabled {
5110 (InvalidationStrategy::RefreshRequested, None)
5111 } else {
5112 self.splice_inlays(
5113 &self
5114 .visible_inlay_hints(cx)
5115 .iter()
5116 .map(|inlay| inlay.id)
5117 .collect::<Vec<InlayId>>(),
5118 Vec::new(),
5119 cx,
5120 );
5121 return;
5122 }
5123 }
5124 None => return,
5125 }
5126 }
5127 InlayHintRefreshReason::Toggle(enabled) => {
5128 if self.inlay_hint_cache.toggle(enabled) {
5129 if enabled {
5130 (InvalidationStrategy::RefreshRequested, None)
5131 } else {
5132 self.splice_inlays(
5133 &self
5134 .visible_inlay_hints(cx)
5135 .iter()
5136 .map(|inlay| inlay.id)
5137 .collect::<Vec<InlayId>>(),
5138 Vec::new(),
5139 cx,
5140 );
5141 return;
5142 }
5143 } else {
5144 return;
5145 }
5146 }
5147 InlayHintRefreshReason::SettingsChange(new_settings) => {
5148 match self.inlay_hint_cache.update_settings(
5149 &self.buffer,
5150 new_settings,
5151 self.visible_inlay_hints(cx),
5152 cx,
5153 ) {
5154 ControlFlow::Break(Some(InlaySplice {
5155 to_remove,
5156 to_insert,
5157 })) => {
5158 self.splice_inlays(&to_remove, to_insert, cx);
5159 return;
5160 }
5161 ControlFlow::Break(None) => return,
5162 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5163 }
5164 }
5165 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5166 if let Some(InlaySplice {
5167 to_remove,
5168 to_insert,
5169 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5170 {
5171 self.splice_inlays(&to_remove, to_insert, cx);
5172 }
5173 self.display_map.update(cx, |display_map, _| {
5174 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5175 });
5176 return;
5177 }
5178 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5179 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5180 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5181 }
5182 InlayHintRefreshReason::RefreshRequested => {
5183 (InvalidationStrategy::RefreshRequested, None)
5184 }
5185 };
5186
5187 if let Some(InlaySplice {
5188 to_remove,
5189 to_insert,
5190 }) = self.inlay_hint_cache.spawn_hint_refresh(
5191 reason_description,
5192 self.visible_excerpts(required_languages.as_ref(), cx),
5193 invalidate_cache,
5194 ignore_debounce,
5195 cx,
5196 ) {
5197 self.splice_inlays(&to_remove, to_insert, cx);
5198 }
5199 }
5200
5201 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5202 self.display_map
5203 .read(cx)
5204 .current_inlays()
5205 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5206 .cloned()
5207 .collect()
5208 }
5209
5210 pub fn visible_excerpts(
5211 &self,
5212 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5213 cx: &mut Context<Editor>,
5214 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5215 let Some(project) = self.project.as_ref() else {
5216 return HashMap::default();
5217 };
5218 let project = project.read(cx);
5219 let multi_buffer = self.buffer().read(cx);
5220 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5221 let multi_buffer_visible_start = self
5222 .scroll_manager
5223 .anchor()
5224 .anchor
5225 .to_point(&multi_buffer_snapshot);
5226 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5227 multi_buffer_visible_start
5228 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5229 Bias::Left,
5230 );
5231 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5232 multi_buffer_snapshot
5233 .range_to_buffer_ranges(multi_buffer_visible_range)
5234 .into_iter()
5235 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5236 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5237 let buffer_file = project::File::from_dyn(buffer.file())?;
5238 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5239 let worktree_entry = buffer_worktree
5240 .read(cx)
5241 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5242 if worktree_entry.is_ignored {
5243 return None;
5244 }
5245
5246 let language = buffer.language()?;
5247 if let Some(restrict_to_languages) = restrict_to_languages {
5248 if !restrict_to_languages.contains(language) {
5249 return None;
5250 }
5251 }
5252 Some((
5253 excerpt_id,
5254 (
5255 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5256 buffer.version().clone(),
5257 excerpt_visible_range,
5258 ),
5259 ))
5260 })
5261 .collect()
5262 }
5263
5264 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5265 TextLayoutDetails {
5266 text_system: window.text_system().clone(),
5267 editor_style: self.style.clone().unwrap(),
5268 rem_size: window.rem_size(),
5269 scroll_anchor: self.scroll_manager.anchor(),
5270 visible_rows: self.visible_line_count(),
5271 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5272 }
5273 }
5274
5275 pub fn splice_inlays(
5276 &self,
5277 to_remove: &[InlayId],
5278 to_insert: Vec<Inlay>,
5279 cx: &mut Context<Self>,
5280 ) {
5281 self.display_map.update(cx, |display_map, cx| {
5282 display_map.splice_inlays(to_remove, to_insert, cx)
5283 });
5284 cx.notify();
5285 }
5286
5287 fn trigger_on_type_formatting(
5288 &self,
5289 input: String,
5290 window: &mut Window,
5291 cx: &mut Context<Self>,
5292 ) -> Option<Task<Result<()>>> {
5293 if input.len() != 1 {
5294 return None;
5295 }
5296
5297 let project = self.project.as_ref()?;
5298 let position = self.selections.newest_anchor().head();
5299 let (buffer, buffer_position) = self
5300 .buffer
5301 .read(cx)
5302 .text_anchor_for_position(position, cx)?;
5303
5304 let settings = language_settings::language_settings(
5305 buffer
5306 .read(cx)
5307 .language_at(buffer_position)
5308 .map(|l| l.name()),
5309 buffer.read(cx).file(),
5310 cx,
5311 );
5312 if !settings.use_on_type_format {
5313 return None;
5314 }
5315
5316 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5317 // hence we do LSP request & edit on host side only — add formats to host's history.
5318 let push_to_lsp_host_history = true;
5319 // If this is not the host, append its history with new edits.
5320 let push_to_client_history = project.read(cx).is_via_collab();
5321
5322 let on_type_formatting = project.update(cx, |project, cx| {
5323 project.on_type_format(
5324 buffer.clone(),
5325 buffer_position,
5326 input,
5327 push_to_lsp_host_history,
5328 cx,
5329 )
5330 });
5331 Some(cx.spawn_in(window, async move |editor, cx| {
5332 if let Some(transaction) = on_type_formatting.await? {
5333 if push_to_client_history {
5334 buffer
5335 .update(cx, |buffer, _| {
5336 buffer.push_transaction(transaction, Instant::now());
5337 buffer.finalize_last_transaction();
5338 })
5339 .ok();
5340 }
5341 editor.update(cx, |editor, cx| {
5342 editor.refresh_document_highlights(cx);
5343 })?;
5344 }
5345 Ok(())
5346 }))
5347 }
5348
5349 pub fn show_word_completions(
5350 &mut self,
5351 _: &ShowWordCompletions,
5352 window: &mut Window,
5353 cx: &mut Context<Self>,
5354 ) {
5355 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5356 }
5357
5358 pub fn show_completions(
5359 &mut self,
5360 options: &ShowCompletions,
5361 window: &mut Window,
5362 cx: &mut Context<Self>,
5363 ) {
5364 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5365 }
5366
5367 fn open_or_update_completions_menu(
5368 &mut self,
5369 requested_source: Option<CompletionsMenuSource>,
5370 trigger: Option<&str>,
5371 window: &mut Window,
5372 cx: &mut Context<Self>,
5373 ) {
5374 if self.pending_rename.is_some() {
5375 return;
5376 }
5377
5378 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5379
5380 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5381 // inserted and selected. To handle that case, the start of the selection is used so that
5382 // the menu starts with all choices.
5383 let position = self
5384 .selections
5385 .newest_anchor()
5386 .start
5387 .bias_right(&multibuffer_snapshot);
5388 if position.diff_base_anchor.is_some() {
5389 return;
5390 }
5391 let (buffer, buffer_position) =
5392 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5393 output
5394 } else {
5395 return;
5396 };
5397 let buffer_snapshot = buffer.read(cx).snapshot();
5398
5399 let query: Option<Arc<String>> =
5400 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5401
5402 drop(multibuffer_snapshot);
5403
5404 let provider = match requested_source {
5405 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5406 Some(CompletionsMenuSource::Words) => None,
5407 Some(CompletionsMenuSource::SnippetChoices) => {
5408 log::error!("bug: SnippetChoices requested_source is not handled");
5409 None
5410 }
5411 };
5412
5413 let sort_completions = provider
5414 .as_ref()
5415 .map_or(false, |provider| provider.sort_completions());
5416
5417 let filter_completions = provider
5418 .as_ref()
5419 .map_or(true, |provider| provider.filter_completions());
5420
5421 let trigger_kind = match trigger {
5422 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5423 CompletionTriggerKind::TRIGGER_CHARACTER
5424 }
5425 _ => CompletionTriggerKind::INVOKED,
5426 };
5427 let completion_context = CompletionContext {
5428 trigger_character: trigger.and_then(|trigger| {
5429 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5430 Some(String::from(trigger))
5431 } else {
5432 None
5433 }
5434 }),
5435 trigger_kind,
5436 };
5437
5438 // Hide the current completions menu when a trigger char is typed. Without this, cached
5439 // completions from before the trigger char may be reused (#32774). Snippet choices could
5440 // involve trigger chars, so this is skipped in that case.
5441 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5442 {
5443 let menu_is_open = matches!(
5444 self.context_menu.borrow().as_ref(),
5445 Some(CodeContextMenu::Completions(_))
5446 );
5447 if menu_is_open {
5448 self.hide_context_menu(window, cx);
5449 }
5450 }
5451
5452 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5453 if filter_completions {
5454 menu.filter(query.clone(), provider.clone(), window, cx);
5455 }
5456 // When `is_incomplete` is false, no need to re-query completions when the current query
5457 // is a suffix of the initial query.
5458 if !menu.is_incomplete {
5459 // If the new query is a suffix of the old query (typing more characters) and
5460 // the previous result was complete, the existing completions can be filtered.
5461 //
5462 // Note that this is always true for snippet completions.
5463 let query_matches = match (&menu.initial_query, &query) {
5464 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5465 (None, _) => true,
5466 _ => false,
5467 };
5468 if query_matches {
5469 let position_matches = if menu.initial_position == position {
5470 true
5471 } else {
5472 let snapshot = self.buffer.read(cx).read(cx);
5473 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5474 };
5475 if position_matches {
5476 return;
5477 }
5478 }
5479 }
5480 };
5481
5482 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5483 buffer_snapshot.surrounding_word(buffer_position, false)
5484 {
5485 let word_to_exclude = buffer_snapshot
5486 .text_for_range(word_range.clone())
5487 .collect::<String>();
5488 (
5489 buffer_snapshot.anchor_before(word_range.start)
5490 ..buffer_snapshot.anchor_after(buffer_position),
5491 Some(word_to_exclude),
5492 )
5493 } else {
5494 (buffer_position..buffer_position, None)
5495 };
5496
5497 let language = buffer_snapshot
5498 .language_at(buffer_position)
5499 .map(|language| language.name());
5500
5501 let completion_settings =
5502 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5503
5504 let show_completion_documentation = buffer_snapshot
5505 .settings_at(buffer_position, cx)
5506 .show_completion_documentation;
5507
5508 // The document can be large, so stay in reasonable bounds when searching for words,
5509 // otherwise completion pop-up might be slow to appear.
5510 const WORD_LOOKUP_ROWS: u32 = 5_000;
5511 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5512 let min_word_search = buffer_snapshot.clip_point(
5513 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5514 Bias::Left,
5515 );
5516 let max_word_search = buffer_snapshot.clip_point(
5517 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5518 Bias::Right,
5519 );
5520 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5521 ..buffer_snapshot.point_to_offset(max_word_search);
5522
5523 let skip_digits = query
5524 .as_ref()
5525 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5526
5527 let (mut words, provider_responses) = match &provider {
5528 Some(provider) => {
5529 let provider_responses = provider.completions(
5530 position.excerpt_id,
5531 &buffer,
5532 buffer_position,
5533 completion_context,
5534 window,
5535 cx,
5536 );
5537
5538 let words = match completion_settings.words {
5539 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5540 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5541 .background_spawn(async move {
5542 buffer_snapshot.words_in_range(WordsQuery {
5543 fuzzy_contents: None,
5544 range: word_search_range,
5545 skip_digits,
5546 })
5547 }),
5548 };
5549
5550 (words, provider_responses)
5551 }
5552 None => (
5553 cx.background_spawn(async move {
5554 buffer_snapshot.words_in_range(WordsQuery {
5555 fuzzy_contents: None,
5556 range: word_search_range,
5557 skip_digits,
5558 })
5559 }),
5560 Task::ready(Ok(Vec::new())),
5561 ),
5562 };
5563
5564 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5565
5566 let id = post_inc(&mut self.next_completion_id);
5567 let task = cx.spawn_in(window, async move |editor, cx| {
5568 let Ok(()) = editor.update(cx, |this, _| {
5569 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5570 }) else {
5571 return;
5572 };
5573
5574 // TODO: Ideally completions from different sources would be selectively re-queried, so
5575 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5576 let mut completions = Vec::new();
5577 let mut is_incomplete = false;
5578 if let Some(provider_responses) = provider_responses.await.log_err() {
5579 if !provider_responses.is_empty() {
5580 for response in provider_responses {
5581 completions.extend(response.completions);
5582 is_incomplete = is_incomplete || response.is_incomplete;
5583 }
5584 if completion_settings.words == WordsCompletionMode::Fallback {
5585 words = Task::ready(BTreeMap::default());
5586 }
5587 }
5588 }
5589
5590 let mut words = words.await;
5591 if let Some(word_to_exclude) = &word_to_exclude {
5592 words.remove(word_to_exclude);
5593 }
5594 for lsp_completion in &completions {
5595 words.remove(&lsp_completion.new_text);
5596 }
5597 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5598 replace_range: word_replace_range.clone(),
5599 new_text: word.clone(),
5600 label: CodeLabel::plain(word, None),
5601 icon_path: None,
5602 documentation: None,
5603 source: CompletionSource::BufferWord {
5604 word_range,
5605 resolved: false,
5606 },
5607 insert_text_mode: Some(InsertTextMode::AS_IS),
5608 confirm: None,
5609 }));
5610
5611 let menu = if completions.is_empty() {
5612 None
5613 } else {
5614 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5615 let languages = editor
5616 .workspace
5617 .as_ref()
5618 .and_then(|(workspace, _)| workspace.upgrade())
5619 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5620 let menu = CompletionsMenu::new(
5621 id,
5622 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5623 sort_completions,
5624 show_completion_documentation,
5625 position,
5626 query.clone(),
5627 is_incomplete,
5628 buffer.clone(),
5629 completions.into(),
5630 snippet_sort_order,
5631 languages,
5632 language,
5633 cx,
5634 );
5635
5636 let query = if filter_completions { query } else { None };
5637 let matches_task = if let Some(query) = query {
5638 menu.do_async_filtering(query, cx)
5639 } else {
5640 Task::ready(menu.unfiltered_matches())
5641 };
5642 (menu, matches_task)
5643 }) else {
5644 return;
5645 };
5646
5647 let matches = matches_task.await;
5648
5649 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5650 // Newer menu already set, so exit.
5651 match editor.context_menu.borrow().as_ref() {
5652 Some(CodeContextMenu::Completions(prev_menu)) => {
5653 if prev_menu.id > id {
5654 return;
5655 }
5656 }
5657 _ => {}
5658 };
5659
5660 // Only valid to take prev_menu because it the new menu is immediately set
5661 // below, or the menu is hidden.
5662 match editor.context_menu.borrow_mut().take() {
5663 Some(CodeContextMenu::Completions(prev_menu)) => {
5664 let position_matches =
5665 if prev_menu.initial_position == menu.initial_position {
5666 true
5667 } else {
5668 let snapshot = editor.buffer.read(cx).read(cx);
5669 prev_menu.initial_position.to_offset(&snapshot)
5670 == menu.initial_position.to_offset(&snapshot)
5671 };
5672 if position_matches {
5673 // Preserve markdown cache before `set_filter_results` because it will
5674 // try to populate the documentation cache.
5675 menu.preserve_markdown_cache(prev_menu);
5676 }
5677 }
5678 _ => {}
5679 };
5680
5681 menu.set_filter_results(matches, provider, window, cx);
5682 }) else {
5683 return;
5684 };
5685
5686 menu.visible().then_some(menu)
5687 };
5688
5689 editor
5690 .update_in(cx, |editor, window, cx| {
5691 if editor.focus_handle.is_focused(window) {
5692 if let Some(menu) = menu {
5693 *editor.context_menu.borrow_mut() =
5694 Some(CodeContextMenu::Completions(menu));
5695
5696 crate::hover_popover::hide_hover(editor, cx);
5697 if editor.show_edit_predictions_in_menu() {
5698 editor.update_visible_edit_prediction(window, cx);
5699 } else {
5700 editor.discard_edit_prediction(false, cx);
5701 }
5702
5703 cx.notify();
5704 return;
5705 }
5706 }
5707
5708 if editor.completion_tasks.len() <= 1 {
5709 // If there are no more completion tasks and the last menu was empty, we should hide it.
5710 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5711 // If it was already hidden and we don't show edit predictions in the menu,
5712 // we should also show the edit prediction when available.
5713 if was_hidden && editor.show_edit_predictions_in_menu() {
5714 editor.update_visible_edit_prediction(window, cx);
5715 }
5716 }
5717 })
5718 .ok();
5719 });
5720
5721 self.completion_tasks.push((id, task));
5722 }
5723
5724 #[cfg(feature = "test-support")]
5725 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5726 let menu = self.context_menu.borrow();
5727 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5728 let completions = menu.completions.borrow();
5729 Some(completions.to_vec())
5730 } else {
5731 None
5732 }
5733 }
5734
5735 pub fn with_completions_menu_matching_id<R>(
5736 &self,
5737 id: CompletionId,
5738 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5739 ) -> R {
5740 let mut context_menu = self.context_menu.borrow_mut();
5741 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5742 return f(None);
5743 };
5744 if completions_menu.id != id {
5745 return f(None);
5746 }
5747 f(Some(completions_menu))
5748 }
5749
5750 pub fn confirm_completion(
5751 &mut self,
5752 action: &ConfirmCompletion,
5753 window: &mut Window,
5754 cx: &mut Context<Self>,
5755 ) -> Option<Task<Result<()>>> {
5756 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5757 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5758 }
5759
5760 pub fn confirm_completion_insert(
5761 &mut self,
5762 _: &ConfirmCompletionInsert,
5763 window: &mut Window,
5764 cx: &mut Context<Self>,
5765 ) -> Option<Task<Result<()>>> {
5766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5767 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5768 }
5769
5770 pub fn confirm_completion_replace(
5771 &mut self,
5772 _: &ConfirmCompletionReplace,
5773 window: &mut Window,
5774 cx: &mut Context<Self>,
5775 ) -> Option<Task<Result<()>>> {
5776 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5777 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5778 }
5779
5780 pub fn compose_completion(
5781 &mut self,
5782 action: &ComposeCompletion,
5783 window: &mut Window,
5784 cx: &mut Context<Self>,
5785 ) -> Option<Task<Result<()>>> {
5786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5787 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5788 }
5789
5790 fn do_completion(
5791 &mut self,
5792 item_ix: Option<usize>,
5793 intent: CompletionIntent,
5794 window: &mut Window,
5795 cx: &mut Context<Editor>,
5796 ) -> Option<Task<Result<()>>> {
5797 use language::ToOffset as _;
5798
5799 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5800 else {
5801 return None;
5802 };
5803
5804 let candidate_id = {
5805 let entries = completions_menu.entries.borrow();
5806 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5807 if self.show_edit_predictions_in_menu() {
5808 self.discard_edit_prediction(true, cx);
5809 }
5810 mat.candidate_id
5811 };
5812
5813 let completion = completions_menu
5814 .completions
5815 .borrow()
5816 .get(candidate_id)?
5817 .clone();
5818 cx.stop_propagation();
5819
5820 let buffer_handle = completions_menu.buffer.clone();
5821
5822 let CompletionEdit {
5823 new_text,
5824 snippet,
5825 replace_range,
5826 } = process_completion_for_edit(
5827 &completion,
5828 intent,
5829 &buffer_handle,
5830 &completions_menu.initial_position.text_anchor,
5831 cx,
5832 );
5833
5834 let buffer = buffer_handle.read(cx);
5835 let snapshot = self.buffer.read(cx).snapshot(cx);
5836 let newest_anchor = self.selections.newest_anchor();
5837 let replace_range_multibuffer = {
5838 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5839 let multibuffer_anchor = snapshot
5840 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5841 .unwrap()
5842 ..snapshot
5843 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5844 .unwrap();
5845 multibuffer_anchor.start.to_offset(&snapshot)
5846 ..multibuffer_anchor.end.to_offset(&snapshot)
5847 };
5848 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5849 return None;
5850 }
5851
5852 let old_text = buffer
5853 .text_for_range(replace_range.clone())
5854 .collect::<String>();
5855 let lookbehind = newest_anchor
5856 .start
5857 .text_anchor
5858 .to_offset(buffer)
5859 .saturating_sub(replace_range.start);
5860 let lookahead = replace_range
5861 .end
5862 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5863 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5864 let suffix = &old_text[lookbehind.min(old_text.len())..];
5865
5866 let selections = self.selections.all::<usize>(cx);
5867 let mut ranges = Vec::new();
5868 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5869
5870 for selection in &selections {
5871 let range = if selection.id == newest_anchor.id {
5872 replace_range_multibuffer.clone()
5873 } else {
5874 let mut range = selection.range();
5875
5876 // if prefix is present, don't duplicate it
5877 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5878 range.start = range.start.saturating_sub(lookbehind);
5879
5880 // if suffix is also present, mimic the newest cursor and replace it
5881 if selection.id != newest_anchor.id
5882 && snapshot.contains_str_at(range.end, suffix)
5883 {
5884 range.end += lookahead;
5885 }
5886 }
5887 range
5888 };
5889
5890 ranges.push(range.clone());
5891
5892 if !self.linked_edit_ranges.is_empty() {
5893 let start_anchor = snapshot.anchor_before(range.start);
5894 let end_anchor = snapshot.anchor_after(range.end);
5895 if let Some(ranges) = self
5896 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5897 {
5898 for (buffer, edits) in ranges {
5899 linked_edits
5900 .entry(buffer.clone())
5901 .or_default()
5902 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5903 }
5904 }
5905 }
5906 }
5907
5908 let common_prefix_len = old_text
5909 .chars()
5910 .zip(new_text.chars())
5911 .take_while(|(a, b)| a == b)
5912 .map(|(a, _)| a.len_utf8())
5913 .sum::<usize>();
5914
5915 cx.emit(EditorEvent::InputHandled {
5916 utf16_range_to_replace: None,
5917 text: new_text[common_prefix_len..].into(),
5918 });
5919
5920 self.transact(window, cx, |editor, window, cx| {
5921 if let Some(mut snippet) = snippet {
5922 snippet.text = new_text.to_string();
5923 editor
5924 .insert_snippet(&ranges, snippet, window, cx)
5925 .log_err();
5926 } else {
5927 editor.buffer.update(cx, |multi_buffer, cx| {
5928 let auto_indent = match completion.insert_text_mode {
5929 Some(InsertTextMode::AS_IS) => None,
5930 _ => editor.autoindent_mode.clone(),
5931 };
5932 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5933 multi_buffer.edit(edits, auto_indent, cx);
5934 });
5935 }
5936 for (buffer, edits) in linked_edits {
5937 buffer.update(cx, |buffer, cx| {
5938 let snapshot = buffer.snapshot();
5939 let edits = edits
5940 .into_iter()
5941 .map(|(range, text)| {
5942 use text::ToPoint as TP;
5943 let end_point = TP::to_point(&range.end, &snapshot);
5944 let start_point = TP::to_point(&range.start, &snapshot);
5945 (start_point..end_point, text)
5946 })
5947 .sorted_by_key(|(range, _)| range.start);
5948 buffer.edit(edits, None, cx);
5949 })
5950 }
5951
5952 editor.refresh_edit_prediction(true, false, window, cx);
5953 });
5954 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5955
5956 let show_new_completions_on_confirm = completion
5957 .confirm
5958 .as_ref()
5959 .map_or(false, |confirm| confirm(intent, window, cx));
5960 if show_new_completions_on_confirm {
5961 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5962 }
5963
5964 let provider = self.completion_provider.as_ref()?;
5965 drop(completion);
5966 let apply_edits = provider.apply_additional_edits_for_completion(
5967 buffer_handle,
5968 completions_menu.completions.clone(),
5969 candidate_id,
5970 true,
5971 cx,
5972 );
5973
5974 let editor_settings = EditorSettings::get_global(cx);
5975 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5976 // After the code completion is finished, users often want to know what signatures are needed.
5977 // so we should automatically call signature_help
5978 self.show_signature_help(&ShowSignatureHelp, window, cx);
5979 }
5980
5981 Some(cx.foreground_executor().spawn(async move {
5982 apply_edits.await?;
5983 Ok(())
5984 }))
5985 }
5986
5987 pub fn toggle_code_actions(
5988 &mut self,
5989 action: &ToggleCodeActions,
5990 window: &mut Window,
5991 cx: &mut Context<Self>,
5992 ) {
5993 let quick_launch = action.quick_launch;
5994 let mut context_menu = self.context_menu.borrow_mut();
5995 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5996 if code_actions.deployed_from == action.deployed_from {
5997 // Toggle if we're selecting the same one
5998 *context_menu = None;
5999 cx.notify();
6000 return;
6001 } else {
6002 // Otherwise, clear it and start a new one
6003 *context_menu = None;
6004 cx.notify();
6005 }
6006 }
6007 drop(context_menu);
6008 let snapshot = self.snapshot(window, cx);
6009 let deployed_from = action.deployed_from.clone();
6010 let action = action.clone();
6011 self.completion_tasks.clear();
6012 self.discard_edit_prediction(false, cx);
6013
6014 let multibuffer_point = match &action.deployed_from {
6015 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6016 DisplayPoint::new(*row, 0).to_point(&snapshot)
6017 }
6018 _ => self.selections.newest::<Point>(cx).head(),
6019 };
6020 let Some((buffer, buffer_row)) = snapshot
6021 .buffer_snapshot
6022 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6023 .and_then(|(buffer_snapshot, range)| {
6024 self.buffer()
6025 .read(cx)
6026 .buffer(buffer_snapshot.remote_id())
6027 .map(|buffer| (buffer, range.start.row))
6028 })
6029 else {
6030 return;
6031 };
6032 let buffer_id = buffer.read(cx).remote_id();
6033 let tasks = self
6034 .tasks
6035 .get(&(buffer_id, buffer_row))
6036 .map(|t| Arc::new(t.to_owned()));
6037
6038 if !self.focus_handle.is_focused(window) {
6039 return;
6040 }
6041 let project = self.project.clone();
6042
6043 let code_actions_task = match deployed_from {
6044 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6045 _ => self.code_actions(buffer_row, window, cx),
6046 };
6047
6048 let runnable_task = match deployed_from {
6049 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6050 _ => {
6051 let mut task_context_task = Task::ready(None);
6052 if let Some(tasks) = &tasks {
6053 if let Some(project) = project {
6054 task_context_task =
6055 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6056 }
6057 }
6058
6059 cx.spawn_in(window, {
6060 let buffer = buffer.clone();
6061 async move |editor, cx| {
6062 let task_context = task_context_task.await;
6063
6064 let resolved_tasks =
6065 tasks
6066 .zip(task_context.clone())
6067 .map(|(tasks, task_context)| ResolvedTasks {
6068 templates: tasks.resolve(&task_context).collect(),
6069 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6070 multibuffer_point.row,
6071 tasks.column,
6072 )),
6073 });
6074 let debug_scenarios = editor
6075 .update(cx, |editor, cx| {
6076 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6077 })?
6078 .await;
6079 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6080 }
6081 })
6082 }
6083 };
6084
6085 cx.spawn_in(window, async move |editor, cx| {
6086 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6087 let code_actions = code_actions_task.await;
6088 let spawn_straight_away = quick_launch
6089 && resolved_tasks
6090 .as_ref()
6091 .map_or(false, |tasks| tasks.templates.len() == 1)
6092 && code_actions
6093 .as_ref()
6094 .map_or(true, |actions| actions.is_empty())
6095 && debug_scenarios.is_empty();
6096
6097 editor.update_in(cx, |editor, window, cx| {
6098 crate::hover_popover::hide_hover(editor, cx);
6099 let actions = CodeActionContents::new(
6100 resolved_tasks,
6101 code_actions,
6102 debug_scenarios,
6103 task_context.unwrap_or_default(),
6104 );
6105
6106 // Don't show the menu if there are no actions available
6107 if actions.is_empty() {
6108 cx.notify();
6109 return Task::ready(Ok(()));
6110 }
6111
6112 *editor.context_menu.borrow_mut() =
6113 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6114 buffer,
6115 actions,
6116 selected_item: Default::default(),
6117 scroll_handle: UniformListScrollHandle::default(),
6118 deployed_from,
6119 }));
6120 cx.notify();
6121 if spawn_straight_away {
6122 if let Some(task) = editor.confirm_code_action(
6123 &ConfirmCodeAction { item_ix: Some(0) },
6124 window,
6125 cx,
6126 ) {
6127 return task;
6128 }
6129 }
6130
6131 Task::ready(Ok(()))
6132 })
6133 })
6134 .detach_and_log_err(cx);
6135 }
6136
6137 fn debug_scenarios(
6138 &mut self,
6139 resolved_tasks: &Option<ResolvedTasks>,
6140 buffer: &Entity<Buffer>,
6141 cx: &mut App,
6142 ) -> Task<Vec<task::DebugScenario>> {
6143 maybe!({
6144 let project = self.project.as_ref()?;
6145 let dap_store = project.read(cx).dap_store();
6146 let mut scenarios = vec![];
6147 let resolved_tasks = resolved_tasks.as_ref()?;
6148 let buffer = buffer.read(cx);
6149 let language = buffer.language()?;
6150 let file = buffer.file();
6151 let debug_adapter = language_settings(language.name().into(), file, cx)
6152 .debuggers
6153 .first()
6154 .map(SharedString::from)
6155 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6156
6157 dap_store.update(cx, |dap_store, cx| {
6158 for (_, task) in &resolved_tasks.templates {
6159 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6160 task.original_task().clone(),
6161 debug_adapter.clone().into(),
6162 task.display_label().to_owned().into(),
6163 cx,
6164 );
6165 scenarios.push(maybe_scenario);
6166 }
6167 });
6168 Some(cx.background_spawn(async move {
6169 let scenarios = futures::future::join_all(scenarios)
6170 .await
6171 .into_iter()
6172 .flatten()
6173 .collect::<Vec<_>>();
6174 scenarios
6175 }))
6176 })
6177 .unwrap_or_else(|| Task::ready(vec![]))
6178 }
6179
6180 fn code_actions(
6181 &mut self,
6182 buffer_row: u32,
6183 window: &mut Window,
6184 cx: &mut Context<Self>,
6185 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6186 let mut task = self.code_actions_task.take();
6187 cx.spawn_in(window, async move |editor, cx| {
6188 while let Some(prev_task) = task {
6189 prev_task.await.log_err();
6190 task = editor
6191 .update(cx, |this, _| this.code_actions_task.take())
6192 .ok()?;
6193 }
6194
6195 editor
6196 .update(cx, |editor, cx| {
6197 editor
6198 .available_code_actions
6199 .clone()
6200 .and_then(|(location, code_actions)| {
6201 let snapshot = location.buffer.read(cx).snapshot();
6202 let point_range = location.range.to_point(&snapshot);
6203 let point_range = point_range.start.row..=point_range.end.row;
6204 if point_range.contains(&buffer_row) {
6205 Some(code_actions)
6206 } else {
6207 None
6208 }
6209 })
6210 })
6211 .ok()
6212 .flatten()
6213 })
6214 }
6215
6216 pub fn confirm_code_action(
6217 &mut self,
6218 action: &ConfirmCodeAction,
6219 window: &mut Window,
6220 cx: &mut Context<Self>,
6221 ) -> Option<Task<Result<()>>> {
6222 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6223
6224 let actions_menu =
6225 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6226 menu
6227 } else {
6228 return None;
6229 };
6230
6231 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6232 let action = actions_menu.actions.get(action_ix)?;
6233 let title = action.label();
6234 let buffer = actions_menu.buffer;
6235 let workspace = self.workspace()?;
6236
6237 match action {
6238 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6239 workspace.update(cx, |workspace, cx| {
6240 workspace.schedule_resolved_task(
6241 task_source_kind,
6242 resolved_task,
6243 false,
6244 window,
6245 cx,
6246 );
6247
6248 Some(Task::ready(Ok(())))
6249 })
6250 }
6251 CodeActionsItem::CodeAction {
6252 excerpt_id,
6253 action,
6254 provider,
6255 } => {
6256 let apply_code_action =
6257 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6258 let workspace = workspace.downgrade();
6259 Some(cx.spawn_in(window, async move |editor, cx| {
6260 let project_transaction = apply_code_action.await?;
6261 Self::open_project_transaction(
6262 &editor,
6263 workspace,
6264 project_transaction,
6265 title,
6266 cx,
6267 )
6268 .await
6269 }))
6270 }
6271 CodeActionsItem::DebugScenario(scenario) => {
6272 let context = actions_menu.actions.context.clone();
6273
6274 workspace.update(cx, |workspace, cx| {
6275 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6276 workspace.start_debug_session(
6277 scenario,
6278 context,
6279 Some(buffer),
6280 None,
6281 window,
6282 cx,
6283 );
6284 });
6285 Some(Task::ready(Ok(())))
6286 }
6287 }
6288 }
6289
6290 pub async fn open_project_transaction(
6291 this: &WeakEntity<Editor>,
6292 workspace: WeakEntity<Workspace>,
6293 transaction: ProjectTransaction,
6294 title: String,
6295 cx: &mut AsyncWindowContext,
6296 ) -> Result<()> {
6297 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6298 cx.update(|_, cx| {
6299 entries.sort_unstable_by_key(|(buffer, _)| {
6300 buffer.read(cx).file().map(|f| f.path().clone())
6301 });
6302 })?;
6303
6304 // If the project transaction's edits are all contained within this editor, then
6305 // avoid opening a new editor to display them.
6306
6307 if let Some((buffer, transaction)) = entries.first() {
6308 if entries.len() == 1 {
6309 let excerpt = this.update(cx, |editor, cx| {
6310 editor
6311 .buffer()
6312 .read(cx)
6313 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6314 })?;
6315 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6316 if excerpted_buffer == *buffer {
6317 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6318 let excerpt_range = excerpt_range.to_offset(buffer);
6319 buffer
6320 .edited_ranges_for_transaction::<usize>(transaction)
6321 .all(|range| {
6322 excerpt_range.start <= range.start
6323 && excerpt_range.end >= range.end
6324 })
6325 })?;
6326
6327 if all_edits_within_excerpt {
6328 return Ok(());
6329 }
6330 }
6331 }
6332 }
6333 } else {
6334 return Ok(());
6335 }
6336
6337 let mut ranges_to_highlight = Vec::new();
6338 let excerpt_buffer = cx.new(|cx| {
6339 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6340 for (buffer_handle, transaction) in &entries {
6341 let edited_ranges = buffer_handle
6342 .read(cx)
6343 .edited_ranges_for_transaction::<Point>(transaction)
6344 .collect::<Vec<_>>();
6345 let (ranges, _) = multibuffer.set_excerpts_for_path(
6346 PathKey::for_buffer(buffer_handle, cx),
6347 buffer_handle.clone(),
6348 edited_ranges,
6349 DEFAULT_MULTIBUFFER_CONTEXT,
6350 cx,
6351 );
6352
6353 ranges_to_highlight.extend(ranges);
6354 }
6355 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6356 multibuffer
6357 })?;
6358
6359 workspace.update_in(cx, |workspace, window, cx| {
6360 let project = workspace.project().clone();
6361 let editor =
6362 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6363 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6364 editor.update(cx, |editor, cx| {
6365 editor.highlight_background::<Self>(
6366 &ranges_to_highlight,
6367 |theme| theme.colors().editor_highlighted_line_background,
6368 cx,
6369 );
6370 });
6371 })?;
6372
6373 Ok(())
6374 }
6375
6376 pub fn clear_code_action_providers(&mut self) {
6377 self.code_action_providers.clear();
6378 self.available_code_actions.take();
6379 }
6380
6381 pub fn add_code_action_provider(
6382 &mut self,
6383 provider: Rc<dyn CodeActionProvider>,
6384 window: &mut Window,
6385 cx: &mut Context<Self>,
6386 ) {
6387 if self
6388 .code_action_providers
6389 .iter()
6390 .any(|existing_provider| existing_provider.id() == provider.id())
6391 {
6392 return;
6393 }
6394
6395 self.code_action_providers.push(provider);
6396 self.refresh_code_actions(window, cx);
6397 }
6398
6399 pub fn remove_code_action_provider(
6400 &mut self,
6401 id: Arc<str>,
6402 window: &mut Window,
6403 cx: &mut Context<Self>,
6404 ) {
6405 self.code_action_providers
6406 .retain(|provider| provider.id() != id);
6407 self.refresh_code_actions(window, cx);
6408 }
6409
6410 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6411 !self.code_action_providers.is_empty()
6412 && EditorSettings::get_global(cx).toolbar.code_actions
6413 }
6414
6415 pub fn has_available_code_actions(&self) -> bool {
6416 self.available_code_actions
6417 .as_ref()
6418 .is_some_and(|(_, actions)| !actions.is_empty())
6419 }
6420
6421 fn render_inline_code_actions(
6422 &self,
6423 icon_size: ui::IconSize,
6424 display_row: DisplayRow,
6425 is_active: bool,
6426 cx: &mut Context<Self>,
6427 ) -> AnyElement {
6428 let show_tooltip = !self.context_menu_visible();
6429 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6430 .icon_size(icon_size)
6431 .shape(ui::IconButtonShape::Square)
6432 .icon_color(ui::Color::Hidden)
6433 .toggle_state(is_active)
6434 .when(show_tooltip, |this| {
6435 this.tooltip({
6436 let focus_handle = self.focus_handle.clone();
6437 move |window, cx| {
6438 Tooltip::for_action_in(
6439 "Toggle Code Actions",
6440 &ToggleCodeActions {
6441 deployed_from: None,
6442 quick_launch: false,
6443 },
6444 &focus_handle,
6445 window,
6446 cx,
6447 )
6448 }
6449 })
6450 })
6451 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6452 window.focus(&editor.focus_handle(cx));
6453 editor.toggle_code_actions(
6454 &crate::actions::ToggleCodeActions {
6455 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6456 display_row,
6457 )),
6458 quick_launch: false,
6459 },
6460 window,
6461 cx,
6462 );
6463 }))
6464 .into_any_element()
6465 }
6466
6467 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6468 &self.context_menu
6469 }
6470
6471 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6472 let newest_selection = self.selections.newest_anchor().clone();
6473 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6474 let buffer = self.buffer.read(cx);
6475 if newest_selection.head().diff_base_anchor.is_some() {
6476 return None;
6477 }
6478 let (start_buffer, start) =
6479 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6480 let (end_buffer, end) =
6481 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6482 if start_buffer != end_buffer {
6483 return None;
6484 }
6485
6486 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6487 cx.background_executor()
6488 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6489 .await;
6490
6491 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6492 let providers = this.code_action_providers.clone();
6493 let tasks = this
6494 .code_action_providers
6495 .iter()
6496 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6497 .collect::<Vec<_>>();
6498 (providers, tasks)
6499 })?;
6500
6501 let mut actions = Vec::new();
6502 for (provider, provider_actions) in
6503 providers.into_iter().zip(future::join_all(tasks).await)
6504 {
6505 if let Some(provider_actions) = provider_actions.log_err() {
6506 actions.extend(provider_actions.into_iter().map(|action| {
6507 AvailableCodeAction {
6508 excerpt_id: newest_selection.start.excerpt_id,
6509 action,
6510 provider: provider.clone(),
6511 }
6512 }));
6513 }
6514 }
6515
6516 this.update(cx, |this, cx| {
6517 this.available_code_actions = if actions.is_empty() {
6518 None
6519 } else {
6520 Some((
6521 Location {
6522 buffer: start_buffer,
6523 range: start..end,
6524 },
6525 actions.into(),
6526 ))
6527 };
6528 cx.notify();
6529 })
6530 }));
6531 None
6532 }
6533
6534 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6535 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6536 self.show_git_blame_inline = false;
6537
6538 self.show_git_blame_inline_delay_task =
6539 Some(cx.spawn_in(window, async move |this, cx| {
6540 cx.background_executor().timer(delay).await;
6541
6542 this.update(cx, |this, cx| {
6543 this.show_git_blame_inline = true;
6544 cx.notify();
6545 })
6546 .log_err();
6547 }));
6548 }
6549 }
6550
6551 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6552 let snapshot = self.snapshot(window, cx);
6553 let cursor = self.selections.newest::<Point>(cx).head();
6554 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6555 else {
6556 return;
6557 };
6558
6559 let Some(blame) = self.blame.as_ref() else {
6560 return;
6561 };
6562
6563 let row_info = RowInfo {
6564 buffer_id: Some(buffer.remote_id()),
6565 buffer_row: Some(point.row),
6566 ..Default::default()
6567 };
6568 let Some(blame_entry) = blame
6569 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6570 .flatten()
6571 else {
6572 return;
6573 };
6574
6575 let anchor = self.selections.newest_anchor().head();
6576 let position = self.to_pixel_point(anchor, &snapshot, window);
6577 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6578 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6579 };
6580 }
6581
6582 fn show_blame_popover(
6583 &mut self,
6584 blame_entry: &BlameEntry,
6585 position: gpui::Point<Pixels>,
6586 ignore_timeout: bool,
6587 cx: &mut Context<Self>,
6588 ) {
6589 if let Some(state) = &mut self.inline_blame_popover {
6590 state.hide_task.take();
6591 } else {
6592 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6593 let blame_entry = blame_entry.clone();
6594 let show_task = cx.spawn(async move |editor, cx| {
6595 if !ignore_timeout {
6596 cx.background_executor()
6597 .timer(std::time::Duration::from_millis(blame_popover_delay))
6598 .await;
6599 }
6600 editor
6601 .update(cx, |editor, cx| {
6602 editor.inline_blame_popover_show_task.take();
6603 let Some(blame) = editor.blame.as_ref() else {
6604 return;
6605 };
6606 let blame = blame.read(cx);
6607 let details = blame.details_for_entry(&blame_entry);
6608 let markdown = cx.new(|cx| {
6609 Markdown::new(
6610 details
6611 .as_ref()
6612 .map(|message| message.message.clone())
6613 .unwrap_or_default(),
6614 None,
6615 None,
6616 cx,
6617 )
6618 });
6619 editor.inline_blame_popover = Some(InlineBlamePopover {
6620 position,
6621 hide_task: None,
6622 popover_bounds: None,
6623 popover_state: InlineBlamePopoverState {
6624 scroll_handle: ScrollHandle::new(),
6625 commit_message: details,
6626 markdown,
6627 },
6628 keyboard_grace: ignore_timeout,
6629 });
6630 cx.notify();
6631 })
6632 .ok();
6633 });
6634 self.inline_blame_popover_show_task = Some(show_task);
6635 }
6636 }
6637
6638 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6639 self.inline_blame_popover_show_task.take();
6640 if let Some(state) = &mut self.inline_blame_popover {
6641 let hide_task = cx.spawn(async move |editor, cx| {
6642 cx.background_executor()
6643 .timer(std::time::Duration::from_millis(100))
6644 .await;
6645 editor
6646 .update(cx, |editor, cx| {
6647 editor.inline_blame_popover.take();
6648 cx.notify();
6649 })
6650 .ok();
6651 });
6652 state.hide_task = Some(hide_task);
6653 }
6654 }
6655
6656 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6657 if self.pending_rename.is_some() {
6658 return None;
6659 }
6660
6661 let provider = self.semantics_provider.clone()?;
6662 let buffer = self.buffer.read(cx);
6663 let newest_selection = self.selections.newest_anchor().clone();
6664 let cursor_position = newest_selection.head();
6665 let (cursor_buffer, cursor_buffer_position) =
6666 buffer.text_anchor_for_position(cursor_position, cx)?;
6667 let (tail_buffer, tail_buffer_position) =
6668 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6669 if cursor_buffer != tail_buffer {
6670 return None;
6671 }
6672
6673 let snapshot = cursor_buffer.read(cx).snapshot();
6674 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6675 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6676 if start_word_range != end_word_range {
6677 self.document_highlights_task.take();
6678 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6679 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6680 return None;
6681 }
6682
6683 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6684 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6685 cx.background_executor()
6686 .timer(Duration::from_millis(debounce))
6687 .await;
6688
6689 let highlights = if let Some(highlights) = cx
6690 .update(|cx| {
6691 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6692 })
6693 .ok()
6694 .flatten()
6695 {
6696 highlights.await.log_err()
6697 } else {
6698 None
6699 };
6700
6701 if let Some(highlights) = highlights {
6702 this.update(cx, |this, cx| {
6703 if this.pending_rename.is_some() {
6704 return;
6705 }
6706
6707 let buffer_id = cursor_position.buffer_id;
6708 let buffer = this.buffer.read(cx);
6709 if !buffer
6710 .text_anchor_for_position(cursor_position, cx)
6711 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6712 {
6713 return;
6714 }
6715
6716 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6717 let mut write_ranges = Vec::new();
6718 let mut read_ranges = Vec::new();
6719 for highlight in highlights {
6720 for (excerpt_id, excerpt_range) in
6721 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6722 {
6723 let start = highlight
6724 .range
6725 .start
6726 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6727 let end = highlight
6728 .range
6729 .end
6730 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6731 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6732 continue;
6733 }
6734
6735 let range = Anchor {
6736 buffer_id,
6737 excerpt_id,
6738 text_anchor: start,
6739 diff_base_anchor: None,
6740 }..Anchor {
6741 buffer_id,
6742 excerpt_id,
6743 text_anchor: end,
6744 diff_base_anchor: None,
6745 };
6746 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6747 write_ranges.push(range);
6748 } else {
6749 read_ranges.push(range);
6750 }
6751 }
6752 }
6753
6754 this.highlight_background::<DocumentHighlightRead>(
6755 &read_ranges,
6756 |theme| theme.colors().editor_document_highlight_read_background,
6757 cx,
6758 );
6759 this.highlight_background::<DocumentHighlightWrite>(
6760 &write_ranges,
6761 |theme| theme.colors().editor_document_highlight_write_background,
6762 cx,
6763 );
6764 cx.notify();
6765 })
6766 .log_err();
6767 }
6768 }));
6769 None
6770 }
6771
6772 fn prepare_highlight_query_from_selection(
6773 &mut self,
6774 cx: &mut Context<Editor>,
6775 ) -> Option<(String, Range<Anchor>)> {
6776 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6777 return None;
6778 }
6779 if !EditorSettings::get_global(cx).selection_highlight {
6780 return None;
6781 }
6782 if self.selections.count() != 1 || self.selections.line_mode {
6783 return None;
6784 }
6785 let selection = self.selections.newest::<Point>(cx);
6786 if selection.is_empty() || selection.start.row != selection.end.row {
6787 return None;
6788 }
6789 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6790 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6791 let query = multi_buffer_snapshot
6792 .text_for_range(selection_anchor_range.clone())
6793 .collect::<String>();
6794 if query.trim().is_empty() {
6795 return None;
6796 }
6797 Some((query, selection_anchor_range))
6798 }
6799
6800 fn update_selection_occurrence_highlights(
6801 &mut self,
6802 query_text: String,
6803 query_range: Range<Anchor>,
6804 multi_buffer_range_to_query: Range<Point>,
6805 use_debounce: bool,
6806 window: &mut Window,
6807 cx: &mut Context<Editor>,
6808 ) -> Task<()> {
6809 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6810 cx.spawn_in(window, async move |editor, cx| {
6811 if use_debounce {
6812 cx.background_executor()
6813 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6814 .await;
6815 }
6816 let match_task = cx.background_spawn(async move {
6817 let buffer_ranges = multi_buffer_snapshot
6818 .range_to_buffer_ranges(multi_buffer_range_to_query)
6819 .into_iter()
6820 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6821 let mut match_ranges = Vec::new();
6822 let Ok(regex) = project::search::SearchQuery::text(
6823 query_text.clone(),
6824 false,
6825 false,
6826 false,
6827 Default::default(),
6828 Default::default(),
6829 false,
6830 None,
6831 ) else {
6832 return Vec::default();
6833 };
6834 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6835 match_ranges.extend(
6836 regex
6837 .search(&buffer_snapshot, Some(search_range.clone()))
6838 .await
6839 .into_iter()
6840 .filter_map(|match_range| {
6841 let match_start = buffer_snapshot
6842 .anchor_after(search_range.start + match_range.start);
6843 let match_end = buffer_snapshot
6844 .anchor_before(search_range.start + match_range.end);
6845 let match_anchor_range = Anchor::range_in_buffer(
6846 excerpt_id,
6847 buffer_snapshot.remote_id(),
6848 match_start..match_end,
6849 );
6850 (match_anchor_range != query_range).then_some(match_anchor_range)
6851 }),
6852 );
6853 }
6854 match_ranges
6855 });
6856 let match_ranges = match_task.await;
6857 editor
6858 .update_in(cx, |editor, _, cx| {
6859 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6860 if !match_ranges.is_empty() {
6861 editor.highlight_background::<SelectedTextHighlight>(
6862 &match_ranges,
6863 |theme| theme.colors().editor_document_highlight_bracket_background,
6864 cx,
6865 )
6866 }
6867 })
6868 .log_err();
6869 })
6870 }
6871
6872 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6873 struct NewlineFold;
6874 let type_id = std::any::TypeId::of::<NewlineFold>();
6875 if !self.mode.is_single_line() {
6876 return;
6877 }
6878 let snapshot = self.snapshot(window, cx);
6879 if snapshot.buffer_snapshot.max_point().row == 0 {
6880 return;
6881 }
6882 let task = cx.background_spawn(async move {
6883 let new_newlines = snapshot
6884 .buffer_chars_at(0)
6885 .filter_map(|(c, i)| {
6886 if c == '\n' {
6887 Some(
6888 snapshot.buffer_snapshot.anchor_after(i)
6889 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6890 )
6891 } else {
6892 None
6893 }
6894 })
6895 .collect::<Vec<_>>();
6896 let existing_newlines = snapshot
6897 .folds_in_range(0..snapshot.buffer_snapshot.len())
6898 .filter_map(|fold| {
6899 if fold.placeholder.type_tag == Some(type_id) {
6900 Some(fold.range.start..fold.range.end)
6901 } else {
6902 None
6903 }
6904 })
6905 .collect::<Vec<_>>();
6906
6907 (new_newlines, existing_newlines)
6908 });
6909 self.folding_newlines = cx.spawn(async move |this, cx| {
6910 let (new_newlines, existing_newlines) = task.await;
6911 if new_newlines == existing_newlines {
6912 return;
6913 }
6914 let placeholder = FoldPlaceholder {
6915 render: Arc::new(move |_, _, cx| {
6916 div()
6917 .bg(cx.theme().status().hint_background)
6918 .border_b_1()
6919 .size_full()
6920 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6921 .border_color(cx.theme().status().hint)
6922 .child("\\n")
6923 .into_any()
6924 }),
6925 constrain_width: false,
6926 merge_adjacent: false,
6927 type_tag: Some(type_id),
6928 };
6929 let creases = new_newlines
6930 .into_iter()
6931 .map(|range| Crease::simple(range, placeholder.clone()))
6932 .collect();
6933 this.update(cx, |this, cx| {
6934 this.display_map.update(cx, |display_map, cx| {
6935 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6936 display_map.fold(creases, cx);
6937 });
6938 })
6939 .ok();
6940 });
6941 }
6942
6943 fn refresh_selected_text_highlights(
6944 &mut self,
6945 on_buffer_edit: bool,
6946 window: &mut Window,
6947 cx: &mut Context<Editor>,
6948 ) {
6949 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6950 else {
6951 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6952 self.quick_selection_highlight_task.take();
6953 self.debounced_selection_highlight_task.take();
6954 return;
6955 };
6956 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6957 if on_buffer_edit
6958 || self
6959 .quick_selection_highlight_task
6960 .as_ref()
6961 .map_or(true, |(prev_anchor_range, _)| {
6962 prev_anchor_range != &query_range
6963 })
6964 {
6965 let multi_buffer_visible_start = self
6966 .scroll_manager
6967 .anchor()
6968 .anchor
6969 .to_point(&multi_buffer_snapshot);
6970 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6971 multi_buffer_visible_start
6972 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6973 Bias::Left,
6974 );
6975 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6976 self.quick_selection_highlight_task = Some((
6977 query_range.clone(),
6978 self.update_selection_occurrence_highlights(
6979 query_text.clone(),
6980 query_range.clone(),
6981 multi_buffer_visible_range,
6982 false,
6983 window,
6984 cx,
6985 ),
6986 ));
6987 }
6988 if on_buffer_edit
6989 || self
6990 .debounced_selection_highlight_task
6991 .as_ref()
6992 .map_or(true, |(prev_anchor_range, _)| {
6993 prev_anchor_range != &query_range
6994 })
6995 {
6996 let multi_buffer_start = multi_buffer_snapshot
6997 .anchor_before(0)
6998 .to_point(&multi_buffer_snapshot);
6999 let multi_buffer_end = multi_buffer_snapshot
7000 .anchor_after(multi_buffer_snapshot.len())
7001 .to_point(&multi_buffer_snapshot);
7002 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7003 self.debounced_selection_highlight_task = Some((
7004 query_range.clone(),
7005 self.update_selection_occurrence_highlights(
7006 query_text,
7007 query_range,
7008 multi_buffer_full_range,
7009 true,
7010 window,
7011 cx,
7012 ),
7013 ));
7014 }
7015 }
7016
7017 pub fn refresh_edit_prediction(
7018 &mut self,
7019 debounce: bool,
7020 user_requested: bool,
7021 window: &mut Window,
7022 cx: &mut Context<Self>,
7023 ) -> Option<()> {
7024 if DisableAiSettings::get_global(cx).disable_ai {
7025 return None;
7026 }
7027
7028 let provider = self.edit_prediction_provider()?;
7029 let cursor = self.selections.newest_anchor().head();
7030 let (buffer, cursor_buffer_position) =
7031 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7032
7033 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7034 self.discard_edit_prediction(false, cx);
7035 return None;
7036 }
7037
7038 if !user_requested
7039 && (!self.should_show_edit_predictions()
7040 || !self.is_focused(window)
7041 || buffer.read(cx).is_empty())
7042 {
7043 self.discard_edit_prediction(false, cx);
7044 return None;
7045 }
7046
7047 self.update_visible_edit_prediction(window, cx);
7048 provider.refresh(
7049 self.project.clone(),
7050 buffer,
7051 cursor_buffer_position,
7052 debounce,
7053 cx,
7054 );
7055 Some(())
7056 }
7057
7058 fn show_edit_predictions_in_menu(&self) -> bool {
7059 match self.edit_prediction_settings {
7060 EditPredictionSettings::Disabled => false,
7061 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7062 }
7063 }
7064
7065 pub fn edit_predictions_enabled(&self) -> bool {
7066 match self.edit_prediction_settings {
7067 EditPredictionSettings::Disabled => false,
7068 EditPredictionSettings::Enabled { .. } => true,
7069 }
7070 }
7071
7072 fn edit_prediction_requires_modifier(&self) -> bool {
7073 match self.edit_prediction_settings {
7074 EditPredictionSettings::Disabled => false,
7075 EditPredictionSettings::Enabled {
7076 preview_requires_modifier,
7077 ..
7078 } => preview_requires_modifier,
7079 }
7080 }
7081
7082 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7083 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7084 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7085 self.discard_edit_prediction(false, cx);
7086 } else {
7087 let selection = self.selections.newest_anchor();
7088 let cursor = selection.head();
7089
7090 if let Some((buffer, cursor_buffer_position)) =
7091 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7092 {
7093 self.edit_prediction_settings =
7094 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7095 }
7096 }
7097 }
7098
7099 fn edit_prediction_settings_at_position(
7100 &self,
7101 buffer: &Entity<Buffer>,
7102 buffer_position: language::Anchor,
7103 cx: &App,
7104 ) -> EditPredictionSettings {
7105 if !self.mode.is_full()
7106 || !self.show_edit_predictions_override.unwrap_or(true)
7107 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7108 {
7109 return EditPredictionSettings::Disabled;
7110 }
7111
7112 let buffer = buffer.read(cx);
7113
7114 let file = buffer.file();
7115
7116 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7117 return EditPredictionSettings::Disabled;
7118 };
7119
7120 let by_provider = matches!(
7121 self.menu_edit_predictions_policy,
7122 MenuEditPredictionsPolicy::ByProvider
7123 );
7124
7125 let show_in_menu = by_provider
7126 && self
7127 .edit_prediction_provider
7128 .as_ref()
7129 .map_or(false, |provider| {
7130 provider.provider.show_completions_in_menu()
7131 });
7132
7133 let preview_requires_modifier =
7134 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7135
7136 EditPredictionSettings::Enabled {
7137 show_in_menu,
7138 preview_requires_modifier,
7139 }
7140 }
7141
7142 fn should_show_edit_predictions(&self) -> bool {
7143 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7144 }
7145
7146 pub fn edit_prediction_preview_is_active(&self) -> bool {
7147 matches!(
7148 self.edit_prediction_preview,
7149 EditPredictionPreview::Active { .. }
7150 )
7151 }
7152
7153 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7154 let cursor = self.selections.newest_anchor().head();
7155 if let Some((buffer, cursor_position)) =
7156 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7157 {
7158 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7159 } else {
7160 false
7161 }
7162 }
7163
7164 pub fn supports_minimap(&self, cx: &App) -> bool {
7165 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7166 }
7167
7168 fn edit_predictions_enabled_in_buffer(
7169 &self,
7170 buffer: &Entity<Buffer>,
7171 buffer_position: language::Anchor,
7172 cx: &App,
7173 ) -> bool {
7174 maybe!({
7175 if self.read_only(cx) {
7176 return Some(false);
7177 }
7178 let provider = self.edit_prediction_provider()?;
7179 if !provider.is_enabled(&buffer, buffer_position, cx) {
7180 return Some(false);
7181 }
7182 let buffer = buffer.read(cx);
7183 let Some(file) = buffer.file() else {
7184 return Some(true);
7185 };
7186 let settings = all_language_settings(Some(file), cx);
7187 Some(settings.edit_predictions_enabled_for_file(file, cx))
7188 })
7189 .unwrap_or(false)
7190 }
7191
7192 fn cycle_edit_prediction(
7193 &mut self,
7194 direction: Direction,
7195 window: &mut Window,
7196 cx: &mut Context<Self>,
7197 ) -> Option<()> {
7198 let provider = self.edit_prediction_provider()?;
7199 let cursor = self.selections.newest_anchor().head();
7200 let (buffer, cursor_buffer_position) =
7201 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7202 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7203 return None;
7204 }
7205
7206 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7207 self.update_visible_edit_prediction(window, cx);
7208
7209 Some(())
7210 }
7211
7212 pub fn show_edit_prediction(
7213 &mut self,
7214 _: &ShowEditPrediction,
7215 window: &mut Window,
7216 cx: &mut Context<Self>,
7217 ) {
7218 if !self.has_active_edit_prediction() {
7219 self.refresh_edit_prediction(false, true, window, cx);
7220 return;
7221 }
7222
7223 self.update_visible_edit_prediction(window, cx);
7224 }
7225
7226 pub fn display_cursor_names(
7227 &mut self,
7228 _: &DisplayCursorNames,
7229 window: &mut Window,
7230 cx: &mut Context<Self>,
7231 ) {
7232 self.show_cursor_names(window, cx);
7233 }
7234
7235 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7236 self.show_cursor_names = true;
7237 cx.notify();
7238 cx.spawn_in(window, async move |this, cx| {
7239 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7240 this.update(cx, |this, cx| {
7241 this.show_cursor_names = false;
7242 cx.notify()
7243 })
7244 .ok()
7245 })
7246 .detach();
7247 }
7248
7249 pub fn next_edit_prediction(
7250 &mut self,
7251 _: &NextEditPrediction,
7252 window: &mut Window,
7253 cx: &mut Context<Self>,
7254 ) {
7255 if self.has_active_edit_prediction() {
7256 self.cycle_edit_prediction(Direction::Next, window, cx);
7257 } else {
7258 let is_copilot_disabled = self
7259 .refresh_edit_prediction(false, true, window, cx)
7260 .is_none();
7261 if is_copilot_disabled {
7262 cx.propagate();
7263 }
7264 }
7265 }
7266
7267 pub fn previous_edit_prediction(
7268 &mut self,
7269 _: &PreviousEditPrediction,
7270 window: &mut Window,
7271 cx: &mut Context<Self>,
7272 ) {
7273 if self.has_active_edit_prediction() {
7274 self.cycle_edit_prediction(Direction::Prev, window, cx);
7275 } else {
7276 let is_copilot_disabled = self
7277 .refresh_edit_prediction(false, true, window, cx)
7278 .is_none();
7279 if is_copilot_disabled {
7280 cx.propagate();
7281 }
7282 }
7283 }
7284
7285 pub fn accept_edit_prediction(
7286 &mut self,
7287 _: &AcceptEditPrediction,
7288 window: &mut Window,
7289 cx: &mut Context<Self>,
7290 ) {
7291 if self.show_edit_predictions_in_menu() {
7292 self.hide_context_menu(window, cx);
7293 }
7294
7295 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7296 return;
7297 };
7298
7299 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7300
7301 match &active_edit_prediction.completion {
7302 EditPrediction::Move { target, .. } => {
7303 let target = *target;
7304
7305 if let Some(position_map) = &self.last_position_map {
7306 if position_map
7307 .visible_row_range
7308 .contains(&target.to_display_point(&position_map.snapshot).row())
7309 || !self.edit_prediction_requires_modifier()
7310 {
7311 self.unfold_ranges(&[target..target], true, false, cx);
7312 // Note that this is also done in vim's handler of the Tab action.
7313 self.change_selections(
7314 SelectionEffects::scroll(Autoscroll::newest()),
7315 window,
7316 cx,
7317 |selections| {
7318 selections.select_anchor_ranges([target..target]);
7319 },
7320 );
7321 self.clear_row_highlights::<EditPredictionPreview>();
7322
7323 self.edit_prediction_preview
7324 .set_previous_scroll_position(None);
7325 } else {
7326 self.edit_prediction_preview
7327 .set_previous_scroll_position(Some(
7328 position_map.snapshot.scroll_anchor,
7329 ));
7330
7331 self.highlight_rows::<EditPredictionPreview>(
7332 target..target,
7333 cx.theme().colors().editor_highlighted_line_background,
7334 RowHighlightOptions {
7335 autoscroll: true,
7336 ..Default::default()
7337 },
7338 cx,
7339 );
7340 self.request_autoscroll(Autoscroll::fit(), cx);
7341 }
7342 }
7343 }
7344 EditPrediction::Edit { edits, .. } => {
7345 if let Some(provider) = self.edit_prediction_provider() {
7346 provider.accept(cx);
7347 }
7348
7349 // Store the transaction ID and selections before applying the edit
7350 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7351
7352 let snapshot = self.buffer.read(cx).snapshot(cx);
7353 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7354
7355 self.buffer.update(cx, |buffer, cx| {
7356 buffer.edit(edits.iter().cloned(), None, cx)
7357 });
7358
7359 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7360 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7361 });
7362
7363 let selections = self.selections.disjoint_anchors();
7364 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7365 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7366 if has_new_transaction {
7367 self.selection_history
7368 .insert_transaction(transaction_id_now, selections);
7369 }
7370 }
7371
7372 self.update_visible_edit_prediction(window, cx);
7373 if self.active_edit_prediction.is_none() {
7374 self.refresh_edit_prediction(true, true, window, cx);
7375 }
7376
7377 cx.notify();
7378 }
7379 }
7380
7381 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7382 }
7383
7384 pub fn accept_partial_edit_prediction(
7385 &mut self,
7386 _: &AcceptPartialEditPrediction,
7387 window: &mut Window,
7388 cx: &mut Context<Self>,
7389 ) {
7390 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7391 return;
7392 };
7393 if self.selections.count() != 1 {
7394 return;
7395 }
7396
7397 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7398
7399 match &active_edit_prediction.completion {
7400 EditPrediction::Move { target, .. } => {
7401 let target = *target;
7402 self.change_selections(
7403 SelectionEffects::scroll(Autoscroll::newest()),
7404 window,
7405 cx,
7406 |selections| {
7407 selections.select_anchor_ranges([target..target]);
7408 },
7409 );
7410 }
7411 EditPrediction::Edit { edits, .. } => {
7412 // Find an insertion that starts at the cursor position.
7413 let snapshot = self.buffer.read(cx).snapshot(cx);
7414 let cursor_offset = self.selections.newest::<usize>(cx).head();
7415 let insertion = edits.iter().find_map(|(range, text)| {
7416 let range = range.to_offset(&snapshot);
7417 if range.is_empty() && range.start == cursor_offset {
7418 Some(text)
7419 } else {
7420 None
7421 }
7422 });
7423
7424 if let Some(text) = insertion {
7425 let mut partial_completion = text
7426 .chars()
7427 .by_ref()
7428 .take_while(|c| c.is_alphabetic())
7429 .collect::<String>();
7430 if partial_completion.is_empty() {
7431 partial_completion = text
7432 .chars()
7433 .by_ref()
7434 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7435 .collect::<String>();
7436 }
7437
7438 cx.emit(EditorEvent::InputHandled {
7439 utf16_range_to_replace: None,
7440 text: partial_completion.clone().into(),
7441 });
7442
7443 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7444
7445 self.refresh_edit_prediction(true, true, window, cx);
7446 cx.notify();
7447 } else {
7448 self.accept_edit_prediction(&Default::default(), window, cx);
7449 }
7450 }
7451 }
7452 }
7453
7454 fn discard_edit_prediction(
7455 &mut self,
7456 should_report_edit_prediction_event: bool,
7457 cx: &mut Context<Self>,
7458 ) -> bool {
7459 if should_report_edit_prediction_event {
7460 let completion_id = self
7461 .active_edit_prediction
7462 .as_ref()
7463 .and_then(|active_completion| active_completion.completion_id.clone());
7464
7465 self.report_edit_prediction_event(completion_id, false, cx);
7466 }
7467
7468 if let Some(provider) = self.edit_prediction_provider() {
7469 provider.discard(cx);
7470 }
7471
7472 self.take_active_edit_prediction(cx)
7473 }
7474
7475 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7476 let Some(provider) = self.edit_prediction_provider() else {
7477 return;
7478 };
7479
7480 let Some((_, buffer, _)) = self
7481 .buffer
7482 .read(cx)
7483 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7484 else {
7485 return;
7486 };
7487
7488 let extension = buffer
7489 .read(cx)
7490 .file()
7491 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7492
7493 let event_type = match accepted {
7494 true => "Edit Prediction Accepted",
7495 false => "Edit Prediction Discarded",
7496 };
7497 telemetry::event!(
7498 event_type,
7499 provider = provider.name(),
7500 prediction_id = id,
7501 suggestion_accepted = accepted,
7502 file_extension = extension,
7503 );
7504 }
7505
7506 pub fn has_active_edit_prediction(&self) -> bool {
7507 self.active_edit_prediction.is_some()
7508 }
7509
7510 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7511 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7512 return false;
7513 };
7514
7515 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7516 self.clear_highlights::<EditPredictionHighlight>(cx);
7517 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7518 true
7519 }
7520
7521 /// Returns true when we're displaying the edit prediction popover below the cursor
7522 /// like we are not previewing and the LSP autocomplete menu is visible
7523 /// or we are in `when_holding_modifier` mode.
7524 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7525 if self.edit_prediction_preview_is_active()
7526 || !self.show_edit_predictions_in_menu()
7527 || !self.edit_predictions_enabled()
7528 {
7529 return false;
7530 }
7531
7532 if self.has_visible_completions_menu() {
7533 return true;
7534 }
7535
7536 has_completion && self.edit_prediction_requires_modifier()
7537 }
7538
7539 fn handle_modifiers_changed(
7540 &mut self,
7541 modifiers: Modifiers,
7542 position_map: &PositionMap,
7543 window: &mut Window,
7544 cx: &mut Context<Self>,
7545 ) {
7546 if self.show_edit_predictions_in_menu() {
7547 self.update_edit_prediction_preview(&modifiers, window, cx);
7548 }
7549
7550 self.update_selection_mode(&modifiers, position_map, window, cx);
7551
7552 let mouse_position = window.mouse_position();
7553 if !position_map.text_hitbox.is_hovered(window) {
7554 return;
7555 }
7556
7557 self.update_hovered_link(
7558 position_map.point_for_position(mouse_position),
7559 &position_map.snapshot,
7560 modifiers,
7561 window,
7562 cx,
7563 )
7564 }
7565
7566 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7567 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7568 if invert {
7569 match multi_cursor_setting {
7570 MultiCursorModifier::Alt => modifiers.alt,
7571 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7572 }
7573 } else {
7574 match multi_cursor_setting {
7575 MultiCursorModifier::Alt => modifiers.secondary(),
7576 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7577 }
7578 }
7579 }
7580
7581 fn columnar_selection_mode(
7582 modifiers: &Modifiers,
7583 cx: &mut Context<Self>,
7584 ) -> Option<ColumnarMode> {
7585 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7586 if Self::multi_cursor_modifier(false, modifiers, cx) {
7587 Some(ColumnarMode::FromMouse)
7588 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7589 Some(ColumnarMode::FromSelection)
7590 } else {
7591 None
7592 }
7593 } else {
7594 None
7595 }
7596 }
7597
7598 fn update_selection_mode(
7599 &mut self,
7600 modifiers: &Modifiers,
7601 position_map: &PositionMap,
7602 window: &mut Window,
7603 cx: &mut Context<Self>,
7604 ) {
7605 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7606 return;
7607 };
7608 if self.selections.pending.is_none() {
7609 return;
7610 }
7611
7612 let mouse_position = window.mouse_position();
7613 let point_for_position = position_map.point_for_position(mouse_position);
7614 let position = point_for_position.previous_valid;
7615
7616 self.select(
7617 SelectPhase::BeginColumnar {
7618 position,
7619 reset: false,
7620 mode,
7621 goal_column: point_for_position.exact_unclipped.column(),
7622 },
7623 window,
7624 cx,
7625 );
7626 }
7627
7628 fn update_edit_prediction_preview(
7629 &mut self,
7630 modifiers: &Modifiers,
7631 window: &mut Window,
7632 cx: &mut Context<Self>,
7633 ) {
7634 let mut modifiers_held = false;
7635 if let Some(accept_keystroke) = self
7636 .accept_edit_prediction_keybind(false, window, cx)
7637 .keystroke()
7638 {
7639 modifiers_held = modifiers_held
7640 || (&accept_keystroke.modifiers == modifiers
7641 && accept_keystroke.modifiers.modified());
7642 };
7643 if let Some(accept_partial_keystroke) = self
7644 .accept_edit_prediction_keybind(true, window, cx)
7645 .keystroke()
7646 {
7647 modifiers_held = modifiers_held
7648 || (&accept_partial_keystroke.modifiers == modifiers
7649 && accept_partial_keystroke.modifiers.modified());
7650 }
7651
7652 if modifiers_held {
7653 if matches!(
7654 self.edit_prediction_preview,
7655 EditPredictionPreview::Inactive { .. }
7656 ) {
7657 self.edit_prediction_preview = EditPredictionPreview::Active {
7658 previous_scroll_position: None,
7659 since: Instant::now(),
7660 };
7661
7662 self.update_visible_edit_prediction(window, cx);
7663 cx.notify();
7664 }
7665 } else if let EditPredictionPreview::Active {
7666 previous_scroll_position,
7667 since,
7668 } = self.edit_prediction_preview
7669 {
7670 if let (Some(previous_scroll_position), Some(position_map)) =
7671 (previous_scroll_position, self.last_position_map.as_ref())
7672 {
7673 self.set_scroll_position(
7674 previous_scroll_position
7675 .scroll_position(&position_map.snapshot.display_snapshot),
7676 window,
7677 cx,
7678 );
7679 }
7680
7681 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7682 released_too_fast: since.elapsed() < Duration::from_millis(200),
7683 };
7684 self.clear_row_highlights::<EditPredictionPreview>();
7685 self.update_visible_edit_prediction(window, cx);
7686 cx.notify();
7687 }
7688 }
7689
7690 fn update_visible_edit_prediction(
7691 &mut self,
7692 _window: &mut Window,
7693 cx: &mut Context<Self>,
7694 ) -> Option<()> {
7695 if DisableAiSettings::get_global(cx).disable_ai {
7696 return None;
7697 }
7698
7699 let selection = self.selections.newest_anchor();
7700 let cursor = selection.head();
7701 let multibuffer = self.buffer.read(cx).snapshot(cx);
7702 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7703 let excerpt_id = cursor.excerpt_id;
7704
7705 let show_in_menu = self.show_edit_predictions_in_menu();
7706 let completions_menu_has_precedence = !show_in_menu
7707 && (self.context_menu.borrow().is_some()
7708 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7709
7710 if completions_menu_has_precedence
7711 || !offset_selection.is_empty()
7712 || self
7713 .active_edit_prediction
7714 .as_ref()
7715 .map_or(false, |completion| {
7716 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7717 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7718 !invalidation_range.contains(&offset_selection.head())
7719 })
7720 {
7721 self.discard_edit_prediction(false, cx);
7722 return None;
7723 }
7724
7725 self.take_active_edit_prediction(cx);
7726 let Some(provider) = self.edit_prediction_provider() else {
7727 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7728 return None;
7729 };
7730
7731 let (buffer, cursor_buffer_position) =
7732 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7733
7734 self.edit_prediction_settings =
7735 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7736
7737 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7738
7739 if self.edit_prediction_indent_conflict {
7740 let cursor_point = cursor.to_point(&multibuffer);
7741
7742 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7743
7744 if let Some((_, indent)) = indents.iter().next() {
7745 if indent.len == cursor_point.column {
7746 self.edit_prediction_indent_conflict = false;
7747 }
7748 }
7749 }
7750
7751 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7752 let edits = edit_prediction
7753 .edits
7754 .into_iter()
7755 .flat_map(|(range, new_text)| {
7756 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7757 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7758 Some((start..end, new_text))
7759 })
7760 .collect::<Vec<_>>();
7761 if edits.is_empty() {
7762 return None;
7763 }
7764
7765 let first_edit_start = edits.first().unwrap().0.start;
7766 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7767 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7768
7769 let last_edit_end = edits.last().unwrap().0.end;
7770 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7771 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7772
7773 let cursor_row = cursor.to_point(&multibuffer).row;
7774
7775 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7776
7777 let mut inlay_ids = Vec::new();
7778 let invalidation_row_range;
7779 let move_invalidation_row_range = if cursor_row < edit_start_row {
7780 Some(cursor_row..edit_end_row)
7781 } else if cursor_row > edit_end_row {
7782 Some(edit_start_row..cursor_row)
7783 } else {
7784 None
7785 };
7786 let supports_jump = self
7787 .edit_prediction_provider
7788 .as_ref()
7789 .map(|provider| provider.provider.supports_jump_to_edit())
7790 .unwrap_or(true);
7791
7792 let is_move = supports_jump
7793 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7794 let completion = if is_move {
7795 invalidation_row_range =
7796 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7797 let target = first_edit_start;
7798 EditPrediction::Move { target, snapshot }
7799 } else {
7800 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7801 && !self.edit_predictions_hidden_for_vim_mode;
7802
7803 if show_completions_in_buffer {
7804 if edits
7805 .iter()
7806 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7807 {
7808 let mut inlays = Vec::new();
7809 for (range, new_text) in &edits {
7810 let inlay = Inlay::edit_prediction(
7811 post_inc(&mut self.next_inlay_id),
7812 range.start,
7813 new_text.as_str(),
7814 );
7815 inlay_ids.push(inlay.id);
7816 inlays.push(inlay);
7817 }
7818
7819 self.splice_inlays(&[], inlays, cx);
7820 } else {
7821 let background_color = cx.theme().status().deleted_background;
7822 self.highlight_text::<EditPredictionHighlight>(
7823 edits.iter().map(|(range, _)| range.clone()).collect(),
7824 HighlightStyle {
7825 background_color: Some(background_color),
7826 ..Default::default()
7827 },
7828 cx,
7829 );
7830 }
7831 }
7832
7833 invalidation_row_range = edit_start_row..edit_end_row;
7834
7835 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7836 if provider.show_tab_accept_marker() {
7837 EditDisplayMode::TabAccept
7838 } else {
7839 EditDisplayMode::Inline
7840 }
7841 } else {
7842 EditDisplayMode::DiffPopover
7843 };
7844
7845 EditPrediction::Edit {
7846 edits,
7847 edit_preview: edit_prediction.edit_preview,
7848 display_mode,
7849 snapshot,
7850 }
7851 };
7852
7853 let invalidation_range = multibuffer
7854 .anchor_before(Point::new(invalidation_row_range.start, 0))
7855 ..multibuffer.anchor_after(Point::new(
7856 invalidation_row_range.end,
7857 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7858 ));
7859
7860 self.stale_edit_prediction_in_menu = None;
7861 self.active_edit_prediction = Some(EditPredictionState {
7862 inlay_ids,
7863 completion,
7864 completion_id: edit_prediction.id,
7865 invalidation_range,
7866 });
7867
7868 cx.notify();
7869
7870 Some(())
7871 }
7872
7873 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7874 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7875 }
7876
7877 fn clear_tasks(&mut self) {
7878 self.tasks.clear()
7879 }
7880
7881 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7882 if self.tasks.insert(key, value).is_some() {
7883 // This case should hopefully be rare, but just in case...
7884 log::error!(
7885 "multiple different run targets found on a single line, only the last target will be rendered"
7886 )
7887 }
7888 }
7889
7890 /// Get all display points of breakpoints that will be rendered within editor
7891 ///
7892 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7893 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7894 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7895 fn active_breakpoints(
7896 &self,
7897 range: Range<DisplayRow>,
7898 window: &mut Window,
7899 cx: &mut Context<Self>,
7900 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7901 let mut breakpoint_display_points = HashMap::default();
7902
7903 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7904 return breakpoint_display_points;
7905 };
7906
7907 let snapshot = self.snapshot(window, cx);
7908
7909 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7910 let Some(project) = self.project.as_ref() else {
7911 return breakpoint_display_points;
7912 };
7913
7914 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7915 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7916
7917 for (buffer_snapshot, range, excerpt_id) in
7918 multi_buffer_snapshot.range_to_buffer_ranges(range)
7919 {
7920 let Some(buffer) = project
7921 .read(cx)
7922 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7923 else {
7924 continue;
7925 };
7926 let breakpoints = breakpoint_store.read(cx).breakpoints(
7927 &buffer,
7928 Some(
7929 buffer_snapshot.anchor_before(range.start)
7930 ..buffer_snapshot.anchor_after(range.end),
7931 ),
7932 buffer_snapshot,
7933 cx,
7934 );
7935 for (breakpoint, state) in breakpoints {
7936 let multi_buffer_anchor =
7937 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7938 let position = multi_buffer_anchor
7939 .to_point(&multi_buffer_snapshot)
7940 .to_display_point(&snapshot);
7941
7942 breakpoint_display_points.insert(
7943 position.row(),
7944 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7945 );
7946 }
7947 }
7948
7949 breakpoint_display_points
7950 }
7951
7952 fn breakpoint_context_menu(
7953 &self,
7954 anchor: Anchor,
7955 window: &mut Window,
7956 cx: &mut Context<Self>,
7957 ) -> Entity<ui::ContextMenu> {
7958 let weak_editor = cx.weak_entity();
7959 let focus_handle = self.focus_handle(cx);
7960
7961 let row = self
7962 .buffer
7963 .read(cx)
7964 .snapshot(cx)
7965 .summary_for_anchor::<Point>(&anchor)
7966 .row;
7967
7968 let breakpoint = self
7969 .breakpoint_at_row(row, window, cx)
7970 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7971
7972 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7973 "Edit Log Breakpoint"
7974 } else {
7975 "Set Log Breakpoint"
7976 };
7977
7978 let condition_breakpoint_msg = if breakpoint
7979 .as_ref()
7980 .is_some_and(|bp| bp.1.condition.is_some())
7981 {
7982 "Edit Condition Breakpoint"
7983 } else {
7984 "Set Condition Breakpoint"
7985 };
7986
7987 let hit_condition_breakpoint_msg = if breakpoint
7988 .as_ref()
7989 .is_some_and(|bp| bp.1.hit_condition.is_some())
7990 {
7991 "Edit Hit Condition Breakpoint"
7992 } else {
7993 "Set Hit Condition Breakpoint"
7994 };
7995
7996 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7997 "Unset Breakpoint"
7998 } else {
7999 "Set Breakpoint"
8000 };
8001
8002 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8003
8004 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8005 BreakpointState::Enabled => Some("Disable"),
8006 BreakpointState::Disabled => Some("Enable"),
8007 });
8008
8009 let (anchor, breakpoint) =
8010 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8011
8012 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8013 menu.on_blur_subscription(Subscription::new(|| {}))
8014 .context(focus_handle)
8015 .when(run_to_cursor, |this| {
8016 let weak_editor = weak_editor.clone();
8017 this.entry("Run to cursor", None, move |window, cx| {
8018 weak_editor
8019 .update(cx, |editor, cx| {
8020 editor.change_selections(
8021 SelectionEffects::no_scroll(),
8022 window,
8023 cx,
8024 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8025 );
8026 })
8027 .ok();
8028
8029 window.dispatch_action(Box::new(RunToCursor), cx);
8030 })
8031 .separator()
8032 })
8033 .when_some(toggle_state_msg, |this, msg| {
8034 this.entry(msg, None, {
8035 let weak_editor = weak_editor.clone();
8036 let breakpoint = breakpoint.clone();
8037 move |_window, cx| {
8038 weak_editor
8039 .update(cx, |this, cx| {
8040 this.edit_breakpoint_at_anchor(
8041 anchor,
8042 breakpoint.as_ref().clone(),
8043 BreakpointEditAction::InvertState,
8044 cx,
8045 );
8046 })
8047 .log_err();
8048 }
8049 })
8050 })
8051 .entry(set_breakpoint_msg, None, {
8052 let weak_editor = weak_editor.clone();
8053 let breakpoint = breakpoint.clone();
8054 move |_window, cx| {
8055 weak_editor
8056 .update(cx, |this, cx| {
8057 this.edit_breakpoint_at_anchor(
8058 anchor,
8059 breakpoint.as_ref().clone(),
8060 BreakpointEditAction::Toggle,
8061 cx,
8062 );
8063 })
8064 .log_err();
8065 }
8066 })
8067 .entry(log_breakpoint_msg, None, {
8068 let breakpoint = breakpoint.clone();
8069 let weak_editor = weak_editor.clone();
8070 move |window, cx| {
8071 weak_editor
8072 .update(cx, |this, cx| {
8073 this.add_edit_breakpoint_block(
8074 anchor,
8075 breakpoint.as_ref(),
8076 BreakpointPromptEditAction::Log,
8077 window,
8078 cx,
8079 );
8080 })
8081 .log_err();
8082 }
8083 })
8084 .entry(condition_breakpoint_msg, None, {
8085 let breakpoint = breakpoint.clone();
8086 let weak_editor = weak_editor.clone();
8087 move |window, cx| {
8088 weak_editor
8089 .update(cx, |this, cx| {
8090 this.add_edit_breakpoint_block(
8091 anchor,
8092 breakpoint.as_ref(),
8093 BreakpointPromptEditAction::Condition,
8094 window,
8095 cx,
8096 );
8097 })
8098 .log_err();
8099 }
8100 })
8101 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8102 weak_editor
8103 .update(cx, |this, cx| {
8104 this.add_edit_breakpoint_block(
8105 anchor,
8106 breakpoint.as_ref(),
8107 BreakpointPromptEditAction::HitCondition,
8108 window,
8109 cx,
8110 );
8111 })
8112 .log_err();
8113 })
8114 })
8115 }
8116
8117 fn render_breakpoint(
8118 &self,
8119 position: Anchor,
8120 row: DisplayRow,
8121 breakpoint: &Breakpoint,
8122 state: Option<BreakpointSessionState>,
8123 cx: &mut Context<Self>,
8124 ) -> IconButton {
8125 let is_rejected = state.is_some_and(|s| !s.verified);
8126 // Is it a breakpoint that shows up when hovering over gutter?
8127 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8128 (false, false),
8129 |PhantomBreakpointIndicator {
8130 is_active,
8131 display_row,
8132 collides_with_existing_breakpoint,
8133 }| {
8134 (
8135 is_active && display_row == row,
8136 collides_with_existing_breakpoint,
8137 )
8138 },
8139 );
8140
8141 let (color, icon) = {
8142 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8143 (false, false) => ui::IconName::DebugBreakpoint,
8144 (true, false) => ui::IconName::DebugLogBreakpoint,
8145 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8146 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8147 };
8148
8149 let color = if is_phantom {
8150 Color::Hint
8151 } else if is_rejected {
8152 Color::Disabled
8153 } else {
8154 Color::Debugger
8155 };
8156
8157 (color, icon)
8158 };
8159
8160 let breakpoint = Arc::from(breakpoint.clone());
8161
8162 let alt_as_text = gpui::Keystroke {
8163 modifiers: Modifiers::secondary_key(),
8164 ..Default::default()
8165 };
8166 let primary_action_text = if breakpoint.is_disabled() {
8167 "Enable breakpoint"
8168 } else if is_phantom && !collides_with_existing {
8169 "Set breakpoint"
8170 } else {
8171 "Unset breakpoint"
8172 };
8173 let focus_handle = self.focus_handle.clone();
8174
8175 let meta = if is_rejected {
8176 SharedString::from("No executable code is associated with this line.")
8177 } else if collides_with_existing && !breakpoint.is_disabled() {
8178 SharedString::from(format!(
8179 "{alt_as_text}-click to disable,\nright-click for more options."
8180 ))
8181 } else {
8182 SharedString::from("Right-click for more options.")
8183 };
8184 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8185 .icon_size(IconSize::XSmall)
8186 .size(ui::ButtonSize::None)
8187 .when(is_rejected, |this| {
8188 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8189 })
8190 .icon_color(color)
8191 .style(ButtonStyle::Transparent)
8192 .on_click(cx.listener({
8193 let breakpoint = breakpoint.clone();
8194
8195 move |editor, event: &ClickEvent, window, cx| {
8196 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8197 BreakpointEditAction::InvertState
8198 } else {
8199 BreakpointEditAction::Toggle
8200 };
8201
8202 window.focus(&editor.focus_handle(cx));
8203 editor.edit_breakpoint_at_anchor(
8204 position,
8205 breakpoint.as_ref().clone(),
8206 edit_action,
8207 cx,
8208 );
8209 }
8210 }))
8211 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8212 editor.set_breakpoint_context_menu(
8213 row,
8214 Some(position),
8215 event.position(),
8216 window,
8217 cx,
8218 );
8219 }))
8220 .tooltip(move |window, cx| {
8221 Tooltip::with_meta_in(
8222 primary_action_text,
8223 Some(&ToggleBreakpoint),
8224 meta.clone(),
8225 &focus_handle,
8226 window,
8227 cx,
8228 )
8229 })
8230 }
8231
8232 fn build_tasks_context(
8233 project: &Entity<Project>,
8234 buffer: &Entity<Buffer>,
8235 buffer_row: u32,
8236 tasks: &Arc<RunnableTasks>,
8237 cx: &mut Context<Self>,
8238 ) -> Task<Option<task::TaskContext>> {
8239 let position = Point::new(buffer_row, tasks.column);
8240 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8241 let location = Location {
8242 buffer: buffer.clone(),
8243 range: range_start..range_start,
8244 };
8245 // Fill in the environmental variables from the tree-sitter captures
8246 let mut captured_task_variables = TaskVariables::default();
8247 for (capture_name, value) in tasks.extra_variables.clone() {
8248 captured_task_variables.insert(
8249 task::VariableName::Custom(capture_name.into()),
8250 value.clone(),
8251 );
8252 }
8253 project.update(cx, |project, cx| {
8254 project.task_store().update(cx, |task_store, cx| {
8255 task_store.task_context_for_location(captured_task_variables, location, cx)
8256 })
8257 })
8258 }
8259
8260 pub fn spawn_nearest_task(
8261 &mut self,
8262 action: &SpawnNearestTask,
8263 window: &mut Window,
8264 cx: &mut Context<Self>,
8265 ) {
8266 let Some((workspace, _)) = self.workspace.clone() else {
8267 return;
8268 };
8269 let Some(project) = self.project.clone() else {
8270 return;
8271 };
8272
8273 // Try to find a closest, enclosing node using tree-sitter that has a task
8274 let Some((buffer, buffer_row, tasks)) = self
8275 .find_enclosing_node_task(cx)
8276 // Or find the task that's closest in row-distance.
8277 .or_else(|| self.find_closest_task(cx))
8278 else {
8279 return;
8280 };
8281
8282 let reveal_strategy = action.reveal;
8283 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8284 cx.spawn_in(window, async move |_, cx| {
8285 let context = task_context.await?;
8286 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8287
8288 let resolved = &mut resolved_task.resolved;
8289 resolved.reveal = reveal_strategy;
8290
8291 workspace
8292 .update_in(cx, |workspace, window, cx| {
8293 workspace.schedule_resolved_task(
8294 task_source_kind,
8295 resolved_task,
8296 false,
8297 window,
8298 cx,
8299 );
8300 })
8301 .ok()
8302 })
8303 .detach();
8304 }
8305
8306 fn find_closest_task(
8307 &mut self,
8308 cx: &mut Context<Self>,
8309 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8310 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8311
8312 let ((buffer_id, row), tasks) = self
8313 .tasks
8314 .iter()
8315 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8316
8317 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8318 let tasks = Arc::new(tasks.to_owned());
8319 Some((buffer, *row, tasks))
8320 }
8321
8322 fn find_enclosing_node_task(
8323 &mut self,
8324 cx: &mut Context<Self>,
8325 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8326 let snapshot = self.buffer.read(cx).snapshot(cx);
8327 let offset = self.selections.newest::<usize>(cx).head();
8328 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8329 let buffer_id = excerpt.buffer().remote_id();
8330
8331 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8332 let mut cursor = layer.node().walk();
8333
8334 while cursor.goto_first_child_for_byte(offset).is_some() {
8335 if cursor.node().end_byte() == offset {
8336 cursor.goto_next_sibling();
8337 }
8338 }
8339
8340 // Ascend to the smallest ancestor that contains the range and has a task.
8341 loop {
8342 let node = cursor.node();
8343 let node_range = node.byte_range();
8344 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8345
8346 // Check if this node contains our offset
8347 if node_range.start <= offset && node_range.end >= offset {
8348 // If it contains offset, check for task
8349 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8350 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8351 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8352 }
8353 }
8354
8355 if !cursor.goto_parent() {
8356 break;
8357 }
8358 }
8359 None
8360 }
8361
8362 fn render_run_indicator(
8363 &self,
8364 _style: &EditorStyle,
8365 is_active: bool,
8366 row: DisplayRow,
8367 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8368 cx: &mut Context<Self>,
8369 ) -> IconButton {
8370 let color = Color::Muted;
8371 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8372
8373 IconButton::new(
8374 ("run_indicator", row.0 as usize),
8375 ui::IconName::PlayOutlined,
8376 )
8377 .shape(ui::IconButtonShape::Square)
8378 .icon_size(IconSize::XSmall)
8379 .icon_color(color)
8380 .toggle_state(is_active)
8381 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8382 let quick_launch = match e {
8383 ClickEvent::Keyboard(_) => true,
8384 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8385 };
8386
8387 window.focus(&editor.focus_handle(cx));
8388 editor.toggle_code_actions(
8389 &ToggleCodeActions {
8390 deployed_from: Some(CodeActionSource::RunMenu(row)),
8391 quick_launch,
8392 },
8393 window,
8394 cx,
8395 );
8396 }))
8397 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8398 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8399 }))
8400 }
8401
8402 pub fn context_menu_visible(&self) -> bool {
8403 !self.edit_prediction_preview_is_active()
8404 && self
8405 .context_menu
8406 .borrow()
8407 .as_ref()
8408 .map_or(false, |menu| menu.visible())
8409 }
8410
8411 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8412 self.context_menu
8413 .borrow()
8414 .as_ref()
8415 .map(|menu| menu.origin())
8416 }
8417
8418 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8419 self.context_menu_options = Some(options);
8420 }
8421
8422 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8423 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8424
8425 fn render_edit_prediction_popover(
8426 &mut self,
8427 text_bounds: &Bounds<Pixels>,
8428 content_origin: gpui::Point<Pixels>,
8429 right_margin: Pixels,
8430 editor_snapshot: &EditorSnapshot,
8431 visible_row_range: Range<DisplayRow>,
8432 scroll_top: f32,
8433 scroll_bottom: f32,
8434 line_layouts: &[LineWithInvisibles],
8435 line_height: Pixels,
8436 scroll_pixel_position: gpui::Point<Pixels>,
8437 newest_selection_head: Option<DisplayPoint>,
8438 editor_width: Pixels,
8439 style: &EditorStyle,
8440 window: &mut Window,
8441 cx: &mut App,
8442 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8443 if self.mode().is_minimap() {
8444 return None;
8445 }
8446 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8447
8448 if self.edit_prediction_visible_in_cursor_popover(true) {
8449 return None;
8450 }
8451
8452 match &active_edit_prediction.completion {
8453 EditPrediction::Move { target, .. } => {
8454 let target_display_point = target.to_display_point(editor_snapshot);
8455
8456 if self.edit_prediction_requires_modifier() {
8457 if !self.edit_prediction_preview_is_active() {
8458 return None;
8459 }
8460
8461 self.render_edit_prediction_modifier_jump_popover(
8462 text_bounds,
8463 content_origin,
8464 visible_row_range,
8465 line_layouts,
8466 line_height,
8467 scroll_pixel_position,
8468 newest_selection_head,
8469 target_display_point,
8470 window,
8471 cx,
8472 )
8473 } else {
8474 self.render_edit_prediction_eager_jump_popover(
8475 text_bounds,
8476 content_origin,
8477 editor_snapshot,
8478 visible_row_range,
8479 scroll_top,
8480 scroll_bottom,
8481 line_height,
8482 scroll_pixel_position,
8483 target_display_point,
8484 editor_width,
8485 window,
8486 cx,
8487 )
8488 }
8489 }
8490 EditPrediction::Edit {
8491 display_mode: EditDisplayMode::Inline,
8492 ..
8493 } => None,
8494 EditPrediction::Edit {
8495 display_mode: EditDisplayMode::TabAccept,
8496 edits,
8497 ..
8498 } => {
8499 let range = &edits.first()?.0;
8500 let target_display_point = range.end.to_display_point(editor_snapshot);
8501
8502 self.render_edit_prediction_end_of_line_popover(
8503 "Accept",
8504 editor_snapshot,
8505 visible_row_range,
8506 target_display_point,
8507 line_height,
8508 scroll_pixel_position,
8509 content_origin,
8510 editor_width,
8511 window,
8512 cx,
8513 )
8514 }
8515 EditPrediction::Edit {
8516 edits,
8517 edit_preview,
8518 display_mode: EditDisplayMode::DiffPopover,
8519 snapshot,
8520 } => self.render_edit_prediction_diff_popover(
8521 text_bounds,
8522 content_origin,
8523 right_margin,
8524 editor_snapshot,
8525 visible_row_range,
8526 line_layouts,
8527 line_height,
8528 scroll_pixel_position,
8529 newest_selection_head,
8530 editor_width,
8531 style,
8532 edits,
8533 edit_preview,
8534 snapshot,
8535 window,
8536 cx,
8537 ),
8538 }
8539 }
8540
8541 fn render_edit_prediction_modifier_jump_popover(
8542 &mut self,
8543 text_bounds: &Bounds<Pixels>,
8544 content_origin: gpui::Point<Pixels>,
8545 visible_row_range: Range<DisplayRow>,
8546 line_layouts: &[LineWithInvisibles],
8547 line_height: Pixels,
8548 scroll_pixel_position: gpui::Point<Pixels>,
8549 newest_selection_head: Option<DisplayPoint>,
8550 target_display_point: DisplayPoint,
8551 window: &mut Window,
8552 cx: &mut App,
8553 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8554 let scrolled_content_origin =
8555 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8556
8557 const SCROLL_PADDING_Y: Pixels = px(12.);
8558
8559 if target_display_point.row() < visible_row_range.start {
8560 return self.render_edit_prediction_scroll_popover(
8561 |_| SCROLL_PADDING_Y,
8562 IconName::ArrowUp,
8563 visible_row_range,
8564 line_layouts,
8565 newest_selection_head,
8566 scrolled_content_origin,
8567 window,
8568 cx,
8569 );
8570 } else if target_display_point.row() >= visible_row_range.end {
8571 return self.render_edit_prediction_scroll_popover(
8572 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8573 IconName::ArrowDown,
8574 visible_row_range,
8575 line_layouts,
8576 newest_selection_head,
8577 scrolled_content_origin,
8578 window,
8579 cx,
8580 );
8581 }
8582
8583 const POLE_WIDTH: Pixels = px(2.);
8584
8585 let line_layout =
8586 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8587 let target_column = target_display_point.column() as usize;
8588
8589 let target_x = line_layout.x_for_index(target_column);
8590 let target_y =
8591 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8592
8593 let flag_on_right = target_x < text_bounds.size.width / 2.;
8594
8595 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8596 border_color.l += 0.001;
8597
8598 let mut element = v_flex()
8599 .items_end()
8600 .when(flag_on_right, |el| el.items_start())
8601 .child(if flag_on_right {
8602 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8603 .rounded_bl(px(0.))
8604 .rounded_tl(px(0.))
8605 .border_l_2()
8606 .border_color(border_color)
8607 } else {
8608 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8609 .rounded_br(px(0.))
8610 .rounded_tr(px(0.))
8611 .border_r_2()
8612 .border_color(border_color)
8613 })
8614 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8615 .into_any();
8616
8617 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8618
8619 let mut origin = scrolled_content_origin + point(target_x, target_y)
8620 - point(
8621 if flag_on_right {
8622 POLE_WIDTH
8623 } else {
8624 size.width - POLE_WIDTH
8625 },
8626 size.height - line_height,
8627 );
8628
8629 origin.x = origin.x.max(content_origin.x);
8630
8631 element.prepaint_at(origin, window, cx);
8632
8633 Some((element, origin))
8634 }
8635
8636 fn render_edit_prediction_scroll_popover(
8637 &mut self,
8638 to_y: impl Fn(Size<Pixels>) -> Pixels,
8639 scroll_icon: IconName,
8640 visible_row_range: Range<DisplayRow>,
8641 line_layouts: &[LineWithInvisibles],
8642 newest_selection_head: Option<DisplayPoint>,
8643 scrolled_content_origin: gpui::Point<Pixels>,
8644 window: &mut Window,
8645 cx: &mut App,
8646 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8647 let mut element = self
8648 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8649 .into_any();
8650
8651 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8652
8653 let cursor = newest_selection_head?;
8654 let cursor_row_layout =
8655 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8656 let cursor_column = cursor.column() as usize;
8657
8658 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8659
8660 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8661
8662 element.prepaint_at(origin, window, cx);
8663 Some((element, origin))
8664 }
8665
8666 fn render_edit_prediction_eager_jump_popover(
8667 &mut self,
8668 text_bounds: &Bounds<Pixels>,
8669 content_origin: gpui::Point<Pixels>,
8670 editor_snapshot: &EditorSnapshot,
8671 visible_row_range: Range<DisplayRow>,
8672 scroll_top: f32,
8673 scroll_bottom: f32,
8674 line_height: Pixels,
8675 scroll_pixel_position: gpui::Point<Pixels>,
8676 target_display_point: DisplayPoint,
8677 editor_width: Pixels,
8678 window: &mut Window,
8679 cx: &mut App,
8680 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8681 if target_display_point.row().as_f32() < scroll_top {
8682 let mut element = self
8683 .render_edit_prediction_line_popover(
8684 "Jump to Edit",
8685 Some(IconName::ArrowUp),
8686 window,
8687 cx,
8688 )?
8689 .into_any();
8690
8691 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8692 let offset = point(
8693 (text_bounds.size.width - size.width) / 2.,
8694 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8695 );
8696
8697 let origin = text_bounds.origin + offset;
8698 element.prepaint_at(origin, window, cx);
8699 Some((element, origin))
8700 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8701 let mut element = self
8702 .render_edit_prediction_line_popover(
8703 "Jump to Edit",
8704 Some(IconName::ArrowDown),
8705 window,
8706 cx,
8707 )?
8708 .into_any();
8709
8710 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8711 let offset = point(
8712 (text_bounds.size.width - size.width) / 2.,
8713 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8714 );
8715
8716 let origin = text_bounds.origin + offset;
8717 element.prepaint_at(origin, window, cx);
8718 Some((element, origin))
8719 } else {
8720 self.render_edit_prediction_end_of_line_popover(
8721 "Jump to Edit",
8722 editor_snapshot,
8723 visible_row_range,
8724 target_display_point,
8725 line_height,
8726 scroll_pixel_position,
8727 content_origin,
8728 editor_width,
8729 window,
8730 cx,
8731 )
8732 }
8733 }
8734
8735 fn render_edit_prediction_end_of_line_popover(
8736 self: &mut Editor,
8737 label: &'static str,
8738 editor_snapshot: &EditorSnapshot,
8739 visible_row_range: Range<DisplayRow>,
8740 target_display_point: DisplayPoint,
8741 line_height: Pixels,
8742 scroll_pixel_position: gpui::Point<Pixels>,
8743 content_origin: gpui::Point<Pixels>,
8744 editor_width: Pixels,
8745 window: &mut Window,
8746 cx: &mut App,
8747 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8748 let target_line_end = DisplayPoint::new(
8749 target_display_point.row(),
8750 editor_snapshot.line_len(target_display_point.row()),
8751 );
8752
8753 let mut element = self
8754 .render_edit_prediction_line_popover(label, None, window, cx)?
8755 .into_any();
8756
8757 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8758
8759 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8760
8761 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8762 let mut origin = start_point
8763 + line_origin
8764 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8765 origin.x = origin.x.max(content_origin.x);
8766
8767 let max_x = content_origin.x + editor_width - size.width;
8768
8769 if origin.x > max_x {
8770 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8771
8772 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8773 origin.y += offset;
8774 IconName::ArrowUp
8775 } else {
8776 origin.y -= offset;
8777 IconName::ArrowDown
8778 };
8779
8780 element = self
8781 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8782 .into_any();
8783
8784 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8785
8786 origin.x = content_origin.x + editor_width - size.width - px(2.);
8787 }
8788
8789 element.prepaint_at(origin, window, cx);
8790 Some((element, origin))
8791 }
8792
8793 fn render_edit_prediction_diff_popover(
8794 self: &Editor,
8795 text_bounds: &Bounds<Pixels>,
8796 content_origin: gpui::Point<Pixels>,
8797 right_margin: Pixels,
8798 editor_snapshot: &EditorSnapshot,
8799 visible_row_range: Range<DisplayRow>,
8800 line_layouts: &[LineWithInvisibles],
8801 line_height: Pixels,
8802 scroll_pixel_position: gpui::Point<Pixels>,
8803 newest_selection_head: Option<DisplayPoint>,
8804 editor_width: Pixels,
8805 style: &EditorStyle,
8806 edits: &Vec<(Range<Anchor>, String)>,
8807 edit_preview: &Option<language::EditPreview>,
8808 snapshot: &language::BufferSnapshot,
8809 window: &mut Window,
8810 cx: &mut App,
8811 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8812 let edit_start = edits
8813 .first()
8814 .unwrap()
8815 .0
8816 .start
8817 .to_display_point(editor_snapshot);
8818 let edit_end = edits
8819 .last()
8820 .unwrap()
8821 .0
8822 .end
8823 .to_display_point(editor_snapshot);
8824
8825 let is_visible = visible_row_range.contains(&edit_start.row())
8826 || visible_row_range.contains(&edit_end.row());
8827 if !is_visible {
8828 return None;
8829 }
8830
8831 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8832 crate::edit_prediction_edit_text(&snapshot, edits, edit_preview, false, cx)
8833 } else {
8834 // Fallback for providers without edit_preview
8835 crate::edit_prediction_fallback_text(edits, cx)
8836 };
8837
8838 let styled_text = highlighted_edits.to_styled_text(&style.text);
8839 let line_count = highlighted_edits.text.lines().count();
8840
8841 const BORDER_WIDTH: Pixels = px(1.);
8842
8843 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8844 let has_keybind = keybind.is_some();
8845
8846 let mut element = h_flex()
8847 .items_start()
8848 .child(
8849 h_flex()
8850 .bg(cx.theme().colors().editor_background)
8851 .border(BORDER_WIDTH)
8852 .shadow_xs()
8853 .border_color(cx.theme().colors().border)
8854 .rounded_l_lg()
8855 .when(line_count > 1, |el| el.rounded_br_lg())
8856 .pr_1()
8857 .child(styled_text),
8858 )
8859 .child(
8860 h_flex()
8861 .h(line_height + BORDER_WIDTH * 2.)
8862 .px_1p5()
8863 .gap_1()
8864 // Workaround: For some reason, there's a gap if we don't do this
8865 .ml(-BORDER_WIDTH)
8866 .shadow(vec![gpui::BoxShadow {
8867 color: gpui::black().opacity(0.05),
8868 offset: point(px(1.), px(1.)),
8869 blur_radius: px(2.),
8870 spread_radius: px(0.),
8871 }])
8872 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8873 .border(BORDER_WIDTH)
8874 .border_color(cx.theme().colors().border)
8875 .rounded_r_lg()
8876 .id("edit_prediction_diff_popover_keybind")
8877 .when(!has_keybind, |el| {
8878 let status_colors = cx.theme().status();
8879
8880 el.bg(status_colors.error_background)
8881 .border_color(status_colors.error.opacity(0.6))
8882 .child(Icon::new(IconName::Info).color(Color::Error))
8883 .cursor_default()
8884 .hoverable_tooltip(move |_window, cx| {
8885 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8886 })
8887 })
8888 .children(keybind),
8889 )
8890 .into_any();
8891
8892 let longest_row =
8893 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8894 let longest_line_width = if visible_row_range.contains(&longest_row) {
8895 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8896 } else {
8897 layout_line(
8898 longest_row,
8899 editor_snapshot,
8900 style,
8901 editor_width,
8902 |_| false,
8903 window,
8904 cx,
8905 )
8906 .width
8907 };
8908
8909 let viewport_bounds =
8910 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8911 right: -right_margin,
8912 ..Default::default()
8913 });
8914
8915 let x_after_longest =
8916 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8917 - scroll_pixel_position.x;
8918
8919 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8920
8921 // Fully visible if it can be displayed within the window (allow overlapping other
8922 // panes). However, this is only allowed if the popover starts within text_bounds.
8923 let can_position_to_the_right = x_after_longest < text_bounds.right()
8924 && x_after_longest + element_bounds.width < viewport_bounds.right();
8925
8926 let mut origin = if can_position_to_the_right {
8927 point(
8928 x_after_longest,
8929 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8930 - scroll_pixel_position.y,
8931 )
8932 } else {
8933 let cursor_row = newest_selection_head.map(|head| head.row());
8934 let above_edit = edit_start
8935 .row()
8936 .0
8937 .checked_sub(line_count as u32)
8938 .map(DisplayRow);
8939 let below_edit = Some(edit_end.row() + 1);
8940 let above_cursor =
8941 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8942 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8943
8944 // Place the edit popover adjacent to the edit if there is a location
8945 // available that is onscreen and does not obscure the cursor. Otherwise,
8946 // place it adjacent to the cursor.
8947 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8948 .into_iter()
8949 .flatten()
8950 .find(|&start_row| {
8951 let end_row = start_row + line_count as u32;
8952 visible_row_range.contains(&start_row)
8953 && visible_row_range.contains(&end_row)
8954 && cursor_row.map_or(true, |cursor_row| {
8955 !((start_row..end_row).contains(&cursor_row))
8956 })
8957 })?;
8958
8959 content_origin
8960 + point(
8961 -scroll_pixel_position.x,
8962 row_target.as_f32() * line_height - scroll_pixel_position.y,
8963 )
8964 };
8965
8966 origin.x -= BORDER_WIDTH;
8967
8968 window.defer_draw(element, origin, 1);
8969
8970 // Do not return an element, since it will already be drawn due to defer_draw.
8971 None
8972 }
8973
8974 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8975 px(30.)
8976 }
8977
8978 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8979 if self.read_only(cx) {
8980 cx.theme().players().read_only()
8981 } else {
8982 self.style.as_ref().unwrap().local_player
8983 }
8984 }
8985
8986 fn render_edit_prediction_accept_keybind(
8987 &self,
8988 window: &mut Window,
8989 cx: &App,
8990 ) -> Option<AnyElement> {
8991 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8992 let accept_keystroke = accept_binding.keystroke()?;
8993
8994 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8995
8996 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8997 Color::Accent
8998 } else {
8999 Color::Muted
9000 };
9001
9002 h_flex()
9003 .px_0p5()
9004 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9005 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9006 .text_size(TextSize::XSmall.rems(cx))
9007 .child(h_flex().children(ui::render_modifiers(
9008 &accept_keystroke.modifiers,
9009 PlatformStyle::platform(),
9010 Some(modifiers_color),
9011 Some(IconSize::XSmall.rems().into()),
9012 true,
9013 )))
9014 .when(is_platform_style_mac, |parent| {
9015 parent.child(accept_keystroke.key.clone())
9016 })
9017 .when(!is_platform_style_mac, |parent| {
9018 parent.child(
9019 Key::new(
9020 util::capitalize(&accept_keystroke.key),
9021 Some(Color::Default),
9022 )
9023 .size(Some(IconSize::XSmall.rems().into())),
9024 )
9025 })
9026 .into_any()
9027 .into()
9028 }
9029
9030 fn render_edit_prediction_line_popover(
9031 &self,
9032 label: impl Into<SharedString>,
9033 icon: Option<IconName>,
9034 window: &mut Window,
9035 cx: &App,
9036 ) -> Option<Stateful<Div>> {
9037 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9038
9039 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9040 let has_keybind = keybind.is_some();
9041
9042 let result = h_flex()
9043 .id("ep-line-popover")
9044 .py_0p5()
9045 .pl_1()
9046 .pr(padding_right)
9047 .gap_1()
9048 .rounded_md()
9049 .border_1()
9050 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9051 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9052 .shadow_xs()
9053 .when(!has_keybind, |el| {
9054 let status_colors = cx.theme().status();
9055
9056 el.bg(status_colors.error_background)
9057 .border_color(status_colors.error.opacity(0.6))
9058 .pl_2()
9059 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9060 .cursor_default()
9061 .hoverable_tooltip(move |_window, cx| {
9062 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9063 })
9064 })
9065 .children(keybind)
9066 .child(
9067 Label::new(label)
9068 .size(LabelSize::Small)
9069 .when(!has_keybind, |el| {
9070 el.color(cx.theme().status().error.into()).strikethrough()
9071 }),
9072 )
9073 .when(!has_keybind, |el| {
9074 el.child(
9075 h_flex().ml_1().child(
9076 Icon::new(IconName::Info)
9077 .size(IconSize::Small)
9078 .color(cx.theme().status().error.into()),
9079 ),
9080 )
9081 })
9082 .when_some(icon, |element, icon| {
9083 element.child(
9084 div()
9085 .mt(px(1.5))
9086 .child(Icon::new(icon).size(IconSize::Small)),
9087 )
9088 });
9089
9090 Some(result)
9091 }
9092
9093 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9094 let accent_color = cx.theme().colors().text_accent;
9095 let editor_bg_color = cx.theme().colors().editor_background;
9096 editor_bg_color.blend(accent_color.opacity(0.1))
9097 }
9098
9099 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9100 let accent_color = cx.theme().colors().text_accent;
9101 let editor_bg_color = cx.theme().colors().editor_background;
9102 editor_bg_color.blend(accent_color.opacity(0.6))
9103 }
9104 fn get_prediction_provider_icon_name(
9105 provider: &Option<RegisteredEditPredictionProvider>,
9106 ) -> IconName {
9107 match provider {
9108 Some(provider) => match provider.provider.name() {
9109 "copilot" => IconName::Copilot,
9110 "supermaven" => IconName::Supermaven,
9111 _ => IconName::ZedPredict,
9112 },
9113 None => IconName::ZedPredict,
9114 }
9115 }
9116
9117 fn render_edit_prediction_cursor_popover(
9118 &self,
9119 min_width: Pixels,
9120 max_width: Pixels,
9121 cursor_point: Point,
9122 style: &EditorStyle,
9123 accept_keystroke: Option<&gpui::Keystroke>,
9124 _window: &Window,
9125 cx: &mut Context<Editor>,
9126 ) -> Option<AnyElement> {
9127 let provider = self.edit_prediction_provider.as_ref()?;
9128 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9129
9130 if provider.provider.needs_terms_acceptance(cx) {
9131 return Some(
9132 h_flex()
9133 .min_w(min_width)
9134 .flex_1()
9135 .px_2()
9136 .py_1()
9137 .gap_3()
9138 .elevation_2(cx)
9139 .hover(|style| style.bg(cx.theme().colors().element_hover))
9140 .id("accept-terms")
9141 .cursor_pointer()
9142 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9143 .on_click(cx.listener(|this, _event, window, cx| {
9144 cx.stop_propagation();
9145 this.report_editor_event(ReportEditorEvent::ZetaTosClicked, None, cx);
9146 window.dispatch_action(
9147 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9148 cx,
9149 );
9150 }))
9151 .child(
9152 h_flex()
9153 .flex_1()
9154 .gap_2()
9155 .child(Icon::new(provider_icon))
9156 .child(Label::new("Accept Terms of Service"))
9157 .child(div().w_full())
9158 .child(
9159 Icon::new(IconName::ArrowUpRight)
9160 .color(Color::Muted)
9161 .size(IconSize::Small),
9162 )
9163 .into_any_element(),
9164 )
9165 .into_any(),
9166 );
9167 }
9168
9169 let is_refreshing = provider.provider.is_refreshing(cx);
9170
9171 fn pending_completion_container(icon: IconName) -> Div {
9172 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9173 }
9174
9175 let completion = match &self.active_edit_prediction {
9176 Some(prediction) => {
9177 if !self.has_visible_completions_menu() {
9178 const RADIUS: Pixels = px(6.);
9179 const BORDER_WIDTH: Pixels = px(1.);
9180
9181 return Some(
9182 h_flex()
9183 .elevation_2(cx)
9184 .border(BORDER_WIDTH)
9185 .border_color(cx.theme().colors().border)
9186 .when(accept_keystroke.is_none(), |el| {
9187 el.border_color(cx.theme().status().error)
9188 })
9189 .rounded(RADIUS)
9190 .rounded_tl(px(0.))
9191 .overflow_hidden()
9192 .child(div().px_1p5().child(match &prediction.completion {
9193 EditPrediction::Move { target, snapshot } => {
9194 use text::ToPoint as _;
9195 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9196 {
9197 Icon::new(IconName::ZedPredictDown)
9198 } else {
9199 Icon::new(IconName::ZedPredictUp)
9200 }
9201 }
9202 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9203 }))
9204 .child(
9205 h_flex()
9206 .gap_1()
9207 .py_1()
9208 .px_2()
9209 .rounded_r(RADIUS - BORDER_WIDTH)
9210 .border_l_1()
9211 .border_color(cx.theme().colors().border)
9212 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9213 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9214 el.child(
9215 Label::new("Hold")
9216 .size(LabelSize::Small)
9217 .when(accept_keystroke.is_none(), |el| {
9218 el.strikethrough()
9219 })
9220 .line_height_style(LineHeightStyle::UiLabel),
9221 )
9222 })
9223 .id("edit_prediction_cursor_popover_keybind")
9224 .when(accept_keystroke.is_none(), |el| {
9225 let status_colors = cx.theme().status();
9226
9227 el.bg(status_colors.error_background)
9228 .border_color(status_colors.error.opacity(0.6))
9229 .child(Icon::new(IconName::Info).color(Color::Error))
9230 .cursor_default()
9231 .hoverable_tooltip(move |_window, cx| {
9232 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9233 .into()
9234 })
9235 })
9236 .when_some(
9237 accept_keystroke.as_ref(),
9238 |el, accept_keystroke| {
9239 el.child(h_flex().children(ui::render_modifiers(
9240 &accept_keystroke.modifiers,
9241 PlatformStyle::platform(),
9242 Some(Color::Default),
9243 Some(IconSize::XSmall.rems().into()),
9244 false,
9245 )))
9246 },
9247 ),
9248 )
9249 .into_any(),
9250 );
9251 }
9252
9253 self.render_edit_prediction_cursor_popover_preview(
9254 prediction,
9255 cursor_point,
9256 style,
9257 cx,
9258 )?
9259 }
9260
9261 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9262 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9263 stale_completion,
9264 cursor_point,
9265 style,
9266 cx,
9267 )?,
9268
9269 None => pending_completion_container(provider_icon)
9270 .child(Label::new("...").size(LabelSize::Small)),
9271 },
9272
9273 None => pending_completion_container(provider_icon)
9274 .child(Label::new("...").size(LabelSize::Small)),
9275 };
9276
9277 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9278 completion
9279 .with_animation(
9280 "loading-completion",
9281 Animation::new(Duration::from_secs(2))
9282 .repeat()
9283 .with_easing(pulsating_between(0.4, 0.8)),
9284 |label, delta| label.opacity(delta),
9285 )
9286 .into_any_element()
9287 } else {
9288 completion.into_any_element()
9289 };
9290
9291 let has_completion = self.active_edit_prediction.is_some();
9292
9293 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9294 Some(
9295 h_flex()
9296 .min_w(min_width)
9297 .max_w(max_width)
9298 .flex_1()
9299 .elevation_2(cx)
9300 .border_color(cx.theme().colors().border)
9301 .child(
9302 div()
9303 .flex_1()
9304 .py_1()
9305 .px_2()
9306 .overflow_hidden()
9307 .child(completion),
9308 )
9309 .when_some(accept_keystroke, |el, accept_keystroke| {
9310 if !accept_keystroke.modifiers.modified() {
9311 return el;
9312 }
9313
9314 el.child(
9315 h_flex()
9316 .h_full()
9317 .border_l_1()
9318 .rounded_r_lg()
9319 .border_color(cx.theme().colors().border)
9320 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9321 .gap_1()
9322 .py_1()
9323 .px_2()
9324 .child(
9325 h_flex()
9326 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9327 .when(is_platform_style_mac, |parent| parent.gap_1())
9328 .child(h_flex().children(ui::render_modifiers(
9329 &accept_keystroke.modifiers,
9330 PlatformStyle::platform(),
9331 Some(if !has_completion {
9332 Color::Muted
9333 } else {
9334 Color::Default
9335 }),
9336 None,
9337 false,
9338 ))),
9339 )
9340 .child(Label::new("Preview").into_any_element())
9341 .opacity(if has_completion { 1.0 } else { 0.4 }),
9342 )
9343 })
9344 .into_any(),
9345 )
9346 }
9347
9348 fn render_edit_prediction_cursor_popover_preview(
9349 &self,
9350 completion: &EditPredictionState,
9351 cursor_point: Point,
9352 style: &EditorStyle,
9353 cx: &mut Context<Editor>,
9354 ) -> Option<Div> {
9355 use text::ToPoint as _;
9356
9357 fn render_relative_row_jump(
9358 prefix: impl Into<String>,
9359 current_row: u32,
9360 target_row: u32,
9361 ) -> Div {
9362 let (row_diff, arrow) = if target_row < current_row {
9363 (current_row - target_row, IconName::ArrowUp)
9364 } else {
9365 (target_row - current_row, IconName::ArrowDown)
9366 };
9367
9368 h_flex()
9369 .child(
9370 Label::new(format!("{}{}", prefix.into(), row_diff))
9371 .color(Color::Muted)
9372 .size(LabelSize::Small),
9373 )
9374 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9375 }
9376
9377 let supports_jump = self
9378 .edit_prediction_provider
9379 .as_ref()
9380 .map(|provider| provider.provider.supports_jump_to_edit())
9381 .unwrap_or(true);
9382
9383 match &completion.completion {
9384 EditPrediction::Move {
9385 target, snapshot, ..
9386 } => {
9387 if !supports_jump {
9388 return None;
9389 }
9390
9391 Some(
9392 h_flex()
9393 .px_2()
9394 .gap_2()
9395 .flex_1()
9396 .child(
9397 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9398 Icon::new(IconName::ZedPredictDown)
9399 } else {
9400 Icon::new(IconName::ZedPredictUp)
9401 },
9402 )
9403 .child(Label::new("Jump to Edit")),
9404 )
9405 }
9406
9407 EditPrediction::Edit {
9408 edits,
9409 edit_preview,
9410 snapshot,
9411 display_mode: _,
9412 } => {
9413 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9414
9415 let (highlighted_edits, has_more_lines) =
9416 if let Some(edit_preview) = edit_preview.as_ref() {
9417 crate::edit_prediction_edit_text(&snapshot, &edits, edit_preview, true, cx)
9418 .first_line_preview()
9419 } else {
9420 crate::edit_prediction_fallback_text(&edits, cx).first_line_preview()
9421 };
9422
9423 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9424 .with_default_highlights(&style.text, highlighted_edits.highlights);
9425
9426 let preview = h_flex()
9427 .gap_1()
9428 .min_w_16()
9429 .child(styled_text)
9430 .when(has_more_lines, |parent| parent.child("…"));
9431
9432 let left = if supports_jump && first_edit_row != cursor_point.row {
9433 render_relative_row_jump("", cursor_point.row, first_edit_row)
9434 .into_any_element()
9435 } else {
9436 let icon_name =
9437 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9438 Icon::new(icon_name).into_any_element()
9439 };
9440
9441 Some(
9442 h_flex()
9443 .h_full()
9444 .flex_1()
9445 .gap_2()
9446 .pr_1()
9447 .overflow_x_hidden()
9448 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9449 .child(left)
9450 .child(preview),
9451 )
9452 }
9453 }
9454 }
9455
9456 pub fn render_context_menu(
9457 &self,
9458 style: &EditorStyle,
9459 max_height_in_lines: u32,
9460 window: &mut Window,
9461 cx: &mut Context<Editor>,
9462 ) -> Option<AnyElement> {
9463 let menu = self.context_menu.borrow();
9464 let menu = menu.as_ref()?;
9465 if !menu.visible() {
9466 return None;
9467 };
9468 Some(menu.render(style, max_height_in_lines, window, cx))
9469 }
9470
9471 fn render_context_menu_aside(
9472 &mut self,
9473 max_size: Size<Pixels>,
9474 window: &mut Window,
9475 cx: &mut Context<Editor>,
9476 ) -> Option<AnyElement> {
9477 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9478 if menu.visible() {
9479 menu.render_aside(max_size, window, cx)
9480 } else {
9481 None
9482 }
9483 })
9484 }
9485
9486 fn hide_context_menu(
9487 &mut self,
9488 window: &mut Window,
9489 cx: &mut Context<Self>,
9490 ) -> Option<CodeContextMenu> {
9491 cx.notify();
9492 self.completion_tasks.clear();
9493 let context_menu = self.context_menu.borrow_mut().take();
9494 self.stale_edit_prediction_in_menu.take();
9495 self.update_visible_edit_prediction(window, cx);
9496 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9497 if let Some(completion_provider) = &self.completion_provider {
9498 completion_provider.selection_changed(None, window, cx);
9499 }
9500 }
9501 context_menu
9502 }
9503
9504 fn show_snippet_choices(
9505 &mut self,
9506 choices: &Vec<String>,
9507 selection: Range<Anchor>,
9508 cx: &mut Context<Self>,
9509 ) {
9510 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9511 (Some(a), Some(b)) if a == b => a,
9512 _ => {
9513 log::error!("expected anchor range to have matching buffer IDs");
9514 return;
9515 }
9516 };
9517 let multi_buffer = self.buffer().read(cx);
9518 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9519 return;
9520 };
9521
9522 let id = post_inc(&mut self.next_completion_id);
9523 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9524 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9525 CompletionsMenu::new_snippet_choices(
9526 id,
9527 true,
9528 choices,
9529 selection,
9530 buffer,
9531 snippet_sort_order,
9532 ),
9533 ));
9534 }
9535
9536 pub fn insert_snippet(
9537 &mut self,
9538 insertion_ranges: &[Range<usize>],
9539 snippet: Snippet,
9540 window: &mut Window,
9541 cx: &mut Context<Self>,
9542 ) -> Result<()> {
9543 struct Tabstop<T> {
9544 is_end_tabstop: bool,
9545 ranges: Vec<Range<T>>,
9546 choices: Option<Vec<String>>,
9547 }
9548
9549 let tabstops = self.buffer.update(cx, |buffer, cx| {
9550 let snippet_text: Arc<str> = snippet.text.clone().into();
9551 let edits = insertion_ranges
9552 .iter()
9553 .cloned()
9554 .map(|range| (range, snippet_text.clone()));
9555 let autoindent_mode = AutoindentMode::Block {
9556 original_indent_columns: Vec::new(),
9557 };
9558 buffer.edit(edits, Some(autoindent_mode), cx);
9559
9560 let snapshot = &*buffer.read(cx);
9561 let snippet = &snippet;
9562 snippet
9563 .tabstops
9564 .iter()
9565 .map(|tabstop| {
9566 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9567 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9568 });
9569 let mut tabstop_ranges = tabstop
9570 .ranges
9571 .iter()
9572 .flat_map(|tabstop_range| {
9573 let mut delta = 0_isize;
9574 insertion_ranges.iter().map(move |insertion_range| {
9575 let insertion_start = insertion_range.start as isize + delta;
9576 delta +=
9577 snippet.text.len() as isize - insertion_range.len() as isize;
9578
9579 let start = ((insertion_start + tabstop_range.start) as usize)
9580 .min(snapshot.len());
9581 let end = ((insertion_start + tabstop_range.end) as usize)
9582 .min(snapshot.len());
9583 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9584 })
9585 })
9586 .collect::<Vec<_>>();
9587 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9588
9589 Tabstop {
9590 is_end_tabstop,
9591 ranges: tabstop_ranges,
9592 choices: tabstop.choices.clone(),
9593 }
9594 })
9595 .collect::<Vec<_>>()
9596 });
9597 if let Some(tabstop) = tabstops.first() {
9598 self.change_selections(Default::default(), window, cx, |s| {
9599 // Reverse order so that the first range is the newest created selection.
9600 // Completions will use it and autoscroll will prioritize it.
9601 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9602 });
9603
9604 if let Some(choices) = &tabstop.choices {
9605 if let Some(selection) = tabstop.ranges.first() {
9606 self.show_snippet_choices(choices, selection.clone(), cx)
9607 }
9608 }
9609
9610 // If we're already at the last tabstop and it's at the end of the snippet,
9611 // we're done, we don't need to keep the state around.
9612 if !tabstop.is_end_tabstop {
9613 let choices = tabstops
9614 .iter()
9615 .map(|tabstop| tabstop.choices.clone())
9616 .collect();
9617
9618 let ranges = tabstops
9619 .into_iter()
9620 .map(|tabstop| tabstop.ranges)
9621 .collect::<Vec<_>>();
9622
9623 self.snippet_stack.push(SnippetState {
9624 active_index: 0,
9625 ranges,
9626 choices,
9627 });
9628 }
9629
9630 // Check whether the just-entered snippet ends with an auto-closable bracket.
9631 if self.autoclose_regions.is_empty() {
9632 let snapshot = self.buffer.read(cx).snapshot(cx);
9633 let mut all_selections = self.selections.all::<Point>(cx);
9634 for selection in &mut all_selections {
9635 let selection_head = selection.head();
9636 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9637 continue;
9638 };
9639
9640 let mut bracket_pair = None;
9641 let max_lookup_length = scope
9642 .brackets()
9643 .map(|(pair, _)| {
9644 pair.start
9645 .as_str()
9646 .chars()
9647 .count()
9648 .max(pair.end.as_str().chars().count())
9649 })
9650 .max();
9651 if let Some(max_lookup_length) = max_lookup_length {
9652 let next_text = snapshot
9653 .chars_at(selection_head)
9654 .take(max_lookup_length)
9655 .collect::<String>();
9656 let prev_text = snapshot
9657 .reversed_chars_at(selection_head)
9658 .take(max_lookup_length)
9659 .collect::<String>();
9660
9661 for (pair, enabled) in scope.brackets() {
9662 if enabled
9663 && pair.close
9664 && prev_text.starts_with(pair.start.as_str())
9665 && next_text.starts_with(pair.end.as_str())
9666 {
9667 bracket_pair = Some(pair.clone());
9668 break;
9669 }
9670 }
9671 }
9672
9673 if let Some(pair) = bracket_pair {
9674 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9675 let autoclose_enabled =
9676 self.use_autoclose && snapshot_settings.use_autoclose;
9677 if autoclose_enabled {
9678 let start = snapshot.anchor_after(selection_head);
9679 let end = snapshot.anchor_after(selection_head);
9680 self.autoclose_regions.push(AutocloseRegion {
9681 selection_id: selection.id,
9682 range: start..end,
9683 pair,
9684 });
9685 }
9686 }
9687 }
9688 }
9689 }
9690 Ok(())
9691 }
9692
9693 pub fn move_to_next_snippet_tabstop(
9694 &mut self,
9695 window: &mut Window,
9696 cx: &mut Context<Self>,
9697 ) -> bool {
9698 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9699 }
9700
9701 pub fn move_to_prev_snippet_tabstop(
9702 &mut self,
9703 window: &mut Window,
9704 cx: &mut Context<Self>,
9705 ) -> bool {
9706 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9707 }
9708
9709 pub fn move_to_snippet_tabstop(
9710 &mut self,
9711 bias: Bias,
9712 window: &mut Window,
9713 cx: &mut Context<Self>,
9714 ) -> bool {
9715 if let Some(mut snippet) = self.snippet_stack.pop() {
9716 match bias {
9717 Bias::Left => {
9718 if snippet.active_index > 0 {
9719 snippet.active_index -= 1;
9720 } else {
9721 self.snippet_stack.push(snippet);
9722 return false;
9723 }
9724 }
9725 Bias::Right => {
9726 if snippet.active_index + 1 < snippet.ranges.len() {
9727 snippet.active_index += 1;
9728 } else {
9729 self.snippet_stack.push(snippet);
9730 return false;
9731 }
9732 }
9733 }
9734 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9735 self.change_selections(Default::default(), window, cx, |s| {
9736 // Reverse order so that the first range is the newest created selection.
9737 // Completions will use it and autoscroll will prioritize it.
9738 s.select_ranges(current_ranges.iter().rev().cloned())
9739 });
9740
9741 if let Some(choices) = &snippet.choices[snippet.active_index] {
9742 if let Some(selection) = current_ranges.first() {
9743 self.show_snippet_choices(&choices, selection.clone(), cx);
9744 }
9745 }
9746
9747 // If snippet state is not at the last tabstop, push it back on the stack
9748 if snippet.active_index + 1 < snippet.ranges.len() {
9749 self.snippet_stack.push(snippet);
9750 }
9751 return true;
9752 }
9753 }
9754
9755 false
9756 }
9757
9758 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9759 self.transact(window, cx, |this, window, cx| {
9760 this.select_all(&SelectAll, window, cx);
9761 this.insert("", window, cx);
9762 });
9763 }
9764
9765 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9767 self.transact(window, cx, |this, window, cx| {
9768 this.select_autoclose_pair(window, cx);
9769 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9770 if !this.linked_edit_ranges.is_empty() {
9771 let selections = this.selections.all::<MultiBufferPoint>(cx);
9772 let snapshot = this.buffer.read(cx).snapshot(cx);
9773
9774 for selection in selections.iter() {
9775 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9776 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9777 if selection_start.buffer_id != selection_end.buffer_id {
9778 continue;
9779 }
9780 if let Some(ranges) =
9781 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9782 {
9783 for (buffer, entries) in ranges {
9784 linked_ranges.entry(buffer).or_default().extend(entries);
9785 }
9786 }
9787 }
9788 }
9789
9790 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9791 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9792 for selection in &mut selections {
9793 if selection.is_empty() {
9794 let old_head = selection.head();
9795 let mut new_head =
9796 movement::left(&display_map, old_head.to_display_point(&display_map))
9797 .to_point(&display_map);
9798 if let Some((buffer, line_buffer_range)) = display_map
9799 .buffer_snapshot
9800 .buffer_line_for_row(MultiBufferRow(old_head.row))
9801 {
9802 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9803 let indent_len = match indent_size.kind {
9804 IndentKind::Space => {
9805 buffer.settings_at(line_buffer_range.start, cx).tab_size
9806 }
9807 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9808 };
9809 if old_head.column <= indent_size.len && old_head.column > 0 {
9810 let indent_len = indent_len.get();
9811 new_head = cmp::min(
9812 new_head,
9813 MultiBufferPoint::new(
9814 old_head.row,
9815 ((old_head.column - 1) / indent_len) * indent_len,
9816 ),
9817 );
9818 }
9819 }
9820
9821 selection.set_head(new_head, SelectionGoal::None);
9822 }
9823 }
9824
9825 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9826 this.insert("", window, cx);
9827 let empty_str: Arc<str> = Arc::from("");
9828 for (buffer, edits) in linked_ranges {
9829 let snapshot = buffer.read(cx).snapshot();
9830 use text::ToPoint as TP;
9831
9832 let edits = edits
9833 .into_iter()
9834 .map(|range| {
9835 let end_point = TP::to_point(&range.end, &snapshot);
9836 let mut start_point = TP::to_point(&range.start, &snapshot);
9837
9838 if end_point == start_point {
9839 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9840 .saturating_sub(1);
9841 start_point =
9842 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9843 };
9844
9845 (start_point..end_point, empty_str.clone())
9846 })
9847 .sorted_by_key(|(range, _)| range.start)
9848 .collect::<Vec<_>>();
9849 buffer.update(cx, |this, cx| {
9850 this.edit(edits, None, cx);
9851 })
9852 }
9853 this.refresh_edit_prediction(true, false, window, cx);
9854 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9855 });
9856 }
9857
9858 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9859 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9860 self.transact(window, cx, |this, window, cx| {
9861 this.change_selections(Default::default(), window, cx, |s| {
9862 s.move_with(|map, selection| {
9863 if selection.is_empty() {
9864 let cursor = movement::right(map, selection.head());
9865 selection.end = cursor;
9866 selection.reversed = true;
9867 selection.goal = SelectionGoal::None;
9868 }
9869 })
9870 });
9871 this.insert("", window, cx);
9872 this.refresh_edit_prediction(true, false, window, cx);
9873 });
9874 }
9875
9876 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9877 if self.mode.is_single_line() {
9878 cx.propagate();
9879 return;
9880 }
9881
9882 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9883 if self.move_to_prev_snippet_tabstop(window, cx) {
9884 return;
9885 }
9886 self.outdent(&Outdent, window, cx);
9887 }
9888
9889 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9890 if self.mode.is_single_line() {
9891 cx.propagate();
9892 return;
9893 }
9894
9895 if self.move_to_next_snippet_tabstop(window, cx) {
9896 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9897 return;
9898 }
9899 if self.read_only(cx) {
9900 return;
9901 }
9902 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9903 let mut selections = self.selections.all_adjusted(cx);
9904 let buffer = self.buffer.read(cx);
9905 let snapshot = buffer.snapshot(cx);
9906 let rows_iter = selections.iter().map(|s| s.head().row);
9907 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9908
9909 let has_some_cursor_in_whitespace = selections
9910 .iter()
9911 .filter(|selection| selection.is_empty())
9912 .any(|selection| {
9913 let cursor = selection.head();
9914 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9915 cursor.column < current_indent.len
9916 });
9917
9918 let mut edits = Vec::new();
9919 let mut prev_edited_row = 0;
9920 let mut row_delta = 0;
9921 for selection in &mut selections {
9922 if selection.start.row != prev_edited_row {
9923 row_delta = 0;
9924 }
9925 prev_edited_row = selection.end.row;
9926
9927 // If the selection is non-empty, then increase the indentation of the selected lines.
9928 if !selection.is_empty() {
9929 row_delta =
9930 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9931 continue;
9932 }
9933
9934 let cursor = selection.head();
9935 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9936 if let Some(suggested_indent) =
9937 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9938 {
9939 // Don't do anything if already at suggested indent
9940 // and there is any other cursor which is not
9941 if has_some_cursor_in_whitespace
9942 && cursor.column == current_indent.len
9943 && current_indent.len == suggested_indent.len
9944 {
9945 continue;
9946 }
9947
9948 // Adjust line and move cursor to suggested indent
9949 // if cursor is not at suggested indent
9950 if cursor.column < suggested_indent.len
9951 && cursor.column <= current_indent.len
9952 && current_indent.len <= suggested_indent.len
9953 {
9954 selection.start = Point::new(cursor.row, suggested_indent.len);
9955 selection.end = selection.start;
9956 if row_delta == 0 {
9957 edits.extend(Buffer::edit_for_indent_size_adjustment(
9958 cursor.row,
9959 current_indent,
9960 suggested_indent,
9961 ));
9962 row_delta = suggested_indent.len - current_indent.len;
9963 }
9964 continue;
9965 }
9966
9967 // If current indent is more than suggested indent
9968 // only move cursor to current indent and skip indent
9969 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9970 selection.start = Point::new(cursor.row, current_indent.len);
9971 selection.end = selection.start;
9972 continue;
9973 }
9974 }
9975
9976 // Otherwise, insert a hard or soft tab.
9977 let settings = buffer.language_settings_at(cursor, cx);
9978 let tab_size = if settings.hard_tabs {
9979 IndentSize::tab()
9980 } else {
9981 let tab_size = settings.tab_size.get();
9982 let indent_remainder = snapshot
9983 .text_for_range(Point::new(cursor.row, 0)..cursor)
9984 .flat_map(str::chars)
9985 .fold(row_delta % tab_size, |counter: u32, c| {
9986 if c == '\t' {
9987 0
9988 } else {
9989 (counter + 1) % tab_size
9990 }
9991 });
9992
9993 let chars_to_next_tab_stop = tab_size - indent_remainder;
9994 IndentSize::spaces(chars_to_next_tab_stop)
9995 };
9996 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9997 selection.end = selection.start;
9998 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9999 row_delta += tab_size.len;
10000 }
10001
10002 self.transact(window, cx, |this, window, cx| {
10003 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10004 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10005 this.refresh_edit_prediction(true, false, window, cx);
10006 });
10007 }
10008
10009 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10010 if self.read_only(cx) {
10011 return;
10012 }
10013 if self.mode.is_single_line() {
10014 cx.propagate();
10015 return;
10016 }
10017
10018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10019 let mut selections = self.selections.all::<Point>(cx);
10020 let mut prev_edited_row = 0;
10021 let mut row_delta = 0;
10022 let mut edits = Vec::new();
10023 let buffer = self.buffer.read(cx);
10024 let snapshot = buffer.snapshot(cx);
10025 for selection in &mut selections {
10026 if selection.start.row != prev_edited_row {
10027 row_delta = 0;
10028 }
10029 prev_edited_row = selection.end.row;
10030
10031 row_delta =
10032 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10033 }
10034
10035 self.transact(window, cx, |this, window, cx| {
10036 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10037 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10038 });
10039 }
10040
10041 fn indent_selection(
10042 buffer: &MultiBuffer,
10043 snapshot: &MultiBufferSnapshot,
10044 selection: &mut Selection<Point>,
10045 edits: &mut Vec<(Range<Point>, String)>,
10046 delta_for_start_row: u32,
10047 cx: &App,
10048 ) -> u32 {
10049 let settings = buffer.language_settings_at(selection.start, cx);
10050 let tab_size = settings.tab_size.get();
10051 let indent_kind = if settings.hard_tabs {
10052 IndentKind::Tab
10053 } else {
10054 IndentKind::Space
10055 };
10056 let mut start_row = selection.start.row;
10057 let mut end_row = selection.end.row + 1;
10058
10059 // If a selection ends at the beginning of a line, don't indent
10060 // that last line.
10061 if selection.end.column == 0 && selection.end.row > selection.start.row {
10062 end_row -= 1;
10063 }
10064
10065 // Avoid re-indenting a row that has already been indented by a
10066 // previous selection, but still update this selection's column
10067 // to reflect that indentation.
10068 if delta_for_start_row > 0 {
10069 start_row += 1;
10070 selection.start.column += delta_for_start_row;
10071 if selection.end.row == selection.start.row {
10072 selection.end.column += delta_for_start_row;
10073 }
10074 }
10075
10076 let mut delta_for_end_row = 0;
10077 let has_multiple_rows = start_row + 1 != end_row;
10078 for row in start_row..end_row {
10079 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10080 let indent_delta = match (current_indent.kind, indent_kind) {
10081 (IndentKind::Space, IndentKind::Space) => {
10082 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10083 IndentSize::spaces(columns_to_next_tab_stop)
10084 }
10085 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10086 (_, IndentKind::Tab) => IndentSize::tab(),
10087 };
10088
10089 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10090 0
10091 } else {
10092 selection.start.column
10093 };
10094 let row_start = Point::new(row, start);
10095 edits.push((
10096 row_start..row_start,
10097 indent_delta.chars().collect::<String>(),
10098 ));
10099
10100 // Update this selection's endpoints to reflect the indentation.
10101 if row == selection.start.row {
10102 selection.start.column += indent_delta.len;
10103 }
10104 if row == selection.end.row {
10105 selection.end.column += indent_delta.len;
10106 delta_for_end_row = indent_delta.len;
10107 }
10108 }
10109
10110 if selection.start.row == selection.end.row {
10111 delta_for_start_row + delta_for_end_row
10112 } else {
10113 delta_for_end_row
10114 }
10115 }
10116
10117 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10118 if self.read_only(cx) {
10119 return;
10120 }
10121 if self.mode.is_single_line() {
10122 cx.propagate();
10123 return;
10124 }
10125
10126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10127 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10128 let selections = self.selections.all::<Point>(cx);
10129 let mut deletion_ranges = Vec::new();
10130 let mut last_outdent = None;
10131 {
10132 let buffer = self.buffer.read(cx);
10133 let snapshot = buffer.snapshot(cx);
10134 for selection in &selections {
10135 let settings = buffer.language_settings_at(selection.start, cx);
10136 let tab_size = settings.tab_size.get();
10137 let mut rows = selection.spanned_rows(false, &display_map);
10138
10139 // Avoid re-outdenting a row that has already been outdented by a
10140 // previous selection.
10141 if let Some(last_row) = last_outdent {
10142 if last_row == rows.start {
10143 rows.start = rows.start.next_row();
10144 }
10145 }
10146 let has_multiple_rows = rows.len() > 1;
10147 for row in rows.iter_rows() {
10148 let indent_size = snapshot.indent_size_for_line(row);
10149 if indent_size.len > 0 {
10150 let deletion_len = match indent_size.kind {
10151 IndentKind::Space => {
10152 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10153 if columns_to_prev_tab_stop == 0 {
10154 tab_size
10155 } else {
10156 columns_to_prev_tab_stop
10157 }
10158 }
10159 IndentKind::Tab => 1,
10160 };
10161 let start = if has_multiple_rows
10162 || deletion_len > selection.start.column
10163 || indent_size.len < selection.start.column
10164 {
10165 0
10166 } else {
10167 selection.start.column - deletion_len
10168 };
10169 deletion_ranges.push(
10170 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10171 );
10172 last_outdent = Some(row);
10173 }
10174 }
10175 }
10176 }
10177
10178 self.transact(window, cx, |this, window, cx| {
10179 this.buffer.update(cx, |buffer, cx| {
10180 let empty_str: Arc<str> = Arc::default();
10181 buffer.edit(
10182 deletion_ranges
10183 .into_iter()
10184 .map(|range| (range, empty_str.clone())),
10185 None,
10186 cx,
10187 );
10188 });
10189 let selections = this.selections.all::<usize>(cx);
10190 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10191 });
10192 }
10193
10194 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10195 if self.read_only(cx) {
10196 return;
10197 }
10198 if self.mode.is_single_line() {
10199 cx.propagate();
10200 return;
10201 }
10202
10203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10204 let selections = self
10205 .selections
10206 .all::<usize>(cx)
10207 .into_iter()
10208 .map(|s| s.range());
10209
10210 self.transact(window, cx, |this, window, cx| {
10211 this.buffer.update(cx, |buffer, cx| {
10212 buffer.autoindent_ranges(selections, cx);
10213 });
10214 let selections = this.selections.all::<usize>(cx);
10215 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10216 });
10217 }
10218
10219 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10220 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10221 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10222 let selections = self.selections.all::<Point>(cx);
10223
10224 let mut new_cursors = Vec::new();
10225 let mut edit_ranges = Vec::new();
10226 let mut selections = selections.iter().peekable();
10227 while let Some(selection) = selections.next() {
10228 let mut rows = selection.spanned_rows(false, &display_map);
10229 let goal_display_column = selection.head().to_display_point(&display_map).column();
10230
10231 // Accumulate contiguous regions of rows that we want to delete.
10232 while let Some(next_selection) = selections.peek() {
10233 let next_rows = next_selection.spanned_rows(false, &display_map);
10234 if next_rows.start <= rows.end {
10235 rows.end = next_rows.end;
10236 selections.next().unwrap();
10237 } else {
10238 break;
10239 }
10240 }
10241
10242 let buffer = &display_map.buffer_snapshot;
10243 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10244 let edit_end;
10245 let cursor_buffer_row;
10246 if buffer.max_point().row >= rows.end.0 {
10247 // If there's a line after the range, delete the \n from the end of the row range
10248 // and position the cursor on the next line.
10249 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10250 cursor_buffer_row = rows.end;
10251 } else {
10252 // If there isn't a line after the range, delete the \n from the line before the
10253 // start of the row range and position the cursor there.
10254 edit_start = edit_start.saturating_sub(1);
10255 edit_end = buffer.len();
10256 cursor_buffer_row = rows.start.previous_row();
10257 }
10258
10259 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10260 *cursor.column_mut() =
10261 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10262
10263 new_cursors.push((
10264 selection.id,
10265 buffer.anchor_after(cursor.to_point(&display_map)),
10266 ));
10267 edit_ranges.push(edit_start..edit_end);
10268 }
10269
10270 self.transact(window, cx, |this, window, cx| {
10271 let buffer = this.buffer.update(cx, |buffer, cx| {
10272 let empty_str: Arc<str> = Arc::default();
10273 buffer.edit(
10274 edit_ranges
10275 .into_iter()
10276 .map(|range| (range, empty_str.clone())),
10277 None,
10278 cx,
10279 );
10280 buffer.snapshot(cx)
10281 });
10282 let new_selections = new_cursors
10283 .into_iter()
10284 .map(|(id, cursor)| {
10285 let cursor = cursor.to_point(&buffer);
10286 Selection {
10287 id,
10288 start: cursor,
10289 end: cursor,
10290 reversed: false,
10291 goal: SelectionGoal::None,
10292 }
10293 })
10294 .collect();
10295
10296 this.change_selections(Default::default(), window, cx, |s| {
10297 s.select(new_selections);
10298 });
10299 });
10300 }
10301
10302 pub fn join_lines_impl(
10303 &mut self,
10304 insert_whitespace: bool,
10305 window: &mut Window,
10306 cx: &mut Context<Self>,
10307 ) {
10308 if self.read_only(cx) {
10309 return;
10310 }
10311 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10312 for selection in self.selections.all::<Point>(cx) {
10313 let start = MultiBufferRow(selection.start.row);
10314 // Treat single line selections as if they include the next line. Otherwise this action
10315 // would do nothing for single line selections individual cursors.
10316 let end = if selection.start.row == selection.end.row {
10317 MultiBufferRow(selection.start.row + 1)
10318 } else {
10319 MultiBufferRow(selection.end.row)
10320 };
10321
10322 if let Some(last_row_range) = row_ranges.last_mut() {
10323 if start <= last_row_range.end {
10324 last_row_range.end = end;
10325 continue;
10326 }
10327 }
10328 row_ranges.push(start..end);
10329 }
10330
10331 let snapshot = self.buffer.read(cx).snapshot(cx);
10332 let mut cursor_positions = Vec::new();
10333 for row_range in &row_ranges {
10334 let anchor = snapshot.anchor_before(Point::new(
10335 row_range.end.previous_row().0,
10336 snapshot.line_len(row_range.end.previous_row()),
10337 ));
10338 cursor_positions.push(anchor..anchor);
10339 }
10340
10341 self.transact(window, cx, |this, window, cx| {
10342 for row_range in row_ranges.into_iter().rev() {
10343 for row in row_range.iter_rows().rev() {
10344 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10345 let next_line_row = row.next_row();
10346 let indent = snapshot.indent_size_for_line(next_line_row);
10347 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10348
10349 let replace =
10350 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10351 " "
10352 } else {
10353 ""
10354 };
10355
10356 this.buffer.update(cx, |buffer, cx| {
10357 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10358 });
10359 }
10360 }
10361
10362 this.change_selections(Default::default(), window, cx, |s| {
10363 s.select_anchor_ranges(cursor_positions)
10364 });
10365 });
10366 }
10367
10368 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10369 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10370 self.join_lines_impl(true, window, cx);
10371 }
10372
10373 pub fn sort_lines_case_sensitive(
10374 &mut self,
10375 _: &SortLinesCaseSensitive,
10376 window: &mut Window,
10377 cx: &mut Context<Self>,
10378 ) {
10379 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10380 }
10381
10382 pub fn sort_lines_by_length(
10383 &mut self,
10384 _: &SortLinesByLength,
10385 window: &mut Window,
10386 cx: &mut Context<Self>,
10387 ) {
10388 self.manipulate_immutable_lines(window, cx, |lines| {
10389 lines.sort_by_key(|&line| line.chars().count())
10390 })
10391 }
10392
10393 pub fn sort_lines_case_insensitive(
10394 &mut self,
10395 _: &SortLinesCaseInsensitive,
10396 window: &mut Window,
10397 cx: &mut Context<Self>,
10398 ) {
10399 self.manipulate_immutable_lines(window, cx, |lines| {
10400 lines.sort_by_key(|line| line.to_lowercase())
10401 })
10402 }
10403
10404 pub fn unique_lines_case_insensitive(
10405 &mut self,
10406 _: &UniqueLinesCaseInsensitive,
10407 window: &mut Window,
10408 cx: &mut Context<Self>,
10409 ) {
10410 self.manipulate_immutable_lines(window, cx, |lines| {
10411 let mut seen = HashSet::default();
10412 lines.retain(|line| seen.insert(line.to_lowercase()));
10413 })
10414 }
10415
10416 pub fn unique_lines_case_sensitive(
10417 &mut self,
10418 _: &UniqueLinesCaseSensitive,
10419 window: &mut Window,
10420 cx: &mut Context<Self>,
10421 ) {
10422 self.manipulate_immutable_lines(window, cx, |lines| {
10423 let mut seen = HashSet::default();
10424 lines.retain(|line| seen.insert(*line));
10425 })
10426 }
10427
10428 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10429 let Some(project) = self.project.clone() else {
10430 return;
10431 };
10432 self.reload(project, window, cx)
10433 .detach_and_notify_err(window, cx);
10434 }
10435
10436 pub fn restore_file(
10437 &mut self,
10438 _: &::git::RestoreFile,
10439 window: &mut Window,
10440 cx: &mut Context<Self>,
10441 ) {
10442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10443 let mut buffer_ids = HashSet::default();
10444 let snapshot = self.buffer().read(cx).snapshot(cx);
10445 for selection in self.selections.all::<usize>(cx) {
10446 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10447 }
10448
10449 let buffer = self.buffer().read(cx);
10450 let ranges = buffer_ids
10451 .into_iter()
10452 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10453 .collect::<Vec<_>>();
10454
10455 self.restore_hunks_in_ranges(ranges, window, cx);
10456 }
10457
10458 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10459 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10460 let selections = self
10461 .selections
10462 .all(cx)
10463 .into_iter()
10464 .map(|s| s.range())
10465 .collect();
10466 self.restore_hunks_in_ranges(selections, window, cx);
10467 }
10468
10469 pub fn restore_hunks_in_ranges(
10470 &mut self,
10471 ranges: Vec<Range<Point>>,
10472 window: &mut Window,
10473 cx: &mut Context<Editor>,
10474 ) {
10475 let mut revert_changes = HashMap::default();
10476 let chunk_by = self
10477 .snapshot(window, cx)
10478 .hunks_for_ranges(ranges)
10479 .into_iter()
10480 .chunk_by(|hunk| hunk.buffer_id);
10481 for (buffer_id, hunks) in &chunk_by {
10482 let hunks = hunks.collect::<Vec<_>>();
10483 for hunk in &hunks {
10484 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10485 }
10486 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10487 }
10488 drop(chunk_by);
10489 if !revert_changes.is_empty() {
10490 self.transact(window, cx, |editor, window, cx| {
10491 editor.restore(revert_changes, window, cx);
10492 });
10493 }
10494 }
10495
10496 pub fn open_active_item_in_terminal(
10497 &mut self,
10498 _: &OpenInTerminal,
10499 window: &mut Window,
10500 cx: &mut Context<Self>,
10501 ) {
10502 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10503 let project_path = buffer.read(cx).project_path(cx)?;
10504 let project = self.project.as_ref()?.read(cx);
10505 let entry = project.entry_for_path(&project_path, cx)?;
10506 let parent = match &entry.canonical_path {
10507 Some(canonical_path) => canonical_path.to_path_buf(),
10508 None => project.absolute_path(&project_path, cx)?,
10509 }
10510 .parent()?
10511 .to_path_buf();
10512 Some(parent)
10513 }) {
10514 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10515 }
10516 }
10517
10518 fn set_breakpoint_context_menu(
10519 &mut self,
10520 display_row: DisplayRow,
10521 position: Option<Anchor>,
10522 clicked_point: gpui::Point<Pixels>,
10523 window: &mut Window,
10524 cx: &mut Context<Self>,
10525 ) {
10526 let source = self
10527 .buffer
10528 .read(cx)
10529 .snapshot(cx)
10530 .anchor_before(Point::new(display_row.0, 0u32));
10531
10532 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10533
10534 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10535 self,
10536 source,
10537 clicked_point,
10538 context_menu,
10539 window,
10540 cx,
10541 );
10542 }
10543
10544 fn add_edit_breakpoint_block(
10545 &mut self,
10546 anchor: Anchor,
10547 breakpoint: &Breakpoint,
10548 edit_action: BreakpointPromptEditAction,
10549 window: &mut Window,
10550 cx: &mut Context<Self>,
10551 ) {
10552 let weak_editor = cx.weak_entity();
10553 let bp_prompt = cx.new(|cx| {
10554 BreakpointPromptEditor::new(
10555 weak_editor,
10556 anchor,
10557 breakpoint.clone(),
10558 edit_action,
10559 window,
10560 cx,
10561 )
10562 });
10563
10564 let height = bp_prompt.update(cx, |this, cx| {
10565 this.prompt
10566 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10567 });
10568 let cloned_prompt = bp_prompt.clone();
10569 let blocks = vec![BlockProperties {
10570 style: BlockStyle::Sticky,
10571 placement: BlockPlacement::Above(anchor),
10572 height: Some(height),
10573 render: Arc::new(move |cx| {
10574 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10575 cloned_prompt.clone().into_any_element()
10576 }),
10577 priority: 0,
10578 }];
10579
10580 let focus_handle = bp_prompt.focus_handle(cx);
10581 window.focus(&focus_handle);
10582
10583 let block_ids = self.insert_blocks(blocks, None, cx);
10584 bp_prompt.update(cx, |prompt, _| {
10585 prompt.add_block_ids(block_ids);
10586 });
10587 }
10588
10589 pub(crate) fn breakpoint_at_row(
10590 &self,
10591 row: u32,
10592 window: &mut Window,
10593 cx: &mut Context<Self>,
10594 ) -> Option<(Anchor, Breakpoint)> {
10595 let snapshot = self.snapshot(window, cx);
10596 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10597
10598 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10599 }
10600
10601 pub(crate) fn breakpoint_at_anchor(
10602 &self,
10603 breakpoint_position: Anchor,
10604 snapshot: &EditorSnapshot,
10605 cx: &mut Context<Self>,
10606 ) -> Option<(Anchor, Breakpoint)> {
10607 let project = self.project.clone()?;
10608
10609 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10610 snapshot
10611 .buffer_snapshot
10612 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10613 })?;
10614
10615 let enclosing_excerpt = breakpoint_position.excerpt_id;
10616 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10617 let buffer_snapshot = buffer.read(cx).snapshot();
10618
10619 let row = buffer_snapshot
10620 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10621 .row;
10622
10623 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10624 let anchor_end = snapshot
10625 .buffer_snapshot
10626 .anchor_after(Point::new(row, line_len));
10627
10628 let bp = self
10629 .breakpoint_store
10630 .as_ref()?
10631 .read_with(cx, |breakpoint_store, cx| {
10632 breakpoint_store
10633 .breakpoints(
10634 &buffer,
10635 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10636 &buffer_snapshot,
10637 cx,
10638 )
10639 .next()
10640 .and_then(|(bp, _)| {
10641 let breakpoint_row = buffer_snapshot
10642 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10643 .row;
10644
10645 if breakpoint_row == row {
10646 snapshot
10647 .buffer_snapshot
10648 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10649 .map(|position| (position, bp.bp.clone()))
10650 } else {
10651 None
10652 }
10653 })
10654 });
10655 bp
10656 }
10657
10658 pub fn edit_log_breakpoint(
10659 &mut self,
10660 _: &EditLogBreakpoint,
10661 window: &mut Window,
10662 cx: &mut Context<Self>,
10663 ) {
10664 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10665 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10666 message: None,
10667 state: BreakpointState::Enabled,
10668 condition: None,
10669 hit_condition: None,
10670 });
10671
10672 self.add_edit_breakpoint_block(
10673 anchor,
10674 &breakpoint,
10675 BreakpointPromptEditAction::Log,
10676 window,
10677 cx,
10678 );
10679 }
10680 }
10681
10682 fn breakpoints_at_cursors(
10683 &self,
10684 window: &mut Window,
10685 cx: &mut Context<Self>,
10686 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10687 let snapshot = self.snapshot(window, cx);
10688 let cursors = self
10689 .selections
10690 .disjoint_anchors()
10691 .into_iter()
10692 .map(|selection| {
10693 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10694
10695 let breakpoint_position = self
10696 .breakpoint_at_row(cursor_position.row, window, cx)
10697 .map(|bp| bp.0)
10698 .unwrap_or_else(|| {
10699 snapshot
10700 .display_snapshot
10701 .buffer_snapshot
10702 .anchor_after(Point::new(cursor_position.row, 0))
10703 });
10704
10705 let breakpoint = self
10706 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10707 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10708
10709 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10710 })
10711 // There might be multiple cursors on the same line; all of them should have the same anchors though as their breakpoints positions, which makes it possible to sort and dedup the list.
10712 .collect::<HashMap<Anchor, _>>();
10713
10714 cursors.into_iter().collect()
10715 }
10716
10717 pub fn enable_breakpoint(
10718 &mut self,
10719 _: &crate::actions::EnableBreakpoint,
10720 window: &mut Window,
10721 cx: &mut Context<Self>,
10722 ) {
10723 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10724 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10725 continue;
10726 };
10727 self.edit_breakpoint_at_anchor(
10728 anchor,
10729 breakpoint,
10730 BreakpointEditAction::InvertState,
10731 cx,
10732 );
10733 }
10734 }
10735
10736 pub fn disable_breakpoint(
10737 &mut self,
10738 _: &crate::actions::DisableBreakpoint,
10739 window: &mut Window,
10740 cx: &mut Context<Self>,
10741 ) {
10742 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10743 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10744 continue;
10745 };
10746 self.edit_breakpoint_at_anchor(
10747 anchor,
10748 breakpoint,
10749 BreakpointEditAction::InvertState,
10750 cx,
10751 );
10752 }
10753 }
10754
10755 pub fn toggle_breakpoint(
10756 &mut self,
10757 _: &crate::actions::ToggleBreakpoint,
10758 window: &mut Window,
10759 cx: &mut Context<Self>,
10760 ) {
10761 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10762 if let Some(breakpoint) = breakpoint {
10763 self.edit_breakpoint_at_anchor(
10764 anchor,
10765 breakpoint,
10766 BreakpointEditAction::Toggle,
10767 cx,
10768 );
10769 } else {
10770 self.edit_breakpoint_at_anchor(
10771 anchor,
10772 Breakpoint::new_standard(),
10773 BreakpointEditAction::Toggle,
10774 cx,
10775 );
10776 }
10777 }
10778 }
10779
10780 pub fn edit_breakpoint_at_anchor(
10781 &mut self,
10782 breakpoint_position: Anchor,
10783 breakpoint: Breakpoint,
10784 edit_action: BreakpointEditAction,
10785 cx: &mut Context<Self>,
10786 ) {
10787 let Some(breakpoint_store) = &self.breakpoint_store else {
10788 return;
10789 };
10790
10791 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10792 if breakpoint_position == Anchor::min() {
10793 self.buffer()
10794 .read(cx)
10795 .excerpt_buffer_ids()
10796 .into_iter()
10797 .next()
10798 } else {
10799 None
10800 }
10801 }) else {
10802 return;
10803 };
10804
10805 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10806 return;
10807 };
10808
10809 breakpoint_store.update(cx, |breakpoint_store, cx| {
10810 breakpoint_store.toggle_breakpoint(
10811 buffer,
10812 BreakpointWithPosition {
10813 position: breakpoint_position.text_anchor,
10814 bp: breakpoint,
10815 },
10816 edit_action,
10817 cx,
10818 );
10819 });
10820
10821 cx.notify();
10822 }
10823
10824 #[cfg(any(test, feature = "test-support"))]
10825 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10826 self.breakpoint_store.clone()
10827 }
10828
10829 pub fn prepare_restore_change(
10830 &self,
10831 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10832 hunk: &MultiBufferDiffHunk,
10833 cx: &mut App,
10834 ) -> Option<()> {
10835 if hunk.is_created_file() {
10836 return None;
10837 }
10838 let buffer = self.buffer.read(cx);
10839 let diff = buffer.diff_for(hunk.buffer_id)?;
10840 let buffer = buffer.buffer(hunk.buffer_id)?;
10841 let buffer = buffer.read(cx);
10842 let original_text = diff
10843 .read(cx)
10844 .base_text()
10845 .as_rope()
10846 .slice(hunk.diff_base_byte_range.clone());
10847 let buffer_snapshot = buffer.snapshot();
10848 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10849 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10850 probe
10851 .0
10852 .start
10853 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10854 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10855 }) {
10856 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10857 Some(())
10858 } else {
10859 None
10860 }
10861 }
10862
10863 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10864 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10865 }
10866
10867 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10868 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10869 }
10870
10871 fn manipulate_lines<M>(
10872 &mut self,
10873 window: &mut Window,
10874 cx: &mut Context<Self>,
10875 mut manipulate: M,
10876 ) where
10877 M: FnMut(&str) -> LineManipulationResult,
10878 {
10879 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10880
10881 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10882 let buffer = self.buffer.read(cx).snapshot(cx);
10883
10884 let mut edits = Vec::new();
10885
10886 let selections = self.selections.all::<Point>(cx);
10887 let mut selections = selections.iter().peekable();
10888 let mut contiguous_row_selections = Vec::new();
10889 let mut new_selections = Vec::new();
10890 let mut added_lines = 0;
10891 let mut removed_lines = 0;
10892
10893 while let Some(selection) = selections.next() {
10894 let (start_row, end_row) = consume_contiguous_rows(
10895 &mut contiguous_row_selections,
10896 selection,
10897 &display_map,
10898 &mut selections,
10899 );
10900
10901 let start_point = Point::new(start_row.0, 0);
10902 let end_point = Point::new(
10903 end_row.previous_row().0,
10904 buffer.line_len(end_row.previous_row()),
10905 );
10906 let text = buffer
10907 .text_for_range(start_point..end_point)
10908 .collect::<String>();
10909
10910 let LineManipulationResult {
10911 new_text,
10912 line_count_before,
10913 line_count_after,
10914 } = manipulate(&text);
10915
10916 edits.push((start_point..end_point, new_text));
10917
10918 // Selections must change based on added and removed line count
10919 let start_row =
10920 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10921 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10922 new_selections.push(Selection {
10923 id: selection.id,
10924 start: start_row,
10925 end: end_row,
10926 goal: SelectionGoal::None,
10927 reversed: selection.reversed,
10928 });
10929
10930 if line_count_after > line_count_before {
10931 added_lines += line_count_after - line_count_before;
10932 } else if line_count_before > line_count_after {
10933 removed_lines += line_count_before - line_count_after;
10934 }
10935 }
10936
10937 self.transact(window, cx, |this, window, cx| {
10938 let buffer = this.buffer.update(cx, |buffer, cx| {
10939 buffer.edit(edits, None, cx);
10940 buffer.snapshot(cx)
10941 });
10942
10943 // Recalculate offsets on newly edited buffer
10944 let new_selections = new_selections
10945 .iter()
10946 .map(|s| {
10947 let start_point = Point::new(s.start.0, 0);
10948 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10949 Selection {
10950 id: s.id,
10951 start: buffer.point_to_offset(start_point),
10952 end: buffer.point_to_offset(end_point),
10953 goal: s.goal,
10954 reversed: s.reversed,
10955 }
10956 })
10957 .collect();
10958
10959 this.change_selections(Default::default(), window, cx, |s| {
10960 s.select(new_selections);
10961 });
10962
10963 this.request_autoscroll(Autoscroll::fit(), cx);
10964 });
10965 }
10966
10967 fn manipulate_immutable_lines<Fn>(
10968 &mut self,
10969 window: &mut Window,
10970 cx: &mut Context<Self>,
10971 mut callback: Fn,
10972 ) where
10973 Fn: FnMut(&mut Vec<&str>),
10974 {
10975 self.manipulate_lines(window, cx, |text| {
10976 let mut lines: Vec<&str> = text.split('\n').collect();
10977 let line_count_before = lines.len();
10978
10979 callback(&mut lines);
10980
10981 LineManipulationResult {
10982 new_text: lines.join("\n"),
10983 line_count_before,
10984 line_count_after: lines.len(),
10985 }
10986 });
10987 }
10988
10989 fn manipulate_mutable_lines<Fn>(
10990 &mut self,
10991 window: &mut Window,
10992 cx: &mut Context<Self>,
10993 mut callback: Fn,
10994 ) where
10995 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10996 {
10997 self.manipulate_lines(window, cx, |text| {
10998 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10999 let line_count_before = lines.len();
11000
11001 callback(&mut lines);
11002
11003 LineManipulationResult {
11004 new_text: lines.join("\n"),
11005 line_count_before,
11006 line_count_after: lines.len(),
11007 }
11008 });
11009 }
11010
11011 pub fn convert_indentation_to_spaces(
11012 &mut self,
11013 _: &ConvertIndentationToSpaces,
11014 window: &mut Window,
11015 cx: &mut Context<Self>,
11016 ) {
11017 let settings = self.buffer.read(cx).language_settings(cx);
11018 let tab_size = settings.tab_size.get() as usize;
11019
11020 self.manipulate_mutable_lines(window, cx, |lines| {
11021 // Allocates a reasonably sized scratch buffer once for the whole loop
11022 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11023 // Avoids recomputing spaces that could be inserted many times
11024 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11025 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11026 .collect();
11027
11028 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11029 let mut chars = line.as_ref().chars();
11030 let mut col = 0;
11031 let mut changed = false;
11032
11033 while let Some(ch) = chars.next() {
11034 match ch {
11035 ' ' => {
11036 reindented_line.push(' ');
11037 col += 1;
11038 }
11039 '\t' => {
11040 // \t are converted to spaces depending on the current column
11041 let spaces_len = tab_size - (col % tab_size);
11042 reindented_line.extend(&space_cache[spaces_len - 1]);
11043 col += spaces_len;
11044 changed = true;
11045 }
11046 _ => {
11047 // If we dont append before break, the character is consumed
11048 reindented_line.push(ch);
11049 break;
11050 }
11051 }
11052 }
11053
11054 if !changed {
11055 reindented_line.clear();
11056 continue;
11057 }
11058 // Append the rest of the line and replace old reference with new one
11059 reindented_line.extend(chars);
11060 *line = Cow::Owned(reindented_line.clone());
11061 reindented_line.clear();
11062 }
11063 });
11064 }
11065
11066 pub fn convert_indentation_to_tabs(
11067 &mut self,
11068 _: &ConvertIndentationToTabs,
11069 window: &mut Window,
11070 cx: &mut Context<Self>,
11071 ) {
11072 let settings = self.buffer.read(cx).language_settings(cx);
11073 let tab_size = settings.tab_size.get() as usize;
11074
11075 self.manipulate_mutable_lines(window, cx, |lines| {
11076 // Allocates a reasonably sized buffer once for the whole loop
11077 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11078 // Avoids recomputing spaces that could be inserted many times
11079 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11080 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11081 .collect();
11082
11083 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11084 let mut chars = line.chars();
11085 let mut spaces_count = 0;
11086 let mut first_non_indent_char = None;
11087 let mut changed = false;
11088
11089 while let Some(ch) = chars.next() {
11090 match ch {
11091 ' ' => {
11092 // Keep track of spaces. Append \t when we reach tab_size
11093 spaces_count += 1;
11094 changed = true;
11095 if spaces_count == tab_size {
11096 reindented_line.push('\t');
11097 spaces_count = 0;
11098 }
11099 }
11100 '\t' => {
11101 reindented_line.push('\t');
11102 spaces_count = 0;
11103 }
11104 _ => {
11105 // Dont append it yet, we might have remaining spaces
11106 first_non_indent_char = Some(ch);
11107 break;
11108 }
11109 }
11110 }
11111
11112 if !changed {
11113 reindented_line.clear();
11114 continue;
11115 }
11116 // Remaining spaces that didn't make a full tab stop
11117 if spaces_count > 0 {
11118 reindented_line.extend(&space_cache[spaces_count - 1]);
11119 }
11120 // If we consume an extra character that was not indentation, add it back
11121 if let Some(extra_char) = first_non_indent_char {
11122 reindented_line.push(extra_char);
11123 }
11124 // Append the rest of the line and replace old reference with new one
11125 reindented_line.extend(chars);
11126 *line = Cow::Owned(reindented_line.clone());
11127 reindented_line.clear();
11128 }
11129 });
11130 }
11131
11132 pub fn convert_to_upper_case(
11133 &mut self,
11134 _: &ConvertToUpperCase,
11135 window: &mut Window,
11136 cx: &mut Context<Self>,
11137 ) {
11138 self.manipulate_text(window, cx, |text| text.to_uppercase())
11139 }
11140
11141 pub fn convert_to_lower_case(
11142 &mut self,
11143 _: &ConvertToLowerCase,
11144 window: &mut Window,
11145 cx: &mut Context<Self>,
11146 ) {
11147 self.manipulate_text(window, cx, |text| text.to_lowercase())
11148 }
11149
11150 pub fn convert_to_title_case(
11151 &mut self,
11152 _: &ConvertToTitleCase,
11153 window: &mut Window,
11154 cx: &mut Context<Self>,
11155 ) {
11156 self.manipulate_text(window, cx, |text| {
11157 text.split('\n')
11158 .map(|line| line.to_case(Case::Title))
11159 .join("\n")
11160 })
11161 }
11162
11163 pub fn convert_to_snake_case(
11164 &mut self,
11165 _: &ConvertToSnakeCase,
11166 window: &mut Window,
11167 cx: &mut Context<Self>,
11168 ) {
11169 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11170 }
11171
11172 pub fn convert_to_kebab_case(
11173 &mut self,
11174 _: &ConvertToKebabCase,
11175 window: &mut Window,
11176 cx: &mut Context<Self>,
11177 ) {
11178 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11179 }
11180
11181 pub fn convert_to_upper_camel_case(
11182 &mut self,
11183 _: &ConvertToUpperCamelCase,
11184 window: &mut Window,
11185 cx: &mut Context<Self>,
11186 ) {
11187 self.manipulate_text(window, cx, |text| {
11188 text.split('\n')
11189 .map(|line| line.to_case(Case::UpperCamel))
11190 .join("\n")
11191 })
11192 }
11193
11194 pub fn convert_to_lower_camel_case(
11195 &mut self,
11196 _: &ConvertToLowerCamelCase,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11201 }
11202
11203 pub fn convert_to_opposite_case(
11204 &mut self,
11205 _: &ConvertToOppositeCase,
11206 window: &mut Window,
11207 cx: &mut Context<Self>,
11208 ) {
11209 self.manipulate_text(window, cx, |text| {
11210 text.chars()
11211 .fold(String::with_capacity(text.len()), |mut t, c| {
11212 if c.is_uppercase() {
11213 t.extend(c.to_lowercase());
11214 } else {
11215 t.extend(c.to_uppercase());
11216 }
11217 t
11218 })
11219 })
11220 }
11221
11222 pub fn convert_to_sentence_case(
11223 &mut self,
11224 _: &ConvertToSentenceCase,
11225 window: &mut Window,
11226 cx: &mut Context<Self>,
11227 ) {
11228 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11229 }
11230
11231 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11232 self.manipulate_text(window, cx, |text| {
11233 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11234 if has_upper_case_characters {
11235 text.to_lowercase()
11236 } else {
11237 text.to_uppercase()
11238 }
11239 })
11240 }
11241
11242 pub fn convert_to_rot13(
11243 &mut self,
11244 _: &ConvertToRot13,
11245 window: &mut Window,
11246 cx: &mut Context<Self>,
11247 ) {
11248 self.manipulate_text(window, cx, |text| {
11249 text.chars()
11250 .map(|c| match c {
11251 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11252 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11253 _ => c,
11254 })
11255 .collect()
11256 })
11257 }
11258
11259 pub fn convert_to_rot47(
11260 &mut self,
11261 _: &ConvertToRot47,
11262 window: &mut Window,
11263 cx: &mut Context<Self>,
11264 ) {
11265 self.manipulate_text(window, cx, |text| {
11266 text.chars()
11267 .map(|c| {
11268 let code_point = c as u32;
11269 if code_point >= 33 && code_point <= 126 {
11270 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11271 }
11272 c
11273 })
11274 .collect()
11275 })
11276 }
11277
11278 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11279 where
11280 Fn: FnMut(&str) -> String,
11281 {
11282 let buffer = self.buffer.read(cx).snapshot(cx);
11283
11284 let mut new_selections = Vec::new();
11285 let mut edits = Vec::new();
11286 let mut selection_adjustment = 0i32;
11287
11288 for selection in self.selections.all::<usize>(cx) {
11289 let selection_is_empty = selection.is_empty();
11290
11291 let (start, end) = if selection_is_empty {
11292 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11293 (word_range.start, word_range.end)
11294 } else {
11295 (selection.start, selection.end)
11296 };
11297
11298 let text = buffer.text_for_range(start..end).collect::<String>();
11299 let old_length = text.len() as i32;
11300 let text = callback(&text);
11301
11302 new_selections.push(Selection {
11303 start: (start as i32 - selection_adjustment) as usize,
11304 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11305 goal: SelectionGoal::None,
11306 ..selection
11307 });
11308
11309 selection_adjustment += old_length - text.len() as i32;
11310
11311 edits.push((start..end, text));
11312 }
11313
11314 self.transact(window, cx, |this, window, cx| {
11315 this.buffer.update(cx, |buffer, cx| {
11316 buffer.edit(edits, None, cx);
11317 });
11318
11319 this.change_selections(Default::default(), window, cx, |s| {
11320 s.select(new_selections);
11321 });
11322
11323 this.request_autoscroll(Autoscroll::fit(), cx);
11324 });
11325 }
11326
11327 pub fn move_selection_on_drop(
11328 &mut self,
11329 selection: &Selection<Anchor>,
11330 target: DisplayPoint,
11331 is_cut: bool,
11332 window: &mut Window,
11333 cx: &mut Context<Self>,
11334 ) {
11335 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11336 let buffer = &display_map.buffer_snapshot;
11337 let mut edits = Vec::new();
11338 let insert_point = display_map
11339 .clip_point(target, Bias::Left)
11340 .to_point(&display_map);
11341 let text = buffer
11342 .text_for_range(selection.start..selection.end)
11343 .collect::<String>();
11344 if is_cut {
11345 edits.push(((selection.start..selection.end), String::new()));
11346 }
11347 let insert_anchor = buffer.anchor_before(insert_point);
11348 edits.push(((insert_anchor..insert_anchor), text));
11349 let last_edit_start = insert_anchor.bias_left(buffer);
11350 let last_edit_end = insert_anchor.bias_right(buffer);
11351 self.transact(window, cx, |this, window, cx| {
11352 this.buffer.update(cx, |buffer, cx| {
11353 buffer.edit(edits, None, cx);
11354 });
11355 this.change_selections(Default::default(), window, cx, |s| {
11356 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11357 });
11358 });
11359 }
11360
11361 pub fn clear_selection_drag_state(&mut self) {
11362 self.selection_drag_state = SelectionDragState::None;
11363 }
11364
11365 pub fn duplicate(
11366 &mut self,
11367 upwards: bool,
11368 whole_lines: bool,
11369 window: &mut Window,
11370 cx: &mut Context<Self>,
11371 ) {
11372 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11373
11374 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11375 let buffer = &display_map.buffer_snapshot;
11376 let selections = self.selections.all::<Point>(cx);
11377
11378 let mut edits = Vec::new();
11379 let mut selections_iter = selections.iter().peekable();
11380 while let Some(selection) = selections_iter.next() {
11381 let mut rows = selection.spanned_rows(false, &display_map);
11382 // duplicate line-wise
11383 if whole_lines || selection.start == selection.end {
11384 // Avoid duplicating the same lines twice.
11385 while let Some(next_selection) = selections_iter.peek() {
11386 let next_rows = next_selection.spanned_rows(false, &display_map);
11387 if next_rows.start < rows.end {
11388 rows.end = next_rows.end;
11389 selections_iter.next().unwrap();
11390 } else {
11391 break;
11392 }
11393 }
11394
11395 // Copy the text from the selected row region and splice it either at the start
11396 // or end of the region.
11397 let start = Point::new(rows.start.0, 0);
11398 let end = Point::new(
11399 rows.end.previous_row().0,
11400 buffer.line_len(rows.end.previous_row()),
11401 );
11402 let text = buffer
11403 .text_for_range(start..end)
11404 .chain(Some("\n"))
11405 .collect::<String>();
11406 let insert_location = if upwards {
11407 Point::new(rows.end.0, 0)
11408 } else {
11409 start
11410 };
11411 edits.push((insert_location..insert_location, text));
11412 } else {
11413 // duplicate character-wise
11414 let start = selection.start;
11415 let end = selection.end;
11416 let text = buffer.text_for_range(start..end).collect::<String>();
11417 edits.push((selection.end..selection.end, text));
11418 }
11419 }
11420
11421 self.transact(window, cx, |this, _, cx| {
11422 this.buffer.update(cx, |buffer, cx| {
11423 buffer.edit(edits, None, cx);
11424 });
11425
11426 this.request_autoscroll(Autoscroll::fit(), cx);
11427 });
11428 }
11429
11430 pub fn duplicate_line_up(
11431 &mut self,
11432 _: &DuplicateLineUp,
11433 window: &mut Window,
11434 cx: &mut Context<Self>,
11435 ) {
11436 self.duplicate(true, true, window, cx);
11437 }
11438
11439 pub fn duplicate_line_down(
11440 &mut self,
11441 _: &DuplicateLineDown,
11442 window: &mut Window,
11443 cx: &mut Context<Self>,
11444 ) {
11445 self.duplicate(false, true, window, cx);
11446 }
11447
11448 pub fn duplicate_selection(
11449 &mut self,
11450 _: &DuplicateSelection,
11451 window: &mut Window,
11452 cx: &mut Context<Self>,
11453 ) {
11454 self.duplicate(false, false, window, cx);
11455 }
11456
11457 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11459 if self.mode.is_single_line() {
11460 cx.propagate();
11461 return;
11462 }
11463
11464 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11465 let buffer = self.buffer.read(cx).snapshot(cx);
11466
11467 let mut edits = Vec::new();
11468 let mut unfold_ranges = Vec::new();
11469 let mut refold_creases = Vec::new();
11470
11471 let selections = self.selections.all::<Point>(cx);
11472 let mut selections = selections.iter().peekable();
11473 let mut contiguous_row_selections = Vec::new();
11474 let mut new_selections = Vec::new();
11475
11476 while let Some(selection) = selections.next() {
11477 // Find all the selections that span a contiguous row range
11478 let (start_row, end_row) = consume_contiguous_rows(
11479 &mut contiguous_row_selections,
11480 selection,
11481 &display_map,
11482 &mut selections,
11483 );
11484
11485 // Move the text spanned by the row range to be before the line preceding the row range
11486 if start_row.0 > 0 {
11487 let range_to_move = Point::new(
11488 start_row.previous_row().0,
11489 buffer.line_len(start_row.previous_row()),
11490 )
11491 ..Point::new(
11492 end_row.previous_row().0,
11493 buffer.line_len(end_row.previous_row()),
11494 );
11495 let insertion_point = display_map
11496 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11497 .0;
11498
11499 // Don't move lines across excerpts
11500 if buffer
11501 .excerpt_containing(insertion_point..range_to_move.end)
11502 .is_some()
11503 {
11504 let text = buffer
11505 .text_for_range(range_to_move.clone())
11506 .flat_map(|s| s.chars())
11507 .skip(1)
11508 .chain(['\n'])
11509 .collect::<String>();
11510
11511 edits.push((
11512 buffer.anchor_after(range_to_move.start)
11513 ..buffer.anchor_before(range_to_move.end),
11514 String::new(),
11515 ));
11516 let insertion_anchor = buffer.anchor_after(insertion_point);
11517 edits.push((insertion_anchor..insertion_anchor, text));
11518
11519 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11520
11521 // Move selections up
11522 new_selections.extend(contiguous_row_selections.drain(..).map(
11523 |mut selection| {
11524 selection.start.row -= row_delta;
11525 selection.end.row -= row_delta;
11526 selection
11527 },
11528 ));
11529
11530 // Move folds up
11531 unfold_ranges.push(range_to_move.clone());
11532 for fold in display_map.folds_in_range(
11533 buffer.anchor_before(range_to_move.start)
11534 ..buffer.anchor_after(range_to_move.end),
11535 ) {
11536 let mut start = fold.range.start.to_point(&buffer);
11537 let mut end = fold.range.end.to_point(&buffer);
11538 start.row -= row_delta;
11539 end.row -= row_delta;
11540 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11541 }
11542 }
11543 }
11544
11545 // If we didn't move line(s), preserve the existing selections
11546 new_selections.append(&mut contiguous_row_selections);
11547 }
11548
11549 self.transact(window, cx, |this, window, cx| {
11550 this.unfold_ranges(&unfold_ranges, true, true, cx);
11551 this.buffer.update(cx, |buffer, cx| {
11552 for (range, text) in edits {
11553 buffer.edit([(range, text)], None, cx);
11554 }
11555 });
11556 this.fold_creases(refold_creases, true, window, cx);
11557 this.change_selections(Default::default(), window, cx, |s| {
11558 s.select(new_selections);
11559 })
11560 });
11561 }
11562
11563 pub fn move_line_down(
11564 &mut self,
11565 _: &MoveLineDown,
11566 window: &mut Window,
11567 cx: &mut Context<Self>,
11568 ) {
11569 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11570 if self.mode.is_single_line() {
11571 cx.propagate();
11572 return;
11573 }
11574
11575 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11576 let buffer = self.buffer.read(cx).snapshot(cx);
11577
11578 let mut edits = Vec::new();
11579 let mut unfold_ranges = Vec::new();
11580 let mut refold_creases = Vec::new();
11581
11582 let selections = self.selections.all::<Point>(cx);
11583 let mut selections = selections.iter().peekable();
11584 let mut contiguous_row_selections = Vec::new();
11585 let mut new_selections = Vec::new();
11586
11587 while let Some(selection) = selections.next() {
11588 // Find all the selections that span a contiguous row range
11589 let (start_row, end_row) = consume_contiguous_rows(
11590 &mut contiguous_row_selections,
11591 selection,
11592 &display_map,
11593 &mut selections,
11594 );
11595
11596 // Move the text spanned by the row range to be after the last line of the row range
11597 if end_row.0 <= buffer.max_point().row {
11598 let range_to_move =
11599 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11600 let insertion_point = display_map
11601 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11602 .0;
11603
11604 // Don't move lines across excerpt boundaries
11605 if buffer
11606 .excerpt_containing(range_to_move.start..insertion_point)
11607 .is_some()
11608 {
11609 let mut text = String::from("\n");
11610 text.extend(buffer.text_for_range(range_to_move.clone()));
11611 text.pop(); // Drop trailing newline
11612 edits.push((
11613 buffer.anchor_after(range_to_move.start)
11614 ..buffer.anchor_before(range_to_move.end),
11615 String::new(),
11616 ));
11617 let insertion_anchor = buffer.anchor_after(insertion_point);
11618 edits.push((insertion_anchor..insertion_anchor, text));
11619
11620 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11621
11622 // Move selections down
11623 new_selections.extend(contiguous_row_selections.drain(..).map(
11624 |mut selection| {
11625 selection.start.row += row_delta;
11626 selection.end.row += row_delta;
11627 selection
11628 },
11629 ));
11630
11631 // Move folds down
11632 unfold_ranges.push(range_to_move.clone());
11633 for fold in display_map.folds_in_range(
11634 buffer.anchor_before(range_to_move.start)
11635 ..buffer.anchor_after(range_to_move.end),
11636 ) {
11637 let mut start = fold.range.start.to_point(&buffer);
11638 let mut end = fold.range.end.to_point(&buffer);
11639 start.row += row_delta;
11640 end.row += row_delta;
11641 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11642 }
11643 }
11644 }
11645
11646 // If we didn't move line(s), preserve the existing selections
11647 new_selections.append(&mut contiguous_row_selections);
11648 }
11649
11650 self.transact(window, cx, |this, window, cx| {
11651 this.unfold_ranges(&unfold_ranges, true, true, cx);
11652 this.buffer.update(cx, |buffer, cx| {
11653 for (range, text) in edits {
11654 buffer.edit([(range, text)], None, cx);
11655 }
11656 });
11657 this.fold_creases(refold_creases, true, window, cx);
11658 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11659 });
11660 }
11661
11662 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11663 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11664 let text_layout_details = &self.text_layout_details(window);
11665 self.transact(window, cx, |this, window, cx| {
11666 let edits = this.change_selections(Default::default(), window, cx, |s| {
11667 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11668 s.move_with(|display_map, selection| {
11669 if !selection.is_empty() {
11670 return;
11671 }
11672
11673 let mut head = selection.head();
11674 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11675 if head.column() == display_map.line_len(head.row()) {
11676 transpose_offset = display_map
11677 .buffer_snapshot
11678 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11679 }
11680
11681 if transpose_offset == 0 {
11682 return;
11683 }
11684
11685 *head.column_mut() += 1;
11686 head = display_map.clip_point(head, Bias::Right);
11687 let goal = SelectionGoal::HorizontalPosition(
11688 display_map
11689 .x_for_display_point(head, text_layout_details)
11690 .into(),
11691 );
11692 selection.collapse_to(head, goal);
11693
11694 let transpose_start = display_map
11695 .buffer_snapshot
11696 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11697 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11698 let transpose_end = display_map
11699 .buffer_snapshot
11700 .clip_offset(transpose_offset + 1, Bias::Right);
11701 if let Some(ch) =
11702 display_map.buffer_snapshot.chars_at(transpose_start).next()
11703 {
11704 edits.push((transpose_start..transpose_offset, String::new()));
11705 edits.push((transpose_end..transpose_end, ch.to_string()));
11706 }
11707 }
11708 });
11709 edits
11710 });
11711 this.buffer
11712 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11713 let selections = this.selections.all::<usize>(cx);
11714 this.change_selections(Default::default(), window, cx, |s| {
11715 s.select(selections);
11716 });
11717 });
11718 }
11719
11720 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11721 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11722 if self.mode.is_single_line() {
11723 cx.propagate();
11724 return;
11725 }
11726
11727 self.rewrap_impl(RewrapOptions::default(), cx)
11728 }
11729
11730 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11731 let buffer = self.buffer.read(cx).snapshot(cx);
11732 let selections = self.selections.all::<Point>(cx);
11733
11734 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11735 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11736 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11737 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11738 .peekable();
11739
11740 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11741 row
11742 } else {
11743 return Vec::new();
11744 };
11745
11746 let language_settings = buffer.language_settings_at(selection.head(), cx);
11747 let language_scope = buffer.language_scope_at(selection.head());
11748
11749 let indent_and_prefix_for_row =
11750 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11751 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11752 let (comment_prefix, rewrap_prefix) =
11753 if let Some(language_scope) = &language_scope {
11754 let indent_end = Point::new(row, indent.len);
11755 let comment_prefix = language_scope
11756 .line_comment_prefixes()
11757 .iter()
11758 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11759 .map(|prefix| prefix.to_string());
11760 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11761 let line_text_after_indent = buffer
11762 .text_for_range(indent_end..line_end)
11763 .collect::<String>();
11764 let rewrap_prefix = language_scope
11765 .rewrap_prefixes()
11766 .iter()
11767 .find_map(|prefix_regex| {
11768 prefix_regex.find(&line_text_after_indent).map(|mat| {
11769 if mat.start() == 0 {
11770 Some(mat.as_str().to_string())
11771 } else {
11772 None
11773 }
11774 })
11775 })
11776 .flatten();
11777 (comment_prefix, rewrap_prefix)
11778 } else {
11779 (None, None)
11780 };
11781 (indent, comment_prefix, rewrap_prefix)
11782 };
11783
11784 let mut ranges = Vec::new();
11785 let from_empty_selection = selection.is_empty();
11786
11787 let mut current_range_start = first_row;
11788 let mut prev_row = first_row;
11789 let (
11790 mut current_range_indent,
11791 mut current_range_comment_prefix,
11792 mut current_range_rewrap_prefix,
11793 ) = indent_and_prefix_for_row(first_row);
11794
11795 for row in non_blank_rows_iter.skip(1) {
11796 let has_paragraph_break = row > prev_row + 1;
11797
11798 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11799 indent_and_prefix_for_row(row);
11800
11801 let has_indent_change = row_indent != current_range_indent;
11802 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11803
11804 let has_boundary_change = has_comment_change
11805 || row_rewrap_prefix.is_some()
11806 || (has_indent_change && current_range_comment_prefix.is_some());
11807
11808 if has_paragraph_break || has_boundary_change {
11809 ranges.push((
11810 language_settings.clone(),
11811 Point::new(current_range_start, 0)
11812 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11813 current_range_indent,
11814 current_range_comment_prefix.clone(),
11815 current_range_rewrap_prefix.clone(),
11816 from_empty_selection,
11817 ));
11818 current_range_start = row;
11819 current_range_indent = row_indent;
11820 current_range_comment_prefix = row_comment_prefix;
11821 current_range_rewrap_prefix = row_rewrap_prefix;
11822 }
11823 prev_row = row;
11824 }
11825
11826 ranges.push((
11827 language_settings.clone(),
11828 Point::new(current_range_start, 0)
11829 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11830 current_range_indent,
11831 current_range_comment_prefix,
11832 current_range_rewrap_prefix,
11833 from_empty_selection,
11834 ));
11835
11836 ranges
11837 });
11838
11839 let mut edits = Vec::new();
11840 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11841
11842 for (
11843 language_settings,
11844 wrap_range,
11845 indent_size,
11846 comment_prefix,
11847 rewrap_prefix,
11848 from_empty_selection,
11849 ) in wrap_ranges
11850 {
11851 let mut start_row = wrap_range.start.row;
11852 let mut end_row = wrap_range.end.row;
11853
11854 // Skip selections that overlap with a range that has already been rewrapped.
11855 let selection_range = start_row..end_row;
11856 if rewrapped_row_ranges
11857 .iter()
11858 .any(|range| range.overlaps(&selection_range))
11859 {
11860 continue;
11861 }
11862
11863 let tab_size = language_settings.tab_size;
11864
11865 let indent_prefix = indent_size.chars().collect::<String>();
11866 let mut line_prefix = indent_prefix.clone();
11867 let mut inside_comment = false;
11868 if let Some(prefix) = &comment_prefix {
11869 line_prefix.push_str(prefix);
11870 inside_comment = true;
11871 }
11872 if let Some(prefix) = &rewrap_prefix {
11873 line_prefix.push_str(prefix);
11874 }
11875
11876 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11877 RewrapBehavior::InComments => inside_comment,
11878 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11879 RewrapBehavior::Anywhere => true,
11880 };
11881
11882 let should_rewrap = options.override_language_settings
11883 || allow_rewrap_based_on_language
11884 || self.hard_wrap.is_some();
11885 if !should_rewrap {
11886 continue;
11887 }
11888
11889 if from_empty_selection {
11890 'expand_upwards: while start_row > 0 {
11891 let prev_row = start_row - 1;
11892 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11893 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11894 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11895 {
11896 start_row = prev_row;
11897 } else {
11898 break 'expand_upwards;
11899 }
11900 }
11901
11902 'expand_downwards: while end_row < buffer.max_point().row {
11903 let next_row = end_row + 1;
11904 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11905 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11906 && !buffer.is_line_blank(MultiBufferRow(next_row))
11907 {
11908 end_row = next_row;
11909 } else {
11910 break 'expand_downwards;
11911 }
11912 }
11913 }
11914
11915 let start = Point::new(start_row, 0);
11916 let start_offset = start.to_offset(&buffer);
11917 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11918 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11919 let Some(lines_without_prefixes) = selection_text
11920 .lines()
11921 .enumerate()
11922 .map(|(ix, line)| {
11923 let line_trimmed = line.trim_start();
11924 if rewrap_prefix.is_some() && ix > 0 {
11925 Ok(line_trimmed)
11926 } else {
11927 line_trimmed
11928 .strip_prefix(&line_prefix.trim_start())
11929 .with_context(|| {
11930 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11931 })
11932 }
11933 })
11934 .collect::<Result<Vec<_>, _>>()
11935 .log_err()
11936 else {
11937 continue;
11938 };
11939
11940 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11941 buffer
11942 .language_settings_at(Point::new(start_row, 0), cx)
11943 .preferred_line_length as usize
11944 });
11945
11946 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11947 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11948 } else {
11949 line_prefix.clone()
11950 };
11951
11952 let wrapped_text = wrap_with_prefix(
11953 line_prefix,
11954 subsequent_lines_prefix,
11955 lines_without_prefixes.join("\n"),
11956 wrap_column,
11957 tab_size,
11958 options.preserve_existing_whitespace,
11959 );
11960
11961 // TODO: should always use char-based diff while still supporting cursor behavior that
11962 // matches vim.
11963 let mut diff_options = DiffOptions::default();
11964 if options.override_language_settings {
11965 diff_options.max_word_diff_len = 0;
11966 diff_options.max_word_diff_line_count = 0;
11967 } else {
11968 diff_options.max_word_diff_len = usize::MAX;
11969 diff_options.max_word_diff_line_count = usize::MAX;
11970 }
11971
11972 for (old_range, new_text) in
11973 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11974 {
11975 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11976 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11977 edits.push((edit_start..edit_end, new_text));
11978 }
11979
11980 rewrapped_row_ranges.push(start_row..=end_row);
11981 }
11982
11983 self.buffer
11984 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11985 }
11986
11987 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11988 let mut text = String::new();
11989 let buffer = self.buffer.read(cx).snapshot(cx);
11990 let mut selections = self.selections.all::<Point>(cx);
11991 let mut clipboard_selections = Vec::with_capacity(selections.len());
11992 {
11993 let max_point = buffer.max_point();
11994 let mut is_first = true;
11995 for selection in &mut selections {
11996 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11997 if is_entire_line {
11998 selection.start = Point::new(selection.start.row, 0);
11999 if !selection.is_empty() && selection.end.column == 0 {
12000 selection.end = cmp::min(max_point, selection.end);
12001 } else {
12002 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12003 }
12004 selection.goal = SelectionGoal::None;
12005 }
12006 if is_first {
12007 is_first = false;
12008 } else {
12009 text += "\n";
12010 }
12011 let mut len = 0;
12012 for chunk in buffer.text_for_range(selection.start..selection.end) {
12013 text.push_str(chunk);
12014 len += chunk.len();
12015 }
12016 clipboard_selections.push(ClipboardSelection {
12017 len,
12018 is_entire_line,
12019 first_line_indent: buffer
12020 .indent_size_for_line(MultiBufferRow(selection.start.row))
12021 .len,
12022 });
12023 }
12024 }
12025
12026 self.transact(window, cx, |this, window, cx| {
12027 this.change_selections(Default::default(), window, cx, |s| {
12028 s.select(selections);
12029 });
12030 this.insert("", window, cx);
12031 });
12032 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12033 }
12034
12035 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12036 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12037 let item = self.cut_common(window, cx);
12038 cx.write_to_clipboard(item);
12039 }
12040
12041 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12043 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12044 s.move_with(|snapshot, sel| {
12045 if sel.is_empty() {
12046 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12047 }
12048 });
12049 });
12050 let item = self.cut_common(window, cx);
12051 cx.set_global(KillRing(item))
12052 }
12053
12054 pub fn kill_ring_yank(
12055 &mut self,
12056 _: &KillRingYank,
12057 window: &mut Window,
12058 cx: &mut Context<Self>,
12059 ) {
12060 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12061 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12062 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12063 (kill_ring.text().to_string(), kill_ring.metadata_json())
12064 } else {
12065 return;
12066 }
12067 } else {
12068 return;
12069 };
12070 self.do_paste(&text, metadata, false, window, cx);
12071 }
12072
12073 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12074 self.do_copy(true, cx);
12075 }
12076
12077 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12078 self.do_copy(false, cx);
12079 }
12080
12081 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12082 let selections = self.selections.all::<Point>(cx);
12083 let buffer = self.buffer.read(cx).read(cx);
12084 let mut text = String::new();
12085
12086 let mut clipboard_selections = Vec::with_capacity(selections.len());
12087 {
12088 let max_point = buffer.max_point();
12089 let mut is_first = true;
12090 for selection in &selections {
12091 let mut start = selection.start;
12092 let mut end = selection.end;
12093 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12094 if is_entire_line {
12095 start = Point::new(start.row, 0);
12096 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12097 }
12098
12099 let mut trimmed_selections = Vec::new();
12100 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12101 let row = MultiBufferRow(start.row);
12102 let first_indent = buffer.indent_size_for_line(row);
12103 if first_indent.len == 0 || start.column > first_indent.len {
12104 trimmed_selections.push(start..end);
12105 } else {
12106 trimmed_selections.push(
12107 Point::new(row.0, first_indent.len)
12108 ..Point::new(row.0, buffer.line_len(row)),
12109 );
12110 for row in start.row + 1..=end.row {
12111 let mut line_len = buffer.line_len(MultiBufferRow(row));
12112 if row == end.row {
12113 line_len = end.column;
12114 }
12115 if line_len == 0 {
12116 trimmed_selections
12117 .push(Point::new(row, 0)..Point::new(row, line_len));
12118 continue;
12119 }
12120 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12121 if row_indent_size.len >= first_indent.len {
12122 trimmed_selections.push(
12123 Point::new(row, first_indent.len)..Point::new(row, line_len),
12124 );
12125 } else {
12126 trimmed_selections.clear();
12127 trimmed_selections.push(start..end);
12128 break;
12129 }
12130 }
12131 }
12132 } else {
12133 trimmed_selections.push(start..end);
12134 }
12135
12136 for trimmed_range in trimmed_selections {
12137 if is_first {
12138 is_first = false;
12139 } else {
12140 text += "\n";
12141 }
12142 let mut len = 0;
12143 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12144 text.push_str(chunk);
12145 len += chunk.len();
12146 }
12147 clipboard_selections.push(ClipboardSelection {
12148 len,
12149 is_entire_line,
12150 first_line_indent: buffer
12151 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12152 .len,
12153 });
12154 }
12155 }
12156 }
12157
12158 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12159 text,
12160 clipboard_selections,
12161 ));
12162 }
12163
12164 pub fn do_paste(
12165 &mut self,
12166 text: &String,
12167 clipboard_selections: Option<Vec<ClipboardSelection>>,
12168 handle_entire_lines: bool,
12169 window: &mut Window,
12170 cx: &mut Context<Self>,
12171 ) {
12172 if self.read_only(cx) {
12173 return;
12174 }
12175
12176 let clipboard_text = Cow::Borrowed(text);
12177
12178 self.transact(window, cx, |this, window, cx| {
12179 if let Some(mut clipboard_selections) = clipboard_selections {
12180 let old_selections = this.selections.all::<usize>(cx);
12181 let all_selections_were_entire_line =
12182 clipboard_selections.iter().all(|s| s.is_entire_line);
12183 let first_selection_indent_column =
12184 clipboard_selections.first().map(|s| s.first_line_indent);
12185 if clipboard_selections.len() != old_selections.len() {
12186 clipboard_selections.drain(..);
12187 }
12188 let cursor_offset = this.selections.last::<usize>(cx).head();
12189 let mut auto_indent_on_paste = true;
12190
12191 this.buffer.update(cx, |buffer, cx| {
12192 let snapshot = buffer.read(cx);
12193 auto_indent_on_paste = snapshot
12194 .language_settings_at(cursor_offset, cx)
12195 .auto_indent_on_paste;
12196
12197 let mut start_offset = 0;
12198 let mut edits = Vec::new();
12199 let mut original_indent_columns = Vec::new();
12200 for (ix, selection) in old_selections.iter().enumerate() {
12201 let to_insert;
12202 let entire_line;
12203 let original_indent_column;
12204 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12205 let end_offset = start_offset + clipboard_selection.len;
12206 to_insert = &clipboard_text[start_offset..end_offset];
12207 entire_line = clipboard_selection.is_entire_line;
12208 start_offset = end_offset + 1;
12209 original_indent_column = Some(clipboard_selection.first_line_indent);
12210 } else {
12211 to_insert = clipboard_text.as_str();
12212 entire_line = all_selections_were_entire_line;
12213 original_indent_column = first_selection_indent_column
12214 }
12215
12216 // If the corresponding selection was empty when this slice of the
12217 // clipboard text was written, then the entire line containing the
12218 // selection was copied. If this selection is also currently empty,
12219 // then paste the line before the current line of the buffer.
12220 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12221 let column = selection.start.to_point(&snapshot).column as usize;
12222 let line_start = selection.start - column;
12223 line_start..line_start
12224 } else {
12225 selection.range()
12226 };
12227
12228 edits.push((range, to_insert));
12229 original_indent_columns.push(original_indent_column);
12230 }
12231 drop(snapshot);
12232
12233 buffer.edit(
12234 edits,
12235 if auto_indent_on_paste {
12236 Some(AutoindentMode::Block {
12237 original_indent_columns,
12238 })
12239 } else {
12240 None
12241 },
12242 cx,
12243 );
12244 });
12245
12246 let selections = this.selections.all::<usize>(cx);
12247 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12248 } else {
12249 this.insert(&clipboard_text, window, cx);
12250 }
12251 });
12252 }
12253
12254 pub fn diff_clipboard_with_selection(
12255 &mut self,
12256 _: &DiffClipboardWithSelection,
12257 window: &mut Window,
12258 cx: &mut Context<Self>,
12259 ) {
12260 let selections = self.selections.all::<usize>(cx);
12261
12262 if selections.is_empty() {
12263 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12264 return;
12265 };
12266
12267 let clipboard_text = match cx.read_from_clipboard() {
12268 Some(item) => match item.entries().first() {
12269 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12270 _ => None,
12271 },
12272 None => None,
12273 };
12274
12275 let Some(clipboard_text) = clipboard_text else {
12276 log::warn!("Clipboard doesn't contain text.");
12277 return;
12278 };
12279
12280 window.dispatch_action(
12281 Box::new(DiffClipboardWithSelectionData {
12282 clipboard_text,
12283 editor: cx.entity(),
12284 }),
12285 cx,
12286 );
12287 }
12288
12289 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12290 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12291 if let Some(item) = cx.read_from_clipboard() {
12292 let entries = item.entries();
12293
12294 match entries.first() {
12295 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12296 // of all the pasted entries.
12297 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12298 .do_paste(
12299 clipboard_string.text(),
12300 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12301 true,
12302 window,
12303 cx,
12304 ),
12305 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12306 }
12307 }
12308 }
12309
12310 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12311 if self.read_only(cx) {
12312 return;
12313 }
12314
12315 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12316
12317 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12318 if let Some((selections, _)) =
12319 self.selection_history.transaction(transaction_id).cloned()
12320 {
12321 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12322 s.select_anchors(selections.to_vec());
12323 });
12324 } else {
12325 log::error!(
12326 "No entry in selection_history found for undo. \
12327 This may correspond to a bug where undo does not update the selection. \
12328 If this is occurring, please add details to \
12329 https://github.com/zed-industries/zed/issues/22692"
12330 );
12331 }
12332 self.request_autoscroll(Autoscroll::fit(), cx);
12333 self.unmark_text(window, cx);
12334 self.refresh_edit_prediction(true, false, window, cx);
12335 cx.emit(EditorEvent::Edited { transaction_id });
12336 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12337 }
12338 }
12339
12340 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12341 if self.read_only(cx) {
12342 return;
12343 }
12344
12345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12346
12347 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12348 if let Some((_, Some(selections))) =
12349 self.selection_history.transaction(transaction_id).cloned()
12350 {
12351 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12352 s.select_anchors(selections.to_vec());
12353 });
12354 } else {
12355 log::error!(
12356 "No entry in selection_history found for redo. \
12357 This may correspond to a bug where undo does not update the selection. \
12358 If this is occurring, please add details to \
12359 https://github.com/zed-industries/zed/issues/22692"
12360 );
12361 }
12362 self.request_autoscroll(Autoscroll::fit(), cx);
12363 self.unmark_text(window, cx);
12364 self.refresh_edit_prediction(true, false, window, cx);
12365 cx.emit(EditorEvent::Edited { transaction_id });
12366 }
12367 }
12368
12369 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12370 self.buffer
12371 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12372 }
12373
12374 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12375 self.buffer
12376 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12377 }
12378
12379 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12380 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12381 self.change_selections(Default::default(), window, cx, |s| {
12382 s.move_with(|map, selection| {
12383 let cursor = if selection.is_empty() {
12384 movement::left(map, selection.start)
12385 } else {
12386 selection.start
12387 };
12388 selection.collapse_to(cursor, SelectionGoal::None);
12389 });
12390 })
12391 }
12392
12393 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12394 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12395 self.change_selections(Default::default(), window, cx, |s| {
12396 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12397 })
12398 }
12399
12400 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12402 self.change_selections(Default::default(), window, cx, |s| {
12403 s.move_with(|map, selection| {
12404 let cursor = if selection.is_empty() {
12405 movement::right(map, selection.end)
12406 } else {
12407 selection.end
12408 };
12409 selection.collapse_to(cursor, SelectionGoal::None)
12410 });
12411 })
12412 }
12413
12414 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12415 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12416 self.change_selections(Default::default(), window, cx, |s| {
12417 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12418 })
12419 }
12420
12421 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12422 if self.take_rename(true, window, cx).is_some() {
12423 return;
12424 }
12425
12426 if self.mode.is_single_line() {
12427 cx.propagate();
12428 return;
12429 }
12430
12431 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12432
12433 let text_layout_details = &self.text_layout_details(window);
12434 let selection_count = self.selections.count();
12435 let first_selection = self.selections.first_anchor();
12436
12437 self.change_selections(Default::default(), window, cx, |s| {
12438 s.move_with(|map, selection| {
12439 if !selection.is_empty() {
12440 selection.goal = SelectionGoal::None;
12441 }
12442 let (cursor, goal) = movement::up(
12443 map,
12444 selection.start,
12445 selection.goal,
12446 false,
12447 text_layout_details,
12448 );
12449 selection.collapse_to(cursor, goal);
12450 });
12451 });
12452
12453 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12454 {
12455 cx.propagate();
12456 }
12457 }
12458
12459 pub fn move_up_by_lines(
12460 &mut self,
12461 action: &MoveUpByLines,
12462 window: &mut Window,
12463 cx: &mut Context<Self>,
12464 ) {
12465 if self.take_rename(true, window, cx).is_some() {
12466 return;
12467 }
12468
12469 if self.mode.is_single_line() {
12470 cx.propagate();
12471 return;
12472 }
12473
12474 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12475
12476 let text_layout_details = &self.text_layout_details(window);
12477
12478 self.change_selections(Default::default(), window, cx, |s| {
12479 s.move_with(|map, selection| {
12480 if !selection.is_empty() {
12481 selection.goal = SelectionGoal::None;
12482 }
12483 let (cursor, goal) = movement::up_by_rows(
12484 map,
12485 selection.start,
12486 action.lines,
12487 selection.goal,
12488 false,
12489 text_layout_details,
12490 );
12491 selection.collapse_to(cursor, goal);
12492 });
12493 })
12494 }
12495
12496 pub fn move_down_by_lines(
12497 &mut self,
12498 action: &MoveDownByLines,
12499 window: &mut Window,
12500 cx: &mut Context<Self>,
12501 ) {
12502 if self.take_rename(true, window, cx).is_some() {
12503 return;
12504 }
12505
12506 if self.mode.is_single_line() {
12507 cx.propagate();
12508 return;
12509 }
12510
12511 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12512
12513 let text_layout_details = &self.text_layout_details(window);
12514
12515 self.change_selections(Default::default(), window, cx, |s| {
12516 s.move_with(|map, selection| {
12517 if !selection.is_empty() {
12518 selection.goal = SelectionGoal::None;
12519 }
12520 let (cursor, goal) = movement::down_by_rows(
12521 map,
12522 selection.start,
12523 action.lines,
12524 selection.goal,
12525 false,
12526 text_layout_details,
12527 );
12528 selection.collapse_to(cursor, goal);
12529 });
12530 })
12531 }
12532
12533 pub fn select_down_by_lines(
12534 &mut self,
12535 action: &SelectDownByLines,
12536 window: &mut Window,
12537 cx: &mut Context<Self>,
12538 ) {
12539 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12540 let text_layout_details = &self.text_layout_details(window);
12541 self.change_selections(Default::default(), window, cx, |s| {
12542 s.move_heads_with(|map, head, goal| {
12543 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12544 })
12545 })
12546 }
12547
12548 pub fn select_up_by_lines(
12549 &mut self,
12550 action: &SelectUpByLines,
12551 window: &mut Window,
12552 cx: &mut Context<Self>,
12553 ) {
12554 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12555 let text_layout_details = &self.text_layout_details(window);
12556 self.change_selections(Default::default(), window, cx, |s| {
12557 s.move_heads_with(|map, head, goal| {
12558 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12559 })
12560 })
12561 }
12562
12563 pub fn select_page_up(
12564 &mut self,
12565 _: &SelectPageUp,
12566 window: &mut Window,
12567 cx: &mut Context<Self>,
12568 ) {
12569 let Some(row_count) = self.visible_row_count() else {
12570 return;
12571 };
12572
12573 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12574
12575 let text_layout_details = &self.text_layout_details(window);
12576
12577 self.change_selections(Default::default(), window, cx, |s| {
12578 s.move_heads_with(|map, head, goal| {
12579 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12580 })
12581 })
12582 }
12583
12584 pub fn move_page_up(
12585 &mut self,
12586 action: &MovePageUp,
12587 window: &mut Window,
12588 cx: &mut Context<Self>,
12589 ) {
12590 if self.take_rename(true, window, cx).is_some() {
12591 return;
12592 }
12593
12594 if self
12595 .context_menu
12596 .borrow_mut()
12597 .as_mut()
12598 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12599 .unwrap_or(false)
12600 {
12601 return;
12602 }
12603
12604 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12605 cx.propagate();
12606 return;
12607 }
12608
12609 let Some(row_count) = self.visible_row_count() else {
12610 return;
12611 };
12612
12613 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12614
12615 let effects = if action.center_cursor {
12616 SelectionEffects::scroll(Autoscroll::center())
12617 } else {
12618 SelectionEffects::default()
12619 };
12620
12621 let text_layout_details = &self.text_layout_details(window);
12622
12623 self.change_selections(effects, window, cx, |s| {
12624 s.move_with(|map, selection| {
12625 if !selection.is_empty() {
12626 selection.goal = SelectionGoal::None;
12627 }
12628 let (cursor, goal) = movement::up_by_rows(
12629 map,
12630 selection.end,
12631 row_count,
12632 selection.goal,
12633 false,
12634 text_layout_details,
12635 );
12636 selection.collapse_to(cursor, goal);
12637 });
12638 });
12639 }
12640
12641 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12643 let text_layout_details = &self.text_layout_details(window);
12644 self.change_selections(Default::default(), window, cx, |s| {
12645 s.move_heads_with(|map, head, goal| {
12646 movement::up(map, head, goal, false, text_layout_details)
12647 })
12648 })
12649 }
12650
12651 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12652 self.take_rename(true, window, cx);
12653
12654 if self.mode.is_single_line() {
12655 cx.propagate();
12656 return;
12657 }
12658
12659 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12660
12661 let text_layout_details = &self.text_layout_details(window);
12662 let selection_count = self.selections.count();
12663 let first_selection = self.selections.first_anchor();
12664
12665 self.change_selections(Default::default(), window, cx, |s| {
12666 s.move_with(|map, selection| {
12667 if !selection.is_empty() {
12668 selection.goal = SelectionGoal::None;
12669 }
12670 let (cursor, goal) = movement::down(
12671 map,
12672 selection.end,
12673 selection.goal,
12674 false,
12675 text_layout_details,
12676 );
12677 selection.collapse_to(cursor, goal);
12678 });
12679 });
12680
12681 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12682 {
12683 cx.propagate();
12684 }
12685 }
12686
12687 pub fn select_page_down(
12688 &mut self,
12689 _: &SelectPageDown,
12690 window: &mut Window,
12691 cx: &mut Context<Self>,
12692 ) {
12693 let Some(row_count) = self.visible_row_count() else {
12694 return;
12695 };
12696
12697 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12698
12699 let text_layout_details = &self.text_layout_details(window);
12700
12701 self.change_selections(Default::default(), window, cx, |s| {
12702 s.move_heads_with(|map, head, goal| {
12703 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12704 })
12705 })
12706 }
12707
12708 pub fn move_page_down(
12709 &mut self,
12710 action: &MovePageDown,
12711 window: &mut Window,
12712 cx: &mut Context<Self>,
12713 ) {
12714 if self.take_rename(true, window, cx).is_some() {
12715 return;
12716 }
12717
12718 if self
12719 .context_menu
12720 .borrow_mut()
12721 .as_mut()
12722 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12723 .unwrap_or(false)
12724 {
12725 return;
12726 }
12727
12728 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12729 cx.propagate();
12730 return;
12731 }
12732
12733 let Some(row_count) = self.visible_row_count() else {
12734 return;
12735 };
12736
12737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12738
12739 let effects = if action.center_cursor {
12740 SelectionEffects::scroll(Autoscroll::center())
12741 } else {
12742 SelectionEffects::default()
12743 };
12744
12745 let text_layout_details = &self.text_layout_details(window);
12746 self.change_selections(effects, window, cx, |s| {
12747 s.move_with(|map, selection| {
12748 if !selection.is_empty() {
12749 selection.goal = SelectionGoal::None;
12750 }
12751 let (cursor, goal) = movement::down_by_rows(
12752 map,
12753 selection.end,
12754 row_count,
12755 selection.goal,
12756 false,
12757 text_layout_details,
12758 );
12759 selection.collapse_to(cursor, goal);
12760 });
12761 });
12762 }
12763
12764 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12765 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12766 let text_layout_details = &self.text_layout_details(window);
12767 self.change_selections(Default::default(), window, cx, |s| {
12768 s.move_heads_with(|map, head, goal| {
12769 movement::down(map, head, goal, false, text_layout_details)
12770 })
12771 });
12772 }
12773
12774 pub fn context_menu_first(
12775 &mut self,
12776 _: &ContextMenuFirst,
12777 window: &mut Window,
12778 cx: &mut Context<Self>,
12779 ) {
12780 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12781 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12782 }
12783 }
12784
12785 pub fn context_menu_prev(
12786 &mut self,
12787 _: &ContextMenuPrevious,
12788 window: &mut Window,
12789 cx: &mut Context<Self>,
12790 ) {
12791 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12792 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12793 }
12794 }
12795
12796 pub fn context_menu_next(
12797 &mut self,
12798 _: &ContextMenuNext,
12799 window: &mut Window,
12800 cx: &mut Context<Self>,
12801 ) {
12802 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12803 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12804 }
12805 }
12806
12807 pub fn context_menu_last(
12808 &mut self,
12809 _: &ContextMenuLast,
12810 window: &mut Window,
12811 cx: &mut Context<Self>,
12812 ) {
12813 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12814 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12815 }
12816 }
12817
12818 pub fn signature_help_prev(
12819 &mut self,
12820 _: &SignatureHelpPrevious,
12821 _: &mut Window,
12822 cx: &mut Context<Self>,
12823 ) {
12824 if let Some(popover) = self.signature_help_state.popover_mut() {
12825 if popover.current_signature == 0 {
12826 popover.current_signature = popover.signatures.len() - 1;
12827 } else {
12828 popover.current_signature -= 1;
12829 }
12830 cx.notify();
12831 }
12832 }
12833
12834 pub fn signature_help_next(
12835 &mut self,
12836 _: &SignatureHelpNext,
12837 _: &mut Window,
12838 cx: &mut Context<Self>,
12839 ) {
12840 if let Some(popover) = self.signature_help_state.popover_mut() {
12841 if popover.current_signature + 1 == popover.signatures.len() {
12842 popover.current_signature = 0;
12843 } else {
12844 popover.current_signature += 1;
12845 }
12846 cx.notify();
12847 }
12848 }
12849
12850 pub fn move_to_previous_word_start(
12851 &mut self,
12852 _: &MoveToPreviousWordStart,
12853 window: &mut Window,
12854 cx: &mut Context<Self>,
12855 ) {
12856 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12857 self.change_selections(Default::default(), window, cx, |s| {
12858 s.move_cursors_with(|map, head, _| {
12859 (
12860 movement::previous_word_start(map, head),
12861 SelectionGoal::None,
12862 )
12863 });
12864 })
12865 }
12866
12867 pub fn move_to_previous_subword_start(
12868 &mut self,
12869 _: &MoveToPreviousSubwordStart,
12870 window: &mut Window,
12871 cx: &mut Context<Self>,
12872 ) {
12873 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12874 self.change_selections(Default::default(), window, cx, |s| {
12875 s.move_cursors_with(|map, head, _| {
12876 (
12877 movement::previous_subword_start(map, head),
12878 SelectionGoal::None,
12879 )
12880 });
12881 })
12882 }
12883
12884 pub fn select_to_previous_word_start(
12885 &mut self,
12886 _: &SelectToPreviousWordStart,
12887 window: &mut Window,
12888 cx: &mut Context<Self>,
12889 ) {
12890 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12891 self.change_selections(Default::default(), window, cx, |s| {
12892 s.move_heads_with(|map, head, _| {
12893 (
12894 movement::previous_word_start(map, head),
12895 SelectionGoal::None,
12896 )
12897 });
12898 })
12899 }
12900
12901 pub fn select_to_previous_subword_start(
12902 &mut self,
12903 _: &SelectToPreviousSubwordStart,
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_heads_with(|map, head, _| {
12910 (
12911 movement::previous_subword_start(map, head),
12912 SelectionGoal::None,
12913 )
12914 });
12915 })
12916 }
12917
12918 pub fn delete_to_previous_word_start(
12919 &mut self,
12920 action: &DeleteToPreviousWordStart,
12921 window: &mut Window,
12922 cx: &mut Context<Self>,
12923 ) {
12924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12925 self.transact(window, cx, |this, window, cx| {
12926 this.select_autoclose_pair(window, cx);
12927 this.change_selections(Default::default(), window, cx, |s| {
12928 s.move_with(|map, selection| {
12929 if selection.is_empty() {
12930 let cursor = if action.ignore_newlines {
12931 movement::previous_word_start(map, selection.head())
12932 } else {
12933 movement::previous_word_start_or_newline(map, selection.head())
12934 };
12935 selection.set_head(cursor, SelectionGoal::None);
12936 }
12937 });
12938 });
12939 this.insert("", window, cx);
12940 });
12941 }
12942
12943 pub fn delete_to_previous_subword_start(
12944 &mut self,
12945 _: &DeleteToPreviousSubwordStart,
12946 window: &mut Window,
12947 cx: &mut Context<Self>,
12948 ) {
12949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12950 self.transact(window, cx, |this, window, cx| {
12951 this.select_autoclose_pair(window, cx);
12952 this.change_selections(Default::default(), window, cx, |s| {
12953 s.move_with(|map, selection| {
12954 if selection.is_empty() {
12955 let cursor = movement::previous_subword_start(map, selection.head());
12956 selection.set_head(cursor, SelectionGoal::None);
12957 }
12958 });
12959 });
12960 this.insert("", window, cx);
12961 });
12962 }
12963
12964 pub fn move_to_next_word_end(
12965 &mut self,
12966 _: &MoveToNextWordEnd,
12967 window: &mut Window,
12968 cx: &mut Context<Self>,
12969 ) {
12970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12971 self.change_selections(Default::default(), window, cx, |s| {
12972 s.move_cursors_with(|map, head, _| {
12973 (movement::next_word_end(map, head), SelectionGoal::None)
12974 });
12975 })
12976 }
12977
12978 pub fn move_to_next_subword_end(
12979 &mut self,
12980 _: &MoveToNextSubwordEnd,
12981 window: &mut Window,
12982 cx: &mut Context<Self>,
12983 ) {
12984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12985 self.change_selections(Default::default(), window, cx, |s| {
12986 s.move_cursors_with(|map, head, _| {
12987 (movement::next_subword_end(map, head), SelectionGoal::None)
12988 });
12989 })
12990 }
12991
12992 pub fn select_to_next_word_end(
12993 &mut self,
12994 _: &SelectToNextWordEnd,
12995 window: &mut Window,
12996 cx: &mut Context<Self>,
12997 ) {
12998 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12999 self.change_selections(Default::default(), window, cx, |s| {
13000 s.move_heads_with(|map, head, _| {
13001 (movement::next_word_end(map, head), SelectionGoal::None)
13002 });
13003 })
13004 }
13005
13006 pub fn select_to_next_subword_end(
13007 &mut self,
13008 _: &SelectToNextSubwordEnd,
13009 window: &mut Window,
13010 cx: &mut Context<Self>,
13011 ) {
13012 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13013 self.change_selections(Default::default(), window, cx, |s| {
13014 s.move_heads_with(|map, head, _| {
13015 (movement::next_subword_end(map, head), SelectionGoal::None)
13016 });
13017 })
13018 }
13019
13020 pub fn delete_to_next_word_end(
13021 &mut self,
13022 action: &DeleteToNextWordEnd,
13023 window: &mut Window,
13024 cx: &mut Context<Self>,
13025 ) {
13026 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13027 self.transact(window, cx, |this, window, cx| {
13028 this.change_selections(Default::default(), window, cx, |s| {
13029 s.move_with(|map, selection| {
13030 if selection.is_empty() {
13031 let cursor = if action.ignore_newlines {
13032 movement::next_word_end(map, selection.head())
13033 } else {
13034 movement::next_word_end_or_newline(map, selection.head())
13035 };
13036 selection.set_head(cursor, SelectionGoal::None);
13037 }
13038 });
13039 });
13040 this.insert("", window, cx);
13041 });
13042 }
13043
13044 pub fn delete_to_next_subword_end(
13045 &mut self,
13046 _: &DeleteToNextSubwordEnd,
13047 window: &mut Window,
13048 cx: &mut Context<Self>,
13049 ) {
13050 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13051 self.transact(window, cx, |this, window, cx| {
13052 this.change_selections(Default::default(), window, cx, |s| {
13053 s.move_with(|map, selection| {
13054 if selection.is_empty() {
13055 let cursor = movement::next_subword_end(map, selection.head());
13056 selection.set_head(cursor, SelectionGoal::None);
13057 }
13058 });
13059 });
13060 this.insert("", window, cx);
13061 });
13062 }
13063
13064 pub fn move_to_beginning_of_line(
13065 &mut self,
13066 action: &MoveToBeginningOfLine,
13067 window: &mut Window,
13068 cx: &mut Context<Self>,
13069 ) {
13070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13071 self.change_selections(Default::default(), window, cx, |s| {
13072 s.move_cursors_with(|map, head, _| {
13073 (
13074 movement::indented_line_beginning(
13075 map,
13076 head,
13077 action.stop_at_soft_wraps,
13078 action.stop_at_indent,
13079 ),
13080 SelectionGoal::None,
13081 )
13082 });
13083 })
13084 }
13085
13086 pub fn select_to_beginning_of_line(
13087 &mut self,
13088 action: &SelectToBeginningOfLine,
13089 window: &mut Window,
13090 cx: &mut Context<Self>,
13091 ) {
13092 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13093 self.change_selections(Default::default(), window, cx, |s| {
13094 s.move_heads_with(|map, head, _| {
13095 (
13096 movement::indented_line_beginning(
13097 map,
13098 head,
13099 action.stop_at_soft_wraps,
13100 action.stop_at_indent,
13101 ),
13102 SelectionGoal::None,
13103 )
13104 });
13105 });
13106 }
13107
13108 pub fn delete_to_beginning_of_line(
13109 &mut self,
13110 action: &DeleteToBeginningOfLine,
13111 window: &mut Window,
13112 cx: &mut Context<Self>,
13113 ) {
13114 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13115 self.transact(window, cx, |this, window, cx| {
13116 this.change_selections(Default::default(), window, cx, |s| {
13117 s.move_with(|_, selection| {
13118 selection.reversed = true;
13119 });
13120 });
13121
13122 this.select_to_beginning_of_line(
13123 &SelectToBeginningOfLine {
13124 stop_at_soft_wraps: false,
13125 stop_at_indent: action.stop_at_indent,
13126 },
13127 window,
13128 cx,
13129 );
13130 this.backspace(&Backspace, window, cx);
13131 });
13132 }
13133
13134 pub fn move_to_end_of_line(
13135 &mut self,
13136 action: &MoveToEndOfLine,
13137 window: &mut Window,
13138 cx: &mut Context<Self>,
13139 ) {
13140 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13141 self.change_selections(Default::default(), window, cx, |s| {
13142 s.move_cursors_with(|map, head, _| {
13143 (
13144 movement::line_end(map, head, action.stop_at_soft_wraps),
13145 SelectionGoal::None,
13146 )
13147 });
13148 })
13149 }
13150
13151 pub fn select_to_end_of_line(
13152 &mut self,
13153 action: &SelectToEndOfLine,
13154 window: &mut Window,
13155 cx: &mut Context<Self>,
13156 ) {
13157 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13158 self.change_selections(Default::default(), window, cx, |s| {
13159 s.move_heads_with(|map, head, _| {
13160 (
13161 movement::line_end(map, head, action.stop_at_soft_wraps),
13162 SelectionGoal::None,
13163 )
13164 });
13165 })
13166 }
13167
13168 pub fn delete_to_end_of_line(
13169 &mut self,
13170 _: &DeleteToEndOfLine,
13171 window: &mut Window,
13172 cx: &mut Context<Self>,
13173 ) {
13174 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13175 self.transact(window, cx, |this, window, cx| {
13176 this.select_to_end_of_line(
13177 &SelectToEndOfLine {
13178 stop_at_soft_wraps: false,
13179 },
13180 window,
13181 cx,
13182 );
13183 this.delete(&Delete, window, cx);
13184 });
13185 }
13186
13187 pub fn cut_to_end_of_line(
13188 &mut self,
13189 _: &CutToEndOfLine,
13190 window: &mut Window,
13191 cx: &mut Context<Self>,
13192 ) {
13193 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13194 self.transact(window, cx, |this, window, cx| {
13195 this.select_to_end_of_line(
13196 &SelectToEndOfLine {
13197 stop_at_soft_wraps: false,
13198 },
13199 window,
13200 cx,
13201 );
13202 this.cut(&Cut, window, cx);
13203 });
13204 }
13205
13206 pub fn move_to_start_of_paragraph(
13207 &mut self,
13208 _: &MoveToStartOfParagraph,
13209 window: &mut Window,
13210 cx: &mut Context<Self>,
13211 ) {
13212 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13213 cx.propagate();
13214 return;
13215 }
13216 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13217 self.change_selections(Default::default(), window, cx, |s| {
13218 s.move_with(|map, selection| {
13219 selection.collapse_to(
13220 movement::start_of_paragraph(map, selection.head(), 1),
13221 SelectionGoal::None,
13222 )
13223 });
13224 })
13225 }
13226
13227 pub fn move_to_end_of_paragraph(
13228 &mut self,
13229 _: &MoveToEndOfParagraph,
13230 window: &mut Window,
13231 cx: &mut Context<Self>,
13232 ) {
13233 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13234 cx.propagate();
13235 return;
13236 }
13237 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13238 self.change_selections(Default::default(), window, cx, |s| {
13239 s.move_with(|map, selection| {
13240 selection.collapse_to(
13241 movement::end_of_paragraph(map, selection.head(), 1),
13242 SelectionGoal::None,
13243 )
13244 });
13245 })
13246 }
13247
13248 pub fn select_to_start_of_paragraph(
13249 &mut self,
13250 _: &SelectToStartOfParagraph,
13251 window: &mut Window,
13252 cx: &mut Context<Self>,
13253 ) {
13254 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13255 cx.propagate();
13256 return;
13257 }
13258 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13259 self.change_selections(Default::default(), window, cx, |s| {
13260 s.move_heads_with(|map, head, _| {
13261 (
13262 movement::start_of_paragraph(map, head, 1),
13263 SelectionGoal::None,
13264 )
13265 });
13266 })
13267 }
13268
13269 pub fn select_to_end_of_paragraph(
13270 &mut self,
13271 _: &SelectToEndOfParagraph,
13272 window: &mut Window,
13273 cx: &mut Context<Self>,
13274 ) {
13275 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13276 cx.propagate();
13277 return;
13278 }
13279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13280 self.change_selections(Default::default(), window, cx, |s| {
13281 s.move_heads_with(|map, head, _| {
13282 (
13283 movement::end_of_paragraph(map, head, 1),
13284 SelectionGoal::None,
13285 )
13286 });
13287 })
13288 }
13289
13290 pub fn move_to_start_of_excerpt(
13291 &mut self,
13292 _: &MoveToStartOfExcerpt,
13293 window: &mut Window,
13294 cx: &mut Context<Self>,
13295 ) {
13296 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13297 cx.propagate();
13298 return;
13299 }
13300 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13301 self.change_selections(Default::default(), window, cx, |s| {
13302 s.move_with(|map, selection| {
13303 selection.collapse_to(
13304 movement::start_of_excerpt(
13305 map,
13306 selection.head(),
13307 workspace::searchable::Direction::Prev,
13308 ),
13309 SelectionGoal::None,
13310 )
13311 });
13312 })
13313 }
13314
13315 pub fn move_to_start_of_next_excerpt(
13316 &mut self,
13317 _: &MoveToStartOfNextExcerpt,
13318 window: &mut Window,
13319 cx: &mut Context<Self>,
13320 ) {
13321 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13322 cx.propagate();
13323 return;
13324 }
13325
13326 self.change_selections(Default::default(), window, cx, |s| {
13327 s.move_with(|map, selection| {
13328 selection.collapse_to(
13329 movement::start_of_excerpt(
13330 map,
13331 selection.head(),
13332 workspace::searchable::Direction::Next,
13333 ),
13334 SelectionGoal::None,
13335 )
13336 });
13337 })
13338 }
13339
13340 pub fn move_to_end_of_excerpt(
13341 &mut self,
13342 _: &MoveToEndOfExcerpt,
13343 window: &mut Window,
13344 cx: &mut Context<Self>,
13345 ) {
13346 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13347 cx.propagate();
13348 return;
13349 }
13350 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13351 self.change_selections(Default::default(), window, cx, |s| {
13352 s.move_with(|map, selection| {
13353 selection.collapse_to(
13354 movement::end_of_excerpt(
13355 map,
13356 selection.head(),
13357 workspace::searchable::Direction::Next,
13358 ),
13359 SelectionGoal::None,
13360 )
13361 });
13362 })
13363 }
13364
13365 pub fn move_to_end_of_previous_excerpt(
13366 &mut self,
13367 _: &MoveToEndOfPreviousExcerpt,
13368 window: &mut Window,
13369 cx: &mut Context<Self>,
13370 ) {
13371 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13372 cx.propagate();
13373 return;
13374 }
13375 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13376 self.change_selections(Default::default(), window, cx, |s| {
13377 s.move_with(|map, selection| {
13378 selection.collapse_to(
13379 movement::end_of_excerpt(
13380 map,
13381 selection.head(),
13382 workspace::searchable::Direction::Prev,
13383 ),
13384 SelectionGoal::None,
13385 )
13386 });
13387 })
13388 }
13389
13390 pub fn select_to_start_of_excerpt(
13391 &mut self,
13392 _: &SelectToStartOfExcerpt,
13393 window: &mut Window,
13394 cx: &mut Context<Self>,
13395 ) {
13396 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13397 cx.propagate();
13398 return;
13399 }
13400 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13401 self.change_selections(Default::default(), window, cx, |s| {
13402 s.move_heads_with(|map, head, _| {
13403 (
13404 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13405 SelectionGoal::None,
13406 )
13407 });
13408 })
13409 }
13410
13411 pub fn select_to_start_of_next_excerpt(
13412 &mut self,
13413 _: &SelectToStartOfNextExcerpt,
13414 window: &mut Window,
13415 cx: &mut Context<Self>,
13416 ) {
13417 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13418 cx.propagate();
13419 return;
13420 }
13421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13422 self.change_selections(Default::default(), window, cx, |s| {
13423 s.move_heads_with(|map, head, _| {
13424 (
13425 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13426 SelectionGoal::None,
13427 )
13428 });
13429 })
13430 }
13431
13432 pub fn select_to_end_of_excerpt(
13433 &mut self,
13434 _: &SelectToEndOfExcerpt,
13435 window: &mut Window,
13436 cx: &mut Context<Self>,
13437 ) {
13438 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13439 cx.propagate();
13440 return;
13441 }
13442 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13443 self.change_selections(Default::default(), window, cx, |s| {
13444 s.move_heads_with(|map, head, _| {
13445 (
13446 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13447 SelectionGoal::None,
13448 )
13449 });
13450 })
13451 }
13452
13453 pub fn select_to_end_of_previous_excerpt(
13454 &mut self,
13455 _: &SelectToEndOfPreviousExcerpt,
13456 window: &mut Window,
13457 cx: &mut Context<Self>,
13458 ) {
13459 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13460 cx.propagate();
13461 return;
13462 }
13463 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13464 self.change_selections(Default::default(), window, cx, |s| {
13465 s.move_heads_with(|map, head, _| {
13466 (
13467 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13468 SelectionGoal::None,
13469 )
13470 });
13471 })
13472 }
13473
13474 pub fn move_to_beginning(
13475 &mut self,
13476 _: &MoveToBeginning,
13477 window: &mut Window,
13478 cx: &mut Context<Self>,
13479 ) {
13480 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13481 cx.propagate();
13482 return;
13483 }
13484 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13485 self.change_selections(Default::default(), window, cx, |s| {
13486 s.select_ranges(vec![0..0]);
13487 });
13488 }
13489
13490 pub fn select_to_beginning(
13491 &mut self,
13492 _: &SelectToBeginning,
13493 window: &mut Window,
13494 cx: &mut Context<Self>,
13495 ) {
13496 let mut selection = self.selections.last::<Point>(cx);
13497 selection.set_head(Point::zero(), SelectionGoal::None);
13498 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13499 self.change_selections(Default::default(), window, cx, |s| {
13500 s.select(vec![selection]);
13501 });
13502 }
13503
13504 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13505 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13506 cx.propagate();
13507 return;
13508 }
13509 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13510 let cursor = self.buffer.read(cx).read(cx).len();
13511 self.change_selections(Default::default(), window, cx, |s| {
13512 s.select_ranges(vec![cursor..cursor])
13513 });
13514 }
13515
13516 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13517 self.nav_history = nav_history;
13518 }
13519
13520 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13521 self.nav_history.as_ref()
13522 }
13523
13524 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13525 self.push_to_nav_history(
13526 self.selections.newest_anchor().head(),
13527 None,
13528 false,
13529 true,
13530 cx,
13531 );
13532 }
13533
13534 fn push_to_nav_history(
13535 &mut self,
13536 cursor_anchor: Anchor,
13537 new_position: Option<Point>,
13538 is_deactivate: bool,
13539 always: bool,
13540 cx: &mut Context<Self>,
13541 ) {
13542 if let Some(nav_history) = self.nav_history.as_mut() {
13543 let buffer = self.buffer.read(cx).read(cx);
13544 let cursor_position = cursor_anchor.to_point(&buffer);
13545 let scroll_state = self.scroll_manager.anchor();
13546 let scroll_top_row = scroll_state.top_row(&buffer);
13547 drop(buffer);
13548
13549 if let Some(new_position) = new_position {
13550 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13551 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13552 return;
13553 }
13554 }
13555
13556 nav_history.push(
13557 Some(NavigationData {
13558 cursor_anchor,
13559 cursor_position,
13560 scroll_anchor: scroll_state,
13561 scroll_top_row,
13562 }),
13563 cx,
13564 );
13565 cx.emit(EditorEvent::PushedToNavHistory {
13566 anchor: cursor_anchor,
13567 is_deactivate,
13568 })
13569 }
13570 }
13571
13572 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13573 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13574 let buffer = self.buffer.read(cx).snapshot(cx);
13575 let mut selection = self.selections.first::<usize>(cx);
13576 selection.set_head(buffer.len(), SelectionGoal::None);
13577 self.change_selections(Default::default(), window, cx, |s| {
13578 s.select(vec![selection]);
13579 });
13580 }
13581
13582 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13583 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13584 let end = self.buffer.read(cx).read(cx).len();
13585 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13586 s.select_ranges(vec![0..end]);
13587 });
13588 }
13589
13590 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13592 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13593 let mut selections = self.selections.all::<Point>(cx);
13594 let max_point = display_map.buffer_snapshot.max_point();
13595 for selection in &mut selections {
13596 let rows = selection.spanned_rows(true, &display_map);
13597 selection.start = Point::new(rows.start.0, 0);
13598 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13599 selection.reversed = false;
13600 }
13601 self.change_selections(Default::default(), window, cx, |s| {
13602 s.select(selections);
13603 });
13604 }
13605
13606 pub fn split_selection_into_lines(
13607 &mut self,
13608 _: &SplitSelectionIntoLines,
13609 window: &mut Window,
13610 cx: &mut Context<Self>,
13611 ) {
13612 let selections = self
13613 .selections
13614 .all::<Point>(cx)
13615 .into_iter()
13616 .map(|selection| selection.start..selection.end)
13617 .collect::<Vec<_>>();
13618 self.unfold_ranges(&selections, true, true, cx);
13619
13620 let mut new_selection_ranges = Vec::new();
13621 {
13622 let buffer = self.buffer.read(cx).read(cx);
13623 for selection in selections {
13624 for row in selection.start.row..selection.end.row {
13625 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13626 new_selection_ranges.push(cursor..cursor);
13627 }
13628
13629 let is_multiline_selection = selection.start.row != selection.end.row;
13630 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13631 // so this action feels more ergonomic when paired with other selection operations
13632 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13633 if !should_skip_last {
13634 new_selection_ranges.push(selection.end..selection.end);
13635 }
13636 }
13637 }
13638 self.change_selections(Default::default(), window, cx, |s| {
13639 s.select_ranges(new_selection_ranges);
13640 });
13641 }
13642
13643 pub fn add_selection_above(
13644 &mut self,
13645 _: &AddSelectionAbove,
13646 window: &mut Window,
13647 cx: &mut Context<Self>,
13648 ) {
13649 self.add_selection(true, window, cx);
13650 }
13651
13652 pub fn add_selection_below(
13653 &mut self,
13654 _: &AddSelectionBelow,
13655 window: &mut Window,
13656 cx: &mut Context<Self>,
13657 ) {
13658 self.add_selection(false, window, cx);
13659 }
13660
13661 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13662 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13663
13664 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13665 let all_selections = self.selections.all::<Point>(cx);
13666 let text_layout_details = self.text_layout_details(window);
13667
13668 let (mut columnar_selections, new_selections_to_columnarize) = {
13669 if let Some(state) = self.add_selections_state.as_ref() {
13670 let columnar_selection_ids: HashSet<_> = state
13671 .groups
13672 .iter()
13673 .flat_map(|group| group.stack.iter())
13674 .copied()
13675 .collect();
13676
13677 all_selections
13678 .into_iter()
13679 .partition(|s| columnar_selection_ids.contains(&s.id))
13680 } else {
13681 (Vec::new(), all_selections)
13682 }
13683 };
13684
13685 let mut state = self
13686 .add_selections_state
13687 .take()
13688 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13689
13690 for selection in new_selections_to_columnarize {
13691 let range = selection.display_range(&display_map).sorted();
13692 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13693 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13694 let positions = start_x.min(end_x)..start_x.max(end_x);
13695 let mut stack = Vec::new();
13696 for row in range.start.row().0..=range.end.row().0 {
13697 if let Some(selection) = self.selections.build_columnar_selection(
13698 &display_map,
13699 DisplayRow(row),
13700 &positions,
13701 selection.reversed,
13702 &text_layout_details,
13703 ) {
13704 stack.push(selection.id);
13705 columnar_selections.push(selection);
13706 }
13707 }
13708 if !stack.is_empty() {
13709 if above {
13710 stack.reverse();
13711 }
13712 state.groups.push(AddSelectionsGroup { above, stack });
13713 }
13714 }
13715
13716 let mut final_selections = Vec::new();
13717 let end_row = if above {
13718 DisplayRow(0)
13719 } else {
13720 display_map.max_point().row()
13721 };
13722
13723 let mut last_added_item_per_group = HashMap::default();
13724 for group in state.groups.iter_mut() {
13725 if let Some(last_id) = group.stack.last() {
13726 last_added_item_per_group.insert(*last_id, group);
13727 }
13728 }
13729
13730 for selection in columnar_selections {
13731 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13732 if above == group.above {
13733 let range = selection.display_range(&display_map).sorted();
13734 debug_assert_eq!(range.start.row(), range.end.row());
13735 let mut row = range.start.row();
13736 let positions =
13737 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13738 px(start)..px(end)
13739 } else {
13740 let start_x =
13741 display_map.x_for_display_point(range.start, &text_layout_details);
13742 let end_x =
13743 display_map.x_for_display_point(range.end, &text_layout_details);
13744 start_x.min(end_x)..start_x.max(end_x)
13745 };
13746
13747 let mut maybe_new_selection = None;
13748 while row != end_row {
13749 if above {
13750 row.0 -= 1;
13751 } else {
13752 row.0 += 1;
13753 }
13754 if let Some(new_selection) = self.selections.build_columnar_selection(
13755 &display_map,
13756 row,
13757 &positions,
13758 selection.reversed,
13759 &text_layout_details,
13760 ) {
13761 maybe_new_selection = Some(new_selection);
13762 break;
13763 }
13764 }
13765
13766 if let Some(new_selection) = maybe_new_selection {
13767 group.stack.push(new_selection.id);
13768 if above {
13769 final_selections.push(new_selection);
13770 final_selections.push(selection);
13771 } else {
13772 final_selections.push(selection);
13773 final_selections.push(new_selection);
13774 }
13775 } else {
13776 final_selections.push(selection);
13777 }
13778 } else {
13779 group.stack.pop();
13780 }
13781 } else {
13782 final_selections.push(selection);
13783 }
13784 }
13785
13786 self.change_selections(Default::default(), window, cx, |s| {
13787 s.select(final_selections);
13788 });
13789
13790 let final_selection_ids: HashSet<_> = self
13791 .selections
13792 .all::<Point>(cx)
13793 .iter()
13794 .map(|s| s.id)
13795 .collect();
13796 state.groups.retain_mut(|group| {
13797 // selections might get merged above so we remove invalid items from stacks
13798 group.stack.retain(|id| final_selection_ids.contains(id));
13799
13800 // single selection in stack can be treated as initial state
13801 group.stack.len() > 1
13802 });
13803
13804 if !state.groups.is_empty() {
13805 self.add_selections_state = Some(state);
13806 }
13807 }
13808
13809 fn select_match_ranges(
13810 &mut self,
13811 range: Range<usize>,
13812 reversed: bool,
13813 replace_newest: bool,
13814 auto_scroll: Option<Autoscroll>,
13815 window: &mut Window,
13816 cx: &mut Context<Editor>,
13817 ) {
13818 self.unfold_ranges(
13819 std::slice::from_ref(&range),
13820 false,
13821 auto_scroll.is_some(),
13822 cx,
13823 );
13824 let effects = if let Some(scroll) = auto_scroll {
13825 SelectionEffects::scroll(scroll)
13826 } else {
13827 SelectionEffects::no_scroll()
13828 };
13829 self.change_selections(effects, window, cx, |s| {
13830 if replace_newest {
13831 s.delete(s.newest_anchor().id);
13832 }
13833 if reversed {
13834 s.insert_range(range.end..range.start);
13835 } else {
13836 s.insert_range(range);
13837 }
13838 });
13839 }
13840
13841 pub fn select_next_match_internal(
13842 &mut self,
13843 display_map: &DisplaySnapshot,
13844 replace_newest: bool,
13845 autoscroll: Option<Autoscroll>,
13846 window: &mut Window,
13847 cx: &mut Context<Self>,
13848 ) -> Result<()> {
13849 let buffer = &display_map.buffer_snapshot;
13850 let mut selections = self.selections.all::<usize>(cx);
13851 if let Some(mut select_next_state) = self.select_next_state.take() {
13852 let query = &select_next_state.query;
13853 if !select_next_state.done {
13854 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13855 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13856 let mut next_selected_range = None;
13857
13858 let bytes_after_last_selection =
13859 buffer.bytes_in_range(last_selection.end..buffer.len());
13860 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13861 let query_matches = query
13862 .stream_find_iter(bytes_after_last_selection)
13863 .map(|result| (last_selection.end, result))
13864 .chain(
13865 query
13866 .stream_find_iter(bytes_before_first_selection)
13867 .map(|result| (0, result)),
13868 );
13869
13870 for (start_offset, query_match) in query_matches {
13871 let query_match = query_match.unwrap(); // can only fail due to I/O
13872 let offset_range =
13873 start_offset + query_match.start()..start_offset + query_match.end();
13874
13875 if !select_next_state.wordwise
13876 || (!buffer.is_inside_word(offset_range.start, false)
13877 && !buffer.is_inside_word(offset_range.end, false))
13878 {
13879 // TODO: This is n^2, because we might check all the selections
13880 if !selections
13881 .iter()
13882 .any(|selection| selection.range().overlaps(&offset_range))
13883 {
13884 next_selected_range = Some(offset_range);
13885 break;
13886 }
13887 }
13888 }
13889
13890 if let Some(next_selected_range) = next_selected_range {
13891 self.select_match_ranges(
13892 next_selected_range,
13893 last_selection.reversed,
13894 replace_newest,
13895 autoscroll,
13896 window,
13897 cx,
13898 );
13899 } else {
13900 select_next_state.done = true;
13901 }
13902 }
13903
13904 self.select_next_state = Some(select_next_state);
13905 } else {
13906 let mut only_carets = true;
13907 let mut same_text_selected = true;
13908 let mut selected_text = None;
13909
13910 let mut selections_iter = selections.iter().peekable();
13911 while let Some(selection) = selections_iter.next() {
13912 if selection.start != selection.end {
13913 only_carets = false;
13914 }
13915
13916 if same_text_selected {
13917 if selected_text.is_none() {
13918 selected_text =
13919 Some(buffer.text_for_range(selection.range()).collect::<String>());
13920 }
13921
13922 if let Some(next_selection) = selections_iter.peek() {
13923 if next_selection.range().len() == selection.range().len() {
13924 let next_selected_text = buffer
13925 .text_for_range(next_selection.range())
13926 .collect::<String>();
13927 if Some(next_selected_text) != selected_text {
13928 same_text_selected = false;
13929 selected_text = None;
13930 }
13931 } else {
13932 same_text_selected = false;
13933 selected_text = None;
13934 }
13935 }
13936 }
13937 }
13938
13939 if only_carets {
13940 for selection in &mut selections {
13941 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13942 selection.start = word_range.start;
13943 selection.end = word_range.end;
13944 selection.goal = SelectionGoal::None;
13945 selection.reversed = false;
13946 self.select_match_ranges(
13947 selection.start..selection.end,
13948 selection.reversed,
13949 replace_newest,
13950 autoscroll,
13951 window,
13952 cx,
13953 );
13954 }
13955
13956 if selections.len() == 1 {
13957 let selection = selections
13958 .last()
13959 .expect("ensured that there's only one selection");
13960 let query = buffer
13961 .text_for_range(selection.start..selection.end)
13962 .collect::<String>();
13963 let is_empty = query.is_empty();
13964 let select_state = SelectNextState {
13965 query: AhoCorasick::new(&[query])?,
13966 wordwise: true,
13967 done: is_empty,
13968 };
13969 self.select_next_state = Some(select_state);
13970 } else {
13971 self.select_next_state = None;
13972 }
13973 } else if let Some(selected_text) = selected_text {
13974 self.select_next_state = Some(SelectNextState {
13975 query: AhoCorasick::new(&[selected_text])?,
13976 wordwise: false,
13977 done: false,
13978 });
13979 self.select_next_match_internal(
13980 display_map,
13981 replace_newest,
13982 autoscroll,
13983 window,
13984 cx,
13985 )?;
13986 }
13987 }
13988 Ok(())
13989 }
13990
13991 pub fn select_all_matches(
13992 &mut self,
13993 _action: &SelectAllMatches,
13994 window: &mut Window,
13995 cx: &mut Context<Self>,
13996 ) -> Result<()> {
13997 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13998
13999 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14000
14001 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14002 let Some(select_next_state) = self.select_next_state.as_mut() else {
14003 return Ok(());
14004 };
14005 if select_next_state.done {
14006 return Ok(());
14007 }
14008
14009 let mut new_selections = Vec::new();
14010
14011 let reversed = self.selections.oldest::<usize>(cx).reversed;
14012 let buffer = &display_map.buffer_snapshot;
14013 let query_matches = select_next_state
14014 .query
14015 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14016
14017 for query_match in query_matches.into_iter() {
14018 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14019 let offset_range = if reversed {
14020 query_match.end()..query_match.start()
14021 } else {
14022 query_match.start()..query_match.end()
14023 };
14024
14025 if !select_next_state.wordwise
14026 || (!buffer.is_inside_word(offset_range.start, false)
14027 && !buffer.is_inside_word(offset_range.end, false))
14028 {
14029 new_selections.push(offset_range.start..offset_range.end);
14030 }
14031 }
14032
14033 select_next_state.done = true;
14034
14035 if new_selections.is_empty() {
14036 log::error!("bug: new_selections is empty in select_all_matches");
14037 return Ok(());
14038 }
14039
14040 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14041 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14042 selections.select_ranges(new_selections)
14043 });
14044
14045 Ok(())
14046 }
14047
14048 pub fn select_next(
14049 &mut self,
14050 action: &SelectNext,
14051 window: &mut Window,
14052 cx: &mut Context<Self>,
14053 ) -> Result<()> {
14054 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14055 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14056 self.select_next_match_internal(
14057 &display_map,
14058 action.replace_newest,
14059 Some(Autoscroll::newest()),
14060 window,
14061 cx,
14062 )?;
14063 Ok(())
14064 }
14065
14066 pub fn select_previous(
14067 &mut self,
14068 action: &SelectPrevious,
14069 window: &mut Window,
14070 cx: &mut Context<Self>,
14071 ) -> Result<()> {
14072 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14073 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14074 let buffer = &display_map.buffer_snapshot;
14075 let mut selections = self.selections.all::<usize>(cx);
14076 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14077 let query = &select_prev_state.query;
14078 if !select_prev_state.done {
14079 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14080 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14081 let mut next_selected_range = None;
14082 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14083 let bytes_before_last_selection =
14084 buffer.reversed_bytes_in_range(0..last_selection.start);
14085 let bytes_after_first_selection =
14086 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14087 let query_matches = query
14088 .stream_find_iter(bytes_before_last_selection)
14089 .map(|result| (last_selection.start, result))
14090 .chain(
14091 query
14092 .stream_find_iter(bytes_after_first_selection)
14093 .map(|result| (buffer.len(), result)),
14094 );
14095 for (end_offset, query_match) in query_matches {
14096 let query_match = query_match.unwrap(); // can only fail due to I/O
14097 let offset_range =
14098 end_offset - query_match.end()..end_offset - query_match.start();
14099
14100 if !select_prev_state.wordwise
14101 || (!buffer.is_inside_word(offset_range.start, false)
14102 && !buffer.is_inside_word(offset_range.end, false))
14103 {
14104 next_selected_range = Some(offset_range);
14105 break;
14106 }
14107 }
14108
14109 if let Some(next_selected_range) = next_selected_range {
14110 self.select_match_ranges(
14111 next_selected_range,
14112 last_selection.reversed,
14113 action.replace_newest,
14114 Some(Autoscroll::newest()),
14115 window,
14116 cx,
14117 );
14118 } else {
14119 select_prev_state.done = true;
14120 }
14121 }
14122
14123 self.select_prev_state = Some(select_prev_state);
14124 } else {
14125 let mut only_carets = true;
14126 let mut same_text_selected = true;
14127 let mut selected_text = None;
14128
14129 let mut selections_iter = selections.iter().peekable();
14130 while let Some(selection) = selections_iter.next() {
14131 if selection.start != selection.end {
14132 only_carets = false;
14133 }
14134
14135 if same_text_selected {
14136 if selected_text.is_none() {
14137 selected_text =
14138 Some(buffer.text_for_range(selection.range()).collect::<String>());
14139 }
14140
14141 if let Some(next_selection) = selections_iter.peek() {
14142 if next_selection.range().len() == selection.range().len() {
14143 let next_selected_text = buffer
14144 .text_for_range(next_selection.range())
14145 .collect::<String>();
14146 if Some(next_selected_text) != selected_text {
14147 same_text_selected = false;
14148 selected_text = None;
14149 }
14150 } else {
14151 same_text_selected = false;
14152 selected_text = None;
14153 }
14154 }
14155 }
14156 }
14157
14158 if only_carets {
14159 for selection in &mut selections {
14160 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14161 selection.start = word_range.start;
14162 selection.end = word_range.end;
14163 selection.goal = SelectionGoal::None;
14164 selection.reversed = false;
14165 self.select_match_ranges(
14166 selection.start..selection.end,
14167 selection.reversed,
14168 action.replace_newest,
14169 Some(Autoscroll::newest()),
14170 window,
14171 cx,
14172 );
14173 }
14174 if selections.len() == 1 {
14175 let selection = selections
14176 .last()
14177 .expect("ensured that there's only one selection");
14178 let query = buffer
14179 .text_for_range(selection.start..selection.end)
14180 .collect::<String>();
14181 let is_empty = query.is_empty();
14182 let select_state = SelectNextState {
14183 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14184 wordwise: true,
14185 done: is_empty,
14186 };
14187 self.select_prev_state = Some(select_state);
14188 } else {
14189 self.select_prev_state = None;
14190 }
14191 } else if let Some(selected_text) = selected_text {
14192 self.select_prev_state = Some(SelectNextState {
14193 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14194 wordwise: false,
14195 done: false,
14196 });
14197 self.select_previous(action, window, cx)?;
14198 }
14199 }
14200 Ok(())
14201 }
14202
14203 pub fn find_next_match(
14204 &mut self,
14205 _: &FindNextMatch,
14206 window: &mut Window,
14207 cx: &mut Context<Self>,
14208 ) -> Result<()> {
14209 let selections = self.selections.disjoint_anchors();
14210 match selections.first() {
14211 Some(first) if selections.len() >= 2 => {
14212 self.change_selections(Default::default(), window, cx, |s| {
14213 s.select_ranges([first.range()]);
14214 });
14215 }
14216 _ => self.select_next(
14217 &SelectNext {
14218 replace_newest: true,
14219 },
14220 window,
14221 cx,
14222 )?,
14223 }
14224 Ok(())
14225 }
14226
14227 pub fn find_previous_match(
14228 &mut self,
14229 _: &FindPreviousMatch,
14230 window: &mut Window,
14231 cx: &mut Context<Self>,
14232 ) -> Result<()> {
14233 let selections = self.selections.disjoint_anchors();
14234 match selections.last() {
14235 Some(last) if selections.len() >= 2 => {
14236 self.change_selections(Default::default(), window, cx, |s| {
14237 s.select_ranges([last.range()]);
14238 });
14239 }
14240 _ => self.select_previous(
14241 &SelectPrevious {
14242 replace_newest: true,
14243 },
14244 window,
14245 cx,
14246 )?,
14247 }
14248 Ok(())
14249 }
14250
14251 pub fn toggle_comments(
14252 &mut self,
14253 action: &ToggleComments,
14254 window: &mut Window,
14255 cx: &mut Context<Self>,
14256 ) {
14257 if self.read_only(cx) {
14258 return;
14259 }
14260 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14261 let text_layout_details = &self.text_layout_details(window);
14262 self.transact(window, cx, |this, window, cx| {
14263 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14264 let mut edits = Vec::new();
14265 let mut selection_edit_ranges = Vec::new();
14266 let mut last_toggled_row = None;
14267 let snapshot = this.buffer.read(cx).read(cx);
14268 let empty_str: Arc<str> = Arc::default();
14269 let mut suffixes_inserted = Vec::new();
14270 let ignore_indent = action.ignore_indent;
14271
14272 fn comment_prefix_range(
14273 snapshot: &MultiBufferSnapshot,
14274 row: MultiBufferRow,
14275 comment_prefix: &str,
14276 comment_prefix_whitespace: &str,
14277 ignore_indent: bool,
14278 ) -> Range<Point> {
14279 let indent_size = if ignore_indent {
14280 0
14281 } else {
14282 snapshot.indent_size_for_line(row).len
14283 };
14284
14285 let start = Point::new(row.0, indent_size);
14286
14287 let mut line_bytes = snapshot
14288 .bytes_in_range(start..snapshot.max_point())
14289 .flatten()
14290 .copied();
14291
14292 // If this line currently begins with the line comment prefix, then record
14293 // the range containing the prefix.
14294 if line_bytes
14295 .by_ref()
14296 .take(comment_prefix.len())
14297 .eq(comment_prefix.bytes())
14298 {
14299 // Include any whitespace that matches the comment prefix.
14300 let matching_whitespace_len = line_bytes
14301 .zip(comment_prefix_whitespace.bytes())
14302 .take_while(|(a, b)| a == b)
14303 .count() as u32;
14304 let end = Point::new(
14305 start.row,
14306 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14307 );
14308 start..end
14309 } else {
14310 start..start
14311 }
14312 }
14313
14314 fn comment_suffix_range(
14315 snapshot: &MultiBufferSnapshot,
14316 row: MultiBufferRow,
14317 comment_suffix: &str,
14318 comment_suffix_has_leading_space: bool,
14319 ) -> Range<Point> {
14320 let end = Point::new(row.0, snapshot.line_len(row));
14321 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14322
14323 let mut line_end_bytes = snapshot
14324 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14325 .flatten()
14326 .copied();
14327
14328 let leading_space_len = if suffix_start_column > 0
14329 && line_end_bytes.next() == Some(b' ')
14330 && comment_suffix_has_leading_space
14331 {
14332 1
14333 } else {
14334 0
14335 };
14336
14337 // If this line currently begins with the line comment prefix, then record
14338 // the range containing the prefix.
14339 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14340 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14341 start..end
14342 } else {
14343 end..end
14344 }
14345 }
14346
14347 // TODO: Handle selections that cross excerpts
14348 for selection in &mut selections {
14349 let start_column = snapshot
14350 .indent_size_for_line(MultiBufferRow(selection.start.row))
14351 .len;
14352 let language = if let Some(language) =
14353 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14354 {
14355 language
14356 } else {
14357 continue;
14358 };
14359
14360 selection_edit_ranges.clear();
14361
14362 // If multiple selections contain a given row, avoid processing that
14363 // row more than once.
14364 let mut start_row = MultiBufferRow(selection.start.row);
14365 if last_toggled_row == Some(start_row) {
14366 start_row = start_row.next_row();
14367 }
14368 let end_row =
14369 if selection.end.row > selection.start.row && selection.end.column == 0 {
14370 MultiBufferRow(selection.end.row - 1)
14371 } else {
14372 MultiBufferRow(selection.end.row)
14373 };
14374 last_toggled_row = Some(end_row);
14375
14376 if start_row > end_row {
14377 continue;
14378 }
14379
14380 // If the language has line comments, toggle those.
14381 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14382
14383 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14384 if ignore_indent {
14385 full_comment_prefixes = full_comment_prefixes
14386 .into_iter()
14387 .map(|s| Arc::from(s.trim_end()))
14388 .collect();
14389 }
14390
14391 if !full_comment_prefixes.is_empty() {
14392 let first_prefix = full_comment_prefixes
14393 .first()
14394 .expect("prefixes is non-empty");
14395 let prefix_trimmed_lengths = full_comment_prefixes
14396 .iter()
14397 .map(|p| p.trim_end_matches(' ').len())
14398 .collect::<SmallVec<[usize; 4]>>();
14399
14400 let mut all_selection_lines_are_comments = true;
14401
14402 for row in start_row.0..=end_row.0 {
14403 let row = MultiBufferRow(row);
14404 if start_row < end_row && snapshot.is_line_blank(row) {
14405 continue;
14406 }
14407
14408 let prefix_range = full_comment_prefixes
14409 .iter()
14410 .zip(prefix_trimmed_lengths.iter().copied())
14411 .map(|(prefix, trimmed_prefix_len)| {
14412 comment_prefix_range(
14413 snapshot.deref(),
14414 row,
14415 &prefix[..trimmed_prefix_len],
14416 &prefix[trimmed_prefix_len..],
14417 ignore_indent,
14418 )
14419 })
14420 .max_by_key(|range| range.end.column - range.start.column)
14421 .expect("prefixes is non-empty");
14422
14423 if prefix_range.is_empty() {
14424 all_selection_lines_are_comments = false;
14425 }
14426
14427 selection_edit_ranges.push(prefix_range);
14428 }
14429
14430 if all_selection_lines_are_comments {
14431 edits.extend(
14432 selection_edit_ranges
14433 .iter()
14434 .cloned()
14435 .map(|range| (range, empty_str.clone())),
14436 );
14437 } else {
14438 let min_column = selection_edit_ranges
14439 .iter()
14440 .map(|range| range.start.column)
14441 .min()
14442 .unwrap_or(0);
14443 edits.extend(selection_edit_ranges.iter().map(|range| {
14444 let position = Point::new(range.start.row, min_column);
14445 (position..position, first_prefix.clone())
14446 }));
14447 }
14448 } else if let Some(BlockCommentConfig {
14449 start: full_comment_prefix,
14450 end: comment_suffix,
14451 ..
14452 }) = language.block_comment()
14453 {
14454 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14455 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14456 let prefix_range = comment_prefix_range(
14457 snapshot.deref(),
14458 start_row,
14459 comment_prefix,
14460 comment_prefix_whitespace,
14461 ignore_indent,
14462 );
14463 let suffix_range = comment_suffix_range(
14464 snapshot.deref(),
14465 end_row,
14466 comment_suffix.trim_start_matches(' '),
14467 comment_suffix.starts_with(' '),
14468 );
14469
14470 if prefix_range.is_empty() || suffix_range.is_empty() {
14471 edits.push((
14472 prefix_range.start..prefix_range.start,
14473 full_comment_prefix.clone(),
14474 ));
14475 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14476 suffixes_inserted.push((end_row, comment_suffix.len()));
14477 } else {
14478 edits.push((prefix_range, empty_str.clone()));
14479 edits.push((suffix_range, empty_str.clone()));
14480 }
14481 } else {
14482 continue;
14483 }
14484 }
14485
14486 drop(snapshot);
14487 this.buffer.update(cx, |buffer, cx| {
14488 buffer.edit(edits, None, cx);
14489 });
14490
14491 // Adjust selections so that they end before any comment suffixes that
14492 // were inserted.
14493 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14494 let mut selections = this.selections.all::<Point>(cx);
14495 let snapshot = this.buffer.read(cx).read(cx);
14496 for selection in &mut selections {
14497 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14498 match row.cmp(&MultiBufferRow(selection.end.row)) {
14499 Ordering::Less => {
14500 suffixes_inserted.next();
14501 continue;
14502 }
14503 Ordering::Greater => break,
14504 Ordering::Equal => {
14505 if selection.end.column == snapshot.line_len(row) {
14506 if selection.is_empty() {
14507 selection.start.column -= suffix_len as u32;
14508 }
14509 selection.end.column -= suffix_len as u32;
14510 }
14511 break;
14512 }
14513 }
14514 }
14515 }
14516
14517 drop(snapshot);
14518 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14519
14520 let selections = this.selections.all::<Point>(cx);
14521 let selections_on_single_row = selections.windows(2).all(|selections| {
14522 selections[0].start.row == selections[1].start.row
14523 && selections[0].end.row == selections[1].end.row
14524 && selections[0].start.row == selections[0].end.row
14525 });
14526 let selections_selecting = selections
14527 .iter()
14528 .any(|selection| selection.start != selection.end);
14529 let advance_downwards = action.advance_downwards
14530 && selections_on_single_row
14531 && !selections_selecting
14532 && !matches!(this.mode, EditorMode::SingleLine { .. });
14533
14534 if advance_downwards {
14535 let snapshot = this.buffer.read(cx).snapshot(cx);
14536
14537 this.change_selections(Default::default(), window, cx, |s| {
14538 s.move_cursors_with(|display_snapshot, display_point, _| {
14539 let mut point = display_point.to_point(display_snapshot);
14540 point.row += 1;
14541 point = snapshot.clip_point(point, Bias::Left);
14542 let display_point = point.to_display_point(display_snapshot);
14543 let goal = SelectionGoal::HorizontalPosition(
14544 display_snapshot
14545 .x_for_display_point(display_point, text_layout_details)
14546 .into(),
14547 );
14548 (display_point, goal)
14549 })
14550 });
14551 }
14552 });
14553 }
14554
14555 pub fn select_enclosing_symbol(
14556 &mut self,
14557 _: &SelectEnclosingSymbol,
14558 window: &mut Window,
14559 cx: &mut Context<Self>,
14560 ) {
14561 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14562
14563 let buffer = self.buffer.read(cx).snapshot(cx);
14564 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14565
14566 fn update_selection(
14567 selection: &Selection<usize>,
14568 buffer_snap: &MultiBufferSnapshot,
14569 ) -> Option<Selection<usize>> {
14570 let cursor = selection.head();
14571 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14572 for symbol in symbols.iter().rev() {
14573 let start = symbol.range.start.to_offset(buffer_snap);
14574 let end = symbol.range.end.to_offset(buffer_snap);
14575 let new_range = start..end;
14576 if start < selection.start || end > selection.end {
14577 return Some(Selection {
14578 id: selection.id,
14579 start: new_range.start,
14580 end: new_range.end,
14581 goal: SelectionGoal::None,
14582 reversed: selection.reversed,
14583 });
14584 }
14585 }
14586 None
14587 }
14588
14589 let mut selected_larger_symbol = false;
14590 let new_selections = old_selections
14591 .iter()
14592 .map(|selection| match update_selection(selection, &buffer) {
14593 Some(new_selection) => {
14594 if new_selection.range() != selection.range() {
14595 selected_larger_symbol = true;
14596 }
14597 new_selection
14598 }
14599 None => selection.clone(),
14600 })
14601 .collect::<Vec<_>>();
14602
14603 if selected_larger_symbol {
14604 self.change_selections(Default::default(), window, cx, |s| {
14605 s.select(new_selections);
14606 });
14607 }
14608 }
14609
14610 pub fn select_larger_syntax_node(
14611 &mut self,
14612 _: &SelectLargerSyntaxNode,
14613 window: &mut Window,
14614 cx: &mut Context<Self>,
14615 ) {
14616 let Some(visible_row_count) = self.visible_row_count() else {
14617 return;
14618 };
14619 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14620 if old_selections.is_empty() {
14621 return;
14622 }
14623
14624 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14625
14626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14627 let buffer = self.buffer.read(cx).snapshot(cx);
14628
14629 let mut selected_larger_node = false;
14630 let mut new_selections = old_selections
14631 .iter()
14632 .map(|selection| {
14633 let old_range = selection.start..selection.end;
14634
14635 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14636 // manually select word at selection
14637 if ["string_content", "inline"].contains(&node.kind()) {
14638 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14639 // ignore if word is already selected
14640 if !word_range.is_empty() && old_range != word_range {
14641 let (last_word_range, _) =
14642 buffer.surrounding_word(old_range.end, false);
14643 // only select word if start and end point belongs to same word
14644 if word_range == last_word_range {
14645 selected_larger_node = true;
14646 return Selection {
14647 id: selection.id,
14648 start: word_range.start,
14649 end: word_range.end,
14650 goal: SelectionGoal::None,
14651 reversed: selection.reversed,
14652 };
14653 }
14654 }
14655 }
14656 }
14657
14658 let mut new_range = old_range.clone();
14659 while let Some((_node, containing_range)) =
14660 buffer.syntax_ancestor(new_range.clone())
14661 {
14662 new_range = match containing_range {
14663 MultiOrSingleBufferOffsetRange::Single(_) => break,
14664 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14665 };
14666 if !display_map.intersects_fold(new_range.start)
14667 && !display_map.intersects_fold(new_range.end)
14668 {
14669 break;
14670 }
14671 }
14672
14673 selected_larger_node |= new_range != old_range;
14674 Selection {
14675 id: selection.id,
14676 start: new_range.start,
14677 end: new_range.end,
14678 goal: SelectionGoal::None,
14679 reversed: selection.reversed,
14680 }
14681 })
14682 .collect::<Vec<_>>();
14683
14684 if !selected_larger_node {
14685 return; // don't put this call in the history
14686 }
14687
14688 // scroll based on transformation done to the last selection created by the user
14689 let (last_old, last_new) = old_selections
14690 .last()
14691 .zip(new_selections.last().cloned())
14692 .expect("old_selections isn't empty");
14693
14694 // revert selection
14695 let is_selection_reversed = {
14696 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14697 new_selections.last_mut().expect("checked above").reversed =
14698 should_newest_selection_be_reversed;
14699 should_newest_selection_be_reversed
14700 };
14701
14702 if selected_larger_node {
14703 self.select_syntax_node_history.disable_clearing = true;
14704 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14705 s.select(new_selections.clone());
14706 });
14707 self.select_syntax_node_history.disable_clearing = false;
14708 }
14709
14710 let start_row = last_new.start.to_display_point(&display_map).row().0;
14711 let end_row = last_new.end.to_display_point(&display_map).row().0;
14712 let selection_height = end_row - start_row + 1;
14713 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14714
14715 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14716 let scroll_behavior = if fits_on_the_screen {
14717 self.request_autoscroll(Autoscroll::fit(), cx);
14718 SelectSyntaxNodeScrollBehavior::FitSelection
14719 } else if is_selection_reversed {
14720 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14721 SelectSyntaxNodeScrollBehavior::CursorTop
14722 } else {
14723 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14724 SelectSyntaxNodeScrollBehavior::CursorBottom
14725 };
14726
14727 self.select_syntax_node_history.push((
14728 old_selections,
14729 scroll_behavior,
14730 is_selection_reversed,
14731 ));
14732 }
14733
14734 pub fn select_smaller_syntax_node(
14735 &mut self,
14736 _: &SelectSmallerSyntaxNode,
14737 window: &mut Window,
14738 cx: &mut Context<Self>,
14739 ) {
14740 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14741
14742 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14743 self.select_syntax_node_history.pop()
14744 {
14745 if let Some(selection) = selections.last_mut() {
14746 selection.reversed = is_selection_reversed;
14747 }
14748
14749 self.select_syntax_node_history.disable_clearing = true;
14750 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14751 s.select(selections.to_vec());
14752 });
14753 self.select_syntax_node_history.disable_clearing = false;
14754
14755 match scroll_behavior {
14756 SelectSyntaxNodeScrollBehavior::CursorTop => {
14757 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14758 }
14759 SelectSyntaxNodeScrollBehavior::FitSelection => {
14760 self.request_autoscroll(Autoscroll::fit(), cx);
14761 }
14762 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14763 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14764 }
14765 }
14766 }
14767 }
14768
14769 pub fn unwrap_syntax_node(
14770 &mut self,
14771 _: &UnwrapSyntaxNode,
14772 window: &mut Window,
14773 cx: &mut Context<Self>,
14774 ) {
14775 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14776
14777 let buffer = self.buffer.read(cx).snapshot(cx);
14778 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14779
14780 let edits = old_selections
14781 .iter()
14782 // only consider the first selection for now
14783 .take(1)
14784 .map(|selection| {
14785 // Only requires two branches once if-let-chains stabilize (#53667)
14786 let selection_range = if !selection.is_empty() {
14787 selection.range()
14788 } else if let Some((_, ancestor_range)) =
14789 buffer.syntax_ancestor(selection.start..selection.end)
14790 {
14791 match ancestor_range {
14792 MultiOrSingleBufferOffsetRange::Single(range) => range,
14793 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14794 }
14795 } else {
14796 selection.range()
14797 };
14798
14799 let mut new_range = selection_range.clone();
14800 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(new_range.clone()) {
14801 new_range = match ancestor_range {
14802 MultiOrSingleBufferOffsetRange::Single(range) => range,
14803 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14804 };
14805 if new_range.start < selection_range.start
14806 || new_range.end > selection_range.end
14807 {
14808 break;
14809 }
14810 }
14811
14812 (selection, selection_range, new_range)
14813 })
14814 .collect::<Vec<_>>();
14815
14816 self.transact(window, cx, |editor, window, cx| {
14817 for (_, child, parent) in &edits {
14818 let text = buffer.text_for_range(child.clone()).collect::<String>();
14819 editor.replace_text_in_range(Some(parent.clone()), &text, window, cx);
14820 }
14821
14822 editor.change_selections(
14823 SelectionEffects::scroll(Autoscroll::fit()),
14824 window,
14825 cx,
14826 |s| {
14827 s.select(
14828 edits
14829 .iter()
14830 .map(|(s, old, new)| Selection {
14831 id: s.id,
14832 start: new.start,
14833 end: new.start + old.len(),
14834 goal: SelectionGoal::None,
14835 reversed: s.reversed,
14836 })
14837 .collect(),
14838 );
14839 },
14840 );
14841 });
14842 }
14843
14844 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14845 if !EditorSettings::get_global(cx).gutter.runnables {
14846 self.clear_tasks();
14847 return Task::ready(());
14848 }
14849 let project = self.project.as_ref().map(Entity::downgrade);
14850 let task_sources = self.lsp_task_sources(cx);
14851 let multi_buffer = self.buffer.downgrade();
14852 cx.spawn_in(window, async move |editor, cx| {
14853 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14854 let Some(project) = project.and_then(|p| p.upgrade()) else {
14855 return;
14856 };
14857 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14858 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14859 }) else {
14860 return;
14861 };
14862
14863 let hide_runnables = project
14864 .update(cx, |project, cx| {
14865 // Do not display any test indicators in non-dev server remote projects.
14866 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14867 })
14868 .unwrap_or(true);
14869 if hide_runnables {
14870 return;
14871 }
14872 let new_rows =
14873 cx.background_spawn({
14874 let snapshot = display_snapshot.clone();
14875 async move {
14876 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14877 }
14878 })
14879 .await;
14880 let Ok(lsp_tasks) =
14881 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14882 else {
14883 return;
14884 };
14885 let lsp_tasks = lsp_tasks.await;
14886
14887 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14888 lsp_tasks
14889 .into_iter()
14890 .flat_map(|(kind, tasks)| {
14891 tasks.into_iter().filter_map(move |(location, task)| {
14892 Some((kind.clone(), location?, task))
14893 })
14894 })
14895 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14896 let buffer = location.target.buffer;
14897 let buffer_snapshot = buffer.read(cx).snapshot();
14898 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14899 |(excerpt_id, snapshot, _)| {
14900 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14901 display_snapshot
14902 .buffer_snapshot
14903 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14904 } else {
14905 None
14906 }
14907 },
14908 );
14909 if let Some(offset) = offset {
14910 let task_buffer_range =
14911 location.target.range.to_point(&buffer_snapshot);
14912 let context_buffer_range =
14913 task_buffer_range.to_offset(&buffer_snapshot);
14914 let context_range = BufferOffset(context_buffer_range.start)
14915 ..BufferOffset(context_buffer_range.end);
14916
14917 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14918 .or_insert_with(|| RunnableTasks {
14919 templates: Vec::new(),
14920 offset,
14921 column: task_buffer_range.start.column,
14922 extra_variables: HashMap::default(),
14923 context_range,
14924 })
14925 .templates
14926 .push((kind, task.original_task().clone()));
14927 }
14928
14929 acc
14930 })
14931 }) else {
14932 return;
14933 };
14934
14935 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14936 buffer.language_settings(cx).tasks.prefer_lsp
14937 }) else {
14938 return;
14939 };
14940
14941 let rows = Self::runnable_rows(
14942 project,
14943 display_snapshot,
14944 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14945 new_rows,
14946 cx.clone(),
14947 )
14948 .await;
14949 editor
14950 .update(cx, |editor, _| {
14951 editor.clear_tasks();
14952 for (key, mut value) in rows {
14953 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14954 value.templates.extend(lsp_tasks.templates);
14955 }
14956
14957 editor.insert_tasks(key, value);
14958 }
14959 for (key, value) in lsp_tasks_by_rows {
14960 editor.insert_tasks(key, value);
14961 }
14962 })
14963 .ok();
14964 })
14965 }
14966 fn fetch_runnable_ranges(
14967 snapshot: &DisplaySnapshot,
14968 range: Range<Anchor>,
14969 ) -> Vec<language::RunnableRange> {
14970 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14971 }
14972
14973 fn runnable_rows(
14974 project: Entity<Project>,
14975 snapshot: DisplaySnapshot,
14976 prefer_lsp: bool,
14977 runnable_ranges: Vec<RunnableRange>,
14978 cx: AsyncWindowContext,
14979 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14980 cx.spawn(async move |cx| {
14981 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14982 for mut runnable in runnable_ranges {
14983 let Some(tasks) = cx
14984 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14985 .ok()
14986 else {
14987 continue;
14988 };
14989 let mut tasks = tasks.await;
14990
14991 if prefer_lsp {
14992 tasks.retain(|(task_kind, _)| {
14993 !matches!(task_kind, TaskSourceKind::Language { .. })
14994 });
14995 }
14996 if tasks.is_empty() {
14997 continue;
14998 }
14999
15000 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15001 let Some(row) = snapshot
15002 .buffer_snapshot
15003 .buffer_line_for_row(MultiBufferRow(point.row))
15004 .map(|(_, range)| range.start.row)
15005 else {
15006 continue;
15007 };
15008
15009 let context_range =
15010 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15011 runnable_rows.push((
15012 (runnable.buffer_id, row),
15013 RunnableTasks {
15014 templates: tasks,
15015 offset: snapshot
15016 .buffer_snapshot
15017 .anchor_before(runnable.run_range.start),
15018 context_range,
15019 column: point.column,
15020 extra_variables: runnable.extra_captures,
15021 },
15022 ));
15023 }
15024 runnable_rows
15025 })
15026 }
15027
15028 fn templates_with_tags(
15029 project: &Entity<Project>,
15030 runnable: &mut Runnable,
15031 cx: &mut App,
15032 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15033 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15034 let (worktree_id, file) = project
15035 .buffer_for_id(runnable.buffer, cx)
15036 .and_then(|buffer| buffer.read(cx).file())
15037 .map(|file| (file.worktree_id(cx), file.clone()))
15038 .unzip();
15039
15040 (
15041 project.task_store().read(cx).task_inventory().cloned(),
15042 worktree_id,
15043 file,
15044 )
15045 });
15046
15047 let tags = mem::take(&mut runnable.tags);
15048 let language = runnable.language.clone();
15049 cx.spawn(async move |cx| {
15050 let mut templates_with_tags = Vec::new();
15051 if let Some(inventory) = inventory {
15052 for RunnableTag(tag) in tags {
15053 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15054 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15055 }) else {
15056 return templates_with_tags;
15057 };
15058 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15059 move |(_, template)| {
15060 template.tags.iter().any(|source_tag| source_tag == &tag)
15061 },
15062 ));
15063 }
15064 }
15065 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15066
15067 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15068 // Strongest source wins; if we have worktree tag binding, prefer that to
15069 // global and language bindings;
15070 // if we have a global binding, prefer that to language binding.
15071 let first_mismatch = templates_with_tags
15072 .iter()
15073 .position(|(tag_source, _)| tag_source != leading_tag_source);
15074 if let Some(index) = first_mismatch {
15075 templates_with_tags.truncate(index);
15076 }
15077 }
15078
15079 templates_with_tags
15080 })
15081 }
15082
15083 pub fn move_to_enclosing_bracket(
15084 &mut self,
15085 _: &MoveToEnclosingBracket,
15086 window: &mut Window,
15087 cx: &mut Context<Self>,
15088 ) {
15089 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15090 self.change_selections(Default::default(), window, cx, |s| {
15091 s.move_offsets_with(|snapshot, selection| {
15092 let Some(enclosing_bracket_ranges) =
15093 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15094 else {
15095 return;
15096 };
15097
15098 let mut best_length = usize::MAX;
15099 let mut best_inside = false;
15100 let mut best_in_bracket_range = false;
15101 let mut best_destination = None;
15102 for (open, close) in enclosing_bracket_ranges {
15103 let close = close.to_inclusive();
15104 let length = close.end() - open.start;
15105 let inside = selection.start >= open.end && selection.end <= *close.start();
15106 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15107 || close.contains(&selection.head());
15108
15109 // If best is next to a bracket and current isn't, skip
15110 if !in_bracket_range && best_in_bracket_range {
15111 continue;
15112 }
15113
15114 // Prefer smaller lengths unless best is inside and current isn't
15115 if length > best_length && (best_inside || !inside) {
15116 continue;
15117 }
15118
15119 best_length = length;
15120 best_inside = inside;
15121 best_in_bracket_range = in_bracket_range;
15122 best_destination = Some(
15123 if close.contains(&selection.start) && close.contains(&selection.end) {
15124 if inside { open.end } else { open.start }
15125 } else if inside {
15126 *close.start()
15127 } else {
15128 *close.end()
15129 },
15130 );
15131 }
15132
15133 if let Some(destination) = best_destination {
15134 selection.collapse_to(destination, SelectionGoal::None);
15135 }
15136 })
15137 });
15138 }
15139
15140 pub fn undo_selection(
15141 &mut self,
15142 _: &UndoSelection,
15143 window: &mut Window,
15144 cx: &mut Context<Self>,
15145 ) {
15146 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15147 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15148 self.selection_history.mode = SelectionHistoryMode::Undoing;
15149 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15150 this.end_selection(window, cx);
15151 this.change_selections(
15152 SelectionEffects::scroll(Autoscroll::newest()),
15153 window,
15154 cx,
15155 |s| s.select_anchors(entry.selections.to_vec()),
15156 );
15157 });
15158 self.selection_history.mode = SelectionHistoryMode::Normal;
15159
15160 self.select_next_state = entry.select_next_state;
15161 self.select_prev_state = entry.select_prev_state;
15162 self.add_selections_state = entry.add_selections_state;
15163 }
15164 }
15165
15166 pub fn redo_selection(
15167 &mut self,
15168 _: &RedoSelection,
15169 window: &mut Window,
15170 cx: &mut Context<Self>,
15171 ) {
15172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15173 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15174 self.selection_history.mode = SelectionHistoryMode::Redoing;
15175 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15176 this.end_selection(window, cx);
15177 this.change_selections(
15178 SelectionEffects::scroll(Autoscroll::newest()),
15179 window,
15180 cx,
15181 |s| s.select_anchors(entry.selections.to_vec()),
15182 );
15183 });
15184 self.selection_history.mode = SelectionHistoryMode::Normal;
15185
15186 self.select_next_state = entry.select_next_state;
15187 self.select_prev_state = entry.select_prev_state;
15188 self.add_selections_state = entry.add_selections_state;
15189 }
15190 }
15191
15192 pub fn expand_excerpts(
15193 &mut self,
15194 action: &ExpandExcerpts,
15195 _: &mut Window,
15196 cx: &mut Context<Self>,
15197 ) {
15198 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15199 }
15200
15201 pub fn expand_excerpts_down(
15202 &mut self,
15203 action: &ExpandExcerptsDown,
15204 _: &mut Window,
15205 cx: &mut Context<Self>,
15206 ) {
15207 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15208 }
15209
15210 pub fn expand_excerpts_up(
15211 &mut self,
15212 action: &ExpandExcerptsUp,
15213 _: &mut Window,
15214 cx: &mut Context<Self>,
15215 ) {
15216 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15217 }
15218
15219 pub fn expand_excerpts_for_direction(
15220 &mut self,
15221 lines: u32,
15222 direction: ExpandExcerptDirection,
15223
15224 cx: &mut Context<Self>,
15225 ) {
15226 let selections = self.selections.disjoint_anchors();
15227
15228 let lines = if lines == 0 {
15229 EditorSettings::get_global(cx).expand_excerpt_lines
15230 } else {
15231 lines
15232 };
15233
15234 self.buffer.update(cx, |buffer, cx| {
15235 let snapshot = buffer.snapshot(cx);
15236 let mut excerpt_ids = selections
15237 .iter()
15238 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15239 .collect::<Vec<_>>();
15240 excerpt_ids.sort();
15241 excerpt_ids.dedup();
15242 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15243 })
15244 }
15245
15246 pub fn expand_excerpt(
15247 &mut self,
15248 excerpt: ExcerptId,
15249 direction: ExpandExcerptDirection,
15250 window: &mut Window,
15251 cx: &mut Context<Self>,
15252 ) {
15253 let current_scroll_position = self.scroll_position(cx);
15254 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15255 let mut should_scroll_up = false;
15256
15257 if direction == ExpandExcerptDirection::Down {
15258 let multi_buffer = self.buffer.read(cx);
15259 let snapshot = multi_buffer.snapshot(cx);
15260 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15261 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15262 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15263 let buffer_snapshot = buffer.read(cx).snapshot();
15264 let excerpt_end_row =
15265 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15266 let last_row = buffer_snapshot.max_point().row;
15267 let lines_below = last_row.saturating_sub(excerpt_end_row);
15268 should_scroll_up = lines_below >= lines_to_expand;
15269 }
15270 }
15271 }
15272 }
15273
15274 self.buffer.update(cx, |buffer, cx| {
15275 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15276 });
15277
15278 if should_scroll_up {
15279 let new_scroll_position =
15280 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15281 self.set_scroll_position(new_scroll_position, window, cx);
15282 }
15283 }
15284
15285 pub fn go_to_singleton_buffer_point(
15286 &mut self,
15287 point: Point,
15288 window: &mut Window,
15289 cx: &mut Context<Self>,
15290 ) {
15291 self.go_to_singleton_buffer_range(point..point, window, cx);
15292 }
15293
15294 pub fn go_to_singleton_buffer_range(
15295 &mut self,
15296 range: Range<Point>,
15297 window: &mut Window,
15298 cx: &mut Context<Self>,
15299 ) {
15300 let multibuffer = self.buffer().read(cx);
15301 let Some(buffer) = multibuffer.as_singleton() else {
15302 return;
15303 };
15304 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15305 return;
15306 };
15307 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15308 return;
15309 };
15310 self.change_selections(
15311 SelectionEffects::default().nav_history(true),
15312 window,
15313 cx,
15314 |s| s.select_anchor_ranges([start..end]),
15315 );
15316 }
15317
15318 pub fn go_to_diagnostic(
15319 &mut self,
15320 action: &GoToDiagnostic,
15321 window: &mut Window,
15322 cx: &mut Context<Self>,
15323 ) {
15324 if !self.diagnostics_enabled() {
15325 return;
15326 }
15327 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15328 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15329 }
15330
15331 pub fn go_to_prev_diagnostic(
15332 &mut self,
15333 action: &GoToPreviousDiagnostic,
15334 window: &mut Window,
15335 cx: &mut Context<Self>,
15336 ) {
15337 if !self.diagnostics_enabled() {
15338 return;
15339 }
15340 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15341 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15342 }
15343
15344 pub fn go_to_diagnostic_impl(
15345 &mut self,
15346 direction: Direction,
15347 severity: GoToDiagnosticSeverityFilter,
15348 window: &mut Window,
15349 cx: &mut Context<Self>,
15350 ) {
15351 let buffer = self.buffer.read(cx).snapshot(cx);
15352 let selection = self.selections.newest::<usize>(cx);
15353
15354 let mut active_group_id = None;
15355 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15356 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15357 active_group_id = Some(active_group.group_id);
15358 }
15359 }
15360
15361 fn filtered(
15362 snapshot: EditorSnapshot,
15363 severity: GoToDiagnosticSeverityFilter,
15364 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15365 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15366 diagnostics
15367 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15368 .filter(|entry| entry.range.start != entry.range.end)
15369 .filter(|entry| !entry.diagnostic.is_unnecessary)
15370 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15371 }
15372
15373 let snapshot = self.snapshot(window, cx);
15374 let before = filtered(
15375 snapshot.clone(),
15376 severity,
15377 buffer
15378 .diagnostics_in_range(0..selection.start)
15379 .filter(|entry| entry.range.start <= selection.start),
15380 );
15381 let after = filtered(
15382 snapshot,
15383 severity,
15384 buffer
15385 .diagnostics_in_range(selection.start..buffer.len())
15386 .filter(|entry| entry.range.start >= selection.start),
15387 );
15388
15389 let mut found: Option<DiagnosticEntry<usize>> = None;
15390 if direction == Direction::Prev {
15391 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15392 {
15393 for diagnostic in prev_diagnostics.into_iter().rev() {
15394 if diagnostic.range.start != selection.start
15395 || active_group_id
15396 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15397 {
15398 found = Some(diagnostic);
15399 break 'outer;
15400 }
15401 }
15402 }
15403 } else {
15404 for diagnostic in after.chain(before) {
15405 if diagnostic.range.start != selection.start
15406 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15407 {
15408 found = Some(diagnostic);
15409 break;
15410 }
15411 }
15412 }
15413 let Some(next_diagnostic) = found else {
15414 return;
15415 };
15416
15417 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15418 return;
15419 };
15420 self.change_selections(Default::default(), window, cx, |s| {
15421 s.select_ranges(vec![
15422 next_diagnostic.range.start..next_diagnostic.range.start,
15423 ])
15424 });
15425 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15426 self.refresh_edit_prediction(false, true, window, cx);
15427 }
15428
15429 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15430 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15431 let snapshot = self.snapshot(window, cx);
15432 let selection = self.selections.newest::<Point>(cx);
15433 self.go_to_hunk_before_or_after_position(
15434 &snapshot,
15435 selection.head(),
15436 Direction::Next,
15437 window,
15438 cx,
15439 );
15440 }
15441
15442 pub fn go_to_hunk_before_or_after_position(
15443 &mut self,
15444 snapshot: &EditorSnapshot,
15445 position: Point,
15446 direction: Direction,
15447 window: &mut Window,
15448 cx: &mut Context<Editor>,
15449 ) {
15450 let row = if direction == Direction::Next {
15451 self.hunk_after_position(snapshot, position)
15452 .map(|hunk| hunk.row_range.start)
15453 } else {
15454 self.hunk_before_position(snapshot, position)
15455 };
15456
15457 if let Some(row) = row {
15458 let destination = Point::new(row.0, 0);
15459 let autoscroll = Autoscroll::center();
15460
15461 self.unfold_ranges(&[destination..destination], false, false, cx);
15462 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15463 s.select_ranges([destination..destination]);
15464 });
15465 }
15466 }
15467
15468 fn hunk_after_position(
15469 &mut self,
15470 snapshot: &EditorSnapshot,
15471 position: Point,
15472 ) -> Option<MultiBufferDiffHunk> {
15473 snapshot
15474 .buffer_snapshot
15475 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15476 .find(|hunk| hunk.row_range.start.0 > position.row)
15477 .or_else(|| {
15478 snapshot
15479 .buffer_snapshot
15480 .diff_hunks_in_range(Point::zero()..position)
15481 .find(|hunk| hunk.row_range.end.0 < position.row)
15482 })
15483 }
15484
15485 fn go_to_prev_hunk(
15486 &mut self,
15487 _: &GoToPreviousHunk,
15488 window: &mut Window,
15489 cx: &mut Context<Self>,
15490 ) {
15491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15492 let snapshot = self.snapshot(window, cx);
15493 let selection = self.selections.newest::<Point>(cx);
15494 self.go_to_hunk_before_or_after_position(
15495 &snapshot,
15496 selection.head(),
15497 Direction::Prev,
15498 window,
15499 cx,
15500 );
15501 }
15502
15503 fn hunk_before_position(
15504 &mut self,
15505 snapshot: &EditorSnapshot,
15506 position: Point,
15507 ) -> Option<MultiBufferRow> {
15508 snapshot
15509 .buffer_snapshot
15510 .diff_hunk_before(position)
15511 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15512 }
15513
15514 fn go_to_next_change(
15515 &mut self,
15516 _: &GoToNextChange,
15517 window: &mut Window,
15518 cx: &mut Context<Self>,
15519 ) {
15520 if let Some(selections) = self
15521 .change_list
15522 .next_change(1, Direction::Next)
15523 .map(|s| s.to_vec())
15524 {
15525 self.change_selections(Default::default(), window, cx, |s| {
15526 let map = s.display_map();
15527 s.select_display_ranges(selections.iter().map(|a| {
15528 let point = a.to_display_point(&map);
15529 point..point
15530 }))
15531 })
15532 }
15533 }
15534
15535 fn go_to_previous_change(
15536 &mut self,
15537 _: &GoToPreviousChange,
15538 window: &mut Window,
15539 cx: &mut Context<Self>,
15540 ) {
15541 if let Some(selections) = self
15542 .change_list
15543 .next_change(1, Direction::Prev)
15544 .map(|s| s.to_vec())
15545 {
15546 self.change_selections(Default::default(), window, cx, |s| {
15547 let map = s.display_map();
15548 s.select_display_ranges(selections.iter().map(|a| {
15549 let point = a.to_display_point(&map);
15550 point..point
15551 }))
15552 })
15553 }
15554 }
15555
15556 fn go_to_line<T: 'static>(
15557 &mut self,
15558 position: Anchor,
15559 highlight_color: Option<Hsla>,
15560 window: &mut Window,
15561 cx: &mut Context<Self>,
15562 ) {
15563 let snapshot = self.snapshot(window, cx).display_snapshot;
15564 let position = position.to_point(&snapshot.buffer_snapshot);
15565 let start = snapshot
15566 .buffer_snapshot
15567 .clip_point(Point::new(position.row, 0), Bias::Left);
15568 let end = start + Point::new(1, 0);
15569 let start = snapshot.buffer_snapshot.anchor_before(start);
15570 let end = snapshot.buffer_snapshot.anchor_before(end);
15571
15572 self.highlight_rows::<T>(
15573 start..end,
15574 highlight_color
15575 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15576 Default::default(),
15577 cx,
15578 );
15579
15580 if self.buffer.read(cx).is_singleton() {
15581 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15582 }
15583 }
15584
15585 pub fn go_to_definition(
15586 &mut self,
15587 _: &GoToDefinition,
15588 window: &mut Window,
15589 cx: &mut Context<Self>,
15590 ) -> Task<Result<Navigated>> {
15591 let definition =
15592 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15593 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15594 cx.spawn_in(window, async move |editor, cx| {
15595 if definition.await? == Navigated::Yes {
15596 return Ok(Navigated::Yes);
15597 }
15598 match fallback_strategy {
15599 GoToDefinitionFallback::None => Ok(Navigated::No),
15600 GoToDefinitionFallback::FindAllReferences => {
15601 match editor.update_in(cx, |editor, window, cx| {
15602 editor.find_all_references(&FindAllReferences, window, cx)
15603 })? {
15604 Some(references) => references.await,
15605 None => Ok(Navigated::No),
15606 }
15607 }
15608 }
15609 })
15610 }
15611
15612 pub fn go_to_declaration(
15613 &mut self,
15614 _: &GoToDeclaration,
15615 window: &mut Window,
15616 cx: &mut Context<Self>,
15617 ) -> Task<Result<Navigated>> {
15618 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15619 }
15620
15621 pub fn go_to_declaration_split(
15622 &mut self,
15623 _: &GoToDeclaration,
15624 window: &mut Window,
15625 cx: &mut Context<Self>,
15626 ) -> Task<Result<Navigated>> {
15627 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15628 }
15629
15630 pub fn go_to_implementation(
15631 &mut self,
15632 _: &GoToImplementation,
15633 window: &mut Window,
15634 cx: &mut Context<Self>,
15635 ) -> Task<Result<Navigated>> {
15636 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15637 }
15638
15639 pub fn go_to_implementation_split(
15640 &mut self,
15641 _: &GoToImplementationSplit,
15642 window: &mut Window,
15643 cx: &mut Context<Self>,
15644 ) -> Task<Result<Navigated>> {
15645 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15646 }
15647
15648 pub fn go_to_type_definition(
15649 &mut self,
15650 _: &GoToTypeDefinition,
15651 window: &mut Window,
15652 cx: &mut Context<Self>,
15653 ) -> Task<Result<Navigated>> {
15654 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15655 }
15656
15657 pub fn go_to_definition_split(
15658 &mut self,
15659 _: &GoToDefinitionSplit,
15660 window: &mut Window,
15661 cx: &mut Context<Self>,
15662 ) -> Task<Result<Navigated>> {
15663 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15664 }
15665
15666 pub fn go_to_type_definition_split(
15667 &mut self,
15668 _: &GoToTypeDefinitionSplit,
15669 window: &mut Window,
15670 cx: &mut Context<Self>,
15671 ) -> Task<Result<Navigated>> {
15672 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15673 }
15674
15675 fn go_to_definition_of_kind(
15676 &mut self,
15677 kind: GotoDefinitionKind,
15678 split: bool,
15679 window: &mut Window,
15680 cx: &mut Context<Self>,
15681 ) -> Task<Result<Navigated>> {
15682 let Some(provider) = self.semantics_provider.clone() else {
15683 return Task::ready(Ok(Navigated::No));
15684 };
15685 let head = self.selections.newest::<usize>(cx).head();
15686 let buffer = self.buffer.read(cx);
15687 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15688 return Task::ready(Ok(Navigated::No));
15689 };
15690 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15691 return Task::ready(Ok(Navigated::No));
15692 };
15693
15694 cx.spawn_in(window, async move |editor, cx| {
15695 let definitions = definitions.await?;
15696 let navigated = editor
15697 .update_in(cx, |editor, window, cx| {
15698 editor.navigate_to_hover_links(
15699 Some(kind),
15700 definitions
15701 .into_iter()
15702 .filter(|location| {
15703 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15704 })
15705 .map(HoverLink::Text)
15706 .collect::<Vec<_>>(),
15707 split,
15708 window,
15709 cx,
15710 )
15711 })?
15712 .await?;
15713 anyhow::Ok(navigated)
15714 })
15715 }
15716
15717 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15718 let selection = self.selections.newest_anchor();
15719 let head = selection.head();
15720 let tail = selection.tail();
15721
15722 let Some((buffer, start_position)) =
15723 self.buffer.read(cx).text_anchor_for_position(head, cx)
15724 else {
15725 return;
15726 };
15727
15728 let end_position = if head != tail {
15729 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15730 return;
15731 };
15732 Some(pos)
15733 } else {
15734 None
15735 };
15736
15737 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15738 let url = if let Some(end_pos) = end_position {
15739 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15740 } else {
15741 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15742 };
15743
15744 if let Some(url) = url {
15745 editor.update(cx, |_, cx| {
15746 cx.open_url(&url);
15747 })
15748 } else {
15749 Ok(())
15750 }
15751 });
15752
15753 url_finder.detach();
15754 }
15755
15756 pub fn open_selected_filename(
15757 &mut self,
15758 _: &OpenSelectedFilename,
15759 window: &mut Window,
15760 cx: &mut Context<Self>,
15761 ) {
15762 let Some(workspace) = self.workspace() else {
15763 return;
15764 };
15765
15766 let position = self.selections.newest_anchor().head();
15767
15768 let Some((buffer, buffer_position)) =
15769 self.buffer.read(cx).text_anchor_for_position(position, cx)
15770 else {
15771 return;
15772 };
15773
15774 let project = self.project.clone();
15775
15776 cx.spawn_in(window, async move |_, cx| {
15777 let result = find_file(&buffer, project, buffer_position, cx).await;
15778
15779 if let Some((_, path)) = result {
15780 workspace
15781 .update_in(cx, |workspace, window, cx| {
15782 workspace.open_resolved_path(path, window, cx)
15783 })?
15784 .await?;
15785 }
15786 anyhow::Ok(())
15787 })
15788 .detach();
15789 }
15790
15791 pub(crate) fn navigate_to_hover_links(
15792 &mut self,
15793 kind: Option<GotoDefinitionKind>,
15794 definitions: Vec<HoverLink>,
15795 split: bool,
15796 window: &mut Window,
15797 cx: &mut Context<Editor>,
15798 ) -> Task<Result<Navigated>> {
15799 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15800 let mut first_url_or_file = None;
15801 let definitions: Vec<_> = definitions
15802 .into_iter()
15803 .filter_map(|def| match def {
15804 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15805 HoverLink::InlayHint(lsp_location, server_id) => {
15806 let computation =
15807 self.compute_target_location(lsp_location, server_id, window, cx);
15808 Some(cx.background_spawn(computation))
15809 }
15810 HoverLink::Url(url) => {
15811 first_url_or_file = Some(Either::Left(url));
15812 None
15813 }
15814 HoverLink::File(path) => {
15815 first_url_or_file = Some(Either::Right(path));
15816 None
15817 }
15818 })
15819 .collect();
15820
15821 let workspace = self.workspace();
15822
15823 cx.spawn_in(window, async move |editor, acx| {
15824 let mut locations: Vec<Location> = future::join_all(definitions)
15825 .await
15826 .into_iter()
15827 .filter_map(|location| location.transpose())
15828 .collect::<Result<_>>()
15829 .context("location tasks")?;
15830
15831 if locations.len() > 1 {
15832 let Some(workspace) = workspace else {
15833 return Ok(Navigated::No);
15834 };
15835
15836 let tab_kind = match kind {
15837 Some(GotoDefinitionKind::Implementation) => "Implementations",
15838 _ => "Definitions",
15839 };
15840 let title = editor
15841 .update_in(acx, |_, _, cx| {
15842 let origin = locations.first().unwrap();
15843 let buffer = origin.buffer.read(cx);
15844 format!(
15845 "{} for {}",
15846 tab_kind,
15847 buffer
15848 .text_for_range(origin.range.clone())
15849 .collect::<String>()
15850 )
15851 })
15852 .context("buffer title")?;
15853
15854 let opened = workspace
15855 .update_in(acx, |workspace, window, cx| {
15856 Self::open_locations_in_multibuffer(
15857 workspace,
15858 locations,
15859 title,
15860 split,
15861 MultibufferSelectionMode::First,
15862 window,
15863 cx,
15864 )
15865 })
15866 .is_ok();
15867
15868 anyhow::Ok(Navigated::from_bool(opened))
15869 } else if locations.is_empty() {
15870 // If there is one definition, just open it directly
15871 match first_url_or_file {
15872 Some(Either::Left(url)) => {
15873 acx.update(|_, cx| cx.open_url(&url))?;
15874 Ok(Navigated::Yes)
15875 }
15876 Some(Either::Right(path)) => {
15877 let Some(workspace) = workspace else {
15878 return Ok(Navigated::No);
15879 };
15880
15881 workspace
15882 .update_in(acx, |workspace, window, cx| {
15883 workspace.open_resolved_path(path, window, cx)
15884 })?
15885 .await?;
15886 Ok(Navigated::Yes)
15887 }
15888 None => Ok(Navigated::No),
15889 }
15890 } else {
15891 let Some(workspace) = workspace else {
15892 return Ok(Navigated::No);
15893 };
15894
15895 let target = locations.pop().unwrap();
15896 editor.update_in(acx, |editor, window, cx| {
15897 let pane = workspace.read(cx).active_pane().clone();
15898
15899 let range = target.range.to_point(target.buffer.read(cx));
15900 let range = editor.range_for_match(&range);
15901 let range = collapse_multiline_range(range);
15902
15903 if !split
15904 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15905 {
15906 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15907 } else {
15908 window.defer(cx, move |window, cx| {
15909 let target_editor: Entity<Self> =
15910 workspace.update(cx, |workspace, cx| {
15911 let pane = if split {
15912 workspace.adjacent_pane(window, cx)
15913 } else {
15914 workspace.active_pane().clone()
15915 };
15916
15917 workspace.open_project_item(
15918 pane,
15919 target.buffer.clone(),
15920 true,
15921 true,
15922 window,
15923 cx,
15924 )
15925 });
15926 target_editor.update(cx, |target_editor, cx| {
15927 // When selecting a definition in a different buffer, disable the nav history
15928 // to avoid creating a history entry at the previous cursor location.
15929 pane.update(cx, |pane, _| pane.disable_history());
15930 target_editor.go_to_singleton_buffer_range(range, window, cx);
15931 pane.update(cx, |pane, _| pane.enable_history());
15932 });
15933 });
15934 }
15935 Navigated::Yes
15936 })
15937 }
15938 })
15939 }
15940
15941 fn compute_target_location(
15942 &self,
15943 lsp_location: lsp::Location,
15944 server_id: LanguageServerId,
15945 window: &mut Window,
15946 cx: &mut Context<Self>,
15947 ) -> Task<anyhow::Result<Option<Location>>> {
15948 let Some(project) = self.project.clone() else {
15949 return Task::ready(Ok(None));
15950 };
15951
15952 cx.spawn_in(window, async move |editor, cx| {
15953 let location_task = editor.update(cx, |_, cx| {
15954 project.update(cx, |project, cx| {
15955 let language_server_name = project
15956 .language_server_statuses(cx)
15957 .find(|(id, _)| server_id == *id)
15958 .map(|(_, status)| status.name.clone());
15959 language_server_name.map(|language_server_name| {
15960 project.open_local_buffer_via_lsp(
15961 lsp_location.uri.clone(),
15962 server_id,
15963 language_server_name,
15964 cx,
15965 )
15966 })
15967 })
15968 })?;
15969 let location = match location_task {
15970 Some(task) => Some({
15971 let target_buffer_handle = task.await.context("open local buffer")?;
15972 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15973 let target_start = target_buffer
15974 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15975 let target_end = target_buffer
15976 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15977 target_buffer.anchor_after(target_start)
15978 ..target_buffer.anchor_before(target_end)
15979 })?;
15980 Location {
15981 buffer: target_buffer_handle,
15982 range,
15983 }
15984 }),
15985 None => None,
15986 };
15987 Ok(location)
15988 })
15989 }
15990
15991 pub fn find_all_references(
15992 &mut self,
15993 _: &FindAllReferences,
15994 window: &mut Window,
15995 cx: &mut Context<Self>,
15996 ) -> Option<Task<Result<Navigated>>> {
15997 let selection = self.selections.newest::<usize>(cx);
15998 let multi_buffer = self.buffer.read(cx);
15999 let head = selection.head();
16000
16001 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16002 let head_anchor = multi_buffer_snapshot.anchor_at(
16003 head,
16004 if head < selection.tail() {
16005 Bias::Right
16006 } else {
16007 Bias::Left
16008 },
16009 );
16010
16011 match self
16012 .find_all_references_task_sources
16013 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16014 {
16015 Ok(_) => {
16016 log::info!(
16017 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16018 );
16019 return None;
16020 }
16021 Err(i) => {
16022 self.find_all_references_task_sources.insert(i, head_anchor);
16023 }
16024 }
16025
16026 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16027 let workspace = self.workspace()?;
16028 let project = workspace.read(cx).project().clone();
16029 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16030 Some(cx.spawn_in(window, async move |editor, cx| {
16031 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16032 if let Ok(i) = editor
16033 .find_all_references_task_sources
16034 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16035 {
16036 editor.find_all_references_task_sources.remove(i);
16037 }
16038 });
16039
16040 let locations = references.await?;
16041 if locations.is_empty() {
16042 return anyhow::Ok(Navigated::No);
16043 }
16044
16045 workspace.update_in(cx, |workspace, window, cx| {
16046 let title = locations
16047 .first()
16048 .as_ref()
16049 .map(|location| {
16050 let buffer = location.buffer.read(cx);
16051 format!(
16052 "References to `{}`",
16053 buffer
16054 .text_for_range(location.range.clone())
16055 .collect::<String>()
16056 )
16057 })
16058 .unwrap();
16059 Self::open_locations_in_multibuffer(
16060 workspace,
16061 locations,
16062 title,
16063 false,
16064 MultibufferSelectionMode::First,
16065 window,
16066 cx,
16067 );
16068 Navigated::Yes
16069 })
16070 }))
16071 }
16072
16073 /// Opens a multibuffer with the given project locations in it
16074 pub fn open_locations_in_multibuffer(
16075 workspace: &mut Workspace,
16076 mut locations: Vec<Location>,
16077 title: String,
16078 split: bool,
16079 multibuffer_selection_mode: MultibufferSelectionMode,
16080 window: &mut Window,
16081 cx: &mut Context<Workspace>,
16082 ) {
16083 if locations.is_empty() {
16084 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16085 return;
16086 }
16087
16088 // If there are multiple definitions, open them in a multibuffer
16089 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16090 let mut locations = locations.into_iter().peekable();
16091 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16092 let capability = workspace.project().read(cx).capability();
16093
16094 let excerpt_buffer = cx.new(|cx| {
16095 let mut multibuffer = MultiBuffer::new(capability);
16096 while let Some(location) = locations.next() {
16097 let buffer = location.buffer.read(cx);
16098 let mut ranges_for_buffer = Vec::new();
16099 let range = location.range.to_point(buffer);
16100 ranges_for_buffer.push(range.clone());
16101
16102 while let Some(next_location) = locations.peek() {
16103 if next_location.buffer == location.buffer {
16104 ranges_for_buffer.push(next_location.range.to_point(buffer));
16105 locations.next();
16106 } else {
16107 break;
16108 }
16109 }
16110
16111 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16112 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16113 PathKey::for_buffer(&location.buffer, cx),
16114 location.buffer.clone(),
16115 ranges_for_buffer,
16116 DEFAULT_MULTIBUFFER_CONTEXT,
16117 cx,
16118 );
16119 ranges.extend(new_ranges)
16120 }
16121
16122 multibuffer.with_title(title)
16123 });
16124
16125 let editor = cx.new(|cx| {
16126 Editor::for_multibuffer(
16127 excerpt_buffer,
16128 Some(workspace.project().clone()),
16129 window,
16130 cx,
16131 )
16132 });
16133 editor.update(cx, |editor, cx| {
16134 match multibuffer_selection_mode {
16135 MultibufferSelectionMode::First => {
16136 if let Some(first_range) = ranges.first() {
16137 editor.change_selections(
16138 SelectionEffects::no_scroll(),
16139 window,
16140 cx,
16141 |selections| {
16142 selections.clear_disjoint();
16143 selections
16144 .select_anchor_ranges(std::iter::once(first_range.clone()));
16145 },
16146 );
16147 }
16148 editor.highlight_background::<Self>(
16149 &ranges,
16150 |theme| theme.colors().editor_highlighted_line_background,
16151 cx,
16152 );
16153 }
16154 MultibufferSelectionMode::All => {
16155 editor.change_selections(
16156 SelectionEffects::no_scroll(),
16157 window,
16158 cx,
16159 |selections| {
16160 selections.clear_disjoint();
16161 selections.select_anchor_ranges(ranges);
16162 },
16163 );
16164 }
16165 }
16166 editor.register_buffers_with_language_servers(cx);
16167 });
16168
16169 let item = Box::new(editor);
16170 let item_id = item.item_id();
16171
16172 if split {
16173 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16174 } else {
16175 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16176 let (preview_item_id, preview_item_idx) =
16177 workspace.active_pane().read_with(cx, |pane, _| {
16178 (pane.preview_item_id(), pane.preview_item_idx())
16179 });
16180
16181 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16182
16183 if let Some(preview_item_id) = preview_item_id {
16184 workspace.active_pane().update(cx, |pane, cx| {
16185 pane.remove_item(preview_item_id, false, false, window, cx);
16186 });
16187 }
16188 } else {
16189 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16190 }
16191 }
16192 workspace.active_pane().update(cx, |pane, cx| {
16193 pane.set_preview_item_id(Some(item_id), cx);
16194 });
16195 }
16196
16197 pub fn rename(
16198 &mut self,
16199 _: &Rename,
16200 window: &mut Window,
16201 cx: &mut Context<Self>,
16202 ) -> Option<Task<Result<()>>> {
16203 use language::ToOffset as _;
16204
16205 let provider = self.semantics_provider.clone()?;
16206 let selection = self.selections.newest_anchor().clone();
16207 let (cursor_buffer, cursor_buffer_position) = self
16208 .buffer
16209 .read(cx)
16210 .text_anchor_for_position(selection.head(), cx)?;
16211 let (tail_buffer, cursor_buffer_position_end) = self
16212 .buffer
16213 .read(cx)
16214 .text_anchor_for_position(selection.tail(), cx)?;
16215 if tail_buffer != cursor_buffer {
16216 return None;
16217 }
16218
16219 let snapshot = cursor_buffer.read(cx).snapshot();
16220 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16221 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16222 let prepare_rename = provider
16223 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16224 .unwrap_or_else(|| Task::ready(Ok(None)));
16225 drop(snapshot);
16226
16227 Some(cx.spawn_in(window, async move |this, cx| {
16228 let rename_range = if let Some(range) = prepare_rename.await? {
16229 Some(range)
16230 } else {
16231 this.update(cx, |this, cx| {
16232 let buffer = this.buffer.read(cx).snapshot(cx);
16233 let mut buffer_highlights = this
16234 .document_highlights_for_position(selection.head(), &buffer)
16235 .filter(|highlight| {
16236 highlight.start.excerpt_id == selection.head().excerpt_id
16237 && highlight.end.excerpt_id == selection.head().excerpt_id
16238 });
16239 buffer_highlights
16240 .next()
16241 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16242 })?
16243 };
16244 if let Some(rename_range) = rename_range {
16245 this.update_in(cx, |this, window, cx| {
16246 let snapshot = cursor_buffer.read(cx).snapshot();
16247 let rename_buffer_range = rename_range.to_offset(&snapshot);
16248 let cursor_offset_in_rename_range =
16249 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16250 let cursor_offset_in_rename_range_end =
16251 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16252
16253 this.take_rename(false, window, cx);
16254 let buffer = this.buffer.read(cx).read(cx);
16255 let cursor_offset = selection.head().to_offset(&buffer);
16256 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16257 let rename_end = rename_start + rename_buffer_range.len();
16258 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16259 let mut old_highlight_id = None;
16260 let old_name: Arc<str> = buffer
16261 .chunks(rename_start..rename_end, true)
16262 .map(|chunk| {
16263 if old_highlight_id.is_none() {
16264 old_highlight_id = chunk.syntax_highlight_id;
16265 }
16266 chunk.text
16267 })
16268 .collect::<String>()
16269 .into();
16270
16271 drop(buffer);
16272
16273 // Position the selection in the rename editor so that it matches the current selection.
16274 this.show_local_selections = false;
16275 let rename_editor = cx.new(|cx| {
16276 let mut editor = Editor::single_line(window, cx);
16277 editor.buffer.update(cx, |buffer, cx| {
16278 buffer.edit([(0..0, old_name.clone())], None, cx)
16279 });
16280 let rename_selection_range = match cursor_offset_in_rename_range
16281 .cmp(&cursor_offset_in_rename_range_end)
16282 {
16283 Ordering::Equal => {
16284 editor.select_all(&SelectAll, window, cx);
16285 return editor;
16286 }
16287 Ordering::Less => {
16288 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16289 }
16290 Ordering::Greater => {
16291 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16292 }
16293 };
16294 if rename_selection_range.end > old_name.len() {
16295 editor.select_all(&SelectAll, window, cx);
16296 } else {
16297 editor.change_selections(Default::default(), window, cx, |s| {
16298 s.select_ranges([rename_selection_range]);
16299 });
16300 }
16301 editor
16302 });
16303 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16304 if e == &EditorEvent::Focused {
16305 cx.emit(EditorEvent::FocusedIn)
16306 }
16307 })
16308 .detach();
16309
16310 let write_highlights =
16311 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16312 let read_highlights =
16313 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16314 let ranges = write_highlights
16315 .iter()
16316 .flat_map(|(_, ranges)| ranges.iter())
16317 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16318 .cloned()
16319 .collect();
16320
16321 this.highlight_text::<Rename>(
16322 ranges,
16323 HighlightStyle {
16324 fade_out: Some(0.6),
16325 ..Default::default()
16326 },
16327 cx,
16328 );
16329 let rename_focus_handle = rename_editor.focus_handle(cx);
16330 window.focus(&rename_focus_handle);
16331 let block_id = this.insert_blocks(
16332 [BlockProperties {
16333 style: BlockStyle::Flex,
16334 placement: BlockPlacement::Below(range.start),
16335 height: Some(1),
16336 render: Arc::new({
16337 let rename_editor = rename_editor.clone();
16338 move |cx: &mut BlockContext| {
16339 let mut text_style = cx.editor_style.text.clone();
16340 if let Some(highlight_style) = old_highlight_id
16341 .and_then(|h| h.style(&cx.editor_style.syntax))
16342 {
16343 text_style = text_style.highlight(highlight_style);
16344 }
16345 div()
16346 .block_mouse_except_scroll()
16347 .pl(cx.anchor_x)
16348 .child(EditorElement::new(
16349 &rename_editor,
16350 EditorStyle {
16351 background: cx.theme().system().transparent,
16352 local_player: cx.editor_style.local_player,
16353 text: text_style,
16354 scrollbar_width: cx.editor_style.scrollbar_width,
16355 syntax: cx.editor_style.syntax.clone(),
16356 status: cx.editor_style.status.clone(),
16357 inlay_hints_style: HighlightStyle {
16358 font_weight: Some(FontWeight::BOLD),
16359 ..make_inlay_hints_style(cx.app)
16360 },
16361 edit_prediction_styles: make_suggestion_styles(
16362 cx.app,
16363 ),
16364 ..EditorStyle::default()
16365 },
16366 ))
16367 .into_any_element()
16368 }
16369 }),
16370 priority: 0,
16371 }],
16372 Some(Autoscroll::fit()),
16373 cx,
16374 )[0];
16375 this.pending_rename = Some(RenameState {
16376 range,
16377 old_name,
16378 editor: rename_editor,
16379 block_id,
16380 });
16381 })?;
16382 }
16383
16384 Ok(())
16385 }))
16386 }
16387
16388 pub fn confirm_rename(
16389 &mut self,
16390 _: &ConfirmRename,
16391 window: &mut Window,
16392 cx: &mut Context<Self>,
16393 ) -> Option<Task<Result<()>>> {
16394 let rename = self.take_rename(false, window, cx)?;
16395 let workspace = self.workspace()?.downgrade();
16396 let (buffer, start) = self
16397 .buffer
16398 .read(cx)
16399 .text_anchor_for_position(rename.range.start, cx)?;
16400 let (end_buffer, _) = self
16401 .buffer
16402 .read(cx)
16403 .text_anchor_for_position(rename.range.end, cx)?;
16404 if buffer != end_buffer {
16405 return None;
16406 }
16407
16408 let old_name = rename.old_name;
16409 let new_name = rename.editor.read(cx).text(cx);
16410
16411 let rename = self.semantics_provider.as_ref()?.perform_rename(
16412 &buffer,
16413 start,
16414 new_name.clone(),
16415 cx,
16416 )?;
16417
16418 Some(cx.spawn_in(window, async move |editor, cx| {
16419 let project_transaction = rename.await?;
16420 Self::open_project_transaction(
16421 &editor,
16422 workspace,
16423 project_transaction,
16424 format!("Rename: {} → {}", old_name, new_name),
16425 cx,
16426 )
16427 .await?;
16428
16429 editor.update(cx, |editor, cx| {
16430 editor.refresh_document_highlights(cx);
16431 })?;
16432 Ok(())
16433 }))
16434 }
16435
16436 fn take_rename(
16437 &mut self,
16438 moving_cursor: bool,
16439 window: &mut Window,
16440 cx: &mut Context<Self>,
16441 ) -> Option<RenameState> {
16442 let rename = self.pending_rename.take()?;
16443 if rename.editor.focus_handle(cx).is_focused(window) {
16444 window.focus(&self.focus_handle);
16445 }
16446
16447 self.remove_blocks(
16448 [rename.block_id].into_iter().collect(),
16449 Some(Autoscroll::fit()),
16450 cx,
16451 );
16452 self.clear_highlights::<Rename>(cx);
16453 self.show_local_selections = true;
16454
16455 if moving_cursor {
16456 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16457 editor.selections.newest::<usize>(cx).head()
16458 });
16459
16460 // Update the selection to match the position of the selection inside
16461 // the rename editor.
16462 let snapshot = self.buffer.read(cx).read(cx);
16463 let rename_range = rename.range.to_offset(&snapshot);
16464 let cursor_in_editor = snapshot
16465 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16466 .min(rename_range.end);
16467 drop(snapshot);
16468
16469 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16470 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16471 });
16472 } else {
16473 self.refresh_document_highlights(cx);
16474 }
16475
16476 Some(rename)
16477 }
16478
16479 pub fn pending_rename(&self) -> Option<&RenameState> {
16480 self.pending_rename.as_ref()
16481 }
16482
16483 fn format(
16484 &mut self,
16485 _: &Format,
16486 window: &mut Window,
16487 cx: &mut Context<Self>,
16488 ) -> Option<Task<Result<()>>> {
16489 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16490
16491 let project = match &self.project {
16492 Some(project) => project.clone(),
16493 None => return None,
16494 };
16495
16496 Some(self.perform_format(
16497 project,
16498 FormatTrigger::Manual,
16499 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16500 window,
16501 cx,
16502 ))
16503 }
16504
16505 fn format_selections(
16506 &mut self,
16507 _: &FormatSelections,
16508 window: &mut Window,
16509 cx: &mut Context<Self>,
16510 ) -> Option<Task<Result<()>>> {
16511 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16512
16513 let project = match &self.project {
16514 Some(project) => project.clone(),
16515 None => return None,
16516 };
16517
16518 let ranges = self
16519 .selections
16520 .all_adjusted(cx)
16521 .into_iter()
16522 .map(|selection| selection.range())
16523 .collect_vec();
16524
16525 Some(self.perform_format(
16526 project,
16527 FormatTrigger::Manual,
16528 FormatTarget::Ranges(ranges),
16529 window,
16530 cx,
16531 ))
16532 }
16533
16534 fn perform_format(
16535 &mut self,
16536 project: Entity<Project>,
16537 trigger: FormatTrigger,
16538 target: FormatTarget,
16539 window: &mut Window,
16540 cx: &mut Context<Self>,
16541 ) -> Task<Result<()>> {
16542 let buffer = self.buffer.clone();
16543 let (buffers, target) = match target {
16544 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16545 FormatTarget::Ranges(selection_ranges) => {
16546 let multi_buffer = buffer.read(cx);
16547 let snapshot = multi_buffer.read(cx);
16548 let mut buffers = HashSet::default();
16549 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16550 BTreeMap::new();
16551 for selection_range in selection_ranges {
16552 for (buffer, buffer_range, _) in
16553 snapshot.range_to_buffer_ranges(selection_range)
16554 {
16555 let buffer_id = buffer.remote_id();
16556 let start = buffer.anchor_before(buffer_range.start);
16557 let end = buffer.anchor_after(buffer_range.end);
16558 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16559 buffer_id_to_ranges
16560 .entry(buffer_id)
16561 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16562 .or_insert_with(|| vec![start..end]);
16563 }
16564 }
16565 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16566 }
16567 };
16568
16569 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16570 let selections_prev = transaction_id_prev
16571 .and_then(|transaction_id_prev| {
16572 // default to selections as they were after the last edit, if we have them,
16573 // instead of how they are now.
16574 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16575 // will take you back to where you made the last edit, instead of staying where you scrolled
16576 self.selection_history
16577 .transaction(transaction_id_prev)
16578 .map(|t| t.0.clone())
16579 })
16580 .unwrap_or_else(|| {
16581 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16582 self.selections.disjoint_anchors()
16583 });
16584
16585 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16586 let format = project.update(cx, |project, cx| {
16587 project.format(buffers, target, true, trigger, cx)
16588 });
16589
16590 cx.spawn_in(window, async move |editor, cx| {
16591 let transaction = futures::select_biased! {
16592 transaction = format.log_err().fuse() => transaction,
16593 () = timeout => {
16594 log::warn!("timed out waiting for formatting");
16595 None
16596 }
16597 };
16598
16599 buffer
16600 .update(cx, |buffer, cx| {
16601 if let Some(transaction) = transaction {
16602 if !buffer.is_singleton() {
16603 buffer.push_transaction(&transaction.0, cx);
16604 }
16605 }
16606 cx.notify();
16607 })
16608 .ok();
16609
16610 if let Some(transaction_id_now) =
16611 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16612 {
16613 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16614 if has_new_transaction {
16615 _ = editor.update(cx, |editor, _| {
16616 editor
16617 .selection_history
16618 .insert_transaction(transaction_id_now, selections_prev);
16619 });
16620 }
16621 }
16622
16623 Ok(())
16624 })
16625 }
16626
16627 fn organize_imports(
16628 &mut self,
16629 _: &OrganizeImports,
16630 window: &mut Window,
16631 cx: &mut Context<Self>,
16632 ) -> Option<Task<Result<()>>> {
16633 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16634 let project = match &self.project {
16635 Some(project) => project.clone(),
16636 None => return None,
16637 };
16638 Some(self.perform_code_action_kind(
16639 project,
16640 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16641 window,
16642 cx,
16643 ))
16644 }
16645
16646 fn perform_code_action_kind(
16647 &mut self,
16648 project: Entity<Project>,
16649 kind: CodeActionKind,
16650 window: &mut Window,
16651 cx: &mut Context<Self>,
16652 ) -> Task<Result<()>> {
16653 let buffer = self.buffer.clone();
16654 let buffers = buffer.read(cx).all_buffers();
16655 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16656 let apply_action = project.update(cx, |project, cx| {
16657 project.apply_code_action_kind(buffers, kind, true, cx)
16658 });
16659 cx.spawn_in(window, async move |_, cx| {
16660 let transaction = futures::select_biased! {
16661 () = timeout => {
16662 log::warn!("timed out waiting for executing code action");
16663 None
16664 }
16665 transaction = apply_action.log_err().fuse() => transaction,
16666 };
16667 buffer
16668 .update(cx, |buffer, cx| {
16669 // check if we need this
16670 if let Some(transaction) = transaction {
16671 if !buffer.is_singleton() {
16672 buffer.push_transaction(&transaction.0, cx);
16673 }
16674 }
16675 cx.notify();
16676 })
16677 .ok();
16678 Ok(())
16679 })
16680 }
16681
16682 pub fn restart_language_server(
16683 &mut self,
16684 _: &RestartLanguageServer,
16685 _: &mut Window,
16686 cx: &mut Context<Self>,
16687 ) {
16688 if let Some(project) = self.project.clone() {
16689 self.buffer.update(cx, |multi_buffer, cx| {
16690 project.update(cx, |project, cx| {
16691 project.restart_language_servers_for_buffers(
16692 multi_buffer.all_buffers().into_iter().collect(),
16693 HashSet::default(),
16694 cx,
16695 );
16696 });
16697 })
16698 }
16699 }
16700
16701 pub fn stop_language_server(
16702 &mut self,
16703 _: &StopLanguageServer,
16704 _: &mut Window,
16705 cx: &mut Context<Self>,
16706 ) {
16707 if let Some(project) = self.project.clone() {
16708 self.buffer.update(cx, |multi_buffer, cx| {
16709 project.update(cx, |project, cx| {
16710 project.stop_language_servers_for_buffers(
16711 multi_buffer.all_buffers().into_iter().collect(),
16712 HashSet::default(),
16713 cx,
16714 );
16715 cx.emit(project::Event::RefreshInlayHints);
16716 });
16717 });
16718 }
16719 }
16720
16721 fn cancel_language_server_work(
16722 workspace: &mut Workspace,
16723 _: &actions::CancelLanguageServerWork,
16724 _: &mut Window,
16725 cx: &mut Context<Workspace>,
16726 ) {
16727 let project = workspace.project();
16728 let buffers = workspace
16729 .active_item(cx)
16730 .and_then(|item| item.act_as::<Editor>(cx))
16731 .map_or(HashSet::default(), |editor| {
16732 editor.read(cx).buffer.read(cx).all_buffers()
16733 });
16734 project.update(cx, |project, cx| {
16735 project.cancel_language_server_work_for_buffers(buffers, cx);
16736 });
16737 }
16738
16739 fn show_character_palette(
16740 &mut self,
16741 _: &ShowCharacterPalette,
16742 window: &mut Window,
16743 _: &mut Context<Self>,
16744 ) {
16745 window.show_character_palette();
16746 }
16747
16748 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16749 if !self.diagnostics_enabled() {
16750 return;
16751 }
16752
16753 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16754 let buffer = self.buffer.read(cx).snapshot(cx);
16755 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16756 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16757 let is_valid = buffer
16758 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16759 .any(|entry| {
16760 entry.diagnostic.is_primary
16761 && !entry.range.is_empty()
16762 && entry.range.start == primary_range_start
16763 && entry.diagnostic.message == active_diagnostics.active_message
16764 });
16765
16766 if !is_valid {
16767 self.dismiss_diagnostics(cx);
16768 }
16769 }
16770 }
16771
16772 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16773 match &self.active_diagnostics {
16774 ActiveDiagnostic::Group(group) => Some(group),
16775 _ => None,
16776 }
16777 }
16778
16779 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16780 if !self.diagnostics_enabled() {
16781 return;
16782 }
16783 self.dismiss_diagnostics(cx);
16784 self.active_diagnostics = ActiveDiagnostic::All;
16785 }
16786
16787 fn activate_diagnostics(
16788 &mut self,
16789 buffer_id: BufferId,
16790 diagnostic: DiagnosticEntry<usize>,
16791 window: &mut Window,
16792 cx: &mut Context<Self>,
16793 ) {
16794 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16795 return;
16796 }
16797 self.dismiss_diagnostics(cx);
16798 let snapshot = self.snapshot(window, cx);
16799 let buffer = self.buffer.read(cx).snapshot(cx);
16800 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16801 return;
16802 };
16803
16804 let diagnostic_group = buffer
16805 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16806 .collect::<Vec<_>>();
16807
16808 let blocks =
16809 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16810
16811 let blocks = self.display_map.update(cx, |display_map, cx| {
16812 display_map.insert_blocks(blocks, cx).into_iter().collect()
16813 });
16814 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16815 active_range: buffer.anchor_before(diagnostic.range.start)
16816 ..buffer.anchor_after(diagnostic.range.end),
16817 active_message: diagnostic.diagnostic.message.clone(),
16818 group_id: diagnostic.diagnostic.group_id,
16819 blocks,
16820 });
16821 cx.notify();
16822 }
16823
16824 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16825 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16826 return;
16827 };
16828
16829 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16830 if let ActiveDiagnostic::Group(group) = prev {
16831 self.display_map.update(cx, |display_map, cx| {
16832 display_map.remove_blocks(group.blocks, cx);
16833 });
16834 cx.notify();
16835 }
16836 }
16837
16838 /// Disable inline diagnostics rendering for this editor.
16839 pub fn disable_inline_diagnostics(&mut self) {
16840 self.inline_diagnostics_enabled = false;
16841 self.inline_diagnostics_update = Task::ready(());
16842 self.inline_diagnostics.clear();
16843 }
16844
16845 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16846 self.diagnostics_enabled = false;
16847 self.dismiss_diagnostics(cx);
16848 self.inline_diagnostics_update = Task::ready(());
16849 self.inline_diagnostics.clear();
16850 }
16851
16852 pub fn diagnostics_enabled(&self) -> bool {
16853 self.diagnostics_enabled && self.mode.is_full()
16854 }
16855
16856 pub fn inline_diagnostics_enabled(&self) -> bool {
16857 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16858 }
16859
16860 pub fn show_inline_diagnostics(&self) -> bool {
16861 self.show_inline_diagnostics
16862 }
16863
16864 pub fn toggle_inline_diagnostics(
16865 &mut self,
16866 _: &ToggleInlineDiagnostics,
16867 window: &mut Window,
16868 cx: &mut Context<Editor>,
16869 ) {
16870 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16871 self.refresh_inline_diagnostics(false, window, cx);
16872 }
16873
16874 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16875 self.diagnostics_max_severity = severity;
16876 self.display_map.update(cx, |display_map, _| {
16877 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16878 });
16879 }
16880
16881 pub fn toggle_diagnostics(
16882 &mut self,
16883 _: &ToggleDiagnostics,
16884 window: &mut Window,
16885 cx: &mut Context<Editor>,
16886 ) {
16887 if !self.diagnostics_enabled() {
16888 return;
16889 }
16890
16891 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16892 EditorSettings::get_global(cx)
16893 .diagnostics_max_severity
16894 .filter(|severity| severity != &DiagnosticSeverity::Off)
16895 .unwrap_or(DiagnosticSeverity::Hint)
16896 } else {
16897 DiagnosticSeverity::Off
16898 };
16899 self.set_max_diagnostics_severity(new_severity, cx);
16900 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16901 self.active_diagnostics = ActiveDiagnostic::None;
16902 self.inline_diagnostics_update = Task::ready(());
16903 self.inline_diagnostics.clear();
16904 } else {
16905 self.refresh_inline_diagnostics(false, window, cx);
16906 }
16907
16908 cx.notify();
16909 }
16910
16911 pub fn toggle_minimap(
16912 &mut self,
16913 _: &ToggleMinimap,
16914 window: &mut Window,
16915 cx: &mut Context<Editor>,
16916 ) {
16917 if self.supports_minimap(cx) {
16918 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16919 }
16920 }
16921
16922 fn refresh_inline_diagnostics(
16923 &mut self,
16924 debounce: bool,
16925 window: &mut Window,
16926 cx: &mut Context<Self>,
16927 ) {
16928 let max_severity = ProjectSettings::get_global(cx)
16929 .diagnostics
16930 .inline
16931 .max_severity
16932 .unwrap_or(self.diagnostics_max_severity);
16933
16934 if !self.inline_diagnostics_enabled()
16935 || !self.show_inline_diagnostics
16936 || max_severity == DiagnosticSeverity::Off
16937 {
16938 self.inline_diagnostics_update = Task::ready(());
16939 self.inline_diagnostics.clear();
16940 return;
16941 }
16942
16943 let debounce_ms = ProjectSettings::get_global(cx)
16944 .diagnostics
16945 .inline
16946 .update_debounce_ms;
16947 let debounce = if debounce && debounce_ms > 0 {
16948 Some(Duration::from_millis(debounce_ms))
16949 } else {
16950 None
16951 };
16952 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16953 if let Some(debounce) = debounce {
16954 cx.background_executor().timer(debounce).await;
16955 }
16956 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16957 editor
16958 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16959 .ok()
16960 }) else {
16961 return;
16962 };
16963
16964 let new_inline_diagnostics = cx
16965 .background_spawn(async move {
16966 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16967 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16968 let message = diagnostic_entry
16969 .diagnostic
16970 .message
16971 .split_once('\n')
16972 .map(|(line, _)| line)
16973 .map(SharedString::new)
16974 .unwrap_or_else(|| {
16975 SharedString::from(diagnostic_entry.diagnostic.message)
16976 });
16977 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16978 let (Ok(i) | Err(i)) = inline_diagnostics
16979 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16980 inline_diagnostics.insert(
16981 i,
16982 (
16983 start_anchor,
16984 InlineDiagnostic {
16985 message,
16986 group_id: diagnostic_entry.diagnostic.group_id,
16987 start: diagnostic_entry.range.start.to_point(&snapshot),
16988 is_primary: diagnostic_entry.diagnostic.is_primary,
16989 severity: diagnostic_entry.diagnostic.severity,
16990 },
16991 ),
16992 );
16993 }
16994 inline_diagnostics
16995 })
16996 .await;
16997
16998 editor
16999 .update(cx, |editor, cx| {
17000 editor.inline_diagnostics = new_inline_diagnostics;
17001 cx.notify();
17002 })
17003 .ok();
17004 });
17005 }
17006
17007 fn pull_diagnostics(
17008 &mut self,
17009 buffer_id: Option<BufferId>,
17010 window: &Window,
17011 cx: &mut Context<Self>,
17012 ) -> Option<()> {
17013 if !self.mode().is_full() {
17014 return None;
17015 }
17016 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17017 .diagnostics
17018 .lsp_pull_diagnostics;
17019 if !pull_diagnostics_settings.enabled {
17020 return None;
17021 }
17022 let project = self.project.as_ref()?.downgrade();
17023 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17024 let mut buffers = self.buffer.read(cx).all_buffers();
17025 if let Some(buffer_id) = buffer_id {
17026 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17027 }
17028
17029 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17030 cx.background_executor().timer(debounce).await;
17031
17032 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17033 buffers
17034 .into_iter()
17035 .filter_map(|buffer| {
17036 project
17037 .update(cx, |project, cx| {
17038 project.lsp_store().update(cx, |lsp_store, cx| {
17039 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17040 })
17041 })
17042 .ok()
17043 })
17044 .collect::<FuturesUnordered<_>>()
17045 }) else {
17046 return;
17047 };
17048
17049 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17050 match pull_task {
17051 Ok(()) => {
17052 if editor
17053 .update_in(cx, |editor, window, cx| {
17054 editor.update_diagnostics_state(window, cx);
17055 })
17056 .is_err()
17057 {
17058 return;
17059 }
17060 }
17061 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17062 }
17063 }
17064 });
17065
17066 Some(())
17067 }
17068
17069 pub fn set_selections_from_remote(
17070 &mut self,
17071 selections: Vec<Selection<Anchor>>,
17072 pending_selection: Option<Selection<Anchor>>,
17073 window: &mut Window,
17074 cx: &mut Context<Self>,
17075 ) {
17076 let old_cursor_position = self.selections.newest_anchor().head();
17077 self.selections.change_with(cx, |s| {
17078 s.select_anchors(selections);
17079 if let Some(pending_selection) = pending_selection {
17080 s.set_pending(pending_selection, SelectMode::Character);
17081 } else {
17082 s.clear_pending();
17083 }
17084 });
17085 self.selections_did_change(
17086 false,
17087 &old_cursor_position,
17088 SelectionEffects::default(),
17089 window,
17090 cx,
17091 );
17092 }
17093
17094 pub fn transact(
17095 &mut self,
17096 window: &mut Window,
17097 cx: &mut Context<Self>,
17098 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17099 ) -> Option<TransactionId> {
17100 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17101 this.start_transaction_at(Instant::now(), window, cx);
17102 update(this, window, cx);
17103 this.end_transaction_at(Instant::now(), cx)
17104 })
17105 }
17106
17107 pub fn start_transaction_at(
17108 &mut self,
17109 now: Instant,
17110 window: &mut Window,
17111 cx: &mut Context<Self>,
17112 ) -> Option<TransactionId> {
17113 self.end_selection(window, cx);
17114 if let Some(tx_id) = self
17115 .buffer
17116 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17117 {
17118 self.selection_history
17119 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17120 cx.emit(EditorEvent::TransactionBegun {
17121 transaction_id: tx_id,
17122 });
17123 Some(tx_id)
17124 } else {
17125 None
17126 }
17127 }
17128
17129 pub fn end_transaction_at(
17130 &mut self,
17131 now: Instant,
17132 cx: &mut Context<Self>,
17133 ) -> Option<TransactionId> {
17134 if let Some(transaction_id) = self
17135 .buffer
17136 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17137 {
17138 if let Some((_, end_selections)) =
17139 self.selection_history.transaction_mut(transaction_id)
17140 {
17141 *end_selections = Some(self.selections.disjoint_anchors());
17142 } else {
17143 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17144 }
17145
17146 cx.emit(EditorEvent::Edited { transaction_id });
17147 Some(transaction_id)
17148 } else {
17149 None
17150 }
17151 }
17152
17153 pub fn modify_transaction_selection_history(
17154 &mut self,
17155 transaction_id: TransactionId,
17156 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17157 ) -> bool {
17158 self.selection_history
17159 .transaction_mut(transaction_id)
17160 .map(modify)
17161 .is_some()
17162 }
17163
17164 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17165 if self.selection_mark_mode {
17166 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17167 s.move_with(|_, sel| {
17168 sel.collapse_to(sel.head(), SelectionGoal::None);
17169 });
17170 })
17171 }
17172 self.selection_mark_mode = true;
17173 cx.notify();
17174 }
17175
17176 pub fn swap_selection_ends(
17177 &mut self,
17178 _: &actions::SwapSelectionEnds,
17179 window: &mut Window,
17180 cx: &mut Context<Self>,
17181 ) {
17182 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17183 s.move_with(|_, sel| {
17184 if sel.start != sel.end {
17185 sel.reversed = !sel.reversed
17186 }
17187 });
17188 });
17189 self.request_autoscroll(Autoscroll::newest(), cx);
17190 cx.notify();
17191 }
17192
17193 pub fn toggle_focus(
17194 workspace: &mut Workspace,
17195 _: &actions::ToggleFocus,
17196 window: &mut Window,
17197 cx: &mut Context<Workspace>,
17198 ) {
17199 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17200 return;
17201 };
17202 workspace.activate_item(&item, true, true, window, cx);
17203 }
17204
17205 pub fn toggle_fold(
17206 &mut self,
17207 _: &actions::ToggleFold,
17208 window: &mut Window,
17209 cx: &mut Context<Self>,
17210 ) {
17211 if self.is_singleton(cx) {
17212 let selection = self.selections.newest::<Point>(cx);
17213
17214 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17215 let range = if selection.is_empty() {
17216 let point = selection.head().to_display_point(&display_map);
17217 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17218 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17219 .to_point(&display_map);
17220 start..end
17221 } else {
17222 selection.range()
17223 };
17224 if display_map.folds_in_range(range).next().is_some() {
17225 self.unfold_lines(&Default::default(), window, cx)
17226 } else {
17227 self.fold(&Default::default(), window, cx)
17228 }
17229 } else {
17230 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17231 let buffer_ids: HashSet<_> = self
17232 .selections
17233 .disjoint_anchor_ranges()
17234 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17235 .collect();
17236
17237 let should_unfold = buffer_ids
17238 .iter()
17239 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17240
17241 for buffer_id in buffer_ids {
17242 if should_unfold {
17243 self.unfold_buffer(buffer_id, cx);
17244 } else {
17245 self.fold_buffer(buffer_id, cx);
17246 }
17247 }
17248 }
17249 }
17250
17251 pub fn toggle_fold_recursive(
17252 &mut self,
17253 _: &actions::ToggleFoldRecursive,
17254 window: &mut Window,
17255 cx: &mut Context<Self>,
17256 ) {
17257 let selection = self.selections.newest::<Point>(cx);
17258
17259 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17260 let range = if selection.is_empty() {
17261 let point = selection.head().to_display_point(&display_map);
17262 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17263 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17264 .to_point(&display_map);
17265 start..end
17266 } else {
17267 selection.range()
17268 };
17269 if display_map.folds_in_range(range).next().is_some() {
17270 self.unfold_recursive(&Default::default(), window, cx)
17271 } else {
17272 self.fold_recursive(&Default::default(), window, cx)
17273 }
17274 }
17275
17276 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17277 if self.is_singleton(cx) {
17278 let mut to_fold = Vec::new();
17279 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17280 let selections = self.selections.all_adjusted(cx);
17281
17282 for selection in selections {
17283 let range = selection.range().sorted();
17284 let buffer_start_row = range.start.row;
17285
17286 if range.start.row != range.end.row {
17287 let mut found = false;
17288 let mut row = range.start.row;
17289 while row <= range.end.row {
17290 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17291 {
17292 found = true;
17293 row = crease.range().end.row + 1;
17294 to_fold.push(crease);
17295 } else {
17296 row += 1
17297 }
17298 }
17299 if found {
17300 continue;
17301 }
17302 }
17303
17304 for row in (0..=range.start.row).rev() {
17305 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17306 if crease.range().end.row >= buffer_start_row {
17307 to_fold.push(crease);
17308 if row <= range.start.row {
17309 break;
17310 }
17311 }
17312 }
17313 }
17314 }
17315
17316 self.fold_creases(to_fold, true, window, cx);
17317 } else {
17318 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17319 let buffer_ids = self
17320 .selections
17321 .disjoint_anchor_ranges()
17322 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17323 .collect::<HashSet<_>>();
17324 for buffer_id in buffer_ids {
17325 self.fold_buffer(buffer_id, cx);
17326 }
17327 }
17328 }
17329
17330 pub fn toggle_fold_all(
17331 &mut self,
17332 _: &actions::ToggleFoldAll,
17333 window: &mut Window,
17334 cx: &mut Context<Self>,
17335 ) {
17336 if self.buffer.read(cx).is_singleton() {
17337 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17338 let has_folds = display_map
17339 .folds_in_range(0..display_map.buffer_snapshot.len())
17340 .next()
17341 .is_some();
17342
17343 if has_folds {
17344 self.unfold_all(&actions::UnfoldAll, window, cx);
17345 } else {
17346 self.fold_all(&actions::FoldAll, window, cx);
17347 }
17348 } else {
17349 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17350 let should_unfold = buffer_ids
17351 .iter()
17352 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17353
17354 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17355 editor
17356 .update_in(cx, |editor, _, cx| {
17357 for buffer_id in buffer_ids {
17358 if should_unfold {
17359 editor.unfold_buffer(buffer_id, cx);
17360 } else {
17361 editor.fold_buffer(buffer_id, cx);
17362 }
17363 }
17364 })
17365 .ok();
17366 });
17367 }
17368 }
17369
17370 fn fold_at_level(
17371 &mut self,
17372 fold_at: &FoldAtLevel,
17373 window: &mut Window,
17374 cx: &mut Context<Self>,
17375 ) {
17376 if !self.buffer.read(cx).is_singleton() {
17377 return;
17378 }
17379
17380 let fold_at_level = fold_at.0;
17381 let snapshot = self.buffer.read(cx).snapshot(cx);
17382 let mut to_fold = Vec::new();
17383 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17384
17385 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17386 while start_row < end_row {
17387 match self
17388 .snapshot(window, cx)
17389 .crease_for_buffer_row(MultiBufferRow(start_row))
17390 {
17391 Some(crease) => {
17392 let nested_start_row = crease.range().start.row + 1;
17393 let nested_end_row = crease.range().end.row;
17394
17395 if current_level < fold_at_level {
17396 stack.push((nested_start_row, nested_end_row, current_level + 1));
17397 } else if current_level == fold_at_level {
17398 to_fold.push(crease);
17399 }
17400
17401 start_row = nested_end_row + 1;
17402 }
17403 None => start_row += 1,
17404 }
17405 }
17406 }
17407
17408 self.fold_creases(to_fold, true, window, cx);
17409 }
17410
17411 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17412 if self.buffer.read(cx).is_singleton() {
17413 let mut fold_ranges = Vec::new();
17414 let snapshot = self.buffer.read(cx).snapshot(cx);
17415
17416 for row in 0..snapshot.max_row().0 {
17417 if let Some(foldable_range) = self
17418 .snapshot(window, cx)
17419 .crease_for_buffer_row(MultiBufferRow(row))
17420 {
17421 fold_ranges.push(foldable_range);
17422 }
17423 }
17424
17425 self.fold_creases(fold_ranges, true, window, cx);
17426 } else {
17427 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17428 editor
17429 .update_in(cx, |editor, _, cx| {
17430 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17431 editor.fold_buffer(buffer_id, cx);
17432 }
17433 })
17434 .ok();
17435 });
17436 }
17437 }
17438
17439 pub fn fold_function_bodies(
17440 &mut self,
17441 _: &actions::FoldFunctionBodies,
17442 window: &mut Window,
17443 cx: &mut Context<Self>,
17444 ) {
17445 let snapshot = self.buffer.read(cx).snapshot(cx);
17446
17447 let ranges = snapshot
17448 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17449 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17450 .collect::<Vec<_>>();
17451
17452 let creases = ranges
17453 .into_iter()
17454 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17455 .collect();
17456
17457 self.fold_creases(creases, true, window, cx);
17458 }
17459
17460 pub fn fold_recursive(
17461 &mut self,
17462 _: &actions::FoldRecursive,
17463 window: &mut Window,
17464 cx: &mut Context<Self>,
17465 ) {
17466 let mut to_fold = Vec::new();
17467 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17468 let selections = self.selections.all_adjusted(cx);
17469
17470 for selection in selections {
17471 let range = selection.range().sorted();
17472 let buffer_start_row = range.start.row;
17473
17474 if range.start.row != range.end.row {
17475 let mut found = false;
17476 for row in range.start.row..=range.end.row {
17477 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17478 found = true;
17479 to_fold.push(crease);
17480 }
17481 }
17482 if found {
17483 continue;
17484 }
17485 }
17486
17487 for row in (0..=range.start.row).rev() {
17488 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17489 if crease.range().end.row >= buffer_start_row {
17490 to_fold.push(crease);
17491 } else {
17492 break;
17493 }
17494 }
17495 }
17496 }
17497
17498 self.fold_creases(to_fold, true, window, cx);
17499 }
17500
17501 pub fn fold_at(
17502 &mut self,
17503 buffer_row: MultiBufferRow,
17504 window: &mut Window,
17505 cx: &mut Context<Self>,
17506 ) {
17507 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17508
17509 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17510 let autoscroll = self
17511 .selections
17512 .all::<Point>(cx)
17513 .iter()
17514 .any(|selection| crease.range().overlaps(&selection.range()));
17515
17516 self.fold_creases(vec![crease], autoscroll, window, cx);
17517 }
17518 }
17519
17520 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17521 if self.is_singleton(cx) {
17522 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17523 let buffer = &display_map.buffer_snapshot;
17524 let selections = self.selections.all::<Point>(cx);
17525 let ranges = selections
17526 .iter()
17527 .map(|s| {
17528 let range = s.display_range(&display_map).sorted();
17529 let mut start = range.start.to_point(&display_map);
17530 let mut end = range.end.to_point(&display_map);
17531 start.column = 0;
17532 end.column = buffer.line_len(MultiBufferRow(end.row));
17533 start..end
17534 })
17535 .collect::<Vec<_>>();
17536
17537 self.unfold_ranges(&ranges, true, true, cx);
17538 } else {
17539 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17540 let buffer_ids = self
17541 .selections
17542 .disjoint_anchor_ranges()
17543 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17544 .collect::<HashSet<_>>();
17545 for buffer_id in buffer_ids {
17546 self.unfold_buffer(buffer_id, cx);
17547 }
17548 }
17549 }
17550
17551 pub fn unfold_recursive(
17552 &mut self,
17553 _: &UnfoldRecursive,
17554 _window: &mut Window,
17555 cx: &mut Context<Self>,
17556 ) {
17557 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17558 let selections = self.selections.all::<Point>(cx);
17559 let ranges = selections
17560 .iter()
17561 .map(|s| {
17562 let mut range = s.display_range(&display_map).sorted();
17563 *range.start.column_mut() = 0;
17564 *range.end.column_mut() = display_map.line_len(range.end.row());
17565 let start = range.start.to_point(&display_map);
17566 let end = range.end.to_point(&display_map);
17567 start..end
17568 })
17569 .collect::<Vec<_>>();
17570
17571 self.unfold_ranges(&ranges, true, true, cx);
17572 }
17573
17574 pub fn unfold_at(
17575 &mut self,
17576 buffer_row: MultiBufferRow,
17577 _window: &mut Window,
17578 cx: &mut Context<Self>,
17579 ) {
17580 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17581
17582 let intersection_range = Point::new(buffer_row.0, 0)
17583 ..Point::new(
17584 buffer_row.0,
17585 display_map.buffer_snapshot.line_len(buffer_row),
17586 );
17587
17588 let autoscroll = self
17589 .selections
17590 .all::<Point>(cx)
17591 .iter()
17592 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17593
17594 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17595 }
17596
17597 pub fn unfold_all(
17598 &mut self,
17599 _: &actions::UnfoldAll,
17600 _window: &mut Window,
17601 cx: &mut Context<Self>,
17602 ) {
17603 if self.buffer.read(cx).is_singleton() {
17604 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17605 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17606 } else {
17607 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17608 editor
17609 .update(cx, |editor, cx| {
17610 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17611 editor.unfold_buffer(buffer_id, cx);
17612 }
17613 })
17614 .ok();
17615 });
17616 }
17617 }
17618
17619 pub fn fold_selected_ranges(
17620 &mut self,
17621 _: &FoldSelectedRanges,
17622 window: &mut Window,
17623 cx: &mut Context<Self>,
17624 ) {
17625 let selections = self.selections.all_adjusted(cx);
17626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17627 let ranges = selections
17628 .into_iter()
17629 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17630 .collect::<Vec<_>>();
17631 self.fold_creases(ranges, true, window, cx);
17632 }
17633
17634 pub fn fold_ranges<T: ToOffset + Clone>(
17635 &mut self,
17636 ranges: Vec<Range<T>>,
17637 auto_scroll: bool,
17638 window: &mut Window,
17639 cx: &mut Context<Self>,
17640 ) {
17641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17642 let ranges = ranges
17643 .into_iter()
17644 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17645 .collect::<Vec<_>>();
17646 self.fold_creases(ranges, auto_scroll, window, cx);
17647 }
17648
17649 pub fn fold_creases<T: ToOffset + Clone>(
17650 &mut self,
17651 creases: Vec<Crease<T>>,
17652 auto_scroll: bool,
17653 _window: &mut Window,
17654 cx: &mut Context<Self>,
17655 ) {
17656 if creases.is_empty() {
17657 return;
17658 }
17659
17660 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17661
17662 if auto_scroll {
17663 self.request_autoscroll(Autoscroll::fit(), cx);
17664 }
17665
17666 cx.notify();
17667
17668 self.scrollbar_marker_state.dirty = true;
17669 self.folds_did_change(cx);
17670 }
17671
17672 /// Removes any folds whose ranges intersect any of the given ranges.
17673 pub fn unfold_ranges<T: ToOffset + Clone>(
17674 &mut self,
17675 ranges: &[Range<T>],
17676 inclusive: bool,
17677 auto_scroll: bool,
17678 cx: &mut Context<Self>,
17679 ) {
17680 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17681 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17682 });
17683 self.folds_did_change(cx);
17684 }
17685
17686 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17687 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17688 return;
17689 }
17690 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17691 self.display_map.update(cx, |display_map, cx| {
17692 display_map.fold_buffers([buffer_id], cx)
17693 });
17694 cx.emit(EditorEvent::BufferFoldToggled {
17695 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17696 folded: true,
17697 });
17698 cx.notify();
17699 }
17700
17701 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17702 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17703 return;
17704 }
17705 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17706 self.display_map.update(cx, |display_map, cx| {
17707 display_map.unfold_buffers([buffer_id], cx);
17708 });
17709 cx.emit(EditorEvent::BufferFoldToggled {
17710 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17711 folded: false,
17712 });
17713 cx.notify();
17714 }
17715
17716 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17717 self.display_map.read(cx).is_buffer_folded(buffer)
17718 }
17719
17720 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17721 self.display_map.read(cx).folded_buffers()
17722 }
17723
17724 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17725 self.display_map.update(cx, |display_map, cx| {
17726 display_map.disable_header_for_buffer(buffer_id, cx);
17727 });
17728 cx.notify();
17729 }
17730
17731 /// Removes any folds with the given ranges.
17732 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17733 &mut self,
17734 ranges: &[Range<T>],
17735 type_id: TypeId,
17736 auto_scroll: bool,
17737 cx: &mut Context<Self>,
17738 ) {
17739 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17740 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17741 });
17742 self.folds_did_change(cx);
17743 }
17744
17745 fn remove_folds_with<T: ToOffset + Clone>(
17746 &mut self,
17747 ranges: &[Range<T>],
17748 auto_scroll: bool,
17749 cx: &mut Context<Self>,
17750 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17751 ) {
17752 if ranges.is_empty() {
17753 return;
17754 }
17755
17756 let mut buffers_affected = HashSet::default();
17757 let multi_buffer = self.buffer().read(cx);
17758 for range in ranges {
17759 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17760 buffers_affected.insert(buffer.read(cx).remote_id());
17761 };
17762 }
17763
17764 self.display_map.update(cx, update);
17765
17766 if auto_scroll {
17767 self.request_autoscroll(Autoscroll::fit(), cx);
17768 }
17769
17770 cx.notify();
17771 self.scrollbar_marker_state.dirty = true;
17772 self.active_indent_guides_state.dirty = true;
17773 }
17774
17775 pub fn update_renderer_widths(
17776 &mut self,
17777 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17778 cx: &mut Context<Self>,
17779 ) -> bool {
17780 self.display_map
17781 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17782 }
17783
17784 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17785 self.display_map.read(cx).fold_placeholder.clone()
17786 }
17787
17788 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17789 self.buffer.update(cx, |buffer, cx| {
17790 buffer.set_all_diff_hunks_expanded(cx);
17791 });
17792 }
17793
17794 pub fn expand_all_diff_hunks(
17795 &mut self,
17796 _: &ExpandAllDiffHunks,
17797 _window: &mut Window,
17798 cx: &mut Context<Self>,
17799 ) {
17800 self.buffer.update(cx, |buffer, cx| {
17801 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17802 });
17803 }
17804
17805 pub fn toggle_selected_diff_hunks(
17806 &mut self,
17807 _: &ToggleSelectedDiffHunks,
17808 _window: &mut Window,
17809 cx: &mut Context<Self>,
17810 ) {
17811 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17812 self.toggle_diff_hunks_in_ranges(ranges, cx);
17813 }
17814
17815 pub fn diff_hunks_in_ranges<'a>(
17816 &'a self,
17817 ranges: &'a [Range<Anchor>],
17818 buffer: &'a MultiBufferSnapshot,
17819 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17820 ranges.iter().flat_map(move |range| {
17821 let end_excerpt_id = range.end.excerpt_id;
17822 let range = range.to_point(buffer);
17823 let mut peek_end = range.end;
17824 if range.end.row < buffer.max_row().0 {
17825 peek_end = Point::new(range.end.row + 1, 0);
17826 }
17827 buffer
17828 .diff_hunks_in_range(range.start..peek_end)
17829 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17830 })
17831 }
17832
17833 pub fn has_stageable_diff_hunks_in_ranges(
17834 &self,
17835 ranges: &[Range<Anchor>],
17836 snapshot: &MultiBufferSnapshot,
17837 ) -> bool {
17838 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17839 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17840 }
17841
17842 pub fn toggle_staged_selected_diff_hunks(
17843 &mut self,
17844 _: &::git::ToggleStaged,
17845 _: &mut Window,
17846 cx: &mut Context<Self>,
17847 ) {
17848 let snapshot = self.buffer.read(cx).snapshot(cx);
17849 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17850 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17851 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17852 }
17853
17854 pub fn set_render_diff_hunk_controls(
17855 &mut self,
17856 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17857 cx: &mut Context<Self>,
17858 ) {
17859 self.render_diff_hunk_controls = render_diff_hunk_controls;
17860 cx.notify();
17861 }
17862
17863 pub fn stage_and_next(
17864 &mut self,
17865 _: &::git::StageAndNext,
17866 window: &mut Window,
17867 cx: &mut Context<Self>,
17868 ) {
17869 self.do_stage_or_unstage_and_next(true, window, cx);
17870 }
17871
17872 pub fn unstage_and_next(
17873 &mut self,
17874 _: &::git::UnstageAndNext,
17875 window: &mut Window,
17876 cx: &mut Context<Self>,
17877 ) {
17878 self.do_stage_or_unstage_and_next(false, window, cx);
17879 }
17880
17881 pub fn stage_or_unstage_diff_hunks(
17882 &mut self,
17883 stage: bool,
17884 ranges: Vec<Range<Anchor>>,
17885 cx: &mut Context<Self>,
17886 ) {
17887 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17888 cx.spawn(async move |this, cx| {
17889 task.await?;
17890 this.update(cx, |this, cx| {
17891 let snapshot = this.buffer.read(cx).snapshot(cx);
17892 let chunk_by = this
17893 .diff_hunks_in_ranges(&ranges, &snapshot)
17894 .chunk_by(|hunk| hunk.buffer_id);
17895 for (buffer_id, hunks) in &chunk_by {
17896 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17897 }
17898 })
17899 })
17900 .detach_and_log_err(cx);
17901 }
17902
17903 fn save_buffers_for_ranges_if_needed(
17904 &mut self,
17905 ranges: &[Range<Anchor>],
17906 cx: &mut Context<Editor>,
17907 ) -> Task<Result<()>> {
17908 let multibuffer = self.buffer.read(cx);
17909 let snapshot = multibuffer.read(cx);
17910 let buffer_ids: HashSet<_> = ranges
17911 .iter()
17912 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17913 .collect();
17914 drop(snapshot);
17915
17916 let mut buffers = HashSet::default();
17917 for buffer_id in buffer_ids {
17918 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17919 let buffer = buffer_entity.read(cx);
17920 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17921 {
17922 buffers.insert(buffer_entity);
17923 }
17924 }
17925 }
17926
17927 if let Some(project) = &self.project {
17928 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17929 } else {
17930 Task::ready(Ok(()))
17931 }
17932 }
17933
17934 fn do_stage_or_unstage_and_next(
17935 &mut self,
17936 stage: bool,
17937 window: &mut Window,
17938 cx: &mut Context<Self>,
17939 ) {
17940 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17941
17942 if ranges.iter().any(|range| range.start != range.end) {
17943 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17944 return;
17945 }
17946
17947 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17948 let snapshot = self.snapshot(window, cx);
17949 let position = self.selections.newest::<Point>(cx).head();
17950 let mut row = snapshot
17951 .buffer_snapshot
17952 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17953 .find(|hunk| hunk.row_range.start.0 > position.row)
17954 .map(|hunk| hunk.row_range.start);
17955
17956 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17957 // Outside of the project diff editor, wrap around to the beginning.
17958 if !all_diff_hunks_expanded {
17959 row = row.or_else(|| {
17960 snapshot
17961 .buffer_snapshot
17962 .diff_hunks_in_range(Point::zero()..position)
17963 .find(|hunk| hunk.row_range.end.0 < position.row)
17964 .map(|hunk| hunk.row_range.start)
17965 });
17966 }
17967
17968 if let Some(row) = row {
17969 let destination = Point::new(row.0, 0);
17970 let autoscroll = Autoscroll::center();
17971
17972 self.unfold_ranges(&[destination..destination], false, false, cx);
17973 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17974 s.select_ranges([destination..destination]);
17975 });
17976 }
17977 }
17978
17979 fn do_stage_or_unstage(
17980 &self,
17981 stage: bool,
17982 buffer_id: BufferId,
17983 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17984 cx: &mut App,
17985 ) -> Option<()> {
17986 let project = self.project.as_ref()?;
17987 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17988 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17989 let buffer_snapshot = buffer.read(cx).snapshot();
17990 let file_exists = buffer_snapshot
17991 .file()
17992 .is_some_and(|file| file.disk_state().exists());
17993 diff.update(cx, |diff, cx| {
17994 diff.stage_or_unstage_hunks(
17995 stage,
17996 &hunks
17997 .map(|hunk| buffer_diff::DiffHunk {
17998 buffer_range: hunk.buffer_range,
17999 diff_base_byte_range: hunk.diff_base_byte_range,
18000 secondary_status: hunk.secondary_status,
18001 range: Point::zero()..Point::zero(), // unused
18002 })
18003 .collect::<Vec<_>>(),
18004 &buffer_snapshot,
18005 file_exists,
18006 cx,
18007 )
18008 });
18009 None
18010 }
18011
18012 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18013 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18014 self.buffer
18015 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18016 }
18017
18018 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18019 self.buffer.update(cx, |buffer, cx| {
18020 let ranges = vec![Anchor::min()..Anchor::max()];
18021 if !buffer.all_diff_hunks_expanded()
18022 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18023 {
18024 buffer.collapse_diff_hunks(ranges, cx);
18025 true
18026 } else {
18027 false
18028 }
18029 })
18030 }
18031
18032 fn toggle_diff_hunks_in_ranges(
18033 &mut self,
18034 ranges: Vec<Range<Anchor>>,
18035 cx: &mut Context<Editor>,
18036 ) {
18037 self.buffer.update(cx, |buffer, cx| {
18038 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18039 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18040 })
18041 }
18042
18043 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18044 self.buffer.update(cx, |buffer, cx| {
18045 let snapshot = buffer.snapshot(cx);
18046 let excerpt_id = range.end.excerpt_id;
18047 let point_range = range.to_point(&snapshot);
18048 let expand = !buffer.single_hunk_is_expanded(range, cx);
18049 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18050 })
18051 }
18052
18053 pub(crate) fn apply_all_diff_hunks(
18054 &mut self,
18055 _: &ApplyAllDiffHunks,
18056 window: &mut Window,
18057 cx: &mut Context<Self>,
18058 ) {
18059 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18060
18061 let buffers = self.buffer.read(cx).all_buffers();
18062 for branch_buffer in buffers {
18063 branch_buffer.update(cx, |branch_buffer, cx| {
18064 branch_buffer.merge_into_base(Vec::new(), cx);
18065 });
18066 }
18067
18068 if let Some(project) = self.project.clone() {
18069 self.save(
18070 SaveOptions {
18071 format: true,
18072 autosave: false,
18073 },
18074 project,
18075 window,
18076 cx,
18077 )
18078 .detach_and_log_err(cx);
18079 }
18080 }
18081
18082 pub(crate) fn apply_selected_diff_hunks(
18083 &mut self,
18084 _: &ApplyDiffHunk,
18085 window: &mut Window,
18086 cx: &mut Context<Self>,
18087 ) {
18088 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18089 let snapshot = self.snapshot(window, cx);
18090 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18091 let mut ranges_by_buffer = HashMap::default();
18092 self.transact(window, cx, |editor, _window, cx| {
18093 for hunk in hunks {
18094 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18095 ranges_by_buffer
18096 .entry(buffer.clone())
18097 .or_insert_with(Vec::new)
18098 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18099 }
18100 }
18101
18102 for (buffer, ranges) in ranges_by_buffer {
18103 buffer.update(cx, |buffer, cx| {
18104 buffer.merge_into_base(ranges, cx);
18105 });
18106 }
18107 });
18108
18109 if let Some(project) = self.project.clone() {
18110 self.save(
18111 SaveOptions {
18112 format: true,
18113 autosave: false,
18114 },
18115 project,
18116 window,
18117 cx,
18118 )
18119 .detach_and_log_err(cx);
18120 }
18121 }
18122
18123 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18124 if hovered != self.gutter_hovered {
18125 self.gutter_hovered = hovered;
18126 cx.notify();
18127 }
18128 }
18129
18130 pub fn insert_blocks(
18131 &mut self,
18132 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18133 autoscroll: Option<Autoscroll>,
18134 cx: &mut Context<Self>,
18135 ) -> Vec<CustomBlockId> {
18136 let blocks = self
18137 .display_map
18138 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18139 if let Some(autoscroll) = autoscroll {
18140 self.request_autoscroll(autoscroll, cx);
18141 }
18142 cx.notify();
18143 blocks
18144 }
18145
18146 pub fn resize_blocks(
18147 &mut self,
18148 heights: HashMap<CustomBlockId, u32>,
18149 autoscroll: Option<Autoscroll>,
18150 cx: &mut Context<Self>,
18151 ) {
18152 self.display_map
18153 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18154 if let Some(autoscroll) = autoscroll {
18155 self.request_autoscroll(autoscroll, cx);
18156 }
18157 cx.notify();
18158 }
18159
18160 pub fn replace_blocks(
18161 &mut self,
18162 renderers: HashMap<CustomBlockId, RenderBlock>,
18163 autoscroll: Option<Autoscroll>,
18164 cx: &mut Context<Self>,
18165 ) {
18166 self.display_map
18167 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18168 if let Some(autoscroll) = autoscroll {
18169 self.request_autoscroll(autoscroll, cx);
18170 }
18171 cx.notify();
18172 }
18173
18174 pub fn remove_blocks(
18175 &mut self,
18176 block_ids: HashSet<CustomBlockId>,
18177 autoscroll: Option<Autoscroll>,
18178 cx: &mut Context<Self>,
18179 ) {
18180 self.display_map.update(cx, |display_map, cx| {
18181 display_map.remove_blocks(block_ids, cx)
18182 });
18183 if let Some(autoscroll) = autoscroll {
18184 self.request_autoscroll(autoscroll, cx);
18185 }
18186 cx.notify();
18187 }
18188
18189 pub fn row_for_block(
18190 &self,
18191 block_id: CustomBlockId,
18192 cx: &mut Context<Self>,
18193 ) -> Option<DisplayRow> {
18194 self.display_map
18195 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18196 }
18197
18198 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18199 self.focused_block = Some(focused_block);
18200 }
18201
18202 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18203 self.focused_block.take()
18204 }
18205
18206 pub fn insert_creases(
18207 &mut self,
18208 creases: impl IntoIterator<Item = Crease<Anchor>>,
18209 cx: &mut Context<Self>,
18210 ) -> Vec<CreaseId> {
18211 self.display_map
18212 .update(cx, |map, cx| map.insert_creases(creases, cx))
18213 }
18214
18215 pub fn remove_creases(
18216 &mut self,
18217 ids: impl IntoIterator<Item = CreaseId>,
18218 cx: &mut Context<Self>,
18219 ) -> Vec<(CreaseId, Range<Anchor>)> {
18220 self.display_map
18221 .update(cx, |map, cx| map.remove_creases(ids, cx))
18222 }
18223
18224 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18225 self.display_map
18226 .update(cx, |map, cx| map.snapshot(cx))
18227 .longest_row()
18228 }
18229
18230 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18231 self.display_map
18232 .update(cx, |map, cx| map.snapshot(cx))
18233 .max_point()
18234 }
18235
18236 pub fn text(&self, cx: &App) -> String {
18237 self.buffer.read(cx).read(cx).text()
18238 }
18239
18240 pub fn is_empty(&self, cx: &App) -> bool {
18241 self.buffer.read(cx).read(cx).is_empty()
18242 }
18243
18244 pub fn text_option(&self, cx: &App) -> Option<String> {
18245 let text = self.text(cx);
18246 let text = text.trim();
18247
18248 if text.is_empty() {
18249 return None;
18250 }
18251
18252 Some(text.to_string())
18253 }
18254
18255 pub fn set_text(
18256 &mut self,
18257 text: impl Into<Arc<str>>,
18258 window: &mut Window,
18259 cx: &mut Context<Self>,
18260 ) {
18261 self.transact(window, cx, |this, _, cx| {
18262 this.buffer
18263 .read(cx)
18264 .as_singleton()
18265 .expect("you can only call set_text on editors for singleton buffers")
18266 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18267 });
18268 }
18269
18270 pub fn display_text(&self, cx: &mut App) -> String {
18271 self.display_map
18272 .update(cx, |map, cx| map.snapshot(cx))
18273 .text()
18274 }
18275
18276 fn create_minimap(
18277 &self,
18278 minimap_settings: MinimapSettings,
18279 window: &mut Window,
18280 cx: &mut Context<Self>,
18281 ) -> Option<Entity<Self>> {
18282 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18283 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18284 }
18285
18286 fn initialize_new_minimap(
18287 &self,
18288 minimap_settings: MinimapSettings,
18289 window: &mut Window,
18290 cx: &mut Context<Self>,
18291 ) -> Entity<Self> {
18292 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18293
18294 let mut minimap = Editor::new_internal(
18295 EditorMode::Minimap {
18296 parent: cx.weak_entity(),
18297 },
18298 self.buffer.clone(),
18299 None,
18300 Some(self.display_map.clone()),
18301 window,
18302 cx,
18303 );
18304 minimap.scroll_manager.clone_state(&self.scroll_manager);
18305 minimap.set_text_style_refinement(TextStyleRefinement {
18306 font_size: Some(MINIMAP_FONT_SIZE),
18307 font_weight: Some(MINIMAP_FONT_WEIGHT),
18308 ..Default::default()
18309 });
18310 minimap.update_minimap_configuration(minimap_settings, cx);
18311 cx.new(|_| minimap)
18312 }
18313
18314 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18315 let current_line_highlight = minimap_settings
18316 .current_line_highlight
18317 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18318 self.set_current_line_highlight(Some(current_line_highlight));
18319 }
18320
18321 pub fn minimap(&self) -> Option<&Entity<Self>> {
18322 self.minimap
18323 .as_ref()
18324 .filter(|_| self.minimap_visibility.visible())
18325 }
18326
18327 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18328 let mut wrap_guides = smallvec![];
18329
18330 if self.show_wrap_guides == Some(false) {
18331 return wrap_guides;
18332 }
18333
18334 let settings = self.buffer.read(cx).language_settings(cx);
18335 if settings.show_wrap_guides {
18336 match self.soft_wrap_mode(cx) {
18337 SoftWrap::Column(soft_wrap) => {
18338 wrap_guides.push((soft_wrap as usize, true));
18339 }
18340 SoftWrap::Bounded(soft_wrap) => {
18341 wrap_guides.push((soft_wrap as usize, true));
18342 }
18343 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18344 }
18345 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18346 }
18347
18348 wrap_guides
18349 }
18350
18351 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18352 let settings = self.buffer.read(cx).language_settings(cx);
18353 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18354 match mode {
18355 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18356 SoftWrap::None
18357 }
18358 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18359 language_settings::SoftWrap::PreferredLineLength => {
18360 SoftWrap::Column(settings.preferred_line_length)
18361 }
18362 language_settings::SoftWrap::Bounded => {
18363 SoftWrap::Bounded(settings.preferred_line_length)
18364 }
18365 }
18366 }
18367
18368 pub fn set_soft_wrap_mode(
18369 &mut self,
18370 mode: language_settings::SoftWrap,
18371
18372 cx: &mut Context<Self>,
18373 ) {
18374 self.soft_wrap_mode_override = Some(mode);
18375 cx.notify();
18376 }
18377
18378 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18379 self.hard_wrap = hard_wrap;
18380 cx.notify();
18381 }
18382
18383 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18384 self.text_style_refinement = Some(style);
18385 }
18386
18387 /// called by the Element so we know what style we were most recently rendered with.
18388 pub(crate) fn set_style(
18389 &mut self,
18390 style: EditorStyle,
18391 window: &mut Window,
18392 cx: &mut Context<Self>,
18393 ) {
18394 // We intentionally do not inform the display map about the minimap style
18395 // so that wrapping is not recalculated and stays consistent for the editor
18396 // and its linked minimap.
18397 if !self.mode.is_minimap() {
18398 let rem_size = window.rem_size();
18399 self.display_map.update(cx, |map, cx| {
18400 map.set_font(
18401 style.text.font(),
18402 style.text.font_size.to_pixels(rem_size),
18403 cx,
18404 )
18405 });
18406 }
18407 self.style = Some(style);
18408 }
18409
18410 pub fn style(&self) -> Option<&EditorStyle> {
18411 self.style.as_ref()
18412 }
18413
18414 // Called by the element. This method is not designed to be called outside of the editor
18415 // element's layout code because it does not notify when rewrapping is computed synchronously.
18416 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18417 self.display_map
18418 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18419 }
18420
18421 pub fn set_soft_wrap(&mut self) {
18422 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18423 }
18424
18425 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18426 if self.soft_wrap_mode_override.is_some() {
18427 self.soft_wrap_mode_override.take();
18428 } else {
18429 let soft_wrap = match self.soft_wrap_mode(cx) {
18430 SoftWrap::GitDiff => return,
18431 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18432 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18433 language_settings::SoftWrap::None
18434 }
18435 };
18436 self.soft_wrap_mode_override = Some(soft_wrap);
18437 }
18438 cx.notify();
18439 }
18440
18441 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18442 let Some(workspace) = self.workspace() else {
18443 return;
18444 };
18445 let fs = workspace.read(cx).app_state().fs.clone();
18446 let current_show = TabBarSettings::get_global(cx).show;
18447 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18448 setting.show = Some(!current_show);
18449 });
18450 }
18451
18452 pub fn toggle_indent_guides(
18453 &mut self,
18454 _: &ToggleIndentGuides,
18455 _: &mut Window,
18456 cx: &mut Context<Self>,
18457 ) {
18458 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18459 self.buffer
18460 .read(cx)
18461 .language_settings(cx)
18462 .indent_guides
18463 .enabled
18464 });
18465 self.show_indent_guides = Some(!currently_enabled);
18466 cx.notify();
18467 }
18468
18469 fn should_show_indent_guides(&self) -> Option<bool> {
18470 self.show_indent_guides
18471 }
18472
18473 pub fn toggle_line_numbers(
18474 &mut self,
18475 _: &ToggleLineNumbers,
18476 _: &mut Window,
18477 cx: &mut Context<Self>,
18478 ) {
18479 let mut editor_settings = EditorSettings::get_global(cx).clone();
18480 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18481 EditorSettings::override_global(editor_settings, cx);
18482 }
18483
18484 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18485 if let Some(show_line_numbers) = self.show_line_numbers {
18486 return show_line_numbers;
18487 }
18488 EditorSettings::get_global(cx).gutter.line_numbers
18489 }
18490
18491 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18492 self.use_relative_line_numbers
18493 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18494 }
18495
18496 pub fn toggle_relative_line_numbers(
18497 &mut self,
18498 _: &ToggleRelativeLineNumbers,
18499 _: &mut Window,
18500 cx: &mut Context<Self>,
18501 ) {
18502 let is_relative = self.should_use_relative_line_numbers(cx);
18503 self.set_relative_line_number(Some(!is_relative), cx)
18504 }
18505
18506 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18507 self.use_relative_line_numbers = is_relative;
18508 cx.notify();
18509 }
18510
18511 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18512 self.show_gutter = show_gutter;
18513 cx.notify();
18514 }
18515
18516 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18517 self.show_scrollbars = ScrollbarAxes {
18518 horizontal: show,
18519 vertical: show,
18520 };
18521 cx.notify();
18522 }
18523
18524 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18525 self.show_scrollbars.vertical = show;
18526 cx.notify();
18527 }
18528
18529 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18530 self.show_scrollbars.horizontal = show;
18531 cx.notify();
18532 }
18533
18534 pub fn set_minimap_visibility(
18535 &mut self,
18536 minimap_visibility: MinimapVisibility,
18537 window: &mut Window,
18538 cx: &mut Context<Self>,
18539 ) {
18540 if self.minimap_visibility != minimap_visibility {
18541 if minimap_visibility.visible() && self.minimap.is_none() {
18542 let minimap_settings = EditorSettings::get_global(cx).minimap;
18543 self.minimap =
18544 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18545 }
18546 self.minimap_visibility = minimap_visibility;
18547 cx.notify();
18548 }
18549 }
18550
18551 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18552 self.set_show_scrollbars(false, cx);
18553 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18554 }
18555
18556 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18557 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18558 }
18559
18560 /// Normally the text in full mode and auto height editors is padded on the
18561 /// left side by roughly half a character width for improved hit testing.
18562 ///
18563 /// Use this method to disable this for cases where this is not wanted (e.g.
18564 /// if you want to align the editor text with some other text above or below)
18565 /// or if you want to add this padding to single-line editors.
18566 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18567 self.offset_content = offset_content;
18568 cx.notify();
18569 }
18570
18571 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18572 self.show_line_numbers = Some(show_line_numbers);
18573 cx.notify();
18574 }
18575
18576 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18577 self.disable_expand_excerpt_buttons = true;
18578 cx.notify();
18579 }
18580
18581 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18582 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18583 cx.notify();
18584 }
18585
18586 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18587 self.show_code_actions = Some(show_code_actions);
18588 cx.notify();
18589 }
18590
18591 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18592 self.show_runnables = Some(show_runnables);
18593 cx.notify();
18594 }
18595
18596 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18597 self.show_breakpoints = Some(show_breakpoints);
18598 cx.notify();
18599 }
18600
18601 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18602 if self.display_map.read(cx).masked != masked {
18603 self.display_map.update(cx, |map, _| map.masked = masked);
18604 }
18605 cx.notify()
18606 }
18607
18608 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18609 self.show_wrap_guides = Some(show_wrap_guides);
18610 cx.notify();
18611 }
18612
18613 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18614 self.show_indent_guides = Some(show_indent_guides);
18615 cx.notify();
18616 }
18617
18618 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18619 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18620 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18621 if let Some(dir) = file.abs_path(cx).parent() {
18622 return Some(dir.to_owned());
18623 }
18624 }
18625
18626 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18627 return Some(project_path.path.to_path_buf());
18628 }
18629 }
18630
18631 None
18632 }
18633
18634 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18635 self.active_excerpt(cx)?
18636 .1
18637 .read(cx)
18638 .file()
18639 .and_then(|f| f.as_local())
18640 }
18641
18642 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18643 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18644 let buffer = buffer.read(cx);
18645 if let Some(project_path) = buffer.project_path(cx) {
18646 let project = self.project.as_ref()?.read(cx);
18647 project.absolute_path(&project_path, cx)
18648 } else {
18649 buffer
18650 .file()
18651 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18652 }
18653 })
18654 }
18655
18656 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18657 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18658 let project_path = buffer.read(cx).project_path(cx)?;
18659 let project = self.project.as_ref()?.read(cx);
18660 let entry = project.entry_for_path(&project_path, cx)?;
18661 let path = entry.path.to_path_buf();
18662 Some(path)
18663 })
18664 }
18665
18666 pub fn reveal_in_finder(
18667 &mut self,
18668 _: &RevealInFileManager,
18669 _window: &mut Window,
18670 cx: &mut Context<Self>,
18671 ) {
18672 if let Some(target) = self.target_file(cx) {
18673 cx.reveal_path(&target.abs_path(cx));
18674 }
18675 }
18676
18677 pub fn copy_path(
18678 &mut self,
18679 _: &zed_actions::workspace::CopyPath,
18680 _window: &mut Window,
18681 cx: &mut Context<Self>,
18682 ) {
18683 if let Some(path) = self.target_file_abs_path(cx) {
18684 if let Some(path) = path.to_str() {
18685 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18686 }
18687 }
18688 }
18689
18690 pub fn copy_relative_path(
18691 &mut self,
18692 _: &zed_actions::workspace::CopyRelativePath,
18693 _window: &mut Window,
18694 cx: &mut Context<Self>,
18695 ) {
18696 if let Some(path) = self.target_file_path(cx) {
18697 if let Some(path) = path.to_str() {
18698 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18699 }
18700 }
18701 }
18702
18703 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18704 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18705 buffer.read(cx).project_path(cx)
18706 } else {
18707 None
18708 }
18709 }
18710
18711 // Returns true if the editor handled a go-to-line request
18712 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18713 maybe!({
18714 let breakpoint_store = self.breakpoint_store.as_ref()?;
18715
18716 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18717 else {
18718 self.clear_row_highlights::<ActiveDebugLine>();
18719 return None;
18720 };
18721
18722 let position = active_stack_frame.position;
18723 let buffer_id = position.buffer_id?;
18724 let snapshot = self
18725 .project
18726 .as_ref()?
18727 .read(cx)
18728 .buffer_for_id(buffer_id, cx)?
18729 .read(cx)
18730 .snapshot();
18731
18732 let mut handled = false;
18733 for (id, ExcerptRange { context, .. }) in
18734 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18735 {
18736 if context.start.cmp(&position, &snapshot).is_ge()
18737 || context.end.cmp(&position, &snapshot).is_lt()
18738 {
18739 continue;
18740 }
18741 let snapshot = self.buffer.read(cx).snapshot(cx);
18742 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18743
18744 handled = true;
18745 self.clear_row_highlights::<ActiveDebugLine>();
18746
18747 self.go_to_line::<ActiveDebugLine>(
18748 multibuffer_anchor,
18749 Some(cx.theme().colors().editor_debugger_active_line_background),
18750 window,
18751 cx,
18752 );
18753
18754 cx.notify();
18755 }
18756
18757 handled.then_some(())
18758 })
18759 .is_some()
18760 }
18761
18762 pub fn copy_file_name_without_extension(
18763 &mut self,
18764 _: &CopyFileNameWithoutExtension,
18765 _: &mut Window,
18766 cx: &mut Context<Self>,
18767 ) {
18768 if let Some(file) = self.target_file(cx) {
18769 if let Some(file_stem) = file.path().file_stem() {
18770 if let Some(name) = file_stem.to_str() {
18771 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18772 }
18773 }
18774 }
18775 }
18776
18777 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18778 if let Some(file) = self.target_file(cx) {
18779 if let Some(file_name) = file.path().file_name() {
18780 if let Some(name) = file_name.to_str() {
18781 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18782 }
18783 }
18784 }
18785 }
18786
18787 pub fn toggle_git_blame(
18788 &mut self,
18789 _: &::git::Blame,
18790 window: &mut Window,
18791 cx: &mut Context<Self>,
18792 ) {
18793 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18794
18795 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18796 self.start_git_blame(true, window, cx);
18797 }
18798
18799 cx.notify();
18800 }
18801
18802 pub fn toggle_git_blame_inline(
18803 &mut self,
18804 _: &ToggleGitBlameInline,
18805 window: &mut Window,
18806 cx: &mut Context<Self>,
18807 ) {
18808 self.toggle_git_blame_inline_internal(true, window, cx);
18809 cx.notify();
18810 }
18811
18812 pub fn open_git_blame_commit(
18813 &mut self,
18814 _: &OpenGitBlameCommit,
18815 window: &mut Window,
18816 cx: &mut Context<Self>,
18817 ) {
18818 self.open_git_blame_commit_internal(window, cx);
18819 }
18820
18821 fn open_git_blame_commit_internal(
18822 &mut self,
18823 window: &mut Window,
18824 cx: &mut Context<Self>,
18825 ) -> Option<()> {
18826 let blame = self.blame.as_ref()?;
18827 let snapshot = self.snapshot(window, cx);
18828 let cursor = self.selections.newest::<Point>(cx).head();
18829 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18830 let blame_entry = blame
18831 .update(cx, |blame, cx| {
18832 blame
18833 .blame_for_rows(
18834 &[RowInfo {
18835 buffer_id: Some(buffer.remote_id()),
18836 buffer_row: Some(point.row),
18837 ..Default::default()
18838 }],
18839 cx,
18840 )
18841 .next()
18842 })
18843 .flatten()?;
18844 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18845 let repo = blame.read(cx).repository(cx)?;
18846 let workspace = self.workspace()?.downgrade();
18847 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18848 None
18849 }
18850
18851 pub fn git_blame_inline_enabled(&self) -> bool {
18852 self.git_blame_inline_enabled
18853 }
18854
18855 pub fn toggle_selection_menu(
18856 &mut self,
18857 _: &ToggleSelectionMenu,
18858 _: &mut Window,
18859 cx: &mut Context<Self>,
18860 ) {
18861 self.show_selection_menu = self
18862 .show_selection_menu
18863 .map(|show_selections_menu| !show_selections_menu)
18864 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18865
18866 cx.notify();
18867 }
18868
18869 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18870 self.show_selection_menu
18871 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18872 }
18873
18874 fn start_git_blame(
18875 &mut self,
18876 user_triggered: bool,
18877 window: &mut Window,
18878 cx: &mut Context<Self>,
18879 ) {
18880 if let Some(project) = self.project.as_ref() {
18881 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18882 return;
18883 };
18884
18885 if buffer.read(cx).file().is_none() {
18886 return;
18887 }
18888
18889 let focused = self.focus_handle(cx).contains_focused(window, cx);
18890
18891 let project = project.clone();
18892 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18893 self.blame_subscription =
18894 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18895 self.blame = Some(blame);
18896 }
18897 }
18898
18899 fn toggle_git_blame_inline_internal(
18900 &mut self,
18901 user_triggered: bool,
18902 window: &mut Window,
18903 cx: &mut Context<Self>,
18904 ) {
18905 if self.git_blame_inline_enabled {
18906 self.git_blame_inline_enabled = false;
18907 self.show_git_blame_inline = false;
18908 self.show_git_blame_inline_delay_task.take();
18909 } else {
18910 self.git_blame_inline_enabled = true;
18911 self.start_git_blame_inline(user_triggered, window, cx);
18912 }
18913
18914 cx.notify();
18915 }
18916
18917 fn start_git_blame_inline(
18918 &mut self,
18919 user_triggered: bool,
18920 window: &mut Window,
18921 cx: &mut Context<Self>,
18922 ) {
18923 self.start_git_blame(user_triggered, window, cx);
18924
18925 if ProjectSettings::get_global(cx)
18926 .git
18927 .inline_blame_delay()
18928 .is_some()
18929 {
18930 self.start_inline_blame_timer(window, cx);
18931 } else {
18932 self.show_git_blame_inline = true
18933 }
18934 }
18935
18936 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18937 self.blame.as_ref()
18938 }
18939
18940 pub fn show_git_blame_gutter(&self) -> bool {
18941 self.show_git_blame_gutter
18942 }
18943
18944 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18945 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18946 }
18947
18948 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18949 self.show_git_blame_inline
18950 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18951 && !self.newest_selection_head_on_empty_line(cx)
18952 && self.has_blame_entries(cx)
18953 }
18954
18955 fn has_blame_entries(&self, cx: &App) -> bool {
18956 self.blame()
18957 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18958 }
18959
18960 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18961 let cursor_anchor = self.selections.newest_anchor().head();
18962
18963 let snapshot = self.buffer.read(cx).snapshot(cx);
18964 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18965
18966 snapshot.line_len(buffer_row) == 0
18967 }
18968
18969 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18970 let buffer_and_selection = maybe!({
18971 let selection = self.selections.newest::<Point>(cx);
18972 let selection_range = selection.range();
18973
18974 let multi_buffer = self.buffer().read(cx);
18975 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18976 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18977
18978 let (buffer, range, _) = if selection.reversed {
18979 buffer_ranges.first()
18980 } else {
18981 buffer_ranges.last()
18982 }?;
18983
18984 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18985 ..text::ToPoint::to_point(&range.end, &buffer).row;
18986 Some((
18987 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18988 selection,
18989 ))
18990 });
18991
18992 let Some((buffer, selection)) = buffer_and_selection else {
18993 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18994 };
18995
18996 let Some(project) = self.project.as_ref() else {
18997 return Task::ready(Err(anyhow!("editor does not have project")));
18998 };
18999
19000 project.update(cx, |project, cx| {
19001 project.get_permalink_to_line(&buffer, selection, cx)
19002 })
19003 }
19004
19005 pub fn copy_permalink_to_line(
19006 &mut self,
19007 _: &CopyPermalinkToLine,
19008 window: &mut Window,
19009 cx: &mut Context<Self>,
19010 ) {
19011 let permalink_task = self.get_permalink_to_line(cx);
19012 let workspace = self.workspace();
19013
19014 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19015 Ok(permalink) => {
19016 cx.update(|_, cx| {
19017 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19018 })
19019 .ok();
19020 }
19021 Err(err) => {
19022 let message = format!("Failed to copy permalink: {err}");
19023
19024 anyhow::Result::<()>::Err(err).log_err();
19025
19026 if let Some(workspace) = workspace {
19027 workspace
19028 .update_in(cx, |workspace, _, cx| {
19029 struct CopyPermalinkToLine;
19030
19031 workspace.show_toast(
19032 Toast::new(
19033 NotificationId::unique::<CopyPermalinkToLine>(),
19034 message,
19035 ),
19036 cx,
19037 )
19038 })
19039 .ok();
19040 }
19041 }
19042 })
19043 .detach();
19044 }
19045
19046 pub fn copy_file_location(
19047 &mut self,
19048 _: &CopyFileLocation,
19049 _: &mut Window,
19050 cx: &mut Context<Self>,
19051 ) {
19052 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19053 if let Some(file) = self.target_file(cx) {
19054 if let Some(path) = file.path().to_str() {
19055 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19056 }
19057 }
19058 }
19059
19060 pub fn open_permalink_to_line(
19061 &mut self,
19062 _: &OpenPermalinkToLine,
19063 window: &mut Window,
19064 cx: &mut Context<Self>,
19065 ) {
19066 let permalink_task = self.get_permalink_to_line(cx);
19067 let workspace = self.workspace();
19068
19069 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19070 Ok(permalink) => {
19071 cx.update(|_, cx| {
19072 cx.open_url(permalink.as_ref());
19073 })
19074 .ok();
19075 }
19076 Err(err) => {
19077 let message = format!("Failed to open permalink: {err}");
19078
19079 anyhow::Result::<()>::Err(err).log_err();
19080
19081 if let Some(workspace) = workspace {
19082 workspace
19083 .update(cx, |workspace, cx| {
19084 struct OpenPermalinkToLine;
19085
19086 workspace.show_toast(
19087 Toast::new(
19088 NotificationId::unique::<OpenPermalinkToLine>(),
19089 message,
19090 ),
19091 cx,
19092 )
19093 })
19094 .ok();
19095 }
19096 }
19097 })
19098 .detach();
19099 }
19100
19101 pub fn insert_uuid_v4(
19102 &mut self,
19103 _: &InsertUuidV4,
19104 window: &mut Window,
19105 cx: &mut Context<Self>,
19106 ) {
19107 self.insert_uuid(UuidVersion::V4, window, cx);
19108 }
19109
19110 pub fn insert_uuid_v7(
19111 &mut self,
19112 _: &InsertUuidV7,
19113 window: &mut Window,
19114 cx: &mut Context<Self>,
19115 ) {
19116 self.insert_uuid(UuidVersion::V7, window, cx);
19117 }
19118
19119 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19120 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19121 self.transact(window, cx, |this, window, cx| {
19122 let edits = this
19123 .selections
19124 .all::<Point>(cx)
19125 .into_iter()
19126 .map(|selection| {
19127 let uuid = match version {
19128 UuidVersion::V4 => uuid::Uuid::new_v4(),
19129 UuidVersion::V7 => uuid::Uuid::now_v7(),
19130 };
19131
19132 (selection.range(), uuid.to_string())
19133 });
19134 this.edit(edits, cx);
19135 this.refresh_edit_prediction(true, false, window, cx);
19136 });
19137 }
19138
19139 pub fn open_selections_in_multibuffer(
19140 &mut self,
19141 _: &OpenSelectionsInMultibuffer,
19142 window: &mut Window,
19143 cx: &mut Context<Self>,
19144 ) {
19145 let multibuffer = self.buffer.read(cx);
19146
19147 let Some(buffer) = multibuffer.as_singleton() else {
19148 return;
19149 };
19150
19151 let Some(workspace) = self.workspace() else {
19152 return;
19153 };
19154
19155 let title = multibuffer.title(cx).to_string();
19156
19157 let locations = self
19158 .selections
19159 .all_anchors(cx)
19160 .into_iter()
19161 .map(|selection| Location {
19162 buffer: buffer.clone(),
19163 range: selection.start.text_anchor..selection.end.text_anchor,
19164 })
19165 .collect::<Vec<_>>();
19166
19167 cx.spawn_in(window, async move |_, cx| {
19168 workspace.update_in(cx, |workspace, window, cx| {
19169 Self::open_locations_in_multibuffer(
19170 workspace,
19171 locations,
19172 format!("Selections for '{title}'"),
19173 false,
19174 MultibufferSelectionMode::All,
19175 window,
19176 cx,
19177 );
19178 })
19179 })
19180 .detach();
19181 }
19182
19183 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19184 /// last highlight added will be used.
19185 ///
19186 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19187 pub fn highlight_rows<T: 'static>(
19188 &mut self,
19189 range: Range<Anchor>,
19190 color: Hsla,
19191 options: RowHighlightOptions,
19192 cx: &mut Context<Self>,
19193 ) {
19194 let snapshot = self.buffer().read(cx).snapshot(cx);
19195 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19196 let ix = row_highlights.binary_search_by(|highlight| {
19197 Ordering::Equal
19198 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19199 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19200 });
19201
19202 if let Err(mut ix) = ix {
19203 let index = post_inc(&mut self.highlight_order);
19204
19205 // If this range intersects with the preceding highlight, then merge it with
19206 // the preceding highlight. Otherwise insert a new highlight.
19207 let mut merged = false;
19208 if ix > 0 {
19209 let prev_highlight = &mut row_highlights[ix - 1];
19210 if prev_highlight
19211 .range
19212 .end
19213 .cmp(&range.start, &snapshot)
19214 .is_ge()
19215 {
19216 ix -= 1;
19217 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19218 prev_highlight.range.end = range.end;
19219 }
19220 merged = true;
19221 prev_highlight.index = index;
19222 prev_highlight.color = color;
19223 prev_highlight.options = options;
19224 }
19225 }
19226
19227 if !merged {
19228 row_highlights.insert(
19229 ix,
19230 RowHighlight {
19231 range: range.clone(),
19232 index,
19233 color,
19234 options,
19235 type_id: TypeId::of::<T>(),
19236 },
19237 );
19238 }
19239
19240 // If any of the following highlights intersect with this one, merge them.
19241 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19242 let highlight = &row_highlights[ix];
19243 if next_highlight
19244 .range
19245 .start
19246 .cmp(&highlight.range.end, &snapshot)
19247 .is_le()
19248 {
19249 if next_highlight
19250 .range
19251 .end
19252 .cmp(&highlight.range.end, &snapshot)
19253 .is_gt()
19254 {
19255 row_highlights[ix].range.end = next_highlight.range.end;
19256 }
19257 row_highlights.remove(ix + 1);
19258 } else {
19259 break;
19260 }
19261 }
19262 }
19263 }
19264
19265 /// Remove any highlighted row ranges of the given type that intersect the
19266 /// given ranges.
19267 pub fn remove_highlighted_rows<T: 'static>(
19268 &mut self,
19269 ranges_to_remove: Vec<Range<Anchor>>,
19270 cx: &mut Context<Self>,
19271 ) {
19272 let snapshot = self.buffer().read(cx).snapshot(cx);
19273 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19274 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19275 row_highlights.retain(|highlight| {
19276 while let Some(range_to_remove) = ranges_to_remove.peek() {
19277 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19278 Ordering::Less | Ordering::Equal => {
19279 ranges_to_remove.next();
19280 }
19281 Ordering::Greater => {
19282 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19283 Ordering::Less | Ordering::Equal => {
19284 return false;
19285 }
19286 Ordering::Greater => break,
19287 }
19288 }
19289 }
19290 }
19291
19292 true
19293 })
19294 }
19295
19296 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19297 pub fn clear_row_highlights<T: 'static>(&mut self) {
19298 self.highlighted_rows.remove(&TypeId::of::<T>());
19299 }
19300
19301 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19302 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19303 self.highlighted_rows
19304 .get(&TypeId::of::<T>())
19305 .map_or(&[] as &[_], |vec| vec.as_slice())
19306 .iter()
19307 .map(|highlight| (highlight.range.clone(), highlight.color))
19308 }
19309
19310 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19311 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19312 /// Allows to ignore certain kinds of highlights.
19313 pub fn highlighted_display_rows(
19314 &self,
19315 window: &mut Window,
19316 cx: &mut App,
19317 ) -> BTreeMap<DisplayRow, LineHighlight> {
19318 let snapshot = self.snapshot(window, cx);
19319 let mut used_highlight_orders = HashMap::default();
19320 self.highlighted_rows
19321 .iter()
19322 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19323 .fold(
19324 BTreeMap::<DisplayRow, LineHighlight>::new(),
19325 |mut unique_rows, highlight| {
19326 let start = highlight.range.start.to_display_point(&snapshot);
19327 let end = highlight.range.end.to_display_point(&snapshot);
19328 let start_row = start.row().0;
19329 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19330 && end.column() == 0
19331 {
19332 end.row().0.saturating_sub(1)
19333 } else {
19334 end.row().0
19335 };
19336 for row in start_row..=end_row {
19337 let used_index =
19338 used_highlight_orders.entry(row).or_insert(highlight.index);
19339 if highlight.index >= *used_index {
19340 *used_index = highlight.index;
19341 unique_rows.insert(
19342 DisplayRow(row),
19343 LineHighlight {
19344 include_gutter: highlight.options.include_gutter,
19345 border: None,
19346 background: highlight.color.into(),
19347 type_id: Some(highlight.type_id),
19348 },
19349 );
19350 }
19351 }
19352 unique_rows
19353 },
19354 )
19355 }
19356
19357 pub fn highlighted_display_row_for_autoscroll(
19358 &self,
19359 snapshot: &DisplaySnapshot,
19360 ) -> Option<DisplayRow> {
19361 self.highlighted_rows
19362 .values()
19363 .flat_map(|highlighted_rows| highlighted_rows.iter())
19364 .filter_map(|highlight| {
19365 if highlight.options.autoscroll {
19366 Some(highlight.range.start.to_display_point(snapshot).row())
19367 } else {
19368 None
19369 }
19370 })
19371 .min()
19372 }
19373
19374 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19375 self.highlight_background::<SearchWithinRange>(
19376 ranges,
19377 |colors| colors.colors().editor_document_highlight_read_background,
19378 cx,
19379 )
19380 }
19381
19382 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19383 self.breadcrumb_header = Some(new_header);
19384 }
19385
19386 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19387 self.clear_background_highlights::<SearchWithinRange>(cx);
19388 }
19389
19390 pub fn highlight_background<T: 'static>(
19391 &mut self,
19392 ranges: &[Range<Anchor>],
19393 color_fetcher: fn(&Theme) -> Hsla,
19394 cx: &mut Context<Self>,
19395 ) {
19396 self.background_highlights.insert(
19397 HighlightKey::Type(TypeId::of::<T>()),
19398 (color_fetcher, Arc::from(ranges)),
19399 );
19400 self.scrollbar_marker_state.dirty = true;
19401 cx.notify();
19402 }
19403
19404 pub fn highlight_background_key<T: 'static>(
19405 &mut self,
19406 key: usize,
19407 ranges: &[Range<Anchor>],
19408 color_fetcher: fn(&Theme) -> Hsla,
19409 cx: &mut Context<Self>,
19410 ) {
19411 self.background_highlights.insert(
19412 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19413 (color_fetcher, Arc::from(ranges)),
19414 );
19415 self.scrollbar_marker_state.dirty = true;
19416 cx.notify();
19417 }
19418
19419 pub fn clear_background_highlights<T: 'static>(
19420 &mut self,
19421 cx: &mut Context<Self>,
19422 ) -> Option<BackgroundHighlight> {
19423 let text_highlights = self
19424 .background_highlights
19425 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19426 if !text_highlights.1.is_empty() {
19427 self.scrollbar_marker_state.dirty = true;
19428 cx.notify();
19429 }
19430 Some(text_highlights)
19431 }
19432
19433 pub fn highlight_gutter<T: 'static>(
19434 &mut self,
19435 ranges: impl Into<Vec<Range<Anchor>>>,
19436 color_fetcher: fn(&App) -> Hsla,
19437 cx: &mut Context<Self>,
19438 ) {
19439 self.gutter_highlights
19440 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19441 cx.notify();
19442 }
19443
19444 pub fn clear_gutter_highlights<T: 'static>(
19445 &mut self,
19446 cx: &mut Context<Self>,
19447 ) -> Option<GutterHighlight> {
19448 cx.notify();
19449 self.gutter_highlights.remove(&TypeId::of::<T>())
19450 }
19451
19452 pub fn insert_gutter_highlight<T: 'static>(
19453 &mut self,
19454 range: Range<Anchor>,
19455 color_fetcher: fn(&App) -> Hsla,
19456 cx: &mut Context<Self>,
19457 ) {
19458 let snapshot = self.buffer().read(cx).snapshot(cx);
19459 let mut highlights = self
19460 .gutter_highlights
19461 .remove(&TypeId::of::<T>())
19462 .map(|(_, highlights)| highlights)
19463 .unwrap_or_default();
19464 let ix = highlights.binary_search_by(|highlight| {
19465 Ordering::Equal
19466 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19467 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19468 });
19469 if let Err(ix) = ix {
19470 highlights.insert(ix, range);
19471 }
19472 self.gutter_highlights
19473 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19474 }
19475
19476 pub fn remove_gutter_highlights<T: 'static>(
19477 &mut self,
19478 ranges_to_remove: Vec<Range<Anchor>>,
19479 cx: &mut Context<Self>,
19480 ) {
19481 let snapshot = self.buffer().read(cx).snapshot(cx);
19482 let Some((color_fetcher, mut gutter_highlights)) =
19483 self.gutter_highlights.remove(&TypeId::of::<T>())
19484 else {
19485 return;
19486 };
19487 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19488 gutter_highlights.retain(|highlight| {
19489 while let Some(range_to_remove) = ranges_to_remove.peek() {
19490 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19491 Ordering::Less | Ordering::Equal => {
19492 ranges_to_remove.next();
19493 }
19494 Ordering::Greater => {
19495 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19496 Ordering::Less | Ordering::Equal => {
19497 return false;
19498 }
19499 Ordering::Greater => break,
19500 }
19501 }
19502 }
19503 }
19504
19505 true
19506 });
19507 self.gutter_highlights
19508 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19509 }
19510
19511 #[cfg(feature = "test-support")]
19512 pub fn all_text_highlights(
19513 &self,
19514 window: &mut Window,
19515 cx: &mut Context<Self>,
19516 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19517 let snapshot = self.snapshot(window, cx);
19518 self.display_map.update(cx, |display_map, _| {
19519 display_map
19520 .all_text_highlights()
19521 .map(|highlight| {
19522 let (style, ranges) = highlight.as_ref();
19523 (
19524 *style,
19525 ranges
19526 .iter()
19527 .map(|range| range.clone().to_display_points(&snapshot))
19528 .collect(),
19529 )
19530 })
19531 .collect()
19532 })
19533 }
19534
19535 #[cfg(feature = "test-support")]
19536 pub fn all_text_background_highlights(
19537 &self,
19538 window: &mut Window,
19539 cx: &mut Context<Self>,
19540 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19541 let snapshot = self.snapshot(window, cx);
19542 let buffer = &snapshot.buffer_snapshot;
19543 let start = buffer.anchor_before(0);
19544 let end = buffer.anchor_after(buffer.len());
19545 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19546 }
19547
19548 #[cfg(feature = "test-support")]
19549 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19550 let snapshot = self.buffer().read(cx).snapshot(cx);
19551
19552 let highlights = self
19553 .background_highlights
19554 .get(&HighlightKey::Type(TypeId::of::<
19555 items::BufferSearchHighlights,
19556 >()));
19557
19558 if let Some((_color, ranges)) = highlights {
19559 ranges
19560 .iter()
19561 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19562 .collect_vec()
19563 } else {
19564 vec![]
19565 }
19566 }
19567
19568 fn document_highlights_for_position<'a>(
19569 &'a self,
19570 position: Anchor,
19571 buffer: &'a MultiBufferSnapshot,
19572 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19573 let read_highlights = self
19574 .background_highlights
19575 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19576 .map(|h| &h.1);
19577 let write_highlights = self
19578 .background_highlights
19579 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19580 .map(|h| &h.1);
19581 let left_position = position.bias_left(buffer);
19582 let right_position = position.bias_right(buffer);
19583 read_highlights
19584 .into_iter()
19585 .chain(write_highlights)
19586 .flat_map(move |ranges| {
19587 let start_ix = match ranges.binary_search_by(|probe| {
19588 let cmp = probe.end.cmp(&left_position, buffer);
19589 if cmp.is_ge() {
19590 Ordering::Greater
19591 } else {
19592 Ordering::Less
19593 }
19594 }) {
19595 Ok(i) | Err(i) => i,
19596 };
19597
19598 ranges[start_ix..]
19599 .iter()
19600 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19601 })
19602 }
19603
19604 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19605 self.background_highlights
19606 .get(&HighlightKey::Type(TypeId::of::<T>()))
19607 .map_or(false, |(_, highlights)| !highlights.is_empty())
19608 }
19609
19610 pub fn background_highlights_in_range(
19611 &self,
19612 search_range: Range<Anchor>,
19613 display_snapshot: &DisplaySnapshot,
19614 theme: &Theme,
19615 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19616 let mut results = Vec::new();
19617 for (color_fetcher, ranges) in self.background_highlights.values() {
19618 let color = color_fetcher(theme);
19619 let start_ix = match ranges.binary_search_by(|probe| {
19620 let cmp = probe
19621 .end
19622 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19623 if cmp.is_gt() {
19624 Ordering::Greater
19625 } else {
19626 Ordering::Less
19627 }
19628 }) {
19629 Ok(i) | Err(i) => i,
19630 };
19631 for range in &ranges[start_ix..] {
19632 if range
19633 .start
19634 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19635 .is_ge()
19636 {
19637 break;
19638 }
19639
19640 let start = range.start.to_display_point(display_snapshot);
19641 let end = range.end.to_display_point(display_snapshot);
19642 results.push((start..end, color))
19643 }
19644 }
19645 results
19646 }
19647
19648 pub fn background_highlight_row_ranges<T: 'static>(
19649 &self,
19650 search_range: Range<Anchor>,
19651 display_snapshot: &DisplaySnapshot,
19652 count: usize,
19653 ) -> Vec<RangeInclusive<DisplayPoint>> {
19654 let mut results = Vec::new();
19655 let Some((_, ranges)) = self
19656 .background_highlights
19657 .get(&HighlightKey::Type(TypeId::of::<T>()))
19658 else {
19659 return vec![];
19660 };
19661
19662 let start_ix = match ranges.binary_search_by(|probe| {
19663 let cmp = probe
19664 .end
19665 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19666 if cmp.is_gt() {
19667 Ordering::Greater
19668 } else {
19669 Ordering::Less
19670 }
19671 }) {
19672 Ok(i) | Err(i) => i,
19673 };
19674 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19675 if let (Some(start_display), Some(end_display)) = (start, end) {
19676 results.push(
19677 start_display.to_display_point(display_snapshot)
19678 ..=end_display.to_display_point(display_snapshot),
19679 );
19680 }
19681 };
19682 let mut start_row: Option<Point> = None;
19683 let mut end_row: Option<Point> = None;
19684 if ranges.len() > count {
19685 return Vec::new();
19686 }
19687 for range in &ranges[start_ix..] {
19688 if range
19689 .start
19690 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19691 .is_ge()
19692 {
19693 break;
19694 }
19695 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19696 if let Some(current_row) = &end_row {
19697 if end.row == current_row.row {
19698 continue;
19699 }
19700 }
19701 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19702 if start_row.is_none() {
19703 assert_eq!(end_row, None);
19704 start_row = Some(start);
19705 end_row = Some(end);
19706 continue;
19707 }
19708 if let Some(current_end) = end_row.as_mut() {
19709 if start.row > current_end.row + 1 {
19710 push_region(start_row, end_row);
19711 start_row = Some(start);
19712 end_row = Some(end);
19713 } else {
19714 // Merge two hunks.
19715 *current_end = end;
19716 }
19717 } else {
19718 unreachable!();
19719 }
19720 }
19721 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19722 push_region(start_row, end_row);
19723 results
19724 }
19725
19726 pub fn gutter_highlights_in_range(
19727 &self,
19728 search_range: Range<Anchor>,
19729 display_snapshot: &DisplaySnapshot,
19730 cx: &App,
19731 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19732 let mut results = Vec::new();
19733 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19734 let color = color_fetcher(cx);
19735 let start_ix = match ranges.binary_search_by(|probe| {
19736 let cmp = probe
19737 .end
19738 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19739 if cmp.is_gt() {
19740 Ordering::Greater
19741 } else {
19742 Ordering::Less
19743 }
19744 }) {
19745 Ok(i) | Err(i) => i,
19746 };
19747 for range in &ranges[start_ix..] {
19748 if range
19749 .start
19750 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19751 .is_ge()
19752 {
19753 break;
19754 }
19755
19756 let start = range.start.to_display_point(display_snapshot);
19757 let end = range.end.to_display_point(display_snapshot);
19758 results.push((start..end, color))
19759 }
19760 }
19761 results
19762 }
19763
19764 /// Get the text ranges corresponding to the redaction query
19765 pub fn redacted_ranges(
19766 &self,
19767 search_range: Range<Anchor>,
19768 display_snapshot: &DisplaySnapshot,
19769 cx: &App,
19770 ) -> Vec<Range<DisplayPoint>> {
19771 display_snapshot
19772 .buffer_snapshot
19773 .redacted_ranges(search_range, |file| {
19774 if let Some(file) = file {
19775 file.is_private()
19776 && EditorSettings::get(
19777 Some(SettingsLocation {
19778 worktree_id: file.worktree_id(cx),
19779 path: file.path().as_ref(),
19780 }),
19781 cx,
19782 )
19783 .redact_private_values
19784 } else {
19785 false
19786 }
19787 })
19788 .map(|range| {
19789 range.start.to_display_point(display_snapshot)
19790 ..range.end.to_display_point(display_snapshot)
19791 })
19792 .collect()
19793 }
19794
19795 pub fn highlight_text_key<T: 'static>(
19796 &mut self,
19797 key: usize,
19798 ranges: Vec<Range<Anchor>>,
19799 style: HighlightStyle,
19800 cx: &mut Context<Self>,
19801 ) {
19802 self.display_map.update(cx, |map, _| {
19803 map.highlight_text(
19804 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19805 ranges,
19806 style,
19807 );
19808 });
19809 cx.notify();
19810 }
19811
19812 pub fn highlight_text<T: 'static>(
19813 &mut self,
19814 ranges: Vec<Range<Anchor>>,
19815 style: HighlightStyle,
19816 cx: &mut Context<Self>,
19817 ) {
19818 self.display_map.update(cx, |map, _| {
19819 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19820 });
19821 cx.notify();
19822 }
19823
19824 pub(crate) fn highlight_inlays<T: 'static>(
19825 &mut self,
19826 highlights: Vec<InlayHighlight>,
19827 style: HighlightStyle,
19828 cx: &mut Context<Self>,
19829 ) {
19830 self.display_map.update(cx, |map, _| {
19831 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19832 });
19833 cx.notify();
19834 }
19835
19836 pub fn text_highlights<'a, T: 'static>(
19837 &'a self,
19838 cx: &'a App,
19839 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19840 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19841 }
19842
19843 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19844 let cleared = self
19845 .display_map
19846 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19847 if cleared {
19848 cx.notify();
19849 }
19850 }
19851
19852 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19853 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19854 && self.focus_handle.is_focused(window)
19855 }
19856
19857 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19858 self.show_cursor_when_unfocused = is_enabled;
19859 cx.notify();
19860 }
19861
19862 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19863 cx.notify();
19864 }
19865
19866 fn on_debug_session_event(
19867 &mut self,
19868 _session: Entity<Session>,
19869 event: &SessionEvent,
19870 cx: &mut Context<Self>,
19871 ) {
19872 match event {
19873 SessionEvent::InvalidateInlineValue => {
19874 self.refresh_inline_values(cx);
19875 }
19876 _ => {}
19877 }
19878 }
19879
19880 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19881 let Some(project) = self.project.clone() else {
19882 return;
19883 };
19884
19885 if !self.inline_value_cache.enabled {
19886 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19887 self.splice_inlays(&inlays, Vec::new(), cx);
19888 return;
19889 }
19890
19891 let current_execution_position = self
19892 .highlighted_rows
19893 .get(&TypeId::of::<ActiveDebugLine>())
19894 .and_then(|lines| lines.last().map(|line| line.range.end));
19895
19896 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19897 let inline_values = editor
19898 .update(cx, |editor, cx| {
19899 let Some(current_execution_position) = current_execution_position else {
19900 return Some(Task::ready(Ok(Vec::new())));
19901 };
19902
19903 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19904 let snapshot = buffer.snapshot(cx);
19905
19906 let excerpt = snapshot.excerpt_containing(
19907 current_execution_position..current_execution_position,
19908 )?;
19909
19910 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19911 })?;
19912
19913 let range =
19914 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19915
19916 project.inline_values(buffer, range, cx)
19917 })
19918 .ok()
19919 .flatten()?
19920 .await
19921 .context("refreshing debugger inlays")
19922 .log_err()?;
19923
19924 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19925
19926 for (buffer_id, inline_value) in inline_values
19927 .into_iter()
19928 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19929 {
19930 buffer_inline_values
19931 .entry(buffer_id)
19932 .or_default()
19933 .push(inline_value);
19934 }
19935
19936 editor
19937 .update(cx, |editor, cx| {
19938 let snapshot = editor.buffer.read(cx).snapshot(cx);
19939 let mut new_inlays = Vec::default();
19940
19941 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19942 let buffer_id = buffer_snapshot.remote_id();
19943 buffer_inline_values
19944 .get(&buffer_id)
19945 .into_iter()
19946 .flatten()
19947 .for_each(|hint| {
19948 let inlay = Inlay::debugger(
19949 post_inc(&mut editor.next_inlay_id),
19950 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19951 hint.text(),
19952 );
19953 if !inlay.text.chars().contains(&'\n') {
19954 new_inlays.push(inlay);
19955 }
19956 });
19957 }
19958
19959 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19960 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19961
19962 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19963 })
19964 .ok()?;
19965 Some(())
19966 });
19967 }
19968
19969 fn on_buffer_event(
19970 &mut self,
19971 multibuffer: &Entity<MultiBuffer>,
19972 event: &multi_buffer::Event,
19973 window: &mut Window,
19974 cx: &mut Context<Self>,
19975 ) {
19976 match event {
19977 multi_buffer::Event::Edited {
19978 singleton_buffer_edited,
19979 edited_buffer,
19980 } => {
19981 self.scrollbar_marker_state.dirty = true;
19982 self.active_indent_guides_state.dirty = true;
19983 self.refresh_active_diagnostics(cx);
19984 self.refresh_code_actions(window, cx);
19985 self.refresh_selected_text_highlights(true, window, cx);
19986 self.refresh_single_line_folds(window, cx);
19987 refresh_matching_bracket_highlights(self, window, cx);
19988 if self.has_active_edit_prediction() {
19989 self.update_visible_edit_prediction(window, cx);
19990 }
19991 if let Some(project) = self.project.as_ref() {
19992 if let Some(edited_buffer) = edited_buffer {
19993 project.update(cx, |project, cx| {
19994 self.registered_buffers
19995 .entry(edited_buffer.read(cx).remote_id())
19996 .or_insert_with(|| {
19997 project
19998 .register_buffer_with_language_servers(&edited_buffer, cx)
19999 });
20000 });
20001 }
20002 }
20003 cx.emit(EditorEvent::BufferEdited);
20004 cx.emit(SearchEvent::MatchesInvalidated);
20005
20006 if let Some(buffer) = edited_buffer {
20007 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20008 }
20009
20010 if *singleton_buffer_edited {
20011 if let Some(buffer) = edited_buffer {
20012 if buffer.read(cx).file().is_none() {
20013 cx.emit(EditorEvent::TitleChanged);
20014 }
20015 }
20016 if let Some(project) = &self.project {
20017 #[allow(clippy::mutable_key_type)]
20018 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20019 multibuffer
20020 .all_buffers()
20021 .into_iter()
20022 .filter_map(|buffer| {
20023 buffer.update(cx, |buffer, cx| {
20024 let language = buffer.language()?;
20025 let should_discard = project.update(cx, |project, cx| {
20026 project.is_local()
20027 && !project.has_language_servers_for(buffer, cx)
20028 });
20029 should_discard.not().then_some(language.clone())
20030 })
20031 })
20032 .collect::<HashSet<_>>()
20033 });
20034 if !languages_affected.is_empty() {
20035 self.refresh_inlay_hints(
20036 InlayHintRefreshReason::BufferEdited(languages_affected),
20037 cx,
20038 );
20039 }
20040 }
20041 }
20042
20043 let Some(project) = &self.project else { return };
20044 let (telemetry, is_via_ssh) = {
20045 let project = project.read(cx);
20046 let telemetry = project.client().telemetry().clone();
20047 let is_via_ssh = project.is_via_ssh();
20048 (telemetry, is_via_ssh)
20049 };
20050 refresh_linked_ranges(self, window, cx);
20051 telemetry.log_edit_event("editor", is_via_ssh);
20052 }
20053 multi_buffer::Event::ExcerptsAdded {
20054 buffer,
20055 predecessor,
20056 excerpts,
20057 } => {
20058 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20059 let buffer_id = buffer.read(cx).remote_id();
20060 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
20061 if let Some(project) = &self.project {
20062 update_uncommitted_diff_for_buffer(
20063 cx.entity(),
20064 project,
20065 [buffer.clone()],
20066 self.buffer.clone(),
20067 cx,
20068 )
20069 .detach();
20070 }
20071 }
20072 self.update_lsp_data(false, Some(buffer_id), window, cx);
20073 cx.emit(EditorEvent::ExcerptsAdded {
20074 buffer: buffer.clone(),
20075 predecessor: *predecessor,
20076 excerpts: excerpts.clone(),
20077 });
20078 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20079 }
20080 multi_buffer::Event::ExcerptsRemoved {
20081 ids,
20082 removed_buffer_ids,
20083 } => {
20084 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20085 let buffer = self.buffer.read(cx);
20086 self.registered_buffers
20087 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20088 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20089 cx.emit(EditorEvent::ExcerptsRemoved {
20090 ids: ids.clone(),
20091 removed_buffer_ids: removed_buffer_ids.clone(),
20092 });
20093 }
20094 multi_buffer::Event::ExcerptsEdited {
20095 excerpt_ids,
20096 buffer_ids,
20097 } => {
20098 self.display_map.update(cx, |map, cx| {
20099 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20100 });
20101 cx.emit(EditorEvent::ExcerptsEdited {
20102 ids: excerpt_ids.clone(),
20103 });
20104 }
20105 multi_buffer::Event::ExcerptsExpanded { ids } => {
20106 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20107 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20108 }
20109 multi_buffer::Event::Reparsed(buffer_id) => {
20110 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20111 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20112
20113 cx.emit(EditorEvent::Reparsed(*buffer_id));
20114 }
20115 multi_buffer::Event::DiffHunksToggled => {
20116 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20117 }
20118 multi_buffer::Event::LanguageChanged(buffer_id) => {
20119 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20120 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20121 cx.emit(EditorEvent::Reparsed(*buffer_id));
20122 cx.notify();
20123 }
20124 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20125 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20126 multi_buffer::Event::FileHandleChanged
20127 | multi_buffer::Event::Reloaded
20128 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20129 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20130 multi_buffer::Event::DiagnosticsUpdated => {
20131 self.update_diagnostics_state(window, cx);
20132 }
20133 _ => {}
20134 };
20135 }
20136
20137 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20138 if !self.diagnostics_enabled() {
20139 return;
20140 }
20141 self.refresh_active_diagnostics(cx);
20142 self.refresh_inline_diagnostics(true, window, cx);
20143 self.scrollbar_marker_state.dirty = true;
20144 cx.notify();
20145 }
20146
20147 pub fn start_temporary_diff_override(&mut self) {
20148 self.load_diff_task.take();
20149 self.temporary_diff_override = true;
20150 }
20151
20152 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20153 self.temporary_diff_override = false;
20154 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20155 self.buffer.update(cx, |buffer, cx| {
20156 buffer.set_all_diff_hunks_collapsed(cx);
20157 });
20158
20159 if let Some(project) = self.project.clone() {
20160 self.load_diff_task = Some(
20161 update_uncommitted_diff_for_buffer(
20162 cx.entity(),
20163 &project,
20164 self.buffer.read(cx).all_buffers(),
20165 self.buffer.clone(),
20166 cx,
20167 )
20168 .shared(),
20169 );
20170 }
20171 }
20172
20173 fn on_display_map_changed(
20174 &mut self,
20175 _: Entity<DisplayMap>,
20176 _: &mut Window,
20177 cx: &mut Context<Self>,
20178 ) {
20179 cx.notify();
20180 }
20181
20182 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20183 if self.diagnostics_enabled() {
20184 let new_severity = EditorSettings::get_global(cx)
20185 .diagnostics_max_severity
20186 .unwrap_or(DiagnosticSeverity::Hint);
20187 self.set_max_diagnostics_severity(new_severity, cx);
20188 }
20189 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20190 self.update_edit_prediction_settings(cx);
20191 self.refresh_edit_prediction(true, false, window, cx);
20192 self.refresh_inline_values(cx);
20193 self.refresh_inlay_hints(
20194 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20195 self.selections.newest_anchor().head(),
20196 &self.buffer.read(cx).snapshot(cx),
20197 cx,
20198 )),
20199 cx,
20200 );
20201
20202 let old_cursor_shape = self.cursor_shape;
20203
20204 {
20205 let editor_settings = EditorSettings::get_global(cx);
20206 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20207 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20208 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20209 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20210 }
20211
20212 if old_cursor_shape != self.cursor_shape {
20213 cx.emit(EditorEvent::CursorShapeChanged);
20214 }
20215
20216 let project_settings = ProjectSettings::get_global(cx);
20217 self.serialize_dirty_buffers =
20218 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20219
20220 if self.mode.is_full() {
20221 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20222 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20223 if self.show_inline_diagnostics != show_inline_diagnostics {
20224 self.show_inline_diagnostics = show_inline_diagnostics;
20225 self.refresh_inline_diagnostics(false, window, cx);
20226 }
20227
20228 if self.git_blame_inline_enabled != inline_blame_enabled {
20229 self.toggle_git_blame_inline_internal(false, window, cx);
20230 }
20231
20232 let minimap_settings = EditorSettings::get_global(cx).minimap;
20233 if self.minimap_visibility != MinimapVisibility::Disabled {
20234 if self.minimap_visibility.settings_visibility()
20235 != minimap_settings.minimap_enabled()
20236 {
20237 self.set_minimap_visibility(
20238 MinimapVisibility::for_mode(self.mode(), cx),
20239 window,
20240 cx,
20241 );
20242 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20243 minimap_entity.update(cx, |minimap_editor, cx| {
20244 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20245 })
20246 }
20247 }
20248 }
20249
20250 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20251 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20252 }) {
20253 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20254 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20255 }
20256 self.refresh_colors(false, None, window, cx);
20257 }
20258
20259 cx.notify();
20260 }
20261
20262 pub fn set_searchable(&mut self, searchable: bool) {
20263 self.searchable = searchable;
20264 }
20265
20266 pub fn searchable(&self) -> bool {
20267 self.searchable
20268 }
20269
20270 fn open_proposed_changes_editor(
20271 &mut self,
20272 _: &OpenProposedChangesEditor,
20273 window: &mut Window,
20274 cx: &mut Context<Self>,
20275 ) {
20276 let Some(workspace) = self.workspace() else {
20277 cx.propagate();
20278 return;
20279 };
20280
20281 let selections = self.selections.all::<usize>(cx);
20282 let multi_buffer = self.buffer.read(cx);
20283 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20284 let mut new_selections_by_buffer = HashMap::default();
20285 for selection in selections {
20286 for (buffer, range, _) in
20287 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20288 {
20289 let mut range = range.to_point(buffer);
20290 range.start.column = 0;
20291 range.end.column = buffer.line_len(range.end.row);
20292 new_selections_by_buffer
20293 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20294 .or_insert(Vec::new())
20295 .push(range)
20296 }
20297 }
20298
20299 let proposed_changes_buffers = new_selections_by_buffer
20300 .into_iter()
20301 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20302 .collect::<Vec<_>>();
20303 let proposed_changes_editor = cx.new(|cx| {
20304 ProposedChangesEditor::new(
20305 "Proposed changes",
20306 proposed_changes_buffers,
20307 self.project.clone(),
20308 window,
20309 cx,
20310 )
20311 });
20312
20313 window.defer(cx, move |window, cx| {
20314 workspace.update(cx, |workspace, cx| {
20315 workspace.active_pane().update(cx, |pane, cx| {
20316 pane.add_item(
20317 Box::new(proposed_changes_editor),
20318 true,
20319 true,
20320 None,
20321 window,
20322 cx,
20323 );
20324 });
20325 });
20326 });
20327 }
20328
20329 pub fn open_excerpts_in_split(
20330 &mut self,
20331 _: &OpenExcerptsSplit,
20332 window: &mut Window,
20333 cx: &mut Context<Self>,
20334 ) {
20335 self.open_excerpts_common(None, true, window, cx)
20336 }
20337
20338 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20339 self.open_excerpts_common(None, false, window, cx)
20340 }
20341
20342 fn open_excerpts_common(
20343 &mut self,
20344 jump_data: Option<JumpData>,
20345 split: bool,
20346 window: &mut Window,
20347 cx: &mut Context<Self>,
20348 ) {
20349 let Some(workspace) = self.workspace() else {
20350 cx.propagate();
20351 return;
20352 };
20353
20354 if self.buffer.read(cx).is_singleton() {
20355 cx.propagate();
20356 return;
20357 }
20358
20359 let mut new_selections_by_buffer = HashMap::default();
20360 match &jump_data {
20361 Some(JumpData::MultiBufferPoint {
20362 excerpt_id,
20363 position,
20364 anchor,
20365 line_offset_from_top,
20366 }) => {
20367 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20368 if let Some(buffer) = multi_buffer_snapshot
20369 .buffer_id_for_excerpt(*excerpt_id)
20370 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20371 {
20372 let buffer_snapshot = buffer.read(cx).snapshot();
20373 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20374 language::ToPoint::to_point(anchor, &buffer_snapshot)
20375 } else {
20376 buffer_snapshot.clip_point(*position, Bias::Left)
20377 };
20378 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20379 new_selections_by_buffer.insert(
20380 buffer,
20381 (
20382 vec![jump_to_offset..jump_to_offset],
20383 Some(*line_offset_from_top),
20384 ),
20385 );
20386 }
20387 }
20388 Some(JumpData::MultiBufferRow {
20389 row,
20390 line_offset_from_top,
20391 }) => {
20392 let point = MultiBufferPoint::new(row.0, 0);
20393 if let Some((buffer, buffer_point, _)) =
20394 self.buffer.read(cx).point_to_buffer_point(point, cx)
20395 {
20396 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20397 new_selections_by_buffer
20398 .entry(buffer)
20399 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20400 .0
20401 .push(buffer_offset..buffer_offset)
20402 }
20403 }
20404 None => {
20405 let selections = self.selections.all::<usize>(cx);
20406 let multi_buffer = self.buffer.read(cx);
20407 for selection in selections {
20408 for (snapshot, range, _, anchor) in multi_buffer
20409 .snapshot(cx)
20410 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20411 {
20412 if let Some(anchor) = anchor {
20413 // selection is in a deleted hunk
20414 let Some(buffer_id) = anchor.buffer_id else {
20415 continue;
20416 };
20417 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20418 continue;
20419 };
20420 let offset = text::ToOffset::to_offset(
20421 &anchor.text_anchor,
20422 &buffer_handle.read(cx).snapshot(),
20423 );
20424 let range = offset..offset;
20425 new_selections_by_buffer
20426 .entry(buffer_handle)
20427 .or_insert((Vec::new(), None))
20428 .0
20429 .push(range)
20430 } else {
20431 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20432 else {
20433 continue;
20434 };
20435 new_selections_by_buffer
20436 .entry(buffer_handle)
20437 .or_insert((Vec::new(), None))
20438 .0
20439 .push(range)
20440 }
20441 }
20442 }
20443 }
20444 }
20445
20446 new_selections_by_buffer
20447 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20448
20449 if new_selections_by_buffer.is_empty() {
20450 return;
20451 }
20452
20453 // We defer the pane interaction because we ourselves are a workspace item
20454 // and activating a new item causes the pane to call a method on us reentrantly,
20455 // which panics if we're on the stack.
20456 window.defer(cx, move |window, cx| {
20457 workspace.update(cx, |workspace, cx| {
20458 let pane = if split {
20459 workspace.adjacent_pane(window, cx)
20460 } else {
20461 workspace.active_pane().clone()
20462 };
20463
20464 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20465 let editor = buffer
20466 .read(cx)
20467 .file()
20468 .is_none()
20469 .then(|| {
20470 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20471 // so `workspace.open_project_item` will never find them, always opening a new editor.
20472 // Instead, we try to activate the existing editor in the pane first.
20473 let (editor, pane_item_index) =
20474 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20475 let editor = item.downcast::<Editor>()?;
20476 let singleton_buffer =
20477 editor.read(cx).buffer().read(cx).as_singleton()?;
20478 if singleton_buffer == buffer {
20479 Some((editor, i))
20480 } else {
20481 None
20482 }
20483 })?;
20484 pane.update(cx, |pane, cx| {
20485 pane.activate_item(pane_item_index, true, true, window, cx)
20486 });
20487 Some(editor)
20488 })
20489 .flatten()
20490 .unwrap_or_else(|| {
20491 workspace.open_project_item::<Self>(
20492 pane.clone(),
20493 buffer,
20494 true,
20495 true,
20496 window,
20497 cx,
20498 )
20499 });
20500
20501 editor.update(cx, |editor, cx| {
20502 let autoscroll = match scroll_offset {
20503 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20504 None => Autoscroll::newest(),
20505 };
20506 let nav_history = editor.nav_history.take();
20507 editor.change_selections(
20508 SelectionEffects::scroll(autoscroll),
20509 window,
20510 cx,
20511 |s| {
20512 s.select_ranges(ranges);
20513 },
20514 );
20515 editor.nav_history = nav_history;
20516 });
20517 }
20518 })
20519 });
20520 }
20521
20522 // For now, don't allow opening excerpts in buffers that aren't backed by
20523 // regular project files.
20524 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20525 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20526 }
20527
20528 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20529 let snapshot = self.buffer.read(cx).read(cx);
20530 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20531 Some(
20532 ranges
20533 .iter()
20534 .map(move |range| {
20535 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20536 })
20537 .collect(),
20538 )
20539 }
20540
20541 fn selection_replacement_ranges(
20542 &self,
20543 range: Range<OffsetUtf16>,
20544 cx: &mut App,
20545 ) -> Vec<Range<OffsetUtf16>> {
20546 let selections = self.selections.all::<OffsetUtf16>(cx);
20547 let newest_selection = selections
20548 .iter()
20549 .max_by_key(|selection| selection.id)
20550 .unwrap();
20551 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20552 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20553 let snapshot = self.buffer.read(cx).read(cx);
20554 selections
20555 .into_iter()
20556 .map(|mut selection| {
20557 selection.start.0 =
20558 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20559 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20560 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20561 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20562 })
20563 .collect()
20564 }
20565
20566 fn report_editor_event(
20567 &self,
20568 reported_event: ReportEditorEvent,
20569 file_extension: Option<String>,
20570 cx: &App,
20571 ) {
20572 if cfg!(any(test, feature = "test-support")) {
20573 return;
20574 }
20575
20576 let Some(project) = &self.project else { return };
20577
20578 // If None, we are in a file without an extension
20579 let file = self
20580 .buffer
20581 .read(cx)
20582 .as_singleton()
20583 .and_then(|b| b.read(cx).file());
20584 let file_extension = file_extension.or(file
20585 .as_ref()
20586 .and_then(|file| Path::new(file.file_name(cx)).extension())
20587 .and_then(|e| e.to_str())
20588 .map(|a| a.to_string()));
20589
20590 let vim_mode = vim_enabled(cx);
20591
20592 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20593 let copilot_enabled = edit_predictions_provider
20594 == language::language_settings::EditPredictionProvider::Copilot;
20595 let copilot_enabled_for_language = self
20596 .buffer
20597 .read(cx)
20598 .language_settings(cx)
20599 .show_edit_predictions;
20600
20601 let project = project.read(cx);
20602 let event_type = reported_event.event_type();
20603
20604 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20605 telemetry::event!(
20606 event_type,
20607 type = if auto_saved {"autosave"} else {"manual"},
20608 file_extension,
20609 vim_mode,
20610 copilot_enabled,
20611 copilot_enabled_for_language,
20612 edit_predictions_provider,
20613 is_via_ssh = project.is_via_ssh(),
20614 );
20615 } else {
20616 telemetry::event!(
20617 event_type,
20618 file_extension,
20619 vim_mode,
20620 copilot_enabled,
20621 copilot_enabled_for_language,
20622 edit_predictions_provider,
20623 is_via_ssh = project.is_via_ssh(),
20624 );
20625 };
20626 }
20627
20628 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20629 /// with each line being an array of {text, highlight} objects.
20630 fn copy_highlight_json(
20631 &mut self,
20632 _: &CopyHighlightJson,
20633 window: &mut Window,
20634 cx: &mut Context<Self>,
20635 ) {
20636 #[derive(Serialize)]
20637 struct Chunk<'a> {
20638 text: String,
20639 highlight: Option<&'a str>,
20640 }
20641
20642 let snapshot = self.buffer.read(cx).snapshot(cx);
20643 let range = self
20644 .selected_text_range(false, window, cx)
20645 .and_then(|selection| {
20646 if selection.range.is_empty() {
20647 None
20648 } else {
20649 Some(selection.range)
20650 }
20651 })
20652 .unwrap_or_else(|| 0..snapshot.len());
20653
20654 let chunks = snapshot.chunks(range, true);
20655 let mut lines = Vec::new();
20656 let mut line: VecDeque<Chunk> = VecDeque::new();
20657
20658 let Some(style) = self.style.as_ref() else {
20659 return;
20660 };
20661
20662 for chunk in chunks {
20663 let highlight = chunk
20664 .syntax_highlight_id
20665 .and_then(|id| id.name(&style.syntax));
20666 let mut chunk_lines = chunk.text.split('\n').peekable();
20667 while let Some(text) = chunk_lines.next() {
20668 let mut merged_with_last_token = false;
20669 if let Some(last_token) = line.back_mut() {
20670 if last_token.highlight == highlight {
20671 last_token.text.push_str(text);
20672 merged_with_last_token = true;
20673 }
20674 }
20675
20676 if !merged_with_last_token {
20677 line.push_back(Chunk {
20678 text: text.into(),
20679 highlight,
20680 });
20681 }
20682
20683 if chunk_lines.peek().is_some() {
20684 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20685 line.pop_front();
20686 }
20687 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20688 line.pop_back();
20689 }
20690
20691 lines.push(mem::take(&mut line));
20692 }
20693 }
20694 }
20695
20696 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20697 return;
20698 };
20699 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20700 }
20701
20702 pub fn open_context_menu(
20703 &mut self,
20704 _: &OpenContextMenu,
20705 window: &mut Window,
20706 cx: &mut Context<Self>,
20707 ) {
20708 self.request_autoscroll(Autoscroll::newest(), cx);
20709 let position = self.selections.newest_display(cx).start;
20710 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20711 }
20712
20713 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20714 &self.inlay_hint_cache
20715 }
20716
20717 pub fn replay_insert_event(
20718 &mut self,
20719 text: &str,
20720 relative_utf16_range: Option<Range<isize>>,
20721 window: &mut Window,
20722 cx: &mut Context<Self>,
20723 ) {
20724 if !self.input_enabled {
20725 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20726 return;
20727 }
20728 if let Some(relative_utf16_range) = relative_utf16_range {
20729 let selections = self.selections.all::<OffsetUtf16>(cx);
20730 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20731 let new_ranges = selections.into_iter().map(|range| {
20732 let start = OffsetUtf16(
20733 range
20734 .head()
20735 .0
20736 .saturating_add_signed(relative_utf16_range.start),
20737 );
20738 let end = OffsetUtf16(
20739 range
20740 .head()
20741 .0
20742 .saturating_add_signed(relative_utf16_range.end),
20743 );
20744 start..end
20745 });
20746 s.select_ranges(new_ranges);
20747 });
20748 }
20749
20750 self.handle_input(text, window, cx);
20751 }
20752
20753 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20754 let Some(provider) = self.semantics_provider.as_ref() else {
20755 return false;
20756 };
20757
20758 let mut supports = false;
20759 self.buffer().update(cx, |this, cx| {
20760 this.for_each_buffer(|buffer| {
20761 supports |= provider.supports_inlay_hints(buffer, cx);
20762 });
20763 });
20764
20765 supports
20766 }
20767
20768 pub fn is_focused(&self, window: &Window) -> bool {
20769 self.focus_handle.is_focused(window)
20770 }
20771
20772 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20773 cx.emit(EditorEvent::Focused);
20774
20775 if let Some(descendant) = self
20776 .last_focused_descendant
20777 .take()
20778 .and_then(|descendant| descendant.upgrade())
20779 {
20780 window.focus(&descendant);
20781 } else {
20782 if let Some(blame) = self.blame.as_ref() {
20783 blame.update(cx, GitBlame::focus)
20784 }
20785
20786 self.blink_manager.update(cx, BlinkManager::enable);
20787 self.show_cursor_names(window, cx);
20788 self.buffer.update(cx, |buffer, cx| {
20789 buffer.finalize_last_transaction(cx);
20790 if self.leader_id.is_none() {
20791 buffer.set_active_selections(
20792 &self.selections.disjoint_anchors(),
20793 self.selections.line_mode,
20794 self.cursor_shape,
20795 cx,
20796 );
20797 }
20798 });
20799 }
20800 }
20801
20802 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20803 cx.emit(EditorEvent::FocusedIn)
20804 }
20805
20806 fn handle_focus_out(
20807 &mut self,
20808 event: FocusOutEvent,
20809 _window: &mut Window,
20810 cx: &mut Context<Self>,
20811 ) {
20812 if event.blurred != self.focus_handle {
20813 self.last_focused_descendant = Some(event.blurred);
20814 }
20815 self.selection_drag_state = SelectionDragState::None;
20816 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20817 }
20818
20819 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20820 self.blink_manager.update(cx, BlinkManager::disable);
20821 self.buffer
20822 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20823
20824 if let Some(blame) = self.blame.as_ref() {
20825 blame.update(cx, GitBlame::blur)
20826 }
20827 if !self.hover_state.focused(window, cx) {
20828 hide_hover(self, cx);
20829 }
20830 if !self
20831 .context_menu
20832 .borrow()
20833 .as_ref()
20834 .is_some_and(|context_menu| context_menu.focused(window, cx))
20835 {
20836 self.hide_context_menu(window, cx);
20837 }
20838 self.discard_edit_prediction(false, cx);
20839 cx.emit(EditorEvent::Blurred);
20840 cx.notify();
20841 }
20842
20843 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20844 let mut pending: String = window
20845 .pending_input_keystrokes()
20846 .into_iter()
20847 .flatten()
20848 .filter_map(|keystroke| {
20849 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20850 keystroke.key_char.clone()
20851 } else {
20852 None
20853 }
20854 })
20855 .collect();
20856
20857 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20858 pending = "".to_string();
20859 }
20860
20861 let existing_pending = self
20862 .text_highlights::<PendingInput>(cx)
20863 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20864 if existing_pending.is_none() && pending.is_empty() {
20865 return;
20866 }
20867 let transaction =
20868 self.transact(window, cx, |this, window, cx| {
20869 let selections = this.selections.all::<usize>(cx);
20870 let edits = selections
20871 .iter()
20872 .map(|selection| (selection.end..selection.end, pending.clone()));
20873 this.edit(edits, cx);
20874 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20875 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20876 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20877 }));
20878 });
20879 if let Some(existing_ranges) = existing_pending {
20880 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20881 this.edit(edits, cx);
20882 }
20883 });
20884
20885 let snapshot = self.snapshot(window, cx);
20886 let ranges = self
20887 .selections
20888 .all::<usize>(cx)
20889 .into_iter()
20890 .map(|selection| {
20891 snapshot.buffer_snapshot.anchor_after(selection.end)
20892 ..snapshot
20893 .buffer_snapshot
20894 .anchor_before(selection.end + pending.len())
20895 })
20896 .collect();
20897
20898 if pending.is_empty() {
20899 self.clear_highlights::<PendingInput>(cx);
20900 } else {
20901 self.highlight_text::<PendingInput>(
20902 ranges,
20903 HighlightStyle {
20904 underline: Some(UnderlineStyle {
20905 thickness: px(1.),
20906 color: None,
20907 wavy: false,
20908 }),
20909 ..Default::default()
20910 },
20911 cx,
20912 );
20913 }
20914
20915 self.ime_transaction = self.ime_transaction.or(transaction);
20916 if let Some(transaction) = self.ime_transaction {
20917 self.buffer.update(cx, |buffer, cx| {
20918 buffer.group_until_transaction(transaction, cx);
20919 });
20920 }
20921
20922 if self.text_highlights::<PendingInput>(cx).is_none() {
20923 self.ime_transaction.take();
20924 }
20925 }
20926
20927 pub fn register_action_renderer(
20928 &mut self,
20929 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20930 ) -> Subscription {
20931 let id = self.next_editor_action_id.post_inc();
20932 self.editor_actions
20933 .borrow_mut()
20934 .insert(id, Box::new(listener));
20935
20936 let editor_actions = self.editor_actions.clone();
20937 Subscription::new(move || {
20938 editor_actions.borrow_mut().remove(&id);
20939 })
20940 }
20941
20942 pub fn register_action<A: Action>(
20943 &mut self,
20944 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20945 ) -> Subscription {
20946 let id = self.next_editor_action_id.post_inc();
20947 let listener = Arc::new(listener);
20948 self.editor_actions.borrow_mut().insert(
20949 id,
20950 Box::new(move |_, window, _| {
20951 let listener = listener.clone();
20952 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20953 let action = action.downcast_ref().unwrap();
20954 if phase == DispatchPhase::Bubble {
20955 listener(action, window, cx)
20956 }
20957 })
20958 }),
20959 );
20960
20961 let editor_actions = self.editor_actions.clone();
20962 Subscription::new(move || {
20963 editor_actions.borrow_mut().remove(&id);
20964 })
20965 }
20966
20967 pub fn file_header_size(&self) -> u32 {
20968 FILE_HEADER_HEIGHT
20969 }
20970
20971 pub fn restore(
20972 &mut self,
20973 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20974 window: &mut Window,
20975 cx: &mut Context<Self>,
20976 ) {
20977 let workspace = self.workspace();
20978 let project = self.project.as_ref();
20979 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20980 let mut tasks = Vec::new();
20981 for (buffer_id, changes) in revert_changes {
20982 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20983 buffer.update(cx, |buffer, cx| {
20984 buffer.edit(
20985 changes
20986 .into_iter()
20987 .map(|(range, text)| (range, text.to_string())),
20988 None,
20989 cx,
20990 );
20991 });
20992
20993 if let Some(project) =
20994 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20995 {
20996 project.update(cx, |project, cx| {
20997 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20998 })
20999 }
21000 }
21001 }
21002 tasks
21003 });
21004 cx.spawn_in(window, async move |_, cx| {
21005 for (buffer, task) in save_tasks {
21006 let result = task.await;
21007 if result.is_err() {
21008 let Some(path) = buffer
21009 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21010 .ok()
21011 else {
21012 continue;
21013 };
21014 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21015 let Some(task) = cx
21016 .update_window_entity(&workspace, |workspace, window, cx| {
21017 workspace
21018 .open_path_preview(path, None, false, false, false, window, cx)
21019 })
21020 .ok()
21021 else {
21022 continue;
21023 };
21024 task.await.log_err();
21025 }
21026 }
21027 }
21028 })
21029 .detach();
21030 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21031 selections.refresh()
21032 });
21033 }
21034
21035 pub fn to_pixel_point(
21036 &self,
21037 source: multi_buffer::Anchor,
21038 editor_snapshot: &EditorSnapshot,
21039 window: &mut Window,
21040 ) -> Option<gpui::Point<Pixels>> {
21041 let source_point = source.to_display_point(editor_snapshot);
21042 self.display_to_pixel_point(source_point, editor_snapshot, window)
21043 }
21044
21045 pub fn display_to_pixel_point(
21046 &self,
21047 source: DisplayPoint,
21048 editor_snapshot: &EditorSnapshot,
21049 window: &mut Window,
21050 ) -> Option<gpui::Point<Pixels>> {
21051 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21052 let text_layout_details = self.text_layout_details(window);
21053 let scroll_top = text_layout_details
21054 .scroll_anchor
21055 .scroll_position(editor_snapshot)
21056 .y;
21057
21058 if source.row().as_f32() < scroll_top.floor() {
21059 return None;
21060 }
21061 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21062 let source_y = line_height * (source.row().as_f32() - scroll_top);
21063 Some(gpui::Point::new(source_x, source_y))
21064 }
21065
21066 pub fn has_visible_completions_menu(&self) -> bool {
21067 !self.edit_prediction_preview_is_active()
21068 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
21069 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21070 })
21071 }
21072
21073 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21074 if self.mode.is_minimap() {
21075 return;
21076 }
21077 self.addons
21078 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21079 }
21080
21081 pub fn unregister_addon<T: Addon>(&mut self) {
21082 self.addons.remove(&std::any::TypeId::of::<T>());
21083 }
21084
21085 pub fn addon<T: Addon>(&self) -> Option<&T> {
21086 let type_id = std::any::TypeId::of::<T>();
21087 self.addons
21088 .get(&type_id)
21089 .and_then(|item| item.to_any().downcast_ref::<T>())
21090 }
21091
21092 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21093 let type_id = std::any::TypeId::of::<T>();
21094 self.addons
21095 .get_mut(&type_id)
21096 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21097 }
21098
21099 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21100 let text_layout_details = self.text_layout_details(window);
21101 let style = &text_layout_details.editor_style;
21102 let font_id = window.text_system().resolve_font(&style.text.font());
21103 let font_size = style.text.font_size.to_pixels(window.rem_size());
21104 let line_height = style.text.line_height_in_pixels(window.rem_size());
21105 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21106 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21107
21108 CharacterDimensions {
21109 em_width,
21110 em_advance,
21111 line_height,
21112 }
21113 }
21114
21115 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21116 self.load_diff_task.clone()
21117 }
21118
21119 fn read_metadata_from_db(
21120 &mut self,
21121 item_id: u64,
21122 workspace_id: WorkspaceId,
21123 window: &mut Window,
21124 cx: &mut Context<Editor>,
21125 ) {
21126 if self.is_singleton(cx)
21127 && !self.mode.is_minimap()
21128 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21129 {
21130 let buffer_snapshot = OnceCell::new();
21131
21132 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21133 if !folds.is_empty() {
21134 let snapshot =
21135 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21136 self.fold_ranges(
21137 folds
21138 .into_iter()
21139 .map(|(start, end)| {
21140 snapshot.clip_offset(start, Bias::Left)
21141 ..snapshot.clip_offset(end, Bias::Right)
21142 })
21143 .collect(),
21144 false,
21145 window,
21146 cx,
21147 );
21148 }
21149 }
21150
21151 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21152 if !selections.is_empty() {
21153 let snapshot =
21154 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21155 // skip adding the initial selection to selection history
21156 self.selection_history.mode = SelectionHistoryMode::Skipping;
21157 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21158 s.select_ranges(selections.into_iter().map(|(start, end)| {
21159 snapshot.clip_offset(start, Bias::Left)
21160 ..snapshot.clip_offset(end, Bias::Right)
21161 }));
21162 });
21163 self.selection_history.mode = SelectionHistoryMode::Normal;
21164 }
21165 };
21166 }
21167
21168 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21169 }
21170
21171 fn update_lsp_data(
21172 &mut self,
21173 ignore_cache: bool,
21174 for_buffer: Option<BufferId>,
21175 window: &mut Window,
21176 cx: &mut Context<'_, Self>,
21177 ) {
21178 self.pull_diagnostics(for_buffer, window, cx);
21179 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21180 }
21181}
21182
21183fn vim_enabled(cx: &App) -> bool {
21184 cx.global::<SettingsStore>()
21185 .raw_user_settings()
21186 .get("vim_mode")
21187 == Some(&serde_json::Value::Bool(true))
21188}
21189
21190fn process_completion_for_edit(
21191 completion: &Completion,
21192 intent: CompletionIntent,
21193 buffer: &Entity<Buffer>,
21194 cursor_position: &text::Anchor,
21195 cx: &mut Context<Editor>,
21196) -> CompletionEdit {
21197 let buffer = buffer.read(cx);
21198 let buffer_snapshot = buffer.snapshot();
21199 let (snippet, new_text) = if completion.is_snippet() {
21200 // Workaround for typescript language server issues so that methods don't expand within
21201 // strings and functions with type expressions. The previous point is used because the query
21202 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21203 let mut snippet_source = completion.new_text.clone();
21204 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21205 previous_point.column = previous_point.column.saturating_sub(1);
21206 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21207 if scope.prefers_label_for_snippet_in_completion() {
21208 if let Some(label) = completion.label() {
21209 if matches!(
21210 completion.kind(),
21211 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21212 ) {
21213 snippet_source = label;
21214 }
21215 }
21216 }
21217 }
21218 match Snippet::parse(&snippet_source).log_err() {
21219 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21220 None => (None, completion.new_text.clone()),
21221 }
21222 } else {
21223 (None, completion.new_text.clone())
21224 };
21225
21226 let mut range_to_replace = {
21227 let replace_range = &completion.replace_range;
21228 if let CompletionSource::Lsp {
21229 insert_range: Some(insert_range),
21230 ..
21231 } = &completion.source
21232 {
21233 debug_assert_eq!(
21234 insert_range.start, replace_range.start,
21235 "insert_range and replace_range should start at the same position"
21236 );
21237 debug_assert!(
21238 insert_range
21239 .start
21240 .cmp(&cursor_position, &buffer_snapshot)
21241 .is_le(),
21242 "insert_range should start before or at cursor position"
21243 );
21244 debug_assert!(
21245 replace_range
21246 .start
21247 .cmp(&cursor_position, &buffer_snapshot)
21248 .is_le(),
21249 "replace_range should start before or at cursor position"
21250 );
21251
21252 let should_replace = match intent {
21253 CompletionIntent::CompleteWithInsert => false,
21254 CompletionIntent::CompleteWithReplace => true,
21255 CompletionIntent::Complete | CompletionIntent::Compose => {
21256 let insert_mode =
21257 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21258 .completions
21259 .lsp_insert_mode;
21260 match insert_mode {
21261 LspInsertMode::Insert => false,
21262 LspInsertMode::Replace => true,
21263 LspInsertMode::ReplaceSubsequence => {
21264 let mut text_to_replace = buffer.chars_for_range(
21265 buffer.anchor_before(replace_range.start)
21266 ..buffer.anchor_after(replace_range.end),
21267 );
21268 let mut current_needle = text_to_replace.next();
21269 for haystack_ch in completion.label.text.chars() {
21270 if let Some(needle_ch) = current_needle {
21271 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21272 current_needle = text_to_replace.next();
21273 }
21274 }
21275 }
21276 current_needle.is_none()
21277 }
21278 LspInsertMode::ReplaceSuffix => {
21279 if replace_range
21280 .end
21281 .cmp(&cursor_position, &buffer_snapshot)
21282 .is_gt()
21283 {
21284 let range_after_cursor = *cursor_position..replace_range.end;
21285 let text_after_cursor = buffer
21286 .text_for_range(
21287 buffer.anchor_before(range_after_cursor.start)
21288 ..buffer.anchor_after(range_after_cursor.end),
21289 )
21290 .collect::<String>()
21291 .to_ascii_lowercase();
21292 completion
21293 .label
21294 .text
21295 .to_ascii_lowercase()
21296 .ends_with(&text_after_cursor)
21297 } else {
21298 true
21299 }
21300 }
21301 }
21302 }
21303 };
21304
21305 if should_replace {
21306 replace_range.clone()
21307 } else {
21308 insert_range.clone()
21309 }
21310 } else {
21311 replace_range.clone()
21312 }
21313 };
21314
21315 if range_to_replace
21316 .end
21317 .cmp(&cursor_position, &buffer_snapshot)
21318 .is_lt()
21319 {
21320 range_to_replace.end = *cursor_position;
21321 }
21322
21323 CompletionEdit {
21324 new_text,
21325 replace_range: range_to_replace.to_offset(&buffer),
21326 snippet,
21327 }
21328}
21329
21330struct CompletionEdit {
21331 new_text: String,
21332 replace_range: Range<usize>,
21333 snippet: Option<Snippet>,
21334}
21335
21336fn insert_extra_newline_brackets(
21337 buffer: &MultiBufferSnapshot,
21338 range: Range<usize>,
21339 language: &language::LanguageScope,
21340) -> bool {
21341 let leading_whitespace_len = buffer
21342 .reversed_chars_at(range.start)
21343 .take_while(|c| c.is_whitespace() && *c != '\n')
21344 .map(|c| c.len_utf8())
21345 .sum::<usize>();
21346 let trailing_whitespace_len = buffer
21347 .chars_at(range.end)
21348 .take_while(|c| c.is_whitespace() && *c != '\n')
21349 .map(|c| c.len_utf8())
21350 .sum::<usize>();
21351 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21352
21353 language.brackets().any(|(pair, enabled)| {
21354 let pair_start = pair.start.trim_end();
21355 let pair_end = pair.end.trim_start();
21356
21357 enabled
21358 && pair.newline
21359 && buffer.contains_str_at(range.end, pair_end)
21360 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21361 })
21362}
21363
21364fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21365 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21366 [(buffer, range, _)] => (*buffer, range.clone()),
21367 _ => return false,
21368 };
21369 let pair = {
21370 let mut result: Option<BracketMatch> = None;
21371
21372 for pair in buffer
21373 .all_bracket_ranges(range.clone())
21374 .filter(move |pair| {
21375 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21376 })
21377 {
21378 let len = pair.close_range.end - pair.open_range.start;
21379
21380 if let Some(existing) = &result {
21381 let existing_len = existing.close_range.end - existing.open_range.start;
21382 if len > existing_len {
21383 continue;
21384 }
21385 }
21386
21387 result = Some(pair);
21388 }
21389
21390 result
21391 };
21392 let Some(pair) = pair else {
21393 return false;
21394 };
21395 pair.newline_only
21396 && buffer
21397 .chars_for_range(pair.open_range.end..range.start)
21398 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21399 .all(|c| c.is_whitespace() && c != '\n')
21400}
21401
21402fn update_uncommitted_diff_for_buffer(
21403 editor: Entity<Editor>,
21404 project: &Entity<Project>,
21405 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21406 buffer: Entity<MultiBuffer>,
21407 cx: &mut App,
21408) -> Task<()> {
21409 let mut tasks = Vec::new();
21410 project.update(cx, |project, cx| {
21411 for buffer in buffers {
21412 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21413 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21414 }
21415 }
21416 });
21417 cx.spawn(async move |cx| {
21418 let diffs = future::join_all(tasks).await;
21419 if editor
21420 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21421 .unwrap_or(false)
21422 {
21423 return;
21424 }
21425
21426 buffer
21427 .update(cx, |buffer, cx| {
21428 for diff in diffs.into_iter().flatten() {
21429 buffer.add_diff(diff, cx);
21430 }
21431 })
21432 .ok();
21433 })
21434}
21435
21436fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21437 let tab_size = tab_size.get() as usize;
21438 let mut width = offset;
21439
21440 for ch in text.chars() {
21441 width += if ch == '\t' {
21442 tab_size - (width % tab_size)
21443 } else {
21444 1
21445 };
21446 }
21447
21448 width - offset
21449}
21450
21451#[cfg(test)]
21452mod tests {
21453 use super::*;
21454
21455 #[test]
21456 fn test_string_size_with_expanded_tabs() {
21457 let nz = |val| NonZeroU32::new(val).unwrap();
21458 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21459 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21460 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21461 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21462 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21463 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21464 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21465 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21466 }
21467}
21468
21469/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21470struct WordBreakingTokenizer<'a> {
21471 input: &'a str,
21472}
21473
21474impl<'a> WordBreakingTokenizer<'a> {
21475 fn new(input: &'a str) -> Self {
21476 Self { input }
21477 }
21478}
21479
21480fn is_char_ideographic(ch: char) -> bool {
21481 use unicode_script::Script::*;
21482 use unicode_script::UnicodeScript;
21483 matches!(ch.script(), Han | Tangut | Yi)
21484}
21485
21486fn is_grapheme_ideographic(text: &str) -> bool {
21487 text.chars().any(is_char_ideographic)
21488}
21489
21490fn is_grapheme_whitespace(text: &str) -> bool {
21491 text.chars().any(|x| x.is_whitespace())
21492}
21493
21494fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21495 text.chars().next().map_or(false, |ch| {
21496 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21497 })
21498}
21499
21500#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21501enum WordBreakToken<'a> {
21502 Word { token: &'a str, grapheme_len: usize },
21503 InlineWhitespace { token: &'a str, grapheme_len: usize },
21504 Newline,
21505}
21506
21507impl<'a> Iterator for WordBreakingTokenizer<'a> {
21508 /// Yields a span, the count of graphemes in the token, and whether it was
21509 /// whitespace. Note that it also breaks at word boundaries.
21510 type Item = WordBreakToken<'a>;
21511
21512 fn next(&mut self) -> Option<Self::Item> {
21513 use unicode_segmentation::UnicodeSegmentation;
21514 if self.input.is_empty() {
21515 return None;
21516 }
21517
21518 let mut iter = self.input.graphemes(true).peekable();
21519 let mut offset = 0;
21520 let mut grapheme_len = 0;
21521 if let Some(first_grapheme) = iter.next() {
21522 let is_newline = first_grapheme == "\n";
21523 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21524 offset += first_grapheme.len();
21525 grapheme_len += 1;
21526 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21527 if let Some(grapheme) = iter.peek().copied() {
21528 if should_stay_with_preceding_ideograph(grapheme) {
21529 offset += grapheme.len();
21530 grapheme_len += 1;
21531 }
21532 }
21533 } else {
21534 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21535 let mut next_word_bound = words.peek().copied();
21536 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21537 next_word_bound = words.next();
21538 }
21539 while let Some(grapheme) = iter.peek().copied() {
21540 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21541 break;
21542 };
21543 if is_grapheme_whitespace(grapheme) != is_whitespace
21544 || (grapheme == "\n") != is_newline
21545 {
21546 break;
21547 };
21548 offset += grapheme.len();
21549 grapheme_len += 1;
21550 iter.next();
21551 }
21552 }
21553 let token = &self.input[..offset];
21554 self.input = &self.input[offset..];
21555 if token == "\n" {
21556 Some(WordBreakToken::Newline)
21557 } else if is_whitespace {
21558 Some(WordBreakToken::InlineWhitespace {
21559 token,
21560 grapheme_len,
21561 })
21562 } else {
21563 Some(WordBreakToken::Word {
21564 token,
21565 grapheme_len,
21566 })
21567 }
21568 } else {
21569 None
21570 }
21571 }
21572}
21573
21574#[test]
21575fn test_word_breaking_tokenizer() {
21576 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21577 ("", &[]),
21578 (" ", &[whitespace(" ", 2)]),
21579 ("Ʒ", &[word("Ʒ", 1)]),
21580 ("Ǽ", &[word("Ǽ", 1)]),
21581 ("⋑", &[word("⋑", 1)]),
21582 ("⋑⋑", &[word("⋑⋑", 2)]),
21583 (
21584 "原理,进而",
21585 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21586 ),
21587 (
21588 "hello world",
21589 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21590 ),
21591 (
21592 "hello, world",
21593 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21594 ),
21595 (
21596 " hello world",
21597 &[
21598 whitespace(" ", 2),
21599 word("hello", 5),
21600 whitespace(" ", 1),
21601 word("world", 5),
21602 ],
21603 ),
21604 (
21605 "这是什么 \n 钢笔",
21606 &[
21607 word("这", 1),
21608 word("是", 1),
21609 word("什", 1),
21610 word("么", 1),
21611 whitespace(" ", 1),
21612 newline(),
21613 whitespace(" ", 1),
21614 word("钢", 1),
21615 word("笔", 1),
21616 ],
21617 ),
21618 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21619 ];
21620
21621 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21622 WordBreakToken::Word {
21623 token,
21624 grapheme_len,
21625 }
21626 }
21627
21628 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21629 WordBreakToken::InlineWhitespace {
21630 token,
21631 grapheme_len,
21632 }
21633 }
21634
21635 fn newline() -> WordBreakToken<'static> {
21636 WordBreakToken::Newline
21637 }
21638
21639 for (input, result) in tests {
21640 assert_eq!(
21641 WordBreakingTokenizer::new(input)
21642 .collect::<Vec<_>>()
21643 .as_slice(),
21644 *result,
21645 );
21646 }
21647}
21648
21649fn wrap_with_prefix(
21650 first_line_prefix: String,
21651 subsequent_lines_prefix: String,
21652 unwrapped_text: String,
21653 wrap_column: usize,
21654 tab_size: NonZeroU32,
21655 preserve_existing_whitespace: bool,
21656) -> String {
21657 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21658 let subsequent_lines_prefix_len =
21659 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21660 let mut wrapped_text = String::new();
21661 let mut current_line = first_line_prefix.clone();
21662 let mut is_first_line = true;
21663
21664 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21665 let mut current_line_len = first_line_prefix_len;
21666 let mut in_whitespace = false;
21667 for token in tokenizer {
21668 let have_preceding_whitespace = in_whitespace;
21669 match token {
21670 WordBreakToken::Word {
21671 token,
21672 grapheme_len,
21673 } => {
21674 in_whitespace = false;
21675 let current_prefix_len = if is_first_line {
21676 first_line_prefix_len
21677 } else {
21678 subsequent_lines_prefix_len
21679 };
21680 if current_line_len + grapheme_len > wrap_column
21681 && current_line_len != current_prefix_len
21682 {
21683 wrapped_text.push_str(current_line.trim_end());
21684 wrapped_text.push('\n');
21685 is_first_line = false;
21686 current_line = subsequent_lines_prefix.clone();
21687 current_line_len = subsequent_lines_prefix_len;
21688 }
21689 current_line.push_str(token);
21690 current_line_len += grapheme_len;
21691 }
21692 WordBreakToken::InlineWhitespace {
21693 mut token,
21694 mut grapheme_len,
21695 } => {
21696 in_whitespace = true;
21697 if have_preceding_whitespace && !preserve_existing_whitespace {
21698 continue;
21699 }
21700 if !preserve_existing_whitespace {
21701 token = " ";
21702 grapheme_len = 1;
21703 }
21704 let current_prefix_len = if is_first_line {
21705 first_line_prefix_len
21706 } else {
21707 subsequent_lines_prefix_len
21708 };
21709 if current_line_len + grapheme_len > wrap_column {
21710 wrapped_text.push_str(current_line.trim_end());
21711 wrapped_text.push('\n');
21712 is_first_line = false;
21713 current_line = subsequent_lines_prefix.clone();
21714 current_line_len = subsequent_lines_prefix_len;
21715 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21716 current_line.push_str(token);
21717 current_line_len += grapheme_len;
21718 }
21719 }
21720 WordBreakToken::Newline => {
21721 in_whitespace = true;
21722 let current_prefix_len = if is_first_line {
21723 first_line_prefix_len
21724 } else {
21725 subsequent_lines_prefix_len
21726 };
21727 if preserve_existing_whitespace {
21728 wrapped_text.push_str(current_line.trim_end());
21729 wrapped_text.push('\n');
21730 is_first_line = false;
21731 current_line = subsequent_lines_prefix.clone();
21732 current_line_len = subsequent_lines_prefix_len;
21733 } else if have_preceding_whitespace {
21734 continue;
21735 } else if current_line_len + 1 > wrap_column
21736 && current_line_len != current_prefix_len
21737 {
21738 wrapped_text.push_str(current_line.trim_end());
21739 wrapped_text.push('\n');
21740 is_first_line = false;
21741 current_line = subsequent_lines_prefix.clone();
21742 current_line_len = subsequent_lines_prefix_len;
21743 } else if current_line_len != current_prefix_len {
21744 current_line.push(' ');
21745 current_line_len += 1;
21746 }
21747 }
21748 }
21749 }
21750
21751 if !current_line.is_empty() {
21752 wrapped_text.push_str(¤t_line);
21753 }
21754 wrapped_text
21755}
21756
21757#[test]
21758fn test_wrap_with_prefix() {
21759 assert_eq!(
21760 wrap_with_prefix(
21761 "# ".to_string(),
21762 "# ".to_string(),
21763 "abcdefg".to_string(),
21764 4,
21765 NonZeroU32::new(4).unwrap(),
21766 false,
21767 ),
21768 "# abcdefg"
21769 );
21770 assert_eq!(
21771 wrap_with_prefix(
21772 "".to_string(),
21773 "".to_string(),
21774 "\thello world".to_string(),
21775 8,
21776 NonZeroU32::new(4).unwrap(),
21777 false,
21778 ),
21779 "hello\nworld"
21780 );
21781 assert_eq!(
21782 wrap_with_prefix(
21783 "// ".to_string(),
21784 "// ".to_string(),
21785 "xx \nyy zz aa bb cc".to_string(),
21786 12,
21787 NonZeroU32::new(4).unwrap(),
21788 false,
21789 ),
21790 "// xx yy zz\n// aa bb cc"
21791 );
21792 assert_eq!(
21793 wrap_with_prefix(
21794 String::new(),
21795 String::new(),
21796 "这是什么 \n 钢笔".to_string(),
21797 3,
21798 NonZeroU32::new(4).unwrap(),
21799 false,
21800 ),
21801 "这是什\n么 钢\n笔"
21802 );
21803}
21804
21805pub trait CollaborationHub {
21806 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21807 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21808 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21809}
21810
21811impl CollaborationHub for Entity<Project> {
21812 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21813 self.read(cx).collaborators()
21814 }
21815
21816 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21817 self.read(cx).user_store().read(cx).participant_indices()
21818 }
21819
21820 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21821 let this = self.read(cx);
21822 let user_ids = this.collaborators().values().map(|c| c.user_id);
21823 this.user_store().read(cx).participant_names(user_ids, cx)
21824 }
21825}
21826
21827pub trait SemanticsProvider {
21828 fn hover(
21829 &self,
21830 buffer: &Entity<Buffer>,
21831 position: text::Anchor,
21832 cx: &mut App,
21833 ) -> Option<Task<Vec<project::Hover>>>;
21834
21835 fn inline_values(
21836 &self,
21837 buffer_handle: Entity<Buffer>,
21838 range: Range<text::Anchor>,
21839 cx: &mut App,
21840 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21841
21842 fn inlay_hints(
21843 &self,
21844 buffer_handle: Entity<Buffer>,
21845 range: Range<text::Anchor>,
21846 cx: &mut App,
21847 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21848
21849 fn resolve_inlay_hint(
21850 &self,
21851 hint: InlayHint,
21852 buffer_handle: Entity<Buffer>,
21853 server_id: LanguageServerId,
21854 cx: &mut App,
21855 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21856
21857 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21858
21859 fn document_highlights(
21860 &self,
21861 buffer: &Entity<Buffer>,
21862 position: text::Anchor,
21863 cx: &mut App,
21864 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21865
21866 fn definitions(
21867 &self,
21868 buffer: &Entity<Buffer>,
21869 position: text::Anchor,
21870 kind: GotoDefinitionKind,
21871 cx: &mut App,
21872 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21873
21874 fn range_for_rename(
21875 &self,
21876 buffer: &Entity<Buffer>,
21877 position: text::Anchor,
21878 cx: &mut App,
21879 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21880
21881 fn perform_rename(
21882 &self,
21883 buffer: &Entity<Buffer>,
21884 position: text::Anchor,
21885 new_name: String,
21886 cx: &mut App,
21887 ) -> Option<Task<Result<ProjectTransaction>>>;
21888}
21889
21890pub trait CompletionProvider {
21891 fn completions(
21892 &self,
21893 excerpt_id: ExcerptId,
21894 buffer: &Entity<Buffer>,
21895 buffer_position: text::Anchor,
21896 trigger: CompletionContext,
21897 window: &mut Window,
21898 cx: &mut Context<Editor>,
21899 ) -> Task<Result<Vec<CompletionResponse>>>;
21900
21901 fn resolve_completions(
21902 &self,
21903 _buffer: Entity<Buffer>,
21904 _completion_indices: Vec<usize>,
21905 _completions: Rc<RefCell<Box<[Completion]>>>,
21906 _cx: &mut Context<Editor>,
21907 ) -> Task<Result<bool>> {
21908 Task::ready(Ok(false))
21909 }
21910
21911 fn apply_additional_edits_for_completion(
21912 &self,
21913 _buffer: Entity<Buffer>,
21914 _completions: Rc<RefCell<Box<[Completion]>>>,
21915 _completion_index: usize,
21916 _push_to_history: bool,
21917 _cx: &mut Context<Editor>,
21918 ) -> Task<Result<Option<language::Transaction>>> {
21919 Task::ready(Ok(None))
21920 }
21921
21922 fn is_completion_trigger(
21923 &self,
21924 buffer: &Entity<Buffer>,
21925 position: language::Anchor,
21926 text: &str,
21927 trigger_in_words: bool,
21928 menu_is_open: bool,
21929 cx: &mut Context<Editor>,
21930 ) -> bool;
21931
21932 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21933
21934 fn sort_completions(&self) -> bool {
21935 true
21936 }
21937
21938 fn filter_completions(&self) -> bool {
21939 true
21940 }
21941}
21942
21943pub trait CodeActionProvider {
21944 fn id(&self) -> Arc<str>;
21945
21946 fn code_actions(
21947 &self,
21948 buffer: &Entity<Buffer>,
21949 range: Range<text::Anchor>,
21950 window: &mut Window,
21951 cx: &mut App,
21952 ) -> Task<Result<Vec<CodeAction>>>;
21953
21954 fn apply_code_action(
21955 &self,
21956 buffer_handle: Entity<Buffer>,
21957 action: CodeAction,
21958 excerpt_id: ExcerptId,
21959 push_to_history: bool,
21960 window: &mut Window,
21961 cx: &mut App,
21962 ) -> Task<Result<ProjectTransaction>>;
21963}
21964
21965impl CodeActionProvider for Entity<Project> {
21966 fn id(&self) -> Arc<str> {
21967 "project".into()
21968 }
21969
21970 fn code_actions(
21971 &self,
21972 buffer: &Entity<Buffer>,
21973 range: Range<text::Anchor>,
21974 _window: &mut Window,
21975 cx: &mut App,
21976 ) -> Task<Result<Vec<CodeAction>>> {
21977 self.update(cx, |project, cx| {
21978 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21979 let code_actions = project.code_actions(buffer, range, None, cx);
21980 cx.background_spawn(async move {
21981 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21982 Ok(code_lens_actions
21983 .context("code lens fetch")?
21984 .into_iter()
21985 .chain(code_actions.context("code action fetch")?)
21986 .collect())
21987 })
21988 })
21989 }
21990
21991 fn apply_code_action(
21992 &self,
21993 buffer_handle: Entity<Buffer>,
21994 action: CodeAction,
21995 _excerpt_id: ExcerptId,
21996 push_to_history: bool,
21997 _window: &mut Window,
21998 cx: &mut App,
21999 ) -> Task<Result<ProjectTransaction>> {
22000 self.update(cx, |project, cx| {
22001 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22002 })
22003 }
22004}
22005
22006fn snippet_completions(
22007 project: &Project,
22008 buffer: &Entity<Buffer>,
22009 buffer_position: text::Anchor,
22010 cx: &mut App,
22011) -> Task<Result<CompletionResponse>> {
22012 let languages = buffer.read(cx).languages_at(buffer_position);
22013 let snippet_store = project.snippets().read(cx);
22014
22015 let scopes: Vec<_> = languages
22016 .iter()
22017 .filter_map(|language| {
22018 let language_name = language.lsp_id();
22019 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22020
22021 if snippets.is_empty() {
22022 None
22023 } else {
22024 Some((language.default_scope(), snippets))
22025 }
22026 })
22027 .collect();
22028
22029 if scopes.is_empty() {
22030 return Task::ready(Ok(CompletionResponse {
22031 completions: vec![],
22032 is_incomplete: false,
22033 }));
22034 }
22035
22036 let snapshot = buffer.read(cx).text_snapshot();
22037 let chars: String = snapshot
22038 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22039 .collect();
22040 let executor = cx.background_executor().clone();
22041
22042 cx.background_spawn(async move {
22043 let mut is_incomplete = false;
22044 let mut completions: Vec<Completion> = Vec::new();
22045 for (scope, snippets) in scopes.into_iter() {
22046 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22047 let mut last_word = chars
22048 .chars()
22049 .take_while(|c| classifier.is_word(*c))
22050 .collect::<String>();
22051 last_word = last_word.chars().rev().collect();
22052
22053 if last_word.is_empty() {
22054 return Ok(CompletionResponse {
22055 completions: vec![],
22056 is_incomplete: true,
22057 });
22058 }
22059
22060 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22061 let to_lsp = |point: &text::Anchor| {
22062 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22063 point_to_lsp(end)
22064 };
22065 let lsp_end = to_lsp(&buffer_position);
22066
22067 let candidates = snippets
22068 .iter()
22069 .enumerate()
22070 .flat_map(|(ix, snippet)| {
22071 snippet
22072 .prefix
22073 .iter()
22074 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
22075 })
22076 .collect::<Vec<StringMatchCandidate>>();
22077
22078 const MAX_RESULTS: usize = 100;
22079 let mut matches = fuzzy::match_strings(
22080 &candidates,
22081 &last_word,
22082 last_word.chars().any(|c| c.is_uppercase()),
22083 true,
22084 MAX_RESULTS,
22085 &Default::default(),
22086 executor.clone(),
22087 )
22088 .await;
22089
22090 if matches.len() >= MAX_RESULTS {
22091 is_incomplete = true;
22092 }
22093
22094 // Remove all candidates where the query's start does not match the start of any word in the candidate
22095 if let Some(query_start) = last_word.chars().next() {
22096 matches.retain(|string_match| {
22097 split_words(&string_match.string).any(|word| {
22098 // Check that the first codepoint of the word as lowercase matches the first
22099 // codepoint of the query as lowercase
22100 word.chars()
22101 .flat_map(|codepoint| codepoint.to_lowercase())
22102 .zip(query_start.to_lowercase())
22103 .all(|(word_cp, query_cp)| word_cp == query_cp)
22104 })
22105 });
22106 }
22107
22108 let matched_strings = matches
22109 .into_iter()
22110 .map(|m| m.string)
22111 .collect::<HashSet<_>>();
22112
22113 completions.extend(snippets.iter().filter_map(|snippet| {
22114 let matching_prefix = snippet
22115 .prefix
22116 .iter()
22117 .find(|prefix| matched_strings.contains(*prefix))?;
22118 let start = as_offset - last_word.len();
22119 let start = snapshot.anchor_before(start);
22120 let range = start..buffer_position;
22121 let lsp_start = to_lsp(&start);
22122 let lsp_range = lsp::Range {
22123 start: lsp_start,
22124 end: lsp_end,
22125 };
22126 Some(Completion {
22127 replace_range: range,
22128 new_text: snippet.body.clone(),
22129 source: CompletionSource::Lsp {
22130 insert_range: None,
22131 server_id: LanguageServerId(usize::MAX),
22132 resolved: true,
22133 lsp_completion: Box::new(lsp::CompletionItem {
22134 label: snippet.prefix.first().unwrap().clone(),
22135 kind: Some(CompletionItemKind::SNIPPET),
22136 label_details: snippet.description.as_ref().map(|description| {
22137 lsp::CompletionItemLabelDetails {
22138 detail: Some(description.clone()),
22139 description: None,
22140 }
22141 }),
22142 insert_text_format: Some(InsertTextFormat::SNIPPET),
22143 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22144 lsp::InsertReplaceEdit {
22145 new_text: snippet.body.clone(),
22146 insert: lsp_range,
22147 replace: lsp_range,
22148 },
22149 )),
22150 filter_text: Some(snippet.body.clone()),
22151 sort_text: Some(char::MAX.to_string()),
22152 ..lsp::CompletionItem::default()
22153 }),
22154 lsp_defaults: None,
22155 },
22156 label: CodeLabel {
22157 text: matching_prefix.clone(),
22158 runs: Vec::new(),
22159 filter_range: 0..matching_prefix.len(),
22160 },
22161 icon_path: None,
22162 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22163 single_line: snippet.name.clone().into(),
22164 plain_text: snippet
22165 .description
22166 .clone()
22167 .map(|description| description.into()),
22168 }),
22169 insert_text_mode: None,
22170 confirm: None,
22171 })
22172 }))
22173 }
22174
22175 Ok(CompletionResponse {
22176 completions,
22177 is_incomplete,
22178 })
22179 })
22180}
22181
22182impl CompletionProvider for Entity<Project> {
22183 fn completions(
22184 &self,
22185 _excerpt_id: ExcerptId,
22186 buffer: &Entity<Buffer>,
22187 buffer_position: text::Anchor,
22188 options: CompletionContext,
22189 _window: &mut Window,
22190 cx: &mut Context<Editor>,
22191 ) -> Task<Result<Vec<CompletionResponse>>> {
22192 self.update(cx, |project, cx| {
22193 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22194 let project_completions = project.completions(buffer, buffer_position, options, cx);
22195 cx.background_spawn(async move {
22196 let mut responses = project_completions.await?;
22197 let snippets = snippets.await?;
22198 if !snippets.completions.is_empty() {
22199 responses.push(snippets);
22200 }
22201 Ok(responses)
22202 })
22203 })
22204 }
22205
22206 fn resolve_completions(
22207 &self,
22208 buffer: Entity<Buffer>,
22209 completion_indices: Vec<usize>,
22210 completions: Rc<RefCell<Box<[Completion]>>>,
22211 cx: &mut Context<Editor>,
22212 ) -> Task<Result<bool>> {
22213 self.update(cx, |project, cx| {
22214 project.lsp_store().update(cx, |lsp_store, cx| {
22215 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22216 })
22217 })
22218 }
22219
22220 fn apply_additional_edits_for_completion(
22221 &self,
22222 buffer: Entity<Buffer>,
22223 completions: Rc<RefCell<Box<[Completion]>>>,
22224 completion_index: usize,
22225 push_to_history: bool,
22226 cx: &mut Context<Editor>,
22227 ) -> Task<Result<Option<language::Transaction>>> {
22228 self.update(cx, |project, cx| {
22229 project.lsp_store().update(cx, |lsp_store, cx| {
22230 lsp_store.apply_additional_edits_for_completion(
22231 buffer,
22232 completions,
22233 completion_index,
22234 push_to_history,
22235 cx,
22236 )
22237 })
22238 })
22239 }
22240
22241 fn is_completion_trigger(
22242 &self,
22243 buffer: &Entity<Buffer>,
22244 position: language::Anchor,
22245 text: &str,
22246 trigger_in_words: bool,
22247 menu_is_open: bool,
22248 cx: &mut Context<Editor>,
22249 ) -> bool {
22250 let mut chars = text.chars();
22251 let char = if let Some(char) = chars.next() {
22252 char
22253 } else {
22254 return false;
22255 };
22256 if chars.next().is_some() {
22257 return false;
22258 }
22259
22260 let buffer = buffer.read(cx);
22261 let snapshot = buffer.snapshot();
22262 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22263 return false;
22264 }
22265 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22266 if trigger_in_words && classifier.is_word(char) {
22267 return true;
22268 }
22269
22270 buffer.completion_triggers().contains(text)
22271 }
22272}
22273
22274impl SemanticsProvider for Entity<Project> {
22275 fn hover(
22276 &self,
22277 buffer: &Entity<Buffer>,
22278 position: text::Anchor,
22279 cx: &mut App,
22280 ) -> Option<Task<Vec<project::Hover>>> {
22281 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22282 }
22283
22284 fn document_highlights(
22285 &self,
22286 buffer: &Entity<Buffer>,
22287 position: text::Anchor,
22288 cx: &mut App,
22289 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22290 Some(self.update(cx, |project, cx| {
22291 project.document_highlights(buffer, position, cx)
22292 }))
22293 }
22294
22295 fn definitions(
22296 &self,
22297 buffer: &Entity<Buffer>,
22298 position: text::Anchor,
22299 kind: GotoDefinitionKind,
22300 cx: &mut App,
22301 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22302 Some(self.update(cx, |project, cx| match kind {
22303 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22304 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22305 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22306 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22307 }))
22308 }
22309
22310 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22311 self.update(cx, |project, cx| {
22312 if project
22313 .active_debug_session(cx)
22314 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22315 {
22316 return true;
22317 }
22318
22319 buffer.update(cx, |buffer, cx| {
22320 project.any_language_server_supports_inlay_hints(buffer, cx)
22321 })
22322 })
22323 }
22324
22325 fn inline_values(
22326 &self,
22327 buffer_handle: Entity<Buffer>,
22328 range: Range<text::Anchor>,
22329 cx: &mut App,
22330 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22331 self.update(cx, |project, cx| {
22332 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22333
22334 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22335 })
22336 }
22337
22338 fn inlay_hints(
22339 &self,
22340 buffer_handle: Entity<Buffer>,
22341 range: Range<text::Anchor>,
22342 cx: &mut App,
22343 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22344 Some(self.update(cx, |project, cx| {
22345 project.inlay_hints(buffer_handle, range, cx)
22346 }))
22347 }
22348
22349 fn resolve_inlay_hint(
22350 &self,
22351 hint: InlayHint,
22352 buffer_handle: Entity<Buffer>,
22353 server_id: LanguageServerId,
22354 cx: &mut App,
22355 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22356 Some(self.update(cx, |project, cx| {
22357 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22358 }))
22359 }
22360
22361 fn range_for_rename(
22362 &self,
22363 buffer: &Entity<Buffer>,
22364 position: text::Anchor,
22365 cx: &mut App,
22366 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22367 Some(self.update(cx, |project, cx| {
22368 let buffer = buffer.clone();
22369 let task = project.prepare_rename(buffer.clone(), position, cx);
22370 cx.spawn(async move |_, cx| {
22371 Ok(match task.await? {
22372 PrepareRenameResponse::Success(range) => Some(range),
22373 PrepareRenameResponse::InvalidPosition => None,
22374 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22375 // Fallback on using TreeSitter info to determine identifier range
22376 buffer.read_with(cx, |buffer, _| {
22377 let snapshot = buffer.snapshot();
22378 let (range, kind) = snapshot.surrounding_word(position, false);
22379 if kind != Some(CharKind::Word) {
22380 return None;
22381 }
22382 Some(
22383 snapshot.anchor_before(range.start)
22384 ..snapshot.anchor_after(range.end),
22385 )
22386 })?
22387 }
22388 })
22389 })
22390 }))
22391 }
22392
22393 fn perform_rename(
22394 &self,
22395 buffer: &Entity<Buffer>,
22396 position: text::Anchor,
22397 new_name: String,
22398 cx: &mut App,
22399 ) -> Option<Task<Result<ProjectTransaction>>> {
22400 Some(self.update(cx, |project, cx| {
22401 project.perform_rename(buffer.clone(), position, new_name, cx)
22402 }))
22403 }
22404}
22405
22406fn inlay_hint_settings(
22407 location: Anchor,
22408 snapshot: &MultiBufferSnapshot,
22409 cx: &mut Context<Editor>,
22410) -> InlayHintSettings {
22411 let file = snapshot.file_at(location);
22412 let language = snapshot.language_at(location).map(|l| l.name());
22413 language_settings(language, file, cx).inlay_hints
22414}
22415
22416fn consume_contiguous_rows(
22417 contiguous_row_selections: &mut Vec<Selection<Point>>,
22418 selection: &Selection<Point>,
22419 display_map: &DisplaySnapshot,
22420 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22421) -> (MultiBufferRow, MultiBufferRow) {
22422 contiguous_row_selections.push(selection.clone());
22423 let start_row = starting_row(selection, display_map);
22424 let mut end_row = ending_row(selection, display_map);
22425
22426 while let Some(next_selection) = selections.peek() {
22427 if next_selection.start.row <= end_row.0 {
22428 end_row = ending_row(next_selection, display_map);
22429 contiguous_row_selections.push(selections.next().unwrap().clone());
22430 } else {
22431 break;
22432 }
22433 }
22434 (start_row, end_row)
22435}
22436
22437fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22438 if selection.start.column > 0 {
22439 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22440 } else {
22441 MultiBufferRow(selection.start.row)
22442 }
22443}
22444
22445fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22446 if next_selection.end.column > 0 || next_selection.is_empty() {
22447 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22448 } else {
22449 MultiBufferRow(next_selection.end.row)
22450 }
22451}
22452
22453impl EditorSnapshot {
22454 pub fn remote_selections_in_range<'a>(
22455 &'a self,
22456 range: &'a Range<Anchor>,
22457 collaboration_hub: &dyn CollaborationHub,
22458 cx: &'a App,
22459 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22460 let participant_names = collaboration_hub.user_names(cx);
22461 let participant_indices = collaboration_hub.user_participant_indices(cx);
22462 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22463 let collaborators_by_replica_id = collaborators_by_peer_id
22464 .values()
22465 .map(|collaborator| (collaborator.replica_id, collaborator))
22466 .collect::<HashMap<_, _>>();
22467 self.buffer_snapshot
22468 .selections_in_range(range, false)
22469 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22470 if replica_id == AGENT_REPLICA_ID {
22471 Some(RemoteSelection {
22472 replica_id,
22473 selection,
22474 cursor_shape,
22475 line_mode,
22476 collaborator_id: CollaboratorId::Agent,
22477 user_name: Some("Agent".into()),
22478 color: cx.theme().players().agent(),
22479 })
22480 } else {
22481 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22482 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22483 let user_name = participant_names.get(&collaborator.user_id).cloned();
22484 Some(RemoteSelection {
22485 replica_id,
22486 selection,
22487 cursor_shape,
22488 line_mode,
22489 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22490 user_name,
22491 color: if let Some(index) = participant_index {
22492 cx.theme().players().color_for_participant(index.0)
22493 } else {
22494 cx.theme().players().absent()
22495 },
22496 })
22497 }
22498 })
22499 }
22500
22501 pub fn hunks_for_ranges(
22502 &self,
22503 ranges: impl IntoIterator<Item = Range<Point>>,
22504 ) -> Vec<MultiBufferDiffHunk> {
22505 let mut hunks = Vec::new();
22506 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22507 HashMap::default();
22508 for query_range in ranges {
22509 let query_rows =
22510 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22511 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22512 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22513 ) {
22514 // Include deleted hunks that are adjacent to the query range, because
22515 // otherwise they would be missed.
22516 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22517 if hunk.status().is_deleted() {
22518 intersects_range |= hunk.row_range.start == query_rows.end;
22519 intersects_range |= hunk.row_range.end == query_rows.start;
22520 }
22521 if intersects_range {
22522 if !processed_buffer_rows
22523 .entry(hunk.buffer_id)
22524 .or_default()
22525 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22526 {
22527 continue;
22528 }
22529 hunks.push(hunk);
22530 }
22531 }
22532 }
22533
22534 hunks
22535 }
22536
22537 fn display_diff_hunks_for_rows<'a>(
22538 &'a self,
22539 display_rows: Range<DisplayRow>,
22540 folded_buffers: &'a HashSet<BufferId>,
22541 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22542 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22543 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22544
22545 self.buffer_snapshot
22546 .diff_hunks_in_range(buffer_start..buffer_end)
22547 .filter_map(|hunk| {
22548 if folded_buffers.contains(&hunk.buffer_id) {
22549 return None;
22550 }
22551
22552 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22553 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22554
22555 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22556 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22557
22558 let display_hunk = if hunk_display_start.column() != 0 {
22559 DisplayDiffHunk::Folded {
22560 display_row: hunk_display_start.row(),
22561 }
22562 } else {
22563 let mut end_row = hunk_display_end.row();
22564 if hunk_display_end.column() > 0 {
22565 end_row.0 += 1;
22566 }
22567 let is_created_file = hunk.is_created_file();
22568 DisplayDiffHunk::Unfolded {
22569 status: hunk.status(),
22570 diff_base_byte_range: hunk.diff_base_byte_range,
22571 display_row_range: hunk_display_start.row()..end_row,
22572 multi_buffer_range: Anchor::range_in_buffer(
22573 hunk.excerpt_id,
22574 hunk.buffer_id,
22575 hunk.buffer_range,
22576 ),
22577 is_created_file,
22578 }
22579 };
22580
22581 Some(display_hunk)
22582 })
22583 }
22584
22585 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22586 self.display_snapshot.buffer_snapshot.language_at(position)
22587 }
22588
22589 pub fn is_focused(&self) -> bool {
22590 self.is_focused
22591 }
22592
22593 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22594 self.placeholder_text.as_ref()
22595 }
22596
22597 pub fn scroll_position(&self) -> gpui::Point<f32> {
22598 self.scroll_anchor.scroll_position(&self.display_snapshot)
22599 }
22600
22601 fn gutter_dimensions(
22602 &self,
22603 font_id: FontId,
22604 font_size: Pixels,
22605 max_line_number_width: Pixels,
22606 cx: &App,
22607 ) -> Option<GutterDimensions> {
22608 if !self.show_gutter {
22609 return None;
22610 }
22611
22612 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22613 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22614
22615 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22616 matches!(
22617 ProjectSettings::get_global(cx).git.git_gutter,
22618 Some(GitGutterSetting::TrackedFiles)
22619 )
22620 });
22621 let gutter_settings = EditorSettings::get_global(cx).gutter;
22622 let show_line_numbers = self
22623 .show_line_numbers
22624 .unwrap_or(gutter_settings.line_numbers);
22625 let line_gutter_width = if show_line_numbers {
22626 // Avoid flicker-like gutter resizes when the line number gains another digit by
22627 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22628 let min_width_for_number_on_gutter =
22629 ch_advance * gutter_settings.min_line_number_digits as f32;
22630 max_line_number_width.max(min_width_for_number_on_gutter)
22631 } else {
22632 0.0.into()
22633 };
22634
22635 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22636 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22637
22638 let git_blame_entries_width =
22639 self.git_blame_gutter_max_author_length
22640 .map(|max_author_length| {
22641 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22642 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22643
22644 /// The number of characters to dedicate to gaps and margins.
22645 const SPACING_WIDTH: usize = 4;
22646
22647 let max_char_count = max_author_length.min(renderer.max_author_length())
22648 + ::git::SHORT_SHA_LENGTH
22649 + MAX_RELATIVE_TIMESTAMP.len()
22650 + SPACING_WIDTH;
22651
22652 ch_advance * max_char_count
22653 });
22654
22655 let is_singleton = self.buffer_snapshot.is_singleton();
22656
22657 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22658 left_padding += if !is_singleton {
22659 ch_width * 4.0
22660 } else if show_runnables || show_breakpoints {
22661 ch_width * 3.0
22662 } else if show_git_gutter && show_line_numbers {
22663 ch_width * 2.0
22664 } else if show_git_gutter || show_line_numbers {
22665 ch_width
22666 } else {
22667 px(0.)
22668 };
22669
22670 let shows_folds = is_singleton && gutter_settings.folds;
22671
22672 let right_padding = if shows_folds && show_line_numbers {
22673 ch_width * 4.0
22674 } else if shows_folds || (!is_singleton && show_line_numbers) {
22675 ch_width * 3.0
22676 } else if show_line_numbers {
22677 ch_width
22678 } else {
22679 px(0.)
22680 };
22681
22682 Some(GutterDimensions {
22683 left_padding,
22684 right_padding,
22685 width: line_gutter_width + left_padding + right_padding,
22686 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22687 git_blame_entries_width,
22688 })
22689 }
22690
22691 pub fn render_crease_toggle(
22692 &self,
22693 buffer_row: MultiBufferRow,
22694 row_contains_cursor: bool,
22695 editor: Entity<Editor>,
22696 window: &mut Window,
22697 cx: &mut App,
22698 ) -> Option<AnyElement> {
22699 let folded = self.is_line_folded(buffer_row);
22700 let mut is_foldable = false;
22701
22702 if let Some(crease) = self
22703 .crease_snapshot
22704 .query_row(buffer_row, &self.buffer_snapshot)
22705 {
22706 is_foldable = true;
22707 match crease {
22708 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22709 if let Some(render_toggle) = render_toggle {
22710 let toggle_callback =
22711 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22712 if folded {
22713 editor.update(cx, |editor, cx| {
22714 editor.fold_at(buffer_row, window, cx)
22715 });
22716 } else {
22717 editor.update(cx, |editor, cx| {
22718 editor.unfold_at(buffer_row, window, cx)
22719 });
22720 }
22721 });
22722 return Some((render_toggle)(
22723 buffer_row,
22724 folded,
22725 toggle_callback,
22726 window,
22727 cx,
22728 ));
22729 }
22730 }
22731 }
22732 }
22733
22734 is_foldable |= self.starts_indent(buffer_row);
22735
22736 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22737 Some(
22738 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22739 .toggle_state(folded)
22740 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22741 if folded {
22742 this.unfold_at(buffer_row, window, cx);
22743 } else {
22744 this.fold_at(buffer_row, window, cx);
22745 }
22746 }))
22747 .into_any_element(),
22748 )
22749 } else {
22750 None
22751 }
22752 }
22753
22754 pub fn render_crease_trailer(
22755 &self,
22756 buffer_row: MultiBufferRow,
22757 window: &mut Window,
22758 cx: &mut App,
22759 ) -> Option<AnyElement> {
22760 let folded = self.is_line_folded(buffer_row);
22761 if let Crease::Inline { render_trailer, .. } = self
22762 .crease_snapshot
22763 .query_row(buffer_row, &self.buffer_snapshot)?
22764 {
22765 let render_trailer = render_trailer.as_ref()?;
22766 Some(render_trailer(buffer_row, folded, window, cx))
22767 } else {
22768 None
22769 }
22770 }
22771}
22772
22773impl Deref for EditorSnapshot {
22774 type Target = DisplaySnapshot;
22775
22776 fn deref(&self) -> &Self::Target {
22777 &self.display_snapshot
22778 }
22779}
22780
22781#[derive(Clone, Debug, PartialEq, Eq)]
22782pub enum EditorEvent {
22783 InputIgnored {
22784 text: Arc<str>,
22785 },
22786 InputHandled {
22787 utf16_range_to_replace: Option<Range<isize>>,
22788 text: Arc<str>,
22789 },
22790 ExcerptsAdded {
22791 buffer: Entity<Buffer>,
22792 predecessor: ExcerptId,
22793 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22794 },
22795 ExcerptsRemoved {
22796 ids: Vec<ExcerptId>,
22797 removed_buffer_ids: Vec<BufferId>,
22798 },
22799 BufferFoldToggled {
22800 ids: Vec<ExcerptId>,
22801 folded: bool,
22802 },
22803 ExcerptsEdited {
22804 ids: Vec<ExcerptId>,
22805 },
22806 ExcerptsExpanded {
22807 ids: Vec<ExcerptId>,
22808 },
22809 BufferEdited,
22810 Edited {
22811 transaction_id: clock::Lamport,
22812 },
22813 Reparsed(BufferId),
22814 Focused,
22815 FocusedIn,
22816 Blurred,
22817 DirtyChanged,
22818 Saved,
22819 TitleChanged,
22820 DiffBaseChanged,
22821 SelectionsChanged {
22822 local: bool,
22823 },
22824 ScrollPositionChanged {
22825 local: bool,
22826 autoscroll: bool,
22827 },
22828 Closed,
22829 TransactionUndone {
22830 transaction_id: clock::Lamport,
22831 },
22832 TransactionBegun {
22833 transaction_id: clock::Lamport,
22834 },
22835 Reloaded,
22836 CursorShapeChanged,
22837 PushedToNavHistory {
22838 anchor: Anchor,
22839 is_deactivate: bool,
22840 },
22841}
22842
22843impl EventEmitter<EditorEvent> for Editor {}
22844
22845impl Focusable for Editor {
22846 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22847 self.focus_handle.clone()
22848 }
22849}
22850
22851impl Render for Editor {
22852 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22853 let settings = ThemeSettings::get_global(cx);
22854
22855 let mut text_style = match self.mode {
22856 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22857 color: cx.theme().colors().editor_foreground,
22858 font_family: settings.ui_font.family.clone(),
22859 font_features: settings.ui_font.features.clone(),
22860 font_fallbacks: settings.ui_font.fallbacks.clone(),
22861 font_size: rems(0.875).into(),
22862 font_weight: settings.ui_font.weight,
22863 line_height: relative(settings.buffer_line_height.value()),
22864 ..Default::default()
22865 },
22866 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22867 color: cx.theme().colors().editor_foreground,
22868 font_family: settings.buffer_font.family.clone(),
22869 font_features: settings.buffer_font.features.clone(),
22870 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22871 font_size: settings.buffer_font_size(cx).into(),
22872 font_weight: settings.buffer_font.weight,
22873 line_height: relative(settings.buffer_line_height.value()),
22874 ..Default::default()
22875 },
22876 };
22877 if let Some(text_style_refinement) = &self.text_style_refinement {
22878 text_style.refine(text_style_refinement)
22879 }
22880
22881 let background = match self.mode {
22882 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22883 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22884 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22885 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22886 };
22887
22888 EditorElement::new(
22889 &cx.entity(),
22890 EditorStyle {
22891 background,
22892 border: cx.theme().colors().border,
22893 local_player: cx.theme().players().local(),
22894 text: text_style,
22895 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22896 syntax: cx.theme().syntax().clone(),
22897 status: cx.theme().status().clone(),
22898 inlay_hints_style: make_inlay_hints_style(cx),
22899 edit_prediction_styles: make_suggestion_styles(cx),
22900 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22901 show_underlines: self.diagnostics_enabled(),
22902 },
22903 )
22904 }
22905}
22906
22907impl EntityInputHandler for Editor {
22908 fn text_for_range(
22909 &mut self,
22910 range_utf16: Range<usize>,
22911 adjusted_range: &mut Option<Range<usize>>,
22912 _: &mut Window,
22913 cx: &mut Context<Self>,
22914 ) -> Option<String> {
22915 let snapshot = self.buffer.read(cx).read(cx);
22916 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22917 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22918 if (start.0..end.0) != range_utf16 {
22919 adjusted_range.replace(start.0..end.0);
22920 }
22921 Some(snapshot.text_for_range(start..end).collect())
22922 }
22923
22924 fn selected_text_range(
22925 &mut self,
22926 ignore_disabled_input: bool,
22927 _: &mut Window,
22928 cx: &mut Context<Self>,
22929 ) -> Option<UTF16Selection> {
22930 // Prevent the IME menu from appearing when holding down an alphabetic key
22931 // while input is disabled.
22932 if !ignore_disabled_input && !self.input_enabled {
22933 return None;
22934 }
22935
22936 let selection = self.selections.newest::<OffsetUtf16>(cx);
22937 let range = selection.range();
22938
22939 Some(UTF16Selection {
22940 range: range.start.0..range.end.0,
22941 reversed: selection.reversed,
22942 })
22943 }
22944
22945 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22946 let snapshot = self.buffer.read(cx).read(cx);
22947 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22948 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22949 }
22950
22951 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22952 self.clear_highlights::<InputComposition>(cx);
22953 self.ime_transaction.take();
22954 }
22955
22956 fn replace_text_in_range(
22957 &mut self,
22958 range_utf16: Option<Range<usize>>,
22959 text: &str,
22960 window: &mut Window,
22961 cx: &mut Context<Self>,
22962 ) {
22963 if !self.input_enabled {
22964 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22965 return;
22966 }
22967
22968 self.transact(window, cx, |this, window, cx| {
22969 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22970 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22971 Some(this.selection_replacement_ranges(range_utf16, cx))
22972 } else {
22973 this.marked_text_ranges(cx)
22974 };
22975
22976 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22977 let newest_selection_id = this.selections.newest_anchor().id;
22978 this.selections
22979 .all::<OffsetUtf16>(cx)
22980 .iter()
22981 .zip(ranges_to_replace.iter())
22982 .find_map(|(selection, range)| {
22983 if selection.id == newest_selection_id {
22984 Some(
22985 (range.start.0 as isize - selection.head().0 as isize)
22986 ..(range.end.0 as isize - selection.head().0 as isize),
22987 )
22988 } else {
22989 None
22990 }
22991 })
22992 });
22993
22994 cx.emit(EditorEvent::InputHandled {
22995 utf16_range_to_replace: range_to_replace,
22996 text: text.into(),
22997 });
22998
22999 if let Some(new_selected_ranges) = new_selected_ranges {
23000 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23001 selections.select_ranges(new_selected_ranges)
23002 });
23003 this.backspace(&Default::default(), window, cx);
23004 }
23005
23006 this.handle_input(text, window, cx);
23007 });
23008
23009 if let Some(transaction) = self.ime_transaction {
23010 self.buffer.update(cx, |buffer, cx| {
23011 buffer.group_until_transaction(transaction, cx);
23012 });
23013 }
23014
23015 self.unmark_text(window, cx);
23016 }
23017
23018 fn replace_and_mark_text_in_range(
23019 &mut self,
23020 range_utf16: Option<Range<usize>>,
23021 text: &str,
23022 new_selected_range_utf16: Option<Range<usize>>,
23023 window: &mut Window,
23024 cx: &mut Context<Self>,
23025 ) {
23026 if !self.input_enabled {
23027 return;
23028 }
23029
23030 let transaction = self.transact(window, cx, |this, window, cx| {
23031 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23032 let snapshot = this.buffer.read(cx).read(cx);
23033 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23034 for marked_range in &mut marked_ranges {
23035 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23036 marked_range.start.0 += relative_range_utf16.start;
23037 marked_range.start =
23038 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23039 marked_range.end =
23040 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23041 }
23042 }
23043 Some(marked_ranges)
23044 } else if let Some(range_utf16) = range_utf16 {
23045 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23046 Some(this.selection_replacement_ranges(range_utf16, cx))
23047 } else {
23048 None
23049 };
23050
23051 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23052 let newest_selection_id = this.selections.newest_anchor().id;
23053 this.selections
23054 .all::<OffsetUtf16>(cx)
23055 .iter()
23056 .zip(ranges_to_replace.iter())
23057 .find_map(|(selection, range)| {
23058 if selection.id == newest_selection_id {
23059 Some(
23060 (range.start.0 as isize - selection.head().0 as isize)
23061 ..(range.end.0 as isize - selection.head().0 as isize),
23062 )
23063 } else {
23064 None
23065 }
23066 })
23067 });
23068
23069 cx.emit(EditorEvent::InputHandled {
23070 utf16_range_to_replace: range_to_replace,
23071 text: text.into(),
23072 });
23073
23074 if let Some(ranges) = ranges_to_replace {
23075 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23076 s.select_ranges(ranges)
23077 });
23078 }
23079
23080 let marked_ranges = {
23081 let snapshot = this.buffer.read(cx).read(cx);
23082 this.selections
23083 .disjoint_anchors()
23084 .iter()
23085 .map(|selection| {
23086 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23087 })
23088 .collect::<Vec<_>>()
23089 };
23090
23091 if text.is_empty() {
23092 this.unmark_text(window, cx);
23093 } else {
23094 this.highlight_text::<InputComposition>(
23095 marked_ranges.clone(),
23096 HighlightStyle {
23097 underline: Some(UnderlineStyle {
23098 thickness: px(1.),
23099 color: None,
23100 wavy: false,
23101 }),
23102 ..Default::default()
23103 },
23104 cx,
23105 );
23106 }
23107
23108 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23109 let use_autoclose = this.use_autoclose;
23110 let use_auto_surround = this.use_auto_surround;
23111 this.set_use_autoclose(false);
23112 this.set_use_auto_surround(false);
23113 this.handle_input(text, window, cx);
23114 this.set_use_autoclose(use_autoclose);
23115 this.set_use_auto_surround(use_auto_surround);
23116
23117 if let Some(new_selected_range) = new_selected_range_utf16 {
23118 let snapshot = this.buffer.read(cx).read(cx);
23119 let new_selected_ranges = marked_ranges
23120 .into_iter()
23121 .map(|marked_range| {
23122 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23123 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23124 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23125 snapshot.clip_offset_utf16(new_start, Bias::Left)
23126 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23127 })
23128 .collect::<Vec<_>>();
23129
23130 drop(snapshot);
23131 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23132 selections.select_ranges(new_selected_ranges)
23133 });
23134 }
23135 });
23136
23137 self.ime_transaction = self.ime_transaction.or(transaction);
23138 if let Some(transaction) = self.ime_transaction {
23139 self.buffer.update(cx, |buffer, cx| {
23140 buffer.group_until_transaction(transaction, cx);
23141 });
23142 }
23143
23144 if self.text_highlights::<InputComposition>(cx).is_none() {
23145 self.ime_transaction.take();
23146 }
23147 }
23148
23149 fn bounds_for_range(
23150 &mut self,
23151 range_utf16: Range<usize>,
23152 element_bounds: gpui::Bounds<Pixels>,
23153 window: &mut Window,
23154 cx: &mut Context<Self>,
23155 ) -> Option<gpui::Bounds<Pixels>> {
23156 let text_layout_details = self.text_layout_details(window);
23157 let CharacterDimensions {
23158 em_width,
23159 em_advance,
23160 line_height,
23161 } = self.character_dimensions(window);
23162
23163 let snapshot = self.snapshot(window, cx);
23164 let scroll_position = snapshot.scroll_position();
23165 let scroll_left = scroll_position.x * em_advance;
23166
23167 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23168 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23169 + self.gutter_dimensions.full_width();
23170 let y = line_height * (start.row().as_f32() - scroll_position.y);
23171
23172 Some(Bounds {
23173 origin: element_bounds.origin + point(x, y),
23174 size: size(em_width, line_height),
23175 })
23176 }
23177
23178 fn character_index_for_point(
23179 &mut self,
23180 point: gpui::Point<Pixels>,
23181 _window: &mut Window,
23182 _cx: &mut Context<Self>,
23183 ) -> Option<usize> {
23184 let position_map = self.last_position_map.as_ref()?;
23185 if !position_map.text_hitbox.contains(&point) {
23186 return None;
23187 }
23188 let display_point = position_map.point_for_position(point).previous_valid;
23189 let anchor = position_map
23190 .snapshot
23191 .display_point_to_anchor(display_point, Bias::Left);
23192 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23193 Some(utf16_offset.0)
23194 }
23195}
23196
23197trait SelectionExt {
23198 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23199 fn spanned_rows(
23200 &self,
23201 include_end_if_at_line_start: bool,
23202 map: &DisplaySnapshot,
23203 ) -> Range<MultiBufferRow>;
23204}
23205
23206impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23207 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23208 let start = self
23209 .start
23210 .to_point(&map.buffer_snapshot)
23211 .to_display_point(map);
23212 let end = self
23213 .end
23214 .to_point(&map.buffer_snapshot)
23215 .to_display_point(map);
23216 if self.reversed {
23217 end..start
23218 } else {
23219 start..end
23220 }
23221 }
23222
23223 fn spanned_rows(
23224 &self,
23225 include_end_if_at_line_start: bool,
23226 map: &DisplaySnapshot,
23227 ) -> Range<MultiBufferRow> {
23228 let start = self.start.to_point(&map.buffer_snapshot);
23229 let mut end = self.end.to_point(&map.buffer_snapshot);
23230 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23231 end.row -= 1;
23232 }
23233
23234 let buffer_start = map.prev_line_boundary(start).0;
23235 let buffer_end = map.next_line_boundary(end).0;
23236 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23237 }
23238}
23239
23240impl<T: InvalidationRegion> InvalidationStack<T> {
23241 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23242 where
23243 S: Clone + ToOffset,
23244 {
23245 while let Some(region) = self.last() {
23246 let all_selections_inside_invalidation_ranges =
23247 if selections.len() == region.ranges().len() {
23248 selections
23249 .iter()
23250 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23251 .all(|(selection, invalidation_range)| {
23252 let head = selection.head().to_offset(buffer);
23253 invalidation_range.start <= head && invalidation_range.end >= head
23254 })
23255 } else {
23256 false
23257 };
23258
23259 if all_selections_inside_invalidation_ranges {
23260 break;
23261 } else {
23262 self.pop();
23263 }
23264 }
23265 }
23266}
23267
23268impl<T> Default for InvalidationStack<T> {
23269 fn default() -> Self {
23270 Self(Default::default())
23271 }
23272}
23273
23274impl<T> Deref for InvalidationStack<T> {
23275 type Target = Vec<T>;
23276
23277 fn deref(&self) -> &Self::Target {
23278 &self.0
23279 }
23280}
23281
23282impl<T> DerefMut for InvalidationStack<T> {
23283 fn deref_mut(&mut self) -> &mut Self::Target {
23284 &mut self.0
23285 }
23286}
23287
23288impl InvalidationRegion for SnippetState {
23289 fn ranges(&self) -> &[Range<Anchor>] {
23290 &self.ranges[self.active_index]
23291 }
23292}
23293
23294fn edit_prediction_edit_text(
23295 current_snapshot: &BufferSnapshot,
23296 edits: &[(Range<Anchor>, String)],
23297 edit_preview: &EditPreview,
23298 include_deletions: bool,
23299 cx: &App,
23300) -> HighlightedText {
23301 let edits = edits
23302 .iter()
23303 .map(|(anchor, text)| {
23304 (
23305 anchor.start.text_anchor..anchor.end.text_anchor,
23306 text.clone(),
23307 )
23308 })
23309 .collect::<Vec<_>>();
23310
23311 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23312}
23313
23314fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23315 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23316 // Just show the raw edit text with basic styling
23317 let mut text = String::new();
23318 let mut highlights = Vec::new();
23319
23320 let insertion_highlight_style = HighlightStyle {
23321 color: Some(cx.theme().colors().text),
23322 ..Default::default()
23323 };
23324
23325 for (_, edit_text) in edits {
23326 let start_offset = text.len();
23327 text.push_str(edit_text);
23328 let end_offset = text.len();
23329
23330 if start_offset < end_offset {
23331 highlights.push((start_offset..end_offset, insertion_highlight_style));
23332 }
23333 }
23334
23335 HighlightedText {
23336 text: text.into(),
23337 highlights,
23338 }
23339}
23340
23341pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23342 match severity {
23343 lsp::DiagnosticSeverity::ERROR => colors.error,
23344 lsp::DiagnosticSeverity::WARNING => colors.warning,
23345 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23346 lsp::DiagnosticSeverity::HINT => colors.info,
23347 _ => colors.ignored,
23348 }
23349}
23350
23351pub fn styled_runs_for_code_label<'a>(
23352 label: &'a CodeLabel,
23353 syntax_theme: &'a theme::SyntaxTheme,
23354) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23355 let fade_out = HighlightStyle {
23356 fade_out: Some(0.35),
23357 ..Default::default()
23358 };
23359
23360 let mut prev_end = label.filter_range.end;
23361 label
23362 .runs
23363 .iter()
23364 .enumerate()
23365 .flat_map(move |(ix, (range, highlight_id))| {
23366 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23367 style
23368 } else {
23369 return Default::default();
23370 };
23371 let mut muted_style = style;
23372 muted_style.highlight(fade_out);
23373
23374 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23375 if range.start >= label.filter_range.end {
23376 if range.start > prev_end {
23377 runs.push((prev_end..range.start, fade_out));
23378 }
23379 runs.push((range.clone(), muted_style));
23380 } else if range.end <= label.filter_range.end {
23381 runs.push((range.clone(), style));
23382 } else {
23383 runs.push((range.start..label.filter_range.end, style));
23384 runs.push((label.filter_range.end..range.end, muted_style));
23385 }
23386 prev_end = cmp::max(prev_end, range.end);
23387
23388 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23389 runs.push((prev_end..label.text.len(), fade_out));
23390 }
23391
23392 runs
23393 })
23394}
23395
23396pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23397 let mut prev_index = 0;
23398 let mut prev_codepoint: Option<char> = None;
23399 text.char_indices()
23400 .chain([(text.len(), '\0')])
23401 .filter_map(move |(index, codepoint)| {
23402 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23403 let is_boundary = index == text.len()
23404 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23405 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23406 if is_boundary {
23407 let chunk = &text[prev_index..index];
23408 prev_index = index;
23409 Some(chunk)
23410 } else {
23411 None
23412 }
23413 })
23414}
23415
23416pub trait RangeToAnchorExt: Sized {
23417 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23418
23419 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23420 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23421 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23422 }
23423}
23424
23425impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23426 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23427 let start_offset = self.start.to_offset(snapshot);
23428 let end_offset = self.end.to_offset(snapshot);
23429 if start_offset == end_offset {
23430 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23431 } else {
23432 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23433 }
23434 }
23435}
23436
23437pub trait RowExt {
23438 fn as_f32(&self) -> f32;
23439
23440 fn next_row(&self) -> Self;
23441
23442 fn previous_row(&self) -> Self;
23443
23444 fn minus(&self, other: Self) -> u32;
23445}
23446
23447impl RowExt for DisplayRow {
23448 fn as_f32(&self) -> f32 {
23449 self.0 as f32
23450 }
23451
23452 fn next_row(&self) -> Self {
23453 Self(self.0 + 1)
23454 }
23455
23456 fn previous_row(&self) -> Self {
23457 Self(self.0.saturating_sub(1))
23458 }
23459
23460 fn minus(&self, other: Self) -> u32 {
23461 self.0 - other.0
23462 }
23463}
23464
23465impl RowExt for MultiBufferRow {
23466 fn as_f32(&self) -> f32 {
23467 self.0 as f32
23468 }
23469
23470 fn next_row(&self) -> Self {
23471 Self(self.0 + 1)
23472 }
23473
23474 fn previous_row(&self) -> Self {
23475 Self(self.0.saturating_sub(1))
23476 }
23477
23478 fn minus(&self, other: Self) -> u32 {
23479 self.0 - other.0
23480 }
23481}
23482
23483trait RowRangeExt {
23484 type Row;
23485
23486 fn len(&self) -> usize;
23487
23488 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23489}
23490
23491impl RowRangeExt for Range<MultiBufferRow> {
23492 type Row = MultiBufferRow;
23493
23494 fn len(&self) -> usize {
23495 (self.end.0 - self.start.0) as usize
23496 }
23497
23498 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23499 (self.start.0..self.end.0).map(MultiBufferRow)
23500 }
23501}
23502
23503impl RowRangeExt for Range<DisplayRow> {
23504 type Row = DisplayRow;
23505
23506 fn len(&self) -> usize {
23507 (self.end.0 - self.start.0) as usize
23508 }
23509
23510 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23511 (self.start.0..self.end.0).map(DisplayRow)
23512 }
23513}
23514
23515/// If select range has more than one line, we
23516/// just point the cursor to range.start.
23517fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23518 if range.start.row == range.end.row {
23519 range
23520 } else {
23521 range.start..range.start
23522 }
23523}
23524pub struct KillRing(ClipboardItem);
23525impl Global for KillRing {}
23526
23527const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23528
23529enum BreakpointPromptEditAction {
23530 Log,
23531 Condition,
23532 HitCondition,
23533}
23534
23535struct BreakpointPromptEditor {
23536 pub(crate) prompt: Entity<Editor>,
23537 editor: WeakEntity<Editor>,
23538 breakpoint_anchor: Anchor,
23539 breakpoint: Breakpoint,
23540 edit_action: BreakpointPromptEditAction,
23541 block_ids: HashSet<CustomBlockId>,
23542 editor_margins: Arc<Mutex<EditorMargins>>,
23543 _subscriptions: Vec<Subscription>,
23544}
23545
23546impl BreakpointPromptEditor {
23547 const MAX_LINES: u8 = 4;
23548
23549 fn new(
23550 editor: WeakEntity<Editor>,
23551 breakpoint_anchor: Anchor,
23552 breakpoint: Breakpoint,
23553 edit_action: BreakpointPromptEditAction,
23554 window: &mut Window,
23555 cx: &mut Context<Self>,
23556 ) -> Self {
23557 let base_text = match edit_action {
23558 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23559 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23560 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23561 }
23562 .map(|msg| msg.to_string())
23563 .unwrap_or_default();
23564
23565 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23566 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23567
23568 let prompt = cx.new(|cx| {
23569 let mut prompt = Editor::new(
23570 EditorMode::AutoHeight {
23571 min_lines: 1,
23572 max_lines: Some(Self::MAX_LINES as usize),
23573 },
23574 buffer,
23575 None,
23576 window,
23577 cx,
23578 );
23579 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23580 prompt.set_show_cursor_when_unfocused(false, cx);
23581 prompt.set_placeholder_text(
23582 match edit_action {
23583 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23584 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23585 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23586 },
23587 cx,
23588 );
23589
23590 prompt
23591 });
23592
23593 Self {
23594 prompt,
23595 editor,
23596 breakpoint_anchor,
23597 breakpoint,
23598 edit_action,
23599 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23600 block_ids: Default::default(),
23601 _subscriptions: vec![],
23602 }
23603 }
23604
23605 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23606 self.block_ids.extend(block_ids)
23607 }
23608
23609 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23610 if let Some(editor) = self.editor.upgrade() {
23611 let message = self
23612 .prompt
23613 .read(cx)
23614 .buffer
23615 .read(cx)
23616 .as_singleton()
23617 .expect("A multi buffer in breakpoint prompt isn't possible")
23618 .read(cx)
23619 .as_rope()
23620 .to_string();
23621
23622 editor.update(cx, |editor, cx| {
23623 editor.edit_breakpoint_at_anchor(
23624 self.breakpoint_anchor,
23625 self.breakpoint.clone(),
23626 match self.edit_action {
23627 BreakpointPromptEditAction::Log => {
23628 BreakpointEditAction::EditLogMessage(message.into())
23629 }
23630 BreakpointPromptEditAction::Condition => {
23631 BreakpointEditAction::EditCondition(message.into())
23632 }
23633 BreakpointPromptEditAction::HitCondition => {
23634 BreakpointEditAction::EditHitCondition(message.into())
23635 }
23636 },
23637 cx,
23638 );
23639
23640 editor.remove_blocks(self.block_ids.clone(), None, cx);
23641 cx.focus_self(window);
23642 });
23643 }
23644 }
23645
23646 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23647 self.editor
23648 .update(cx, |editor, cx| {
23649 editor.remove_blocks(self.block_ids.clone(), None, cx);
23650 window.focus(&editor.focus_handle);
23651 })
23652 .log_err();
23653 }
23654
23655 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23656 let settings = ThemeSettings::get_global(cx);
23657 let text_style = TextStyle {
23658 color: if self.prompt.read(cx).read_only(cx) {
23659 cx.theme().colors().text_disabled
23660 } else {
23661 cx.theme().colors().text
23662 },
23663 font_family: settings.buffer_font.family.clone(),
23664 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23665 font_size: settings.buffer_font_size(cx).into(),
23666 font_weight: settings.buffer_font.weight,
23667 line_height: relative(settings.buffer_line_height.value()),
23668 ..Default::default()
23669 };
23670 EditorElement::new(
23671 &self.prompt,
23672 EditorStyle {
23673 background: cx.theme().colors().editor_background,
23674 local_player: cx.theme().players().local(),
23675 text: text_style,
23676 ..Default::default()
23677 },
23678 )
23679 }
23680}
23681
23682impl Render for BreakpointPromptEditor {
23683 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23684 let editor_margins = *self.editor_margins.lock();
23685 let gutter_dimensions = editor_margins.gutter;
23686 h_flex()
23687 .key_context("Editor")
23688 .bg(cx.theme().colors().editor_background)
23689 .border_y_1()
23690 .border_color(cx.theme().status().info_border)
23691 .size_full()
23692 .py(window.line_height() / 2.5)
23693 .on_action(cx.listener(Self::confirm))
23694 .on_action(cx.listener(Self::cancel))
23695 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23696 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23697 }
23698}
23699
23700impl Focusable for BreakpointPromptEditor {
23701 fn focus_handle(&self, cx: &App) -> FocusHandle {
23702 self.prompt.focus_handle(cx)
23703 }
23704}
23705
23706fn all_edits_insertions_or_deletions(
23707 edits: &Vec<(Range<Anchor>, String)>,
23708 snapshot: &MultiBufferSnapshot,
23709) -> bool {
23710 let mut all_insertions = true;
23711 let mut all_deletions = true;
23712
23713 for (range, new_text) in edits.iter() {
23714 let range_is_empty = range.to_offset(&snapshot).is_empty();
23715 let text_is_empty = new_text.is_empty();
23716
23717 if range_is_empty != text_is_empty {
23718 if range_is_empty {
23719 all_deletions = false;
23720 } else {
23721 all_insertions = false;
23722 }
23723 } else {
23724 return false;
23725 }
23726
23727 if !all_insertions && !all_deletions {
23728 return false;
23729 }
23730 }
23731 all_insertions || all_deletions
23732}
23733
23734struct MissingEditPredictionKeybindingTooltip;
23735
23736impl Render for MissingEditPredictionKeybindingTooltip {
23737 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23738 ui::tooltip_container(window, cx, |container, _, cx| {
23739 container
23740 .flex_shrink_0()
23741 .max_w_80()
23742 .min_h(rems_from_px(124.))
23743 .justify_between()
23744 .child(
23745 v_flex()
23746 .flex_1()
23747 .text_ui_sm(cx)
23748 .child(Label::new("Conflict with Accept Keybinding"))
23749 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23750 )
23751 .child(
23752 h_flex()
23753 .pb_1()
23754 .gap_1()
23755 .items_end()
23756 .w_full()
23757 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23758 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23759 }))
23760 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23761 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23762 })),
23763 )
23764 })
23765 }
23766}
23767
23768#[derive(Debug, Clone, Copy, PartialEq)]
23769pub struct LineHighlight {
23770 pub background: Background,
23771 pub border: Option<gpui::Hsla>,
23772 pub include_gutter: bool,
23773 pub type_id: Option<TypeId>,
23774}
23775
23776struct LineManipulationResult {
23777 pub new_text: String,
23778 pub line_count_before: usize,
23779 pub line_count_after: usize,
23780}
23781
23782fn render_diff_hunk_controls(
23783 row: u32,
23784 status: &DiffHunkStatus,
23785 hunk_range: Range<Anchor>,
23786 is_created_file: bool,
23787 line_height: Pixels,
23788 editor: &Entity<Editor>,
23789 _window: &mut Window,
23790 cx: &mut App,
23791) -> AnyElement {
23792 h_flex()
23793 .h(line_height)
23794 .mr_1()
23795 .gap_1()
23796 .px_0p5()
23797 .pb_1()
23798 .border_x_1()
23799 .border_b_1()
23800 .border_color(cx.theme().colors().border_variant)
23801 .rounded_b_lg()
23802 .bg(cx.theme().colors().editor_background)
23803 .gap_1()
23804 .block_mouse_except_scroll()
23805 .shadow_md()
23806 .child(if status.has_secondary_hunk() {
23807 Button::new(("stage", row as u64), "Stage")
23808 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23809 .tooltip({
23810 let focus_handle = editor.focus_handle(cx);
23811 move |window, cx| {
23812 Tooltip::for_action_in(
23813 "Stage Hunk",
23814 &::git::ToggleStaged,
23815 &focus_handle,
23816 window,
23817 cx,
23818 )
23819 }
23820 })
23821 .on_click({
23822 let editor = editor.clone();
23823 move |_event, _window, cx| {
23824 editor.update(cx, |editor, cx| {
23825 editor.stage_or_unstage_diff_hunks(
23826 true,
23827 vec![hunk_range.start..hunk_range.start],
23828 cx,
23829 );
23830 });
23831 }
23832 })
23833 } else {
23834 Button::new(("unstage", row as u64), "Unstage")
23835 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23836 .tooltip({
23837 let focus_handle = editor.focus_handle(cx);
23838 move |window, cx| {
23839 Tooltip::for_action_in(
23840 "Unstage Hunk",
23841 &::git::ToggleStaged,
23842 &focus_handle,
23843 window,
23844 cx,
23845 )
23846 }
23847 })
23848 .on_click({
23849 let editor = editor.clone();
23850 move |_event, _window, cx| {
23851 editor.update(cx, |editor, cx| {
23852 editor.stage_or_unstage_diff_hunks(
23853 false,
23854 vec![hunk_range.start..hunk_range.start],
23855 cx,
23856 );
23857 });
23858 }
23859 })
23860 })
23861 .child(
23862 Button::new(("restore", row as u64), "Restore")
23863 .tooltip({
23864 let focus_handle = editor.focus_handle(cx);
23865 move |window, cx| {
23866 Tooltip::for_action_in(
23867 "Restore Hunk",
23868 &::git::Restore,
23869 &focus_handle,
23870 window,
23871 cx,
23872 )
23873 }
23874 })
23875 .on_click({
23876 let editor = editor.clone();
23877 move |_event, window, cx| {
23878 editor.update(cx, |editor, cx| {
23879 let snapshot = editor.snapshot(window, cx);
23880 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23881 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23882 });
23883 }
23884 })
23885 .disabled(is_created_file),
23886 )
23887 .when(
23888 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23889 |el| {
23890 el.child(
23891 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23892 .shape(IconButtonShape::Square)
23893 .icon_size(IconSize::Small)
23894 // .disabled(!has_multiple_hunks)
23895 .tooltip({
23896 let focus_handle = editor.focus_handle(cx);
23897 move |window, cx| {
23898 Tooltip::for_action_in(
23899 "Next Hunk",
23900 &GoToHunk,
23901 &focus_handle,
23902 window,
23903 cx,
23904 )
23905 }
23906 })
23907 .on_click({
23908 let editor = editor.clone();
23909 move |_event, window, cx| {
23910 editor.update(cx, |editor, cx| {
23911 let snapshot = editor.snapshot(window, cx);
23912 let position =
23913 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23914 editor.go_to_hunk_before_or_after_position(
23915 &snapshot,
23916 position,
23917 Direction::Next,
23918 window,
23919 cx,
23920 );
23921 editor.expand_selected_diff_hunks(cx);
23922 });
23923 }
23924 }),
23925 )
23926 .child(
23927 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23928 .shape(IconButtonShape::Square)
23929 .icon_size(IconSize::Small)
23930 // .disabled(!has_multiple_hunks)
23931 .tooltip({
23932 let focus_handle = editor.focus_handle(cx);
23933 move |window, cx| {
23934 Tooltip::for_action_in(
23935 "Previous Hunk",
23936 &GoToPreviousHunk,
23937 &focus_handle,
23938 window,
23939 cx,
23940 )
23941 }
23942 })
23943 .on_click({
23944 let editor = editor.clone();
23945 move |_event, window, cx| {
23946 editor.update(cx, |editor, cx| {
23947 let snapshot = editor.snapshot(window, cx);
23948 let point =
23949 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23950 editor.go_to_hunk_before_or_after_position(
23951 &snapshot,
23952 point,
23953 Direction::Prev,
23954 window,
23955 cx,
23956 );
23957 editor.expand_selected_diff_hunks(cx);
23958 });
23959 }
23960 }),
23961 )
23962 },
23963 )
23964 .into_any_element()
23965}