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 project: Option<Entity<Project>>,
1043 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1044 completion_provider: Option<Rc<dyn CompletionProvider>>,
1045 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1046 blink_manager: Entity<BlinkManager>,
1047 show_cursor_names: bool,
1048 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1049 pub show_local_selections: bool,
1050 mode: EditorMode,
1051 show_breadcrumbs: bool,
1052 show_gutter: bool,
1053 show_scrollbars: ScrollbarAxes,
1054 minimap_visibility: MinimapVisibility,
1055 offset_content: bool,
1056 disable_expand_excerpt_buttons: bool,
1057 show_line_numbers: Option<bool>,
1058 use_relative_line_numbers: Option<bool>,
1059 show_git_diff_gutter: Option<bool>,
1060 show_code_actions: Option<bool>,
1061 show_runnables: Option<bool>,
1062 show_breakpoints: Option<bool>,
1063 show_wrap_guides: Option<bool>,
1064 show_indent_guides: Option<bool>,
1065 placeholder_text: Option<Arc<str>>,
1066 highlight_order: usize,
1067 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1068 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1069 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1070 scrollbar_marker_state: ScrollbarMarkerState,
1071 active_indent_guides_state: ActiveIndentGuidesState,
1072 nav_history: Option<ItemNavHistory>,
1073 context_menu: RefCell<Option<CodeContextMenu>>,
1074 context_menu_options: Option<ContextMenuOptions>,
1075 mouse_context_menu: Option<MouseContextMenu>,
1076 completion_tasks: Vec<(CompletionId, Task<()>)>,
1077 inline_blame_popover: Option<InlineBlamePopover>,
1078 inline_blame_popover_show_task: Option<Task<()>>,
1079 signature_help_state: SignatureHelpState,
1080 auto_signature_help: Option<bool>,
1081 find_all_references_task_sources: Vec<Anchor>,
1082 next_completion_id: CompletionId,
1083 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1084 code_actions_task: Option<Task<Result<()>>>,
1085 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1086 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1087 document_highlights_task: Option<Task<()>>,
1088 linked_editing_range_task: Option<Task<Option<()>>>,
1089 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1090 pending_rename: Option<RenameState>,
1091 searchable: bool,
1092 cursor_shape: CursorShape,
1093 current_line_highlight: Option<CurrentLineHighlight>,
1094 collapse_matches: bool,
1095 autoindent_mode: Option<AutoindentMode>,
1096 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1097 input_enabled: bool,
1098 use_modal_editing: bool,
1099 read_only: bool,
1100 leader_id: Option<CollaboratorId>,
1101 remote_id: Option<ViewId>,
1102 pub hover_state: HoverState,
1103 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1104 gutter_hovered: bool,
1105 hovered_link_state: Option<HoveredLinkState>,
1106 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1107 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1108 active_edit_prediction: Option<EditPredictionState>,
1109 /// Used to prevent flickering as the user types while the menu is open
1110 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1111 edit_prediction_settings: EditPredictionSettings,
1112 edit_predictions_hidden_for_vim_mode: bool,
1113 show_edit_predictions_override: Option<bool>,
1114 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1115 edit_prediction_preview: EditPredictionPreview,
1116 edit_prediction_indent_conflict: bool,
1117 edit_prediction_requires_modifier_in_indent_conflict: bool,
1118 inlay_hint_cache: InlayHintCache,
1119 next_inlay_id: usize,
1120 _subscriptions: Vec<Subscription>,
1121 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1122 gutter_dimensions: GutterDimensions,
1123 style: Option<EditorStyle>,
1124 text_style_refinement: Option<TextStyleRefinement>,
1125 next_editor_action_id: EditorActionId,
1126 editor_actions: Rc<
1127 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1128 >,
1129 use_autoclose: bool,
1130 use_auto_surround: bool,
1131 auto_replace_emoji_shortcode: bool,
1132 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1133 show_git_blame_gutter: bool,
1134 show_git_blame_inline: bool,
1135 show_git_blame_inline_delay_task: Option<Task<()>>,
1136 git_blame_inline_enabled: bool,
1137 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1138 serialize_dirty_buffers: bool,
1139 show_selection_menu: Option<bool>,
1140 blame: Option<Entity<GitBlame>>,
1141 blame_subscription: Option<Subscription>,
1142 custom_context_menu: Option<
1143 Box<
1144 dyn 'static
1145 + Fn(
1146 &mut Self,
1147 DisplayPoint,
1148 &mut Window,
1149 &mut Context<Self>,
1150 ) -> Option<Entity<ui::ContextMenu>>,
1151 >,
1152 >,
1153 last_bounds: Option<Bounds<Pixels>>,
1154 last_position_map: Option<Rc<PositionMap>>,
1155 expect_bounds_change: Option<Bounds<Pixels>>,
1156 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1157 tasks_update_task: Option<Task<()>>,
1158 breakpoint_store: Option<Entity<BreakpointStore>>,
1159 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1160 hovered_diff_hunk_row: Option<DisplayRow>,
1161 pull_diagnostics_task: Task<()>,
1162 in_project_search: bool,
1163 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1164 breadcrumb_header: Option<String>,
1165 focused_block: Option<FocusedBlock>,
1166 next_scroll_position: NextScrollCursorCenterTopBottom,
1167 addons: HashMap<TypeId, Box<dyn Addon>>,
1168 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1169 load_diff_task: Option<Shared<Task<()>>>,
1170 /// Whether we are temporarily displaying a diff other than git's
1171 temporary_diff_override: bool,
1172 selection_mark_mode: bool,
1173 toggle_fold_multiple_buffers: Task<()>,
1174 _scroll_cursor_center_top_bottom_task: Task<()>,
1175 serialize_selections: Task<()>,
1176 serialize_folds: Task<()>,
1177 mouse_cursor_hidden: bool,
1178 minimap: Option<Entity<Self>>,
1179 hide_mouse_mode: HideMouseMode,
1180 pub change_list: ChangeList,
1181 inline_value_cache: InlineValueCache,
1182 selection_drag_state: SelectionDragState,
1183 next_color_inlay_id: usize,
1184 colors: Option<LspColorData>,
1185 folding_newlines: Task<()>,
1186}
1187
1188#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1189enum NextScrollCursorCenterTopBottom {
1190 #[default]
1191 Center,
1192 Top,
1193 Bottom,
1194}
1195
1196impl NextScrollCursorCenterTopBottom {
1197 fn next(&self) -> Self {
1198 match self {
1199 Self::Center => Self::Top,
1200 Self::Top => Self::Bottom,
1201 Self::Bottom => Self::Center,
1202 }
1203 }
1204}
1205
1206#[derive(Clone)]
1207pub struct EditorSnapshot {
1208 pub mode: EditorMode,
1209 show_gutter: bool,
1210 show_line_numbers: Option<bool>,
1211 show_git_diff_gutter: Option<bool>,
1212 show_code_actions: Option<bool>,
1213 show_runnables: Option<bool>,
1214 show_breakpoints: Option<bool>,
1215 git_blame_gutter_max_author_length: Option<usize>,
1216 pub display_snapshot: DisplaySnapshot,
1217 pub placeholder_text: Option<Arc<str>>,
1218 is_focused: bool,
1219 scroll_anchor: ScrollAnchor,
1220 ongoing_scroll: OngoingScroll,
1221 current_line_highlight: CurrentLineHighlight,
1222 gutter_hovered: bool,
1223}
1224
1225#[derive(Default, Debug, Clone, Copy)]
1226pub struct GutterDimensions {
1227 pub left_padding: Pixels,
1228 pub right_padding: Pixels,
1229 pub width: Pixels,
1230 pub margin: Pixels,
1231 pub git_blame_entries_width: Option<Pixels>,
1232}
1233
1234impl GutterDimensions {
1235 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1236 Self {
1237 margin: Self::default_gutter_margin(font_id, font_size, cx),
1238 ..Default::default()
1239 }
1240 }
1241
1242 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1243 -cx.text_system().descent(font_id, font_size)
1244 }
1245 /// The full width of the space taken up by the gutter.
1246 pub fn full_width(&self) -> Pixels {
1247 self.margin + self.width
1248 }
1249
1250 /// The width of the space reserved for the fold indicators,
1251 /// use alongside 'justify_end' and `gutter_width` to
1252 /// right align content with the line numbers
1253 pub fn fold_area_width(&self) -> Pixels {
1254 self.margin + self.right_padding
1255 }
1256}
1257
1258struct CharacterDimensions {
1259 em_width: Pixels,
1260 em_advance: Pixels,
1261 line_height: Pixels,
1262}
1263
1264#[derive(Debug)]
1265pub struct RemoteSelection {
1266 pub replica_id: ReplicaId,
1267 pub selection: Selection<Anchor>,
1268 pub cursor_shape: CursorShape,
1269 pub collaborator_id: CollaboratorId,
1270 pub line_mode: bool,
1271 pub user_name: Option<SharedString>,
1272 pub color: PlayerColor,
1273}
1274
1275#[derive(Clone, Debug)]
1276struct SelectionHistoryEntry {
1277 selections: Arc<[Selection<Anchor>]>,
1278 select_next_state: Option<SelectNextState>,
1279 select_prev_state: Option<SelectNextState>,
1280 add_selections_state: Option<AddSelectionsState>,
1281}
1282
1283#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1284enum SelectionHistoryMode {
1285 Normal,
1286 Undoing,
1287 Redoing,
1288 Skipping,
1289}
1290
1291#[derive(Clone, PartialEq, Eq, Hash)]
1292struct HoveredCursor {
1293 replica_id: u16,
1294 selection_id: usize,
1295}
1296
1297impl Default for SelectionHistoryMode {
1298 fn default() -> Self {
1299 Self::Normal
1300 }
1301}
1302
1303#[derive(Debug)]
1304/// SelectionEffects controls the side-effects of updating the selection.
1305///
1306/// The default behaviour does "what you mostly want":
1307/// - it pushes to the nav history if the cursor moved by >10 lines
1308/// - it re-triggers completion requests
1309/// - it scrolls to fit
1310///
1311/// You might want to modify these behaviours. For example when doing a "jump"
1312/// like go to definition, we always want to add to nav history; but when scrolling
1313/// in vim mode we never do.
1314///
1315/// Similarly, you might want to disable scrolling if you don't want the viewport to
1316/// move.
1317#[derive(Clone)]
1318pub struct SelectionEffects {
1319 nav_history: Option<bool>,
1320 completions: bool,
1321 scroll: Option<Autoscroll>,
1322}
1323
1324impl Default for SelectionEffects {
1325 fn default() -> Self {
1326 Self {
1327 nav_history: None,
1328 completions: true,
1329 scroll: Some(Autoscroll::fit()),
1330 }
1331 }
1332}
1333impl SelectionEffects {
1334 pub fn scroll(scroll: Autoscroll) -> Self {
1335 Self {
1336 scroll: Some(scroll),
1337 ..Default::default()
1338 }
1339 }
1340
1341 pub fn no_scroll() -> Self {
1342 Self {
1343 scroll: None,
1344 ..Default::default()
1345 }
1346 }
1347
1348 pub fn completions(self, completions: bool) -> Self {
1349 Self {
1350 completions,
1351 ..self
1352 }
1353 }
1354
1355 pub fn nav_history(self, nav_history: bool) -> Self {
1356 Self {
1357 nav_history: Some(nav_history),
1358 ..self
1359 }
1360 }
1361}
1362
1363struct DeferredSelectionEffectsState {
1364 changed: bool,
1365 effects: SelectionEffects,
1366 old_cursor_position: Anchor,
1367 history_entry: SelectionHistoryEntry,
1368}
1369
1370#[derive(Default)]
1371struct SelectionHistory {
1372 #[allow(clippy::type_complexity)]
1373 selections_by_transaction:
1374 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1375 mode: SelectionHistoryMode,
1376 undo_stack: VecDeque<SelectionHistoryEntry>,
1377 redo_stack: VecDeque<SelectionHistoryEntry>,
1378}
1379
1380impl SelectionHistory {
1381 #[track_caller]
1382 fn insert_transaction(
1383 &mut self,
1384 transaction_id: TransactionId,
1385 selections: Arc<[Selection<Anchor>]>,
1386 ) {
1387 if selections.is_empty() {
1388 log::error!(
1389 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1390 std::panic::Location::caller()
1391 );
1392 return;
1393 }
1394 self.selections_by_transaction
1395 .insert(transaction_id, (selections, None));
1396 }
1397
1398 #[allow(clippy::type_complexity)]
1399 fn transaction(
1400 &self,
1401 transaction_id: TransactionId,
1402 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1403 self.selections_by_transaction.get(&transaction_id)
1404 }
1405
1406 #[allow(clippy::type_complexity)]
1407 fn transaction_mut(
1408 &mut self,
1409 transaction_id: TransactionId,
1410 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1411 self.selections_by_transaction.get_mut(&transaction_id)
1412 }
1413
1414 fn push(&mut self, entry: SelectionHistoryEntry) {
1415 if !entry.selections.is_empty() {
1416 match self.mode {
1417 SelectionHistoryMode::Normal => {
1418 self.push_undo(entry);
1419 self.redo_stack.clear();
1420 }
1421 SelectionHistoryMode::Undoing => self.push_redo(entry),
1422 SelectionHistoryMode::Redoing => self.push_undo(entry),
1423 SelectionHistoryMode::Skipping => {}
1424 }
1425 }
1426 }
1427
1428 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1429 if self
1430 .undo_stack
1431 .back()
1432 .map_or(true, |e| e.selections != entry.selections)
1433 {
1434 self.undo_stack.push_back(entry);
1435 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1436 self.undo_stack.pop_front();
1437 }
1438 }
1439 }
1440
1441 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1442 if self
1443 .redo_stack
1444 .back()
1445 .map_or(true, |e| e.selections != entry.selections)
1446 {
1447 self.redo_stack.push_back(entry);
1448 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1449 self.redo_stack.pop_front();
1450 }
1451 }
1452 }
1453}
1454
1455#[derive(Clone, Copy)]
1456pub struct RowHighlightOptions {
1457 pub autoscroll: bool,
1458 pub include_gutter: bool,
1459}
1460
1461impl Default for RowHighlightOptions {
1462 fn default() -> Self {
1463 Self {
1464 autoscroll: Default::default(),
1465 include_gutter: true,
1466 }
1467 }
1468}
1469
1470struct RowHighlight {
1471 index: usize,
1472 range: Range<Anchor>,
1473 color: Hsla,
1474 options: RowHighlightOptions,
1475 type_id: TypeId,
1476}
1477
1478#[derive(Clone, Debug)]
1479struct AddSelectionsState {
1480 groups: Vec<AddSelectionsGroup>,
1481}
1482
1483#[derive(Clone, Debug)]
1484struct AddSelectionsGroup {
1485 above: bool,
1486 stack: Vec<usize>,
1487}
1488
1489#[derive(Clone)]
1490struct SelectNextState {
1491 query: AhoCorasick,
1492 wordwise: bool,
1493 done: bool,
1494}
1495
1496impl std::fmt::Debug for SelectNextState {
1497 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1498 f.debug_struct(std::any::type_name::<Self>())
1499 .field("wordwise", &self.wordwise)
1500 .field("done", &self.done)
1501 .finish()
1502 }
1503}
1504
1505#[derive(Debug)]
1506struct AutocloseRegion {
1507 selection_id: usize,
1508 range: Range<Anchor>,
1509 pair: BracketPair,
1510}
1511
1512#[derive(Debug)]
1513struct SnippetState {
1514 ranges: Vec<Vec<Range<Anchor>>>,
1515 active_index: usize,
1516 choices: Vec<Option<Vec<String>>>,
1517}
1518
1519#[doc(hidden)]
1520pub struct RenameState {
1521 pub range: Range<Anchor>,
1522 pub old_name: Arc<str>,
1523 pub editor: Entity<Editor>,
1524 block_id: CustomBlockId,
1525}
1526
1527struct InvalidationStack<T>(Vec<T>);
1528
1529struct RegisteredEditPredictionProvider {
1530 provider: Arc<dyn EditPredictionProviderHandle>,
1531 _subscription: Subscription,
1532}
1533
1534#[derive(Debug, PartialEq, Eq)]
1535pub struct ActiveDiagnosticGroup {
1536 pub active_range: Range<Anchor>,
1537 pub active_message: String,
1538 pub group_id: usize,
1539 pub blocks: HashSet<CustomBlockId>,
1540}
1541
1542#[derive(Debug, PartialEq, Eq)]
1543
1544pub(crate) enum ActiveDiagnostic {
1545 None,
1546 All,
1547 Group(ActiveDiagnosticGroup),
1548}
1549
1550#[derive(Serialize, Deserialize, Clone, Debug)]
1551pub struct ClipboardSelection {
1552 /// The number of bytes in this selection.
1553 pub len: usize,
1554 /// Whether this was a full-line selection.
1555 pub is_entire_line: bool,
1556 /// The indentation of the first line when this content was originally copied.
1557 pub first_line_indent: u32,
1558}
1559
1560// selections, scroll behavior, was newest selection reversed
1561type SelectSyntaxNodeHistoryState = (
1562 Box<[Selection<usize>]>,
1563 SelectSyntaxNodeScrollBehavior,
1564 bool,
1565);
1566
1567#[derive(Default)]
1568struct SelectSyntaxNodeHistory {
1569 stack: Vec<SelectSyntaxNodeHistoryState>,
1570 // disable temporarily to allow changing selections without losing the stack
1571 pub disable_clearing: bool,
1572}
1573
1574impl SelectSyntaxNodeHistory {
1575 pub fn try_clear(&mut self) {
1576 if !self.disable_clearing {
1577 self.stack.clear();
1578 }
1579 }
1580
1581 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1582 self.stack.push(selection);
1583 }
1584
1585 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1586 self.stack.pop()
1587 }
1588}
1589
1590enum SelectSyntaxNodeScrollBehavior {
1591 CursorTop,
1592 FitSelection,
1593 CursorBottom,
1594}
1595
1596#[derive(Debug)]
1597pub(crate) struct NavigationData {
1598 cursor_anchor: Anchor,
1599 cursor_position: Point,
1600 scroll_anchor: ScrollAnchor,
1601 scroll_top_row: u32,
1602}
1603
1604#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1605pub enum GotoDefinitionKind {
1606 Symbol,
1607 Declaration,
1608 Type,
1609 Implementation,
1610}
1611
1612#[derive(Debug, Clone)]
1613enum InlayHintRefreshReason {
1614 ModifiersChanged(bool),
1615 Toggle(bool),
1616 SettingsChange(InlayHintSettings),
1617 NewLinesShown,
1618 BufferEdited(HashSet<Arc<Language>>),
1619 RefreshRequested,
1620 ExcerptsRemoved(Vec<ExcerptId>),
1621}
1622
1623impl InlayHintRefreshReason {
1624 fn description(&self) -> &'static str {
1625 match self {
1626 Self::ModifiersChanged(_) => "modifiers changed",
1627 Self::Toggle(_) => "toggle",
1628 Self::SettingsChange(_) => "settings change",
1629 Self::NewLinesShown => "new lines shown",
1630 Self::BufferEdited(_) => "buffer edited",
1631 Self::RefreshRequested => "refresh requested",
1632 Self::ExcerptsRemoved(_) => "excerpts removed",
1633 }
1634 }
1635}
1636
1637pub enum FormatTarget {
1638 Buffers(HashSet<Entity<Buffer>>),
1639 Ranges(Vec<Range<MultiBufferPoint>>),
1640}
1641
1642pub(crate) struct FocusedBlock {
1643 id: BlockId,
1644 focus_handle: WeakFocusHandle,
1645}
1646
1647#[derive(Clone)]
1648enum JumpData {
1649 MultiBufferRow {
1650 row: MultiBufferRow,
1651 line_offset_from_top: u32,
1652 },
1653 MultiBufferPoint {
1654 excerpt_id: ExcerptId,
1655 position: Point,
1656 anchor: text::Anchor,
1657 line_offset_from_top: u32,
1658 },
1659}
1660
1661pub enum MultibufferSelectionMode {
1662 First,
1663 All,
1664}
1665
1666#[derive(Clone, Copy, Debug, Default)]
1667pub struct RewrapOptions {
1668 pub override_language_settings: bool,
1669 pub preserve_existing_whitespace: bool,
1670}
1671
1672impl Editor {
1673 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1674 let buffer = cx.new(|cx| Buffer::local("", cx));
1675 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1676 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1677 }
1678
1679 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1680 let buffer = cx.new(|cx| Buffer::local("", cx));
1681 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1682 Self::new(EditorMode::full(), buffer, None, window, cx)
1683 }
1684
1685 pub fn auto_height(
1686 min_lines: usize,
1687 max_lines: usize,
1688 window: &mut Window,
1689 cx: &mut Context<Self>,
1690 ) -> Self {
1691 let buffer = cx.new(|cx| Buffer::local("", cx));
1692 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1693 Self::new(
1694 EditorMode::AutoHeight {
1695 min_lines,
1696 max_lines: Some(max_lines),
1697 },
1698 buffer,
1699 None,
1700 window,
1701 cx,
1702 )
1703 }
1704
1705 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1706 /// The editor grows as tall as needed to fit its content.
1707 pub fn auto_height_unbounded(
1708 min_lines: usize,
1709 window: &mut Window,
1710 cx: &mut Context<Self>,
1711 ) -> Self {
1712 let buffer = cx.new(|cx| Buffer::local("", cx));
1713 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1714 Self::new(
1715 EditorMode::AutoHeight {
1716 min_lines,
1717 max_lines: None,
1718 },
1719 buffer,
1720 None,
1721 window,
1722 cx,
1723 )
1724 }
1725
1726 pub fn for_buffer(
1727 buffer: Entity<Buffer>,
1728 project: Option<Entity<Project>>,
1729 window: &mut Window,
1730 cx: &mut Context<Self>,
1731 ) -> Self {
1732 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1733 Self::new(EditorMode::full(), buffer, project, window, cx)
1734 }
1735
1736 pub fn for_multibuffer(
1737 buffer: Entity<MultiBuffer>,
1738 project: Option<Entity<Project>>,
1739 window: &mut Window,
1740 cx: &mut Context<Self>,
1741 ) -> Self {
1742 Self::new(EditorMode::full(), buffer, project, window, cx)
1743 }
1744
1745 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1746 let mut clone = Self::new(
1747 self.mode.clone(),
1748 self.buffer.clone(),
1749 self.project.clone(),
1750 window,
1751 cx,
1752 );
1753 self.display_map.update(cx, |display_map, cx| {
1754 let snapshot = display_map.snapshot(cx);
1755 clone.display_map.update(cx, |display_map, cx| {
1756 display_map.set_state(&snapshot, cx);
1757 });
1758 });
1759 clone.folds_did_change(cx);
1760 clone.selections.clone_state(&self.selections);
1761 clone.scroll_manager.clone_state(&self.scroll_manager);
1762 clone.searchable = self.searchable;
1763 clone.read_only = self.read_only;
1764 clone
1765 }
1766
1767 pub fn new(
1768 mode: EditorMode,
1769 buffer: Entity<MultiBuffer>,
1770 project: Option<Entity<Project>>,
1771 window: &mut Window,
1772 cx: &mut Context<Self>,
1773 ) -> Self {
1774 Editor::new_internal(mode, buffer, project, None, window, cx)
1775 }
1776
1777 fn new_internal(
1778 mode: EditorMode,
1779 buffer: Entity<MultiBuffer>,
1780 project: Option<Entity<Project>>,
1781 display_map: Option<Entity<DisplayMap>>,
1782 window: &mut Window,
1783 cx: &mut Context<Self>,
1784 ) -> Self {
1785 debug_assert!(
1786 display_map.is_none() || mode.is_minimap(),
1787 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1788 );
1789
1790 let full_mode = mode.is_full();
1791 let is_minimap = mode.is_minimap();
1792 let diagnostics_max_severity = if full_mode {
1793 EditorSettings::get_global(cx)
1794 .diagnostics_max_severity
1795 .unwrap_or(DiagnosticSeverity::Hint)
1796 } else {
1797 DiagnosticSeverity::Off
1798 };
1799 let style = window.text_style();
1800 let font_size = style.font_size.to_pixels(window.rem_size());
1801 let editor = cx.entity().downgrade();
1802 let fold_placeholder = FoldPlaceholder {
1803 constrain_width: true,
1804 render: Arc::new(move |fold_id, fold_range, cx| {
1805 let editor = editor.clone();
1806 div()
1807 .id(fold_id)
1808 .bg(cx.theme().colors().ghost_element_background)
1809 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1810 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1811 .rounded_xs()
1812 .size_full()
1813 .cursor_pointer()
1814 .child("⋯")
1815 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1816 .on_click(move |_, _window, cx| {
1817 editor
1818 .update(cx, |editor, cx| {
1819 editor.unfold_ranges(
1820 &[fold_range.start..fold_range.end],
1821 true,
1822 false,
1823 cx,
1824 );
1825 cx.stop_propagation();
1826 })
1827 .ok();
1828 })
1829 .into_any()
1830 }),
1831 merge_adjacent: true,
1832 ..FoldPlaceholder::default()
1833 };
1834 let display_map = display_map.unwrap_or_else(|| {
1835 cx.new(|cx| {
1836 DisplayMap::new(
1837 buffer.clone(),
1838 style.font(),
1839 font_size,
1840 None,
1841 FILE_HEADER_HEIGHT,
1842 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1843 fold_placeholder,
1844 diagnostics_max_severity,
1845 cx,
1846 )
1847 })
1848 });
1849
1850 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1851
1852 let blink_manager = cx.new(|cx| {
1853 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1854 if is_minimap {
1855 blink_manager.disable(cx);
1856 }
1857 blink_manager
1858 });
1859
1860 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1861 .then(|| language_settings::SoftWrap::None);
1862
1863 let mut project_subscriptions = Vec::new();
1864 if full_mode {
1865 if let Some(project) = project.as_ref() {
1866 project_subscriptions.push(cx.subscribe_in(
1867 project,
1868 window,
1869 |editor, _, event, window, cx| match event {
1870 project::Event::RefreshCodeLens => {
1871 // we always query lens with actions, without storing them, always refreshing them
1872 }
1873 project::Event::RefreshInlayHints => {
1874 editor
1875 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1876 }
1877 project::Event::LanguageServerAdded(..)
1878 | project::Event::LanguageServerRemoved(..) => {
1879 if editor.tasks_update_task.is_none() {
1880 editor.tasks_update_task =
1881 Some(editor.refresh_runnables(window, cx));
1882 }
1883 }
1884 project::Event::SnippetEdit(id, snippet_edits) => {
1885 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1886 let focus_handle = editor.focus_handle(cx);
1887 if focus_handle.is_focused(window) {
1888 let snapshot = buffer.read(cx).snapshot();
1889 for (range, snippet) in snippet_edits {
1890 let editor_range =
1891 language::range_from_lsp(*range).to_offset(&snapshot);
1892 editor
1893 .insert_snippet(
1894 &[editor_range],
1895 snippet.clone(),
1896 window,
1897 cx,
1898 )
1899 .ok();
1900 }
1901 }
1902 }
1903 }
1904 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1905 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1906 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1907 }
1908 }
1909 _ => {}
1910 },
1911 ));
1912 if let Some(task_inventory) = project
1913 .read(cx)
1914 .task_store()
1915 .read(cx)
1916 .task_inventory()
1917 .cloned()
1918 {
1919 project_subscriptions.push(cx.observe_in(
1920 &task_inventory,
1921 window,
1922 |editor, _, window, cx| {
1923 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1924 },
1925 ));
1926 };
1927
1928 project_subscriptions.push(cx.subscribe_in(
1929 &project.read(cx).breakpoint_store(),
1930 window,
1931 |editor, _, event, window, cx| match event {
1932 BreakpointStoreEvent::ClearDebugLines => {
1933 editor.clear_row_highlights::<ActiveDebugLine>();
1934 editor.refresh_inline_values(cx);
1935 }
1936 BreakpointStoreEvent::SetDebugLine => {
1937 if editor.go_to_active_debug_line(window, cx) {
1938 cx.stop_propagation();
1939 }
1940
1941 editor.refresh_inline_values(cx);
1942 }
1943 _ => {}
1944 },
1945 ));
1946 let git_store = project.read(cx).git_store().clone();
1947 let project = project.clone();
1948 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1949 match event {
1950 GitStoreEvent::RepositoryUpdated(
1951 _,
1952 RepositoryEvent::Updated {
1953 new_instance: true, ..
1954 },
1955 _,
1956 ) => {
1957 this.load_diff_task = Some(
1958 update_uncommitted_diff_for_buffer(
1959 cx.entity(),
1960 &project,
1961 this.buffer.read(cx).all_buffers(),
1962 this.buffer.clone(),
1963 cx,
1964 )
1965 .shared(),
1966 );
1967 }
1968 _ => {}
1969 }
1970 }));
1971 }
1972 }
1973
1974 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1975
1976 let inlay_hint_settings =
1977 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1978 let focus_handle = cx.focus_handle();
1979 if !is_minimap {
1980 cx.on_focus(&focus_handle, window, Self::handle_focus)
1981 .detach();
1982 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1983 .detach();
1984 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1985 .detach();
1986 cx.on_blur(&focus_handle, window, Self::handle_blur)
1987 .detach();
1988 cx.observe_pending_input(window, Self::observe_pending_input)
1989 .detach();
1990 }
1991
1992 let show_indent_guides = if matches!(
1993 mode,
1994 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1995 ) {
1996 Some(false)
1997 } else {
1998 None
1999 };
2000
2001 let breakpoint_store = match (&mode, project.as_ref()) {
2002 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2003 _ => None,
2004 };
2005
2006 let mut code_action_providers = Vec::new();
2007 let mut load_uncommitted_diff = None;
2008 if let Some(project) = project.clone() {
2009 load_uncommitted_diff = Some(
2010 update_uncommitted_diff_for_buffer(
2011 cx.entity(),
2012 &project,
2013 buffer.read(cx).all_buffers(),
2014 buffer.clone(),
2015 cx,
2016 )
2017 .shared(),
2018 );
2019 code_action_providers.push(Rc::new(project) as Rc<_>);
2020 }
2021
2022 let mut editor = Self {
2023 focus_handle,
2024 show_cursor_when_unfocused: false,
2025 last_focused_descendant: None,
2026 buffer: buffer.clone(),
2027 display_map: display_map.clone(),
2028 selections,
2029 scroll_manager: ScrollManager::new(cx),
2030 columnar_selection_state: None,
2031 add_selections_state: None,
2032 select_next_state: None,
2033 select_prev_state: None,
2034 selection_history: SelectionHistory::default(),
2035 defer_selection_effects: false,
2036 deferred_selection_effects_state: None,
2037 autoclose_regions: Vec::new(),
2038 snippet_stack: InvalidationStack::default(),
2039 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2040 ime_transaction: None,
2041 active_diagnostics: ActiveDiagnostic::None,
2042 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2043 inline_diagnostics_update: Task::ready(()),
2044 inline_diagnostics: Vec::new(),
2045 soft_wrap_mode_override,
2046 diagnostics_max_severity,
2047 hard_wrap: None,
2048 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2049 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2050 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2051 project,
2052 blink_manager: blink_manager.clone(),
2053 show_local_selections: true,
2054 show_scrollbars: ScrollbarAxes {
2055 horizontal: full_mode,
2056 vertical: full_mode,
2057 },
2058 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2059 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2060 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2061 show_gutter: full_mode,
2062 show_line_numbers: (!full_mode).then_some(false),
2063 use_relative_line_numbers: None,
2064 disable_expand_excerpt_buttons: !full_mode,
2065 show_git_diff_gutter: None,
2066 show_code_actions: None,
2067 show_runnables: None,
2068 show_breakpoints: None,
2069 show_wrap_guides: None,
2070 show_indent_guides,
2071 placeholder_text: None,
2072 highlight_order: 0,
2073 highlighted_rows: HashMap::default(),
2074 background_highlights: TreeMap::default(),
2075 gutter_highlights: TreeMap::default(),
2076 scrollbar_marker_state: ScrollbarMarkerState::default(),
2077 active_indent_guides_state: ActiveIndentGuidesState::default(),
2078 nav_history: None,
2079 context_menu: RefCell::new(None),
2080 context_menu_options: None,
2081 mouse_context_menu: None,
2082 completion_tasks: Vec::new(),
2083 inline_blame_popover: None,
2084 inline_blame_popover_show_task: None,
2085 signature_help_state: SignatureHelpState::default(),
2086 auto_signature_help: None,
2087 find_all_references_task_sources: Vec::new(),
2088 next_completion_id: 0,
2089 next_inlay_id: 0,
2090 code_action_providers,
2091 available_code_actions: None,
2092 code_actions_task: None,
2093 quick_selection_highlight_task: None,
2094 debounced_selection_highlight_task: None,
2095 document_highlights_task: None,
2096 linked_editing_range_task: None,
2097 pending_rename: None,
2098 searchable: !is_minimap,
2099 cursor_shape: EditorSettings::get_global(cx)
2100 .cursor_shape
2101 .unwrap_or_default(),
2102 current_line_highlight: None,
2103 autoindent_mode: Some(AutoindentMode::EachLine),
2104 collapse_matches: false,
2105 workspace: None,
2106 input_enabled: !is_minimap,
2107 use_modal_editing: full_mode,
2108 read_only: is_minimap,
2109 use_autoclose: true,
2110 use_auto_surround: true,
2111 auto_replace_emoji_shortcode: false,
2112 jsx_tag_auto_close_enabled_in_any_buffer: false,
2113 leader_id: None,
2114 remote_id: None,
2115 hover_state: HoverState::default(),
2116 pending_mouse_down: None,
2117 hovered_link_state: None,
2118 edit_prediction_provider: None,
2119 active_edit_prediction: None,
2120 stale_edit_prediction_in_menu: None,
2121 edit_prediction_preview: EditPredictionPreview::Inactive {
2122 released_too_fast: false,
2123 },
2124 inline_diagnostics_enabled: full_mode,
2125 diagnostics_enabled: full_mode,
2126 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2127 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2128 gutter_hovered: false,
2129 pixel_position_of_newest_cursor: None,
2130 last_bounds: None,
2131 last_position_map: None,
2132 expect_bounds_change: None,
2133 gutter_dimensions: GutterDimensions::default(),
2134 style: None,
2135 show_cursor_names: false,
2136 hovered_cursors: HashMap::default(),
2137 next_editor_action_id: EditorActionId::default(),
2138 editor_actions: Rc::default(),
2139 edit_predictions_hidden_for_vim_mode: false,
2140 show_edit_predictions_override: None,
2141 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2142 edit_prediction_settings: EditPredictionSettings::Disabled,
2143 edit_prediction_indent_conflict: false,
2144 edit_prediction_requires_modifier_in_indent_conflict: true,
2145 custom_context_menu: None,
2146 show_git_blame_gutter: false,
2147 show_git_blame_inline: false,
2148 show_selection_menu: None,
2149 show_git_blame_inline_delay_task: None,
2150 git_blame_inline_enabled: full_mode
2151 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2152 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2153 serialize_dirty_buffers: !is_minimap
2154 && ProjectSettings::get_global(cx)
2155 .session
2156 .restore_unsaved_buffers,
2157 blame: None,
2158 blame_subscription: None,
2159 tasks: BTreeMap::default(),
2160
2161 breakpoint_store,
2162 gutter_breakpoint_indicator: (None, None),
2163 hovered_diff_hunk_row: None,
2164 _subscriptions: (!is_minimap)
2165 .then(|| {
2166 vec![
2167 cx.observe(&buffer, Self::on_buffer_changed),
2168 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2169 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2170 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2171 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2172 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2173 cx.observe_window_activation(window, |editor, window, cx| {
2174 let active = window.is_window_active();
2175 editor.blink_manager.update(cx, |blink_manager, cx| {
2176 if active {
2177 blink_manager.enable(cx);
2178 } else {
2179 blink_manager.disable(cx);
2180 }
2181 });
2182 if active {
2183 editor.show_mouse_cursor(cx);
2184 }
2185 }),
2186 ]
2187 })
2188 .unwrap_or_default(),
2189 tasks_update_task: None,
2190 pull_diagnostics_task: Task::ready(()),
2191 colors: None,
2192 next_color_inlay_id: 0,
2193 linked_edit_ranges: Default::default(),
2194 in_project_search: false,
2195 previous_search_ranges: None,
2196 breadcrumb_header: None,
2197 focused_block: None,
2198 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2199 addons: HashMap::default(),
2200 registered_buffers: HashMap::default(),
2201 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2202 selection_mark_mode: false,
2203 toggle_fold_multiple_buffers: Task::ready(()),
2204 serialize_selections: Task::ready(()),
2205 serialize_folds: Task::ready(()),
2206 text_style_refinement: None,
2207 load_diff_task: load_uncommitted_diff,
2208 temporary_diff_override: false,
2209 mouse_cursor_hidden: false,
2210 minimap: None,
2211 hide_mouse_mode: EditorSettings::get_global(cx)
2212 .hide_mouse
2213 .unwrap_or_default(),
2214 change_list: ChangeList::new(),
2215 mode,
2216 selection_drag_state: SelectionDragState::None,
2217 folding_newlines: Task::ready(()),
2218 };
2219
2220 if is_minimap {
2221 return editor;
2222 }
2223
2224 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2225 editor
2226 ._subscriptions
2227 .push(cx.observe(breakpoints, |_, _, cx| {
2228 cx.notify();
2229 }));
2230 }
2231 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2232 editor._subscriptions.extend(project_subscriptions);
2233
2234 editor._subscriptions.push(cx.subscribe_in(
2235 &cx.entity(),
2236 window,
2237 |editor, _, e: &EditorEvent, window, cx| match e {
2238 EditorEvent::ScrollPositionChanged { local, .. } => {
2239 if *local {
2240 let new_anchor = editor.scroll_manager.anchor();
2241 let snapshot = editor.snapshot(window, cx);
2242 editor.update_restoration_data(cx, move |data| {
2243 data.scroll_position = (
2244 new_anchor.top_row(&snapshot.buffer_snapshot),
2245 new_anchor.offset,
2246 );
2247 });
2248 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2249 editor.inline_blame_popover.take();
2250 }
2251 }
2252 EditorEvent::Edited { .. } => {
2253 if !vim_enabled(cx) {
2254 let (map, selections) = editor.selections.all_adjusted_display(cx);
2255 let pop_state = editor
2256 .change_list
2257 .last()
2258 .map(|previous| {
2259 previous.len() == selections.len()
2260 && previous.iter().enumerate().all(|(ix, p)| {
2261 p.to_display_point(&map).row()
2262 == selections[ix].head().row()
2263 })
2264 })
2265 .unwrap_or(false);
2266 let new_positions = selections
2267 .into_iter()
2268 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2269 .collect();
2270 editor
2271 .change_list
2272 .push_to_change_list(pop_state, new_positions);
2273 }
2274 }
2275 _ => (),
2276 },
2277 ));
2278
2279 if let Some(dap_store) = editor
2280 .project
2281 .as_ref()
2282 .map(|project| project.read(cx).dap_store())
2283 {
2284 let weak_editor = cx.weak_entity();
2285
2286 editor
2287 ._subscriptions
2288 .push(
2289 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2290 let session_entity = cx.entity();
2291 weak_editor
2292 .update(cx, |editor, cx| {
2293 editor._subscriptions.push(
2294 cx.subscribe(&session_entity, Self::on_debug_session_event),
2295 );
2296 })
2297 .ok();
2298 }),
2299 );
2300
2301 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2302 editor
2303 ._subscriptions
2304 .push(cx.subscribe(&session, Self::on_debug_session_event));
2305 }
2306 }
2307
2308 // skip adding the initial selection to selection history
2309 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2310 editor.end_selection(window, cx);
2311 editor.selection_history.mode = SelectionHistoryMode::Normal;
2312
2313 editor.scroll_manager.show_scrollbars(window, cx);
2314 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2315
2316 if full_mode {
2317 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2318 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2319
2320 if editor.git_blame_inline_enabled {
2321 editor.start_git_blame_inline(false, window, cx);
2322 }
2323
2324 editor.go_to_active_debug_line(window, cx);
2325
2326 if let Some(buffer) = buffer.read(cx).as_singleton() {
2327 if let Some(project) = editor.project() {
2328 let handle = project.update(cx, |project, cx| {
2329 project.register_buffer_with_language_servers(&buffer, cx)
2330 });
2331 editor
2332 .registered_buffers
2333 .insert(buffer.read(cx).remote_id(), handle);
2334 }
2335 }
2336
2337 editor.minimap =
2338 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2339 editor.colors = Some(LspColorData::new(cx));
2340 editor.update_lsp_data(false, None, window, cx);
2341 }
2342
2343 if editor.mode.is_full() {
2344 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2345 }
2346
2347 editor
2348 }
2349
2350 pub fn deploy_mouse_context_menu(
2351 &mut self,
2352 position: gpui::Point<Pixels>,
2353 context_menu: Entity<ContextMenu>,
2354 window: &mut Window,
2355 cx: &mut Context<Self>,
2356 ) {
2357 self.mouse_context_menu = Some(MouseContextMenu::new(
2358 self,
2359 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2360 context_menu,
2361 window,
2362 cx,
2363 ));
2364 }
2365
2366 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2367 self.mouse_context_menu
2368 .as_ref()
2369 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2370 }
2371
2372 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2373 if self
2374 .selections
2375 .pending
2376 .as_ref()
2377 .is_some_and(|pending_selection| {
2378 let snapshot = self.buffer().read(cx).snapshot(cx);
2379 pending_selection
2380 .selection
2381 .range()
2382 .includes(range, &snapshot)
2383 })
2384 {
2385 return true;
2386 }
2387
2388 self.selections
2389 .disjoint_in_range::<usize>(range.clone(), cx)
2390 .into_iter()
2391 .any(|selection| {
2392 // This is needed to cover a corner case, if we just check for an existing
2393 // selection in the fold range, having a cursor at the start of the fold
2394 // marks it as selected. Non-empty selections don't cause this.
2395 let length = selection.end - selection.start;
2396 length > 0
2397 })
2398 }
2399
2400 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2401 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2402 }
2403
2404 fn key_context_internal(
2405 &self,
2406 has_active_edit_prediction: bool,
2407 window: &Window,
2408 cx: &App,
2409 ) -> KeyContext {
2410 let mut key_context = KeyContext::new_with_defaults();
2411 key_context.add("Editor");
2412 let mode = match self.mode {
2413 EditorMode::SingleLine { .. } => "single_line",
2414 EditorMode::AutoHeight { .. } => "auto_height",
2415 EditorMode::Minimap { .. } => "minimap",
2416 EditorMode::Full { .. } => "full",
2417 };
2418
2419 if EditorSettings::jupyter_enabled(cx) {
2420 key_context.add("jupyter");
2421 }
2422
2423 key_context.set("mode", mode);
2424 if self.pending_rename.is_some() {
2425 key_context.add("renaming");
2426 }
2427
2428 match self.context_menu.borrow().as_ref() {
2429 Some(CodeContextMenu::Completions(menu)) => {
2430 if menu.visible() {
2431 key_context.add("menu");
2432 key_context.add("showing_completions");
2433 }
2434 }
2435 Some(CodeContextMenu::CodeActions(menu)) => {
2436 if menu.visible() {
2437 key_context.add("menu");
2438 key_context.add("showing_code_actions")
2439 }
2440 }
2441 None => {}
2442 }
2443
2444 if self.signature_help_state.has_multiple_signatures() {
2445 key_context.add("showing_signature_help");
2446 }
2447
2448 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2449 if !self.focus_handle(cx).contains_focused(window, cx)
2450 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2451 {
2452 for addon in self.addons.values() {
2453 addon.extend_key_context(&mut key_context, cx)
2454 }
2455 }
2456
2457 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2458 if let Some(extension) = singleton_buffer
2459 .read(cx)
2460 .file()
2461 .and_then(|file| file.path().extension()?.to_str())
2462 {
2463 key_context.set("extension", extension.to_string());
2464 }
2465 } else {
2466 key_context.add("multibuffer");
2467 }
2468
2469 if has_active_edit_prediction {
2470 if self.edit_prediction_in_conflict() {
2471 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2472 } else {
2473 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2474 key_context.add("copilot_suggestion");
2475 }
2476 }
2477
2478 if self.selection_mark_mode {
2479 key_context.add("selection_mode");
2480 }
2481
2482 key_context
2483 }
2484
2485 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2486 if self.mouse_cursor_hidden {
2487 self.mouse_cursor_hidden = false;
2488 cx.notify();
2489 }
2490 }
2491
2492 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2493 let hide_mouse_cursor = match origin {
2494 HideMouseCursorOrigin::TypingAction => {
2495 matches!(
2496 self.hide_mouse_mode,
2497 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2498 )
2499 }
2500 HideMouseCursorOrigin::MovementAction => {
2501 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2502 }
2503 };
2504 if self.mouse_cursor_hidden != hide_mouse_cursor {
2505 self.mouse_cursor_hidden = hide_mouse_cursor;
2506 cx.notify();
2507 }
2508 }
2509
2510 pub fn edit_prediction_in_conflict(&self) -> bool {
2511 if !self.show_edit_predictions_in_menu() {
2512 return false;
2513 }
2514
2515 let showing_completions = self
2516 .context_menu
2517 .borrow()
2518 .as_ref()
2519 .map_or(false, |context| {
2520 matches!(context, CodeContextMenu::Completions(_))
2521 });
2522
2523 showing_completions
2524 || self.edit_prediction_requires_modifier()
2525 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2526 // bindings to insert tab characters.
2527 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2528 }
2529
2530 pub fn accept_edit_prediction_keybind(
2531 &self,
2532 accept_partial: bool,
2533 window: &Window,
2534 cx: &App,
2535 ) -> AcceptEditPredictionBinding {
2536 let key_context = self.key_context_internal(true, window, cx);
2537 let in_conflict = self.edit_prediction_in_conflict();
2538
2539 let bindings = if accept_partial {
2540 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2541 } else {
2542 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2543 };
2544
2545 // TODO: if the binding contains multiple keystrokes, display all of them, not
2546 // just the first one.
2547 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2548 !in_conflict
2549 || binding
2550 .keystrokes()
2551 .first()
2552 .map_or(false, |keystroke| keystroke.modifiers.modified())
2553 }))
2554 }
2555
2556 pub fn new_file(
2557 workspace: &mut Workspace,
2558 _: &workspace::NewFile,
2559 window: &mut Window,
2560 cx: &mut Context<Workspace>,
2561 ) {
2562 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2563 "Failed to create buffer",
2564 window,
2565 cx,
2566 |e, _, _| match e.error_code() {
2567 ErrorCode::RemoteUpgradeRequired => Some(format!(
2568 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2569 e.error_tag("required").unwrap_or("the latest version")
2570 )),
2571 _ => None,
2572 },
2573 );
2574 }
2575
2576 pub fn new_in_workspace(
2577 workspace: &mut Workspace,
2578 window: &mut Window,
2579 cx: &mut Context<Workspace>,
2580 ) -> Task<Result<Entity<Editor>>> {
2581 let project = workspace.project().clone();
2582 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2583
2584 cx.spawn_in(window, async move |workspace, cx| {
2585 let buffer = create.await?;
2586 workspace.update_in(cx, |workspace, window, cx| {
2587 let editor =
2588 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2589 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2590 editor
2591 })
2592 })
2593 }
2594
2595 fn new_file_vertical(
2596 workspace: &mut Workspace,
2597 _: &workspace::NewFileSplitVertical,
2598 window: &mut Window,
2599 cx: &mut Context<Workspace>,
2600 ) {
2601 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2602 }
2603
2604 fn new_file_horizontal(
2605 workspace: &mut Workspace,
2606 _: &workspace::NewFileSplitHorizontal,
2607 window: &mut Window,
2608 cx: &mut Context<Workspace>,
2609 ) {
2610 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2611 }
2612
2613 fn new_file_in_direction(
2614 workspace: &mut Workspace,
2615 direction: SplitDirection,
2616 window: &mut Window,
2617 cx: &mut Context<Workspace>,
2618 ) {
2619 let project = workspace.project().clone();
2620 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2621
2622 cx.spawn_in(window, async move |workspace, cx| {
2623 let buffer = create.await?;
2624 workspace.update_in(cx, move |workspace, window, cx| {
2625 workspace.split_item(
2626 direction,
2627 Box::new(
2628 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2629 ),
2630 window,
2631 cx,
2632 )
2633 })?;
2634 anyhow::Ok(())
2635 })
2636 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2637 match e.error_code() {
2638 ErrorCode::RemoteUpgradeRequired => Some(format!(
2639 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2640 e.error_tag("required").unwrap_or("the latest version")
2641 )),
2642 _ => None,
2643 }
2644 });
2645 }
2646
2647 pub fn leader_id(&self) -> Option<CollaboratorId> {
2648 self.leader_id
2649 }
2650
2651 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2652 &self.buffer
2653 }
2654
2655 pub fn project(&self) -> Option<&Entity<Project>> {
2656 self.project.as_ref()
2657 }
2658
2659 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2660 self.workspace.as_ref()?.0.upgrade()
2661 }
2662
2663 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2664 self.buffer().read(cx).title(cx)
2665 }
2666
2667 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2668 let git_blame_gutter_max_author_length = self
2669 .render_git_blame_gutter(cx)
2670 .then(|| {
2671 if let Some(blame) = self.blame.as_ref() {
2672 let max_author_length =
2673 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2674 Some(max_author_length)
2675 } else {
2676 None
2677 }
2678 })
2679 .flatten();
2680
2681 EditorSnapshot {
2682 mode: self.mode.clone(),
2683 show_gutter: self.show_gutter,
2684 show_line_numbers: self.show_line_numbers,
2685 show_git_diff_gutter: self.show_git_diff_gutter,
2686 show_code_actions: self.show_code_actions,
2687 show_runnables: self.show_runnables,
2688 show_breakpoints: self.show_breakpoints,
2689 git_blame_gutter_max_author_length,
2690 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2691 scroll_anchor: self.scroll_manager.anchor(),
2692 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2693 placeholder_text: self.placeholder_text.clone(),
2694 is_focused: self.focus_handle.is_focused(window),
2695 current_line_highlight: self
2696 .current_line_highlight
2697 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2698 gutter_hovered: self.gutter_hovered,
2699 }
2700 }
2701
2702 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2703 self.buffer.read(cx).language_at(point, cx)
2704 }
2705
2706 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2707 self.buffer.read(cx).read(cx).file_at(point).cloned()
2708 }
2709
2710 pub fn active_excerpt(
2711 &self,
2712 cx: &App,
2713 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2714 self.buffer
2715 .read(cx)
2716 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2717 }
2718
2719 pub fn mode(&self) -> &EditorMode {
2720 &self.mode
2721 }
2722
2723 pub fn set_mode(&mut self, mode: EditorMode) {
2724 self.mode = mode;
2725 }
2726
2727 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2728 self.collaboration_hub.as_deref()
2729 }
2730
2731 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2732 self.collaboration_hub = Some(hub);
2733 }
2734
2735 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2736 self.in_project_search = in_project_search;
2737 }
2738
2739 pub fn set_custom_context_menu(
2740 &mut self,
2741 f: impl 'static
2742 + Fn(
2743 &mut Self,
2744 DisplayPoint,
2745 &mut Window,
2746 &mut Context<Self>,
2747 ) -> Option<Entity<ui::ContextMenu>>,
2748 ) {
2749 self.custom_context_menu = Some(Box::new(f))
2750 }
2751
2752 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2753 self.completion_provider = provider;
2754 }
2755
2756 #[cfg(any(test, feature = "test-support"))]
2757 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2758 self.completion_provider.clone()
2759 }
2760
2761 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2762 self.semantics_provider.clone()
2763 }
2764
2765 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2766 self.semantics_provider = provider;
2767 }
2768
2769 pub fn set_edit_prediction_provider<T>(
2770 &mut self,
2771 provider: Option<Entity<T>>,
2772 window: &mut Window,
2773 cx: &mut Context<Self>,
2774 ) where
2775 T: EditPredictionProvider,
2776 {
2777 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2778 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2779 if this.focus_handle.is_focused(window) {
2780 this.update_visible_edit_prediction(window, cx);
2781 }
2782 }),
2783 provider: Arc::new(provider),
2784 });
2785 self.update_edit_prediction_settings(cx);
2786 self.refresh_edit_prediction(false, false, window, cx);
2787 }
2788
2789 pub fn placeholder_text(&self) -> Option<&str> {
2790 self.placeholder_text.as_deref()
2791 }
2792
2793 pub fn set_placeholder_text(
2794 &mut self,
2795 placeholder_text: impl Into<Arc<str>>,
2796 cx: &mut Context<Self>,
2797 ) {
2798 let placeholder_text = Some(placeholder_text.into());
2799 if self.placeholder_text != placeholder_text {
2800 self.placeholder_text = placeholder_text;
2801 cx.notify();
2802 }
2803 }
2804
2805 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2806 self.cursor_shape = cursor_shape;
2807
2808 // Disrupt blink for immediate user feedback that the cursor shape has changed
2809 self.blink_manager.update(cx, BlinkManager::show_cursor);
2810
2811 cx.notify();
2812 }
2813
2814 pub fn set_current_line_highlight(
2815 &mut self,
2816 current_line_highlight: Option<CurrentLineHighlight>,
2817 ) {
2818 self.current_line_highlight = current_line_highlight;
2819 }
2820
2821 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2822 self.collapse_matches = collapse_matches;
2823 }
2824
2825 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2826 let buffers = self.buffer.read(cx).all_buffers();
2827 let Some(project) = self.project.as_ref() else {
2828 return;
2829 };
2830 project.update(cx, |project, cx| {
2831 for buffer in buffers {
2832 self.registered_buffers
2833 .entry(buffer.read(cx).remote_id())
2834 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2835 }
2836 })
2837 }
2838
2839 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2840 if self.collapse_matches {
2841 return range.start..range.start;
2842 }
2843 range.clone()
2844 }
2845
2846 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2847 if self.display_map.read(cx).clip_at_line_ends != clip {
2848 self.display_map
2849 .update(cx, |map, _| map.clip_at_line_ends = clip);
2850 }
2851 }
2852
2853 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2854 self.input_enabled = input_enabled;
2855 }
2856
2857 pub fn set_edit_predictions_hidden_for_vim_mode(
2858 &mut self,
2859 hidden: bool,
2860 window: &mut Window,
2861 cx: &mut Context<Self>,
2862 ) {
2863 if hidden != self.edit_predictions_hidden_for_vim_mode {
2864 self.edit_predictions_hidden_for_vim_mode = hidden;
2865 if hidden {
2866 self.update_visible_edit_prediction(window, cx);
2867 } else {
2868 self.refresh_edit_prediction(true, false, window, cx);
2869 }
2870 }
2871 }
2872
2873 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2874 self.menu_edit_predictions_policy = value;
2875 }
2876
2877 pub fn set_autoindent(&mut self, autoindent: bool) {
2878 if autoindent {
2879 self.autoindent_mode = Some(AutoindentMode::EachLine);
2880 } else {
2881 self.autoindent_mode = None;
2882 }
2883 }
2884
2885 pub fn read_only(&self, cx: &App) -> bool {
2886 self.read_only || self.buffer.read(cx).read_only()
2887 }
2888
2889 pub fn set_read_only(&mut self, read_only: bool) {
2890 self.read_only = read_only;
2891 }
2892
2893 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2894 self.use_autoclose = autoclose;
2895 }
2896
2897 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2898 self.use_auto_surround = auto_surround;
2899 }
2900
2901 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2902 self.auto_replace_emoji_shortcode = auto_replace;
2903 }
2904
2905 pub fn toggle_edit_predictions(
2906 &mut self,
2907 _: &ToggleEditPrediction,
2908 window: &mut Window,
2909 cx: &mut Context<Self>,
2910 ) {
2911 if self.show_edit_predictions_override.is_some() {
2912 self.set_show_edit_predictions(None, window, cx);
2913 } else {
2914 let show_edit_predictions = !self.edit_predictions_enabled();
2915 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2916 }
2917 }
2918
2919 pub fn set_show_edit_predictions(
2920 &mut self,
2921 show_edit_predictions: Option<bool>,
2922 window: &mut Window,
2923 cx: &mut Context<Self>,
2924 ) {
2925 self.show_edit_predictions_override = show_edit_predictions;
2926 self.update_edit_prediction_settings(cx);
2927
2928 if let Some(false) = show_edit_predictions {
2929 self.discard_edit_prediction(false, cx);
2930 } else {
2931 self.refresh_edit_prediction(false, true, window, cx);
2932 }
2933 }
2934
2935 fn edit_predictions_disabled_in_scope(
2936 &self,
2937 buffer: &Entity<Buffer>,
2938 buffer_position: language::Anchor,
2939 cx: &App,
2940 ) -> bool {
2941 let snapshot = buffer.read(cx).snapshot();
2942 let settings = snapshot.settings_at(buffer_position, cx);
2943
2944 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2945 return false;
2946 };
2947
2948 scope.override_name().map_or(false, |scope_name| {
2949 settings
2950 .edit_predictions_disabled_in
2951 .iter()
2952 .any(|s| s == scope_name)
2953 })
2954 }
2955
2956 pub fn set_use_modal_editing(&mut self, to: bool) {
2957 self.use_modal_editing = to;
2958 }
2959
2960 pub fn use_modal_editing(&self) -> bool {
2961 self.use_modal_editing
2962 }
2963
2964 fn selections_did_change(
2965 &mut self,
2966 local: bool,
2967 old_cursor_position: &Anchor,
2968 effects: SelectionEffects,
2969 window: &mut Window,
2970 cx: &mut Context<Self>,
2971 ) {
2972 window.invalidate_character_coordinates();
2973
2974 // Copy selections to primary selection buffer
2975 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2976 if local {
2977 let selections = self.selections.all::<usize>(cx);
2978 let buffer_handle = self.buffer.read(cx).read(cx);
2979
2980 let mut text = String::new();
2981 for (index, selection) in selections.iter().enumerate() {
2982 let text_for_selection = buffer_handle
2983 .text_for_range(selection.start..selection.end)
2984 .collect::<String>();
2985
2986 text.push_str(&text_for_selection);
2987 if index != selections.len() - 1 {
2988 text.push('\n');
2989 }
2990 }
2991
2992 if !text.is_empty() {
2993 cx.write_to_primary(ClipboardItem::new_string(text));
2994 }
2995 }
2996
2997 let selection_anchors = self.selections.disjoint_anchors();
2998
2999 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3000 self.buffer.update(cx, |buffer, cx| {
3001 buffer.set_active_selections(
3002 &selection_anchors,
3003 self.selections.line_mode,
3004 self.cursor_shape,
3005 cx,
3006 )
3007 });
3008 }
3009 let display_map = self
3010 .display_map
3011 .update(cx, |display_map, cx| display_map.snapshot(cx));
3012 let buffer = &display_map.buffer_snapshot;
3013 if self.selections.count() == 1 {
3014 self.add_selections_state = None;
3015 }
3016 self.select_next_state = None;
3017 self.select_prev_state = None;
3018 self.select_syntax_node_history.try_clear();
3019 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3020 self.snippet_stack.invalidate(&selection_anchors, buffer);
3021 self.take_rename(false, window, cx);
3022
3023 let newest_selection = self.selections.newest_anchor();
3024 let new_cursor_position = newest_selection.head();
3025 let selection_start = newest_selection.start;
3026
3027 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3028 self.push_to_nav_history(
3029 *old_cursor_position,
3030 Some(new_cursor_position.to_point(buffer)),
3031 false,
3032 effects.nav_history == Some(true),
3033 cx,
3034 );
3035 }
3036
3037 if local {
3038 if let Some(buffer_id) = new_cursor_position.buffer_id {
3039 if !self.registered_buffers.contains_key(&buffer_id) {
3040 if let Some(project) = self.project.as_ref() {
3041 project.update(cx, |project, cx| {
3042 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3043 return;
3044 };
3045 self.registered_buffers.insert(
3046 buffer_id,
3047 project.register_buffer_with_language_servers(&buffer, cx),
3048 );
3049 })
3050 }
3051 }
3052 }
3053
3054 let mut context_menu = self.context_menu.borrow_mut();
3055 let completion_menu = match context_menu.as_ref() {
3056 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3057 Some(CodeContextMenu::CodeActions(_)) => {
3058 *context_menu = None;
3059 None
3060 }
3061 None => None,
3062 };
3063 let completion_position = completion_menu.map(|menu| menu.initial_position);
3064 drop(context_menu);
3065
3066 if effects.completions {
3067 if let Some(completion_position) = completion_position {
3068 let start_offset = selection_start.to_offset(buffer);
3069 let position_matches = start_offset == completion_position.to_offset(buffer);
3070 let continue_showing = if position_matches {
3071 if self.snippet_stack.is_empty() {
3072 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3073 } else {
3074 // Snippet choices can be shown even when the cursor is in whitespace.
3075 // Dismissing the menu with actions like backspace is handled by
3076 // invalidation regions.
3077 true
3078 }
3079 } else {
3080 false
3081 };
3082
3083 if continue_showing {
3084 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3085 } else {
3086 self.hide_context_menu(window, cx);
3087 }
3088 }
3089 }
3090
3091 hide_hover(self, cx);
3092
3093 if old_cursor_position.to_display_point(&display_map).row()
3094 != new_cursor_position.to_display_point(&display_map).row()
3095 {
3096 self.available_code_actions.take();
3097 }
3098 self.refresh_code_actions(window, cx);
3099 self.refresh_document_highlights(cx);
3100 self.refresh_selected_text_highlights(false, window, cx);
3101 refresh_matching_bracket_highlights(self, window, cx);
3102 self.update_visible_edit_prediction(window, cx);
3103 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3104 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3105 self.inline_blame_popover.take();
3106 if self.git_blame_inline_enabled {
3107 self.start_inline_blame_timer(window, cx);
3108 }
3109 }
3110
3111 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3112 cx.emit(EditorEvent::SelectionsChanged { local });
3113
3114 let selections = &self.selections.disjoint;
3115 if selections.len() == 1 {
3116 cx.emit(SearchEvent::ActiveMatchChanged)
3117 }
3118 if local {
3119 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3120 let inmemory_selections = selections
3121 .iter()
3122 .map(|s| {
3123 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3124 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3125 })
3126 .collect();
3127 self.update_restoration_data(cx, |data| {
3128 data.selections = inmemory_selections;
3129 });
3130
3131 if WorkspaceSettings::get(None, cx).restore_on_startup
3132 != RestoreOnStartupBehavior::None
3133 {
3134 if let Some(workspace_id) =
3135 self.workspace.as_ref().and_then(|workspace| workspace.1)
3136 {
3137 let snapshot = self.buffer().read(cx).snapshot(cx);
3138 let selections = selections.clone();
3139 let background_executor = cx.background_executor().clone();
3140 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3141 self.serialize_selections = cx.background_spawn(async move {
3142 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3143 let db_selections = selections
3144 .iter()
3145 .map(|selection| {
3146 (
3147 selection.start.to_offset(&snapshot),
3148 selection.end.to_offset(&snapshot),
3149 )
3150 })
3151 .collect();
3152
3153 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3154 .await
3155 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3156 .log_err();
3157 });
3158 }
3159 }
3160 }
3161 }
3162
3163 cx.notify();
3164 }
3165
3166 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3167 use text::ToOffset as _;
3168 use text::ToPoint as _;
3169
3170 if self.mode.is_minimap()
3171 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3172 {
3173 return;
3174 }
3175
3176 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3177 return;
3178 };
3179
3180 let snapshot = singleton.read(cx).snapshot();
3181 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3182 let display_snapshot = display_map.snapshot(cx);
3183
3184 display_snapshot
3185 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3186 .map(|fold| {
3187 fold.range.start.text_anchor.to_point(&snapshot)
3188 ..fold.range.end.text_anchor.to_point(&snapshot)
3189 })
3190 .collect()
3191 });
3192 self.update_restoration_data(cx, |data| {
3193 data.folds = inmemory_folds;
3194 });
3195
3196 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3197 return;
3198 };
3199 let background_executor = cx.background_executor().clone();
3200 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3201 let db_folds = self.display_map.update(cx, |display_map, cx| {
3202 display_map
3203 .snapshot(cx)
3204 .folds_in_range(0..snapshot.len())
3205 .map(|fold| {
3206 (
3207 fold.range.start.text_anchor.to_offset(&snapshot),
3208 fold.range.end.text_anchor.to_offset(&snapshot),
3209 )
3210 })
3211 .collect()
3212 });
3213 self.serialize_folds = cx.background_spawn(async move {
3214 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3215 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3216 .await
3217 .with_context(|| {
3218 format!(
3219 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3220 )
3221 })
3222 .log_err();
3223 });
3224 }
3225
3226 pub fn sync_selections(
3227 &mut self,
3228 other: Entity<Editor>,
3229 cx: &mut Context<Self>,
3230 ) -> gpui::Subscription {
3231 let other_selections = other.read(cx).selections.disjoint.to_vec();
3232 self.selections.change_with(cx, |selections| {
3233 selections.select_anchors(other_selections);
3234 });
3235
3236 let other_subscription =
3237 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3238 EditorEvent::SelectionsChanged { local: true } => {
3239 let other_selections = other.read(cx).selections.disjoint.to_vec();
3240 if other_selections.is_empty() {
3241 return;
3242 }
3243 this.selections.change_with(cx, |selections| {
3244 selections.select_anchors(other_selections);
3245 });
3246 }
3247 _ => {}
3248 });
3249
3250 let this_subscription =
3251 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3252 EditorEvent::SelectionsChanged { local: true } => {
3253 let these_selections = this.selections.disjoint.to_vec();
3254 if these_selections.is_empty() {
3255 return;
3256 }
3257 other.update(cx, |other_editor, cx| {
3258 other_editor.selections.change_with(cx, |selections| {
3259 selections.select_anchors(these_selections);
3260 })
3261 });
3262 }
3263 _ => {}
3264 });
3265
3266 Subscription::join(other_subscription, this_subscription)
3267 }
3268
3269 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3270 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3271 /// effects of selection change occur at the end of the transaction.
3272 pub fn change_selections<R>(
3273 &mut self,
3274 effects: SelectionEffects,
3275 window: &mut Window,
3276 cx: &mut Context<Self>,
3277 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3278 ) -> R {
3279 if let Some(state) = &mut self.deferred_selection_effects_state {
3280 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3281 state.effects.completions = effects.completions;
3282 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3283 let (changed, result) = self.selections.change_with(cx, change);
3284 state.changed |= changed;
3285 return result;
3286 }
3287 let mut state = DeferredSelectionEffectsState {
3288 changed: false,
3289 effects,
3290 old_cursor_position: self.selections.newest_anchor().head(),
3291 history_entry: SelectionHistoryEntry {
3292 selections: self.selections.disjoint_anchors(),
3293 select_next_state: self.select_next_state.clone(),
3294 select_prev_state: self.select_prev_state.clone(),
3295 add_selections_state: self.add_selections_state.clone(),
3296 },
3297 };
3298 let (changed, result) = self.selections.change_with(cx, change);
3299 state.changed = state.changed || changed;
3300 if self.defer_selection_effects {
3301 self.deferred_selection_effects_state = Some(state);
3302 } else {
3303 self.apply_selection_effects(state, window, cx);
3304 }
3305 result
3306 }
3307
3308 /// Defers the effects of selection change, so that the effects of multiple calls to
3309 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3310 /// to selection history and the state of popovers based on selection position aren't
3311 /// erroneously updated.
3312 pub fn with_selection_effects_deferred<R>(
3313 &mut self,
3314 window: &mut Window,
3315 cx: &mut Context<Self>,
3316 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3317 ) -> R {
3318 let already_deferred = self.defer_selection_effects;
3319 self.defer_selection_effects = true;
3320 let result = update(self, window, cx);
3321 if !already_deferred {
3322 self.defer_selection_effects = false;
3323 if let Some(state) = self.deferred_selection_effects_state.take() {
3324 self.apply_selection_effects(state, window, cx);
3325 }
3326 }
3327 result
3328 }
3329
3330 fn apply_selection_effects(
3331 &mut self,
3332 state: DeferredSelectionEffectsState,
3333 window: &mut Window,
3334 cx: &mut Context<Self>,
3335 ) {
3336 if state.changed {
3337 self.selection_history.push(state.history_entry);
3338
3339 if let Some(autoscroll) = state.effects.scroll {
3340 self.request_autoscroll(autoscroll, cx);
3341 }
3342
3343 let old_cursor_position = &state.old_cursor_position;
3344
3345 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3346
3347 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3348 self.show_signature_help(&ShowSignatureHelp, window, cx);
3349 }
3350 }
3351 }
3352
3353 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3354 where
3355 I: IntoIterator<Item = (Range<S>, T)>,
3356 S: ToOffset,
3357 T: Into<Arc<str>>,
3358 {
3359 if self.read_only(cx) {
3360 return;
3361 }
3362
3363 self.buffer
3364 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3365 }
3366
3367 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3368 where
3369 I: IntoIterator<Item = (Range<S>, T)>,
3370 S: ToOffset,
3371 T: Into<Arc<str>>,
3372 {
3373 if self.read_only(cx) {
3374 return;
3375 }
3376
3377 self.buffer.update(cx, |buffer, cx| {
3378 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3379 });
3380 }
3381
3382 pub fn edit_with_block_indent<I, S, T>(
3383 &mut self,
3384 edits: I,
3385 original_indent_columns: Vec<Option<u32>>,
3386 cx: &mut Context<Self>,
3387 ) where
3388 I: IntoIterator<Item = (Range<S>, T)>,
3389 S: ToOffset,
3390 T: Into<Arc<str>>,
3391 {
3392 if self.read_only(cx) {
3393 return;
3394 }
3395
3396 self.buffer.update(cx, |buffer, cx| {
3397 buffer.edit(
3398 edits,
3399 Some(AutoindentMode::Block {
3400 original_indent_columns,
3401 }),
3402 cx,
3403 )
3404 });
3405 }
3406
3407 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3408 self.hide_context_menu(window, cx);
3409
3410 match phase {
3411 SelectPhase::Begin {
3412 position,
3413 add,
3414 click_count,
3415 } => self.begin_selection(position, add, click_count, window, cx),
3416 SelectPhase::BeginColumnar {
3417 position,
3418 goal_column,
3419 reset,
3420 mode,
3421 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3422 SelectPhase::Extend {
3423 position,
3424 click_count,
3425 } => self.extend_selection(position, click_count, window, cx),
3426 SelectPhase::Update {
3427 position,
3428 goal_column,
3429 scroll_delta,
3430 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3431 SelectPhase::End => self.end_selection(window, cx),
3432 }
3433 }
3434
3435 fn extend_selection(
3436 &mut self,
3437 position: DisplayPoint,
3438 click_count: usize,
3439 window: &mut Window,
3440 cx: &mut Context<Self>,
3441 ) {
3442 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3443 let tail = self.selections.newest::<usize>(cx).tail();
3444 self.begin_selection(position, false, click_count, window, cx);
3445
3446 let position = position.to_offset(&display_map, Bias::Left);
3447 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3448
3449 let mut pending_selection = self
3450 .selections
3451 .pending_anchor()
3452 .expect("extend_selection not called with pending selection");
3453 if position >= tail {
3454 pending_selection.start = tail_anchor;
3455 } else {
3456 pending_selection.end = tail_anchor;
3457 pending_selection.reversed = true;
3458 }
3459
3460 let mut pending_mode = self.selections.pending_mode().unwrap();
3461 match &mut pending_mode {
3462 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3463 _ => {}
3464 }
3465
3466 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3467 SelectionEffects::scroll(Autoscroll::fit())
3468 } else {
3469 SelectionEffects::no_scroll()
3470 };
3471
3472 self.change_selections(effects, window, cx, |s| {
3473 s.set_pending(pending_selection, pending_mode)
3474 });
3475 }
3476
3477 fn begin_selection(
3478 &mut self,
3479 position: DisplayPoint,
3480 add: bool,
3481 click_count: usize,
3482 window: &mut Window,
3483 cx: &mut Context<Self>,
3484 ) {
3485 if !self.focus_handle.is_focused(window) {
3486 self.last_focused_descendant = None;
3487 window.focus(&self.focus_handle);
3488 }
3489
3490 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3491 let buffer = &display_map.buffer_snapshot;
3492 let position = display_map.clip_point(position, Bias::Left);
3493
3494 let start;
3495 let end;
3496 let mode;
3497 let mut auto_scroll;
3498 match click_count {
3499 1 => {
3500 start = buffer.anchor_before(position.to_point(&display_map));
3501 end = start;
3502 mode = SelectMode::Character;
3503 auto_scroll = true;
3504 }
3505 2 => {
3506 let position = display_map
3507 .clip_point(position, Bias::Left)
3508 .to_offset(&display_map, Bias::Left);
3509 let (range, _) = buffer.surrounding_word(position, false);
3510 start = buffer.anchor_before(range.start);
3511 end = buffer.anchor_before(range.end);
3512 mode = SelectMode::Word(start..end);
3513 auto_scroll = true;
3514 }
3515 3 => {
3516 let position = display_map
3517 .clip_point(position, Bias::Left)
3518 .to_point(&display_map);
3519 let line_start = display_map.prev_line_boundary(position).0;
3520 let next_line_start = buffer.clip_point(
3521 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3522 Bias::Left,
3523 );
3524 start = buffer.anchor_before(line_start);
3525 end = buffer.anchor_before(next_line_start);
3526 mode = SelectMode::Line(start..end);
3527 auto_scroll = true;
3528 }
3529 _ => {
3530 start = buffer.anchor_before(0);
3531 end = buffer.anchor_before(buffer.len());
3532 mode = SelectMode::All;
3533 auto_scroll = false;
3534 }
3535 }
3536 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3537
3538 let point_to_delete: Option<usize> = {
3539 let selected_points: Vec<Selection<Point>> =
3540 self.selections.disjoint_in_range(start..end, cx);
3541
3542 if !add || click_count > 1 {
3543 None
3544 } else if !selected_points.is_empty() {
3545 Some(selected_points[0].id)
3546 } else {
3547 let clicked_point_already_selected =
3548 self.selections.disjoint.iter().find(|selection| {
3549 selection.start.to_point(buffer) == start.to_point(buffer)
3550 || selection.end.to_point(buffer) == end.to_point(buffer)
3551 });
3552
3553 clicked_point_already_selected.map(|selection| selection.id)
3554 }
3555 };
3556
3557 let selections_count = self.selections.count();
3558 let effects = if auto_scroll {
3559 SelectionEffects::default()
3560 } else {
3561 SelectionEffects::no_scroll()
3562 };
3563
3564 self.change_selections(effects, window, cx, |s| {
3565 if let Some(point_to_delete) = point_to_delete {
3566 s.delete(point_to_delete);
3567
3568 if selections_count == 1 {
3569 s.set_pending_anchor_range(start..end, mode);
3570 }
3571 } else {
3572 if !add {
3573 s.clear_disjoint();
3574 }
3575
3576 s.set_pending_anchor_range(start..end, mode);
3577 }
3578 });
3579 }
3580
3581 fn begin_columnar_selection(
3582 &mut self,
3583 position: DisplayPoint,
3584 goal_column: u32,
3585 reset: bool,
3586 mode: ColumnarMode,
3587 window: &mut Window,
3588 cx: &mut Context<Self>,
3589 ) {
3590 if !self.focus_handle.is_focused(window) {
3591 self.last_focused_descendant = None;
3592 window.focus(&self.focus_handle);
3593 }
3594
3595 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3596
3597 if reset {
3598 let pointer_position = display_map
3599 .buffer_snapshot
3600 .anchor_before(position.to_point(&display_map));
3601
3602 self.change_selections(
3603 SelectionEffects::scroll(Autoscroll::newest()),
3604 window,
3605 cx,
3606 |s| {
3607 s.clear_disjoint();
3608 s.set_pending_anchor_range(
3609 pointer_position..pointer_position,
3610 SelectMode::Character,
3611 );
3612 },
3613 );
3614 };
3615
3616 let tail = self.selections.newest::<Point>(cx).tail();
3617 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3618 self.columnar_selection_state = match mode {
3619 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3620 selection_tail: selection_anchor,
3621 display_point: if reset {
3622 if position.column() != goal_column {
3623 Some(DisplayPoint::new(position.row(), goal_column))
3624 } else {
3625 None
3626 }
3627 } else {
3628 None
3629 },
3630 }),
3631 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3632 selection_tail: selection_anchor,
3633 }),
3634 };
3635
3636 if !reset {
3637 self.select_columns(position, goal_column, &display_map, window, cx);
3638 }
3639 }
3640
3641 fn update_selection(
3642 &mut self,
3643 position: DisplayPoint,
3644 goal_column: u32,
3645 scroll_delta: gpui::Point<f32>,
3646 window: &mut Window,
3647 cx: &mut Context<Self>,
3648 ) {
3649 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3650
3651 if self.columnar_selection_state.is_some() {
3652 self.select_columns(position, goal_column, &display_map, window, cx);
3653 } else if let Some(mut pending) = self.selections.pending_anchor() {
3654 let buffer = &display_map.buffer_snapshot;
3655 let head;
3656 let tail;
3657 let mode = self.selections.pending_mode().unwrap();
3658 match &mode {
3659 SelectMode::Character => {
3660 head = position.to_point(&display_map);
3661 tail = pending.tail().to_point(buffer);
3662 }
3663 SelectMode::Word(original_range) => {
3664 let offset = display_map
3665 .clip_point(position, Bias::Left)
3666 .to_offset(&display_map, Bias::Left);
3667 let original_range = original_range.to_offset(buffer);
3668
3669 let head_offset = if buffer.is_inside_word(offset, false)
3670 || original_range.contains(&offset)
3671 {
3672 let (word_range, _) = buffer.surrounding_word(offset, false);
3673 if word_range.start < original_range.start {
3674 word_range.start
3675 } else {
3676 word_range.end
3677 }
3678 } else {
3679 offset
3680 };
3681
3682 head = head_offset.to_point(buffer);
3683 if head_offset <= original_range.start {
3684 tail = original_range.end.to_point(buffer);
3685 } else {
3686 tail = original_range.start.to_point(buffer);
3687 }
3688 }
3689 SelectMode::Line(original_range) => {
3690 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3691
3692 let position = display_map
3693 .clip_point(position, Bias::Left)
3694 .to_point(&display_map);
3695 let line_start = display_map.prev_line_boundary(position).0;
3696 let next_line_start = buffer.clip_point(
3697 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3698 Bias::Left,
3699 );
3700
3701 if line_start < original_range.start {
3702 head = line_start
3703 } else {
3704 head = next_line_start
3705 }
3706
3707 if head <= original_range.start {
3708 tail = original_range.end;
3709 } else {
3710 tail = original_range.start;
3711 }
3712 }
3713 SelectMode::All => {
3714 return;
3715 }
3716 };
3717
3718 if head < tail {
3719 pending.start = buffer.anchor_before(head);
3720 pending.end = buffer.anchor_before(tail);
3721 pending.reversed = true;
3722 } else {
3723 pending.start = buffer.anchor_before(tail);
3724 pending.end = buffer.anchor_before(head);
3725 pending.reversed = false;
3726 }
3727
3728 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3729 s.set_pending(pending, mode);
3730 });
3731 } else {
3732 log::error!("update_selection dispatched with no pending selection");
3733 return;
3734 }
3735
3736 self.apply_scroll_delta(scroll_delta, window, cx);
3737 cx.notify();
3738 }
3739
3740 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3741 self.columnar_selection_state.take();
3742 if self.selections.pending_anchor().is_some() {
3743 let selections = self.selections.all::<usize>(cx);
3744 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3745 s.select(selections);
3746 s.clear_pending();
3747 });
3748 }
3749 }
3750
3751 fn select_columns(
3752 &mut self,
3753 head: DisplayPoint,
3754 goal_column: u32,
3755 display_map: &DisplaySnapshot,
3756 window: &mut Window,
3757 cx: &mut Context<Self>,
3758 ) {
3759 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3760 return;
3761 };
3762
3763 let tail = match columnar_state {
3764 ColumnarSelectionState::FromMouse {
3765 selection_tail,
3766 display_point,
3767 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3768 ColumnarSelectionState::FromSelection { selection_tail } => {
3769 selection_tail.to_display_point(display_map)
3770 }
3771 };
3772
3773 let start_row = cmp::min(tail.row(), head.row());
3774 let end_row = cmp::max(tail.row(), head.row());
3775 let start_column = cmp::min(tail.column(), goal_column);
3776 let end_column = cmp::max(tail.column(), goal_column);
3777 let reversed = start_column < tail.column();
3778
3779 let selection_ranges = (start_row.0..=end_row.0)
3780 .map(DisplayRow)
3781 .filter_map(|row| {
3782 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3783 || start_column <= display_map.line_len(row))
3784 && !display_map.is_block_line(row)
3785 {
3786 let start = display_map
3787 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3788 .to_point(display_map);
3789 let end = display_map
3790 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3791 .to_point(display_map);
3792 if reversed {
3793 Some(end..start)
3794 } else {
3795 Some(start..end)
3796 }
3797 } else {
3798 None
3799 }
3800 })
3801 .collect::<Vec<_>>();
3802
3803 let ranges = match columnar_state {
3804 ColumnarSelectionState::FromMouse { .. } => {
3805 let mut non_empty_ranges = selection_ranges
3806 .iter()
3807 .filter(|selection_range| selection_range.start != selection_range.end)
3808 .peekable();
3809 if non_empty_ranges.peek().is_some() {
3810 non_empty_ranges.cloned().collect()
3811 } else {
3812 selection_ranges
3813 }
3814 }
3815 _ => selection_ranges,
3816 };
3817
3818 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3819 s.select_ranges(ranges);
3820 });
3821 cx.notify();
3822 }
3823
3824 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3825 self.selections
3826 .all_adjusted(cx)
3827 .iter()
3828 .any(|selection| !selection.is_empty())
3829 }
3830
3831 pub fn has_pending_nonempty_selection(&self) -> bool {
3832 let pending_nonempty_selection = match self.selections.pending_anchor() {
3833 Some(Selection { start, end, .. }) => start != end,
3834 None => false,
3835 };
3836
3837 pending_nonempty_selection
3838 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3839 }
3840
3841 pub fn has_pending_selection(&self) -> bool {
3842 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3843 }
3844
3845 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3846 self.selection_mark_mode = false;
3847 self.selection_drag_state = SelectionDragState::None;
3848
3849 if self.clear_expanded_diff_hunks(cx) {
3850 cx.notify();
3851 return;
3852 }
3853 if self.dismiss_menus_and_popups(true, window, cx) {
3854 return;
3855 }
3856
3857 if self.mode.is_full()
3858 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3859 {
3860 return;
3861 }
3862
3863 cx.propagate();
3864 }
3865
3866 pub fn dismiss_menus_and_popups(
3867 &mut self,
3868 is_user_requested: bool,
3869 window: &mut Window,
3870 cx: &mut Context<Self>,
3871 ) -> bool {
3872 if self.take_rename(false, window, cx).is_some() {
3873 return true;
3874 }
3875
3876 if hide_hover(self, cx) {
3877 return true;
3878 }
3879
3880 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3881 return true;
3882 }
3883
3884 if self.hide_context_menu(window, cx).is_some() {
3885 return true;
3886 }
3887
3888 if self.mouse_context_menu.take().is_some() {
3889 return true;
3890 }
3891
3892 if is_user_requested && self.discard_edit_prediction(true, cx) {
3893 return true;
3894 }
3895
3896 if self.snippet_stack.pop().is_some() {
3897 return true;
3898 }
3899
3900 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3901 self.dismiss_diagnostics(cx);
3902 return true;
3903 }
3904
3905 false
3906 }
3907
3908 fn linked_editing_ranges_for(
3909 &self,
3910 selection: Range<text::Anchor>,
3911 cx: &App,
3912 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3913 if self.linked_edit_ranges.is_empty() {
3914 return None;
3915 }
3916 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3917 selection.end.buffer_id.and_then(|end_buffer_id| {
3918 if selection.start.buffer_id != Some(end_buffer_id) {
3919 return None;
3920 }
3921 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3922 let snapshot = buffer.read(cx).snapshot();
3923 self.linked_edit_ranges
3924 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3925 .map(|ranges| (ranges, snapshot, buffer))
3926 })?;
3927 use text::ToOffset as TO;
3928 // find offset from the start of current range to current cursor position
3929 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3930
3931 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3932 let start_difference = start_offset - start_byte_offset;
3933 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3934 let end_difference = end_offset - start_byte_offset;
3935 // Current range has associated linked ranges.
3936 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3937 for range in linked_ranges.iter() {
3938 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3939 let end_offset = start_offset + end_difference;
3940 let start_offset = start_offset + start_difference;
3941 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3942 continue;
3943 }
3944 if self.selections.disjoint_anchor_ranges().any(|s| {
3945 if s.start.buffer_id != selection.start.buffer_id
3946 || s.end.buffer_id != selection.end.buffer_id
3947 {
3948 return false;
3949 }
3950 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3951 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3952 }) {
3953 continue;
3954 }
3955 let start = buffer_snapshot.anchor_after(start_offset);
3956 let end = buffer_snapshot.anchor_after(end_offset);
3957 linked_edits
3958 .entry(buffer.clone())
3959 .or_default()
3960 .push(start..end);
3961 }
3962 Some(linked_edits)
3963 }
3964
3965 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3966 let text: Arc<str> = text.into();
3967
3968 if self.read_only(cx) {
3969 return;
3970 }
3971
3972 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3973
3974 let selections = self.selections.all_adjusted(cx);
3975 let mut bracket_inserted = false;
3976 let mut edits = Vec::new();
3977 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3978 let mut new_selections = Vec::with_capacity(selections.len());
3979 let mut new_autoclose_regions = Vec::new();
3980 let snapshot = self.buffer.read(cx).read(cx);
3981 let mut clear_linked_edit_ranges = false;
3982
3983 for (selection, autoclose_region) in
3984 self.selections_with_autoclose_regions(selections, &snapshot)
3985 {
3986 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3987 // Determine if the inserted text matches the opening or closing
3988 // bracket of any of this language's bracket pairs.
3989 let mut bracket_pair = None;
3990 let mut is_bracket_pair_start = false;
3991 let mut is_bracket_pair_end = false;
3992 if !text.is_empty() {
3993 let mut bracket_pair_matching_end = None;
3994 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3995 // and they are removing the character that triggered IME popup.
3996 for (pair, enabled) in scope.brackets() {
3997 if !pair.close && !pair.surround {
3998 continue;
3999 }
4000
4001 if enabled && pair.start.ends_with(text.as_ref()) {
4002 let prefix_len = pair.start.len() - text.len();
4003 let preceding_text_matches_prefix = prefix_len == 0
4004 || (selection.start.column >= (prefix_len as u32)
4005 && snapshot.contains_str_at(
4006 Point::new(
4007 selection.start.row,
4008 selection.start.column - (prefix_len as u32),
4009 ),
4010 &pair.start[..prefix_len],
4011 ));
4012 if preceding_text_matches_prefix {
4013 bracket_pair = Some(pair.clone());
4014 is_bracket_pair_start = true;
4015 break;
4016 }
4017 }
4018 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4019 {
4020 // take first bracket pair matching end, but don't break in case a later bracket
4021 // pair matches start
4022 bracket_pair_matching_end = Some(pair.clone());
4023 }
4024 }
4025 if let Some(end) = bracket_pair_matching_end
4026 && bracket_pair.is_none()
4027 {
4028 bracket_pair = Some(end);
4029 is_bracket_pair_end = true;
4030 }
4031 }
4032
4033 if let Some(bracket_pair) = bracket_pair {
4034 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4035 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4036 let auto_surround =
4037 self.use_auto_surround && snapshot_settings.use_auto_surround;
4038 if selection.is_empty() {
4039 if is_bracket_pair_start {
4040 // If the inserted text is a suffix of an opening bracket and the
4041 // selection is preceded by the rest of the opening bracket, then
4042 // insert the closing bracket.
4043 let following_text_allows_autoclose = snapshot
4044 .chars_at(selection.start)
4045 .next()
4046 .map_or(true, |c| scope.should_autoclose_before(c));
4047
4048 let preceding_text_allows_autoclose = selection.start.column == 0
4049 || snapshot.reversed_chars_at(selection.start).next().map_or(
4050 true,
4051 |c| {
4052 bracket_pair.start != bracket_pair.end
4053 || !snapshot
4054 .char_classifier_at(selection.start)
4055 .is_word(c)
4056 },
4057 );
4058
4059 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4060 && bracket_pair.start.len() == 1
4061 {
4062 let target = bracket_pair.start.chars().next().unwrap();
4063 let current_line_count = snapshot
4064 .reversed_chars_at(selection.start)
4065 .take_while(|&c| c != '\n')
4066 .filter(|&c| c == target)
4067 .count();
4068 current_line_count % 2 == 1
4069 } else {
4070 false
4071 };
4072
4073 if autoclose
4074 && bracket_pair.close
4075 && following_text_allows_autoclose
4076 && preceding_text_allows_autoclose
4077 && !is_closing_quote
4078 {
4079 let anchor = snapshot.anchor_before(selection.end);
4080 new_selections.push((selection.map(|_| anchor), text.len()));
4081 new_autoclose_regions.push((
4082 anchor,
4083 text.len(),
4084 selection.id,
4085 bracket_pair.clone(),
4086 ));
4087 edits.push((
4088 selection.range(),
4089 format!("{}{}", text, bracket_pair.end).into(),
4090 ));
4091 bracket_inserted = true;
4092 continue;
4093 }
4094 }
4095
4096 if let Some(region) = autoclose_region {
4097 // If the selection is followed by an auto-inserted closing bracket,
4098 // then don't insert that closing bracket again; just move the selection
4099 // past the closing bracket.
4100 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4101 && text.as_ref() == region.pair.end.as_str()
4102 && snapshot.contains_str_at(region.range.end, text.as_ref());
4103 if should_skip {
4104 let anchor = snapshot.anchor_after(selection.end);
4105 new_selections
4106 .push((selection.map(|_| anchor), region.pair.end.len()));
4107 continue;
4108 }
4109 }
4110
4111 let always_treat_brackets_as_autoclosed = snapshot
4112 .language_settings_at(selection.start, cx)
4113 .always_treat_brackets_as_autoclosed;
4114 if always_treat_brackets_as_autoclosed
4115 && is_bracket_pair_end
4116 && snapshot.contains_str_at(selection.end, text.as_ref())
4117 {
4118 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4119 // and the inserted text is a closing bracket and the selection is followed
4120 // by the closing bracket then move the selection past the closing bracket.
4121 let anchor = snapshot.anchor_after(selection.end);
4122 new_selections.push((selection.map(|_| anchor), text.len()));
4123 continue;
4124 }
4125 }
4126 // If an opening bracket is 1 character long and is typed while
4127 // text is selected, then surround that text with the bracket pair.
4128 else if auto_surround
4129 && bracket_pair.surround
4130 && is_bracket_pair_start
4131 && bracket_pair.start.chars().count() == 1
4132 {
4133 edits.push((selection.start..selection.start, text.clone()));
4134 edits.push((
4135 selection.end..selection.end,
4136 bracket_pair.end.as_str().into(),
4137 ));
4138 bracket_inserted = true;
4139 new_selections.push((
4140 Selection {
4141 id: selection.id,
4142 start: snapshot.anchor_after(selection.start),
4143 end: snapshot.anchor_before(selection.end),
4144 reversed: selection.reversed,
4145 goal: selection.goal,
4146 },
4147 0,
4148 ));
4149 continue;
4150 }
4151 }
4152 }
4153
4154 if self.auto_replace_emoji_shortcode
4155 && selection.is_empty()
4156 && text.as_ref().ends_with(':')
4157 {
4158 if let Some(possible_emoji_short_code) =
4159 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4160 {
4161 if !possible_emoji_short_code.is_empty() {
4162 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4163 let emoji_shortcode_start = Point::new(
4164 selection.start.row,
4165 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4166 );
4167
4168 // Remove shortcode from buffer
4169 edits.push((
4170 emoji_shortcode_start..selection.start,
4171 "".to_string().into(),
4172 ));
4173 new_selections.push((
4174 Selection {
4175 id: selection.id,
4176 start: snapshot.anchor_after(emoji_shortcode_start),
4177 end: snapshot.anchor_before(selection.start),
4178 reversed: selection.reversed,
4179 goal: selection.goal,
4180 },
4181 0,
4182 ));
4183
4184 // Insert emoji
4185 let selection_start_anchor = snapshot.anchor_after(selection.start);
4186 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4187 edits.push((selection.start..selection.end, emoji.to_string().into()));
4188
4189 continue;
4190 }
4191 }
4192 }
4193 }
4194
4195 // If not handling any auto-close operation, then just replace the selected
4196 // text with the given input and move the selection to the end of the
4197 // newly inserted text.
4198 let anchor = snapshot.anchor_after(selection.end);
4199 if !self.linked_edit_ranges.is_empty() {
4200 let start_anchor = snapshot.anchor_before(selection.start);
4201
4202 let is_word_char = text.chars().next().map_or(true, |char| {
4203 let classifier = snapshot
4204 .char_classifier_at(start_anchor.to_offset(&snapshot))
4205 .ignore_punctuation(true);
4206 classifier.is_word(char)
4207 });
4208
4209 if is_word_char {
4210 if let Some(ranges) = self
4211 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4212 {
4213 for (buffer, edits) in ranges {
4214 linked_edits
4215 .entry(buffer.clone())
4216 .or_default()
4217 .extend(edits.into_iter().map(|range| (range, text.clone())));
4218 }
4219 }
4220 } else {
4221 clear_linked_edit_ranges = true;
4222 }
4223 }
4224
4225 new_selections.push((selection.map(|_| anchor), 0));
4226 edits.push((selection.start..selection.end, text.clone()));
4227 }
4228
4229 drop(snapshot);
4230
4231 self.transact(window, cx, |this, window, cx| {
4232 if clear_linked_edit_ranges {
4233 this.linked_edit_ranges.clear();
4234 }
4235 let initial_buffer_versions =
4236 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4237
4238 this.buffer.update(cx, |buffer, cx| {
4239 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4240 });
4241 for (buffer, edits) in linked_edits {
4242 buffer.update(cx, |buffer, cx| {
4243 let snapshot = buffer.snapshot();
4244 let edits = edits
4245 .into_iter()
4246 .map(|(range, text)| {
4247 use text::ToPoint as TP;
4248 let end_point = TP::to_point(&range.end, &snapshot);
4249 let start_point = TP::to_point(&range.start, &snapshot);
4250 (start_point..end_point, text)
4251 })
4252 .sorted_by_key(|(range, _)| range.start);
4253 buffer.edit(edits, None, cx);
4254 })
4255 }
4256 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4257 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4258 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4259 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4260 .zip(new_selection_deltas)
4261 .map(|(selection, delta)| Selection {
4262 id: selection.id,
4263 start: selection.start + delta,
4264 end: selection.end + delta,
4265 reversed: selection.reversed,
4266 goal: SelectionGoal::None,
4267 })
4268 .collect::<Vec<_>>();
4269
4270 let mut i = 0;
4271 for (position, delta, selection_id, pair) in new_autoclose_regions {
4272 let position = position.to_offset(&map.buffer_snapshot) + delta;
4273 let start = map.buffer_snapshot.anchor_before(position);
4274 let end = map.buffer_snapshot.anchor_after(position);
4275 while let Some(existing_state) = this.autoclose_regions.get(i) {
4276 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4277 Ordering::Less => i += 1,
4278 Ordering::Greater => break,
4279 Ordering::Equal => {
4280 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4281 Ordering::Less => i += 1,
4282 Ordering::Equal => break,
4283 Ordering::Greater => break,
4284 }
4285 }
4286 }
4287 }
4288 this.autoclose_regions.insert(
4289 i,
4290 AutocloseRegion {
4291 selection_id,
4292 range: start..end,
4293 pair,
4294 },
4295 );
4296 }
4297
4298 let had_active_edit_prediction = this.has_active_edit_prediction();
4299 this.change_selections(
4300 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4301 window,
4302 cx,
4303 |s| s.select(new_selections),
4304 );
4305
4306 if !bracket_inserted {
4307 if let Some(on_type_format_task) =
4308 this.trigger_on_type_formatting(text.to_string(), window, cx)
4309 {
4310 on_type_format_task.detach_and_log_err(cx);
4311 }
4312 }
4313
4314 let editor_settings = EditorSettings::get_global(cx);
4315 if bracket_inserted
4316 && (editor_settings.auto_signature_help
4317 || editor_settings.show_signature_help_after_edits)
4318 {
4319 this.show_signature_help(&ShowSignatureHelp, window, cx);
4320 }
4321
4322 let trigger_in_words =
4323 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4324 if this.hard_wrap.is_some() {
4325 let latest: Range<Point> = this.selections.newest(cx).range();
4326 if latest.is_empty()
4327 && this
4328 .buffer()
4329 .read(cx)
4330 .snapshot(cx)
4331 .line_len(MultiBufferRow(latest.start.row))
4332 == latest.start.column
4333 {
4334 this.rewrap_impl(
4335 RewrapOptions {
4336 override_language_settings: true,
4337 preserve_existing_whitespace: true,
4338 },
4339 cx,
4340 )
4341 }
4342 }
4343 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4344 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4345 this.refresh_edit_prediction(true, false, window, cx);
4346 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4347 });
4348 }
4349
4350 fn find_possible_emoji_shortcode_at_position(
4351 snapshot: &MultiBufferSnapshot,
4352 position: Point,
4353 ) -> Option<String> {
4354 let mut chars = Vec::new();
4355 let mut found_colon = false;
4356 for char in snapshot.reversed_chars_at(position).take(100) {
4357 // Found a possible emoji shortcode in the middle of the buffer
4358 if found_colon {
4359 if char.is_whitespace() {
4360 chars.reverse();
4361 return Some(chars.iter().collect());
4362 }
4363 // If the previous character is not a whitespace, we are in the middle of a word
4364 // and we only want to complete the shortcode if the word is made up of other emojis
4365 let mut containing_word = String::new();
4366 for ch in snapshot
4367 .reversed_chars_at(position)
4368 .skip(chars.len() + 1)
4369 .take(100)
4370 {
4371 if ch.is_whitespace() {
4372 break;
4373 }
4374 containing_word.push(ch);
4375 }
4376 let containing_word = containing_word.chars().rev().collect::<String>();
4377 if util::word_consists_of_emojis(containing_word.as_str()) {
4378 chars.reverse();
4379 return Some(chars.iter().collect());
4380 }
4381 }
4382
4383 if char.is_whitespace() || !char.is_ascii() {
4384 return None;
4385 }
4386 if char == ':' {
4387 found_colon = true;
4388 } else {
4389 chars.push(char);
4390 }
4391 }
4392 // Found a possible emoji shortcode at the beginning of the buffer
4393 chars.reverse();
4394 Some(chars.iter().collect())
4395 }
4396
4397 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4398 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4399 self.transact(window, cx, |this, window, cx| {
4400 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4401 let selections = this.selections.all::<usize>(cx);
4402 let multi_buffer = this.buffer.read(cx);
4403 let buffer = multi_buffer.snapshot(cx);
4404 selections
4405 .iter()
4406 .map(|selection| {
4407 let start_point = selection.start.to_point(&buffer);
4408 let mut existing_indent =
4409 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4410 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4411 let start = selection.start;
4412 let end = selection.end;
4413 let selection_is_empty = start == end;
4414 let language_scope = buffer.language_scope_at(start);
4415 let (
4416 comment_delimiter,
4417 doc_delimiter,
4418 insert_extra_newline,
4419 indent_on_newline,
4420 indent_on_extra_newline,
4421 ) = if let Some(language) = &language_scope {
4422 let mut insert_extra_newline =
4423 insert_extra_newline_brackets(&buffer, start..end, language)
4424 || insert_extra_newline_tree_sitter(&buffer, start..end);
4425
4426 // Comment extension on newline is allowed only for cursor selections
4427 let comment_delimiter = maybe!({
4428 if !selection_is_empty {
4429 return None;
4430 }
4431
4432 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4433 return None;
4434 }
4435
4436 let delimiters = language.line_comment_prefixes();
4437 let max_len_of_delimiter =
4438 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4439 let (snapshot, range) =
4440 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4441
4442 let num_of_whitespaces = snapshot
4443 .chars_for_range(range.clone())
4444 .take_while(|c| c.is_whitespace())
4445 .count();
4446 let comment_candidate = snapshot
4447 .chars_for_range(range.clone())
4448 .skip(num_of_whitespaces)
4449 .take(max_len_of_delimiter)
4450 .collect::<String>();
4451 let (delimiter, trimmed_len) = delimiters
4452 .iter()
4453 .filter_map(|delimiter| {
4454 let prefix = delimiter.trim_end();
4455 if comment_candidate.starts_with(prefix) {
4456 Some((delimiter, prefix.len()))
4457 } else {
4458 None
4459 }
4460 })
4461 .max_by_key(|(_, len)| *len)?;
4462
4463 if let Some(BlockCommentConfig {
4464 start: block_start, ..
4465 }) = language.block_comment()
4466 {
4467 let block_start_trimmed = block_start.trim_end();
4468 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4469 let line_content = snapshot
4470 .chars_for_range(range)
4471 .skip(num_of_whitespaces)
4472 .take(block_start_trimmed.len())
4473 .collect::<String>();
4474
4475 if line_content.starts_with(block_start_trimmed) {
4476 return None;
4477 }
4478 }
4479 }
4480
4481 let cursor_is_placed_after_comment_marker =
4482 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4483 if cursor_is_placed_after_comment_marker {
4484 Some(delimiter.clone())
4485 } else {
4486 None
4487 }
4488 });
4489
4490 let mut indent_on_newline = IndentSize::spaces(0);
4491 let mut indent_on_extra_newline = IndentSize::spaces(0);
4492
4493 let doc_delimiter = maybe!({
4494 if !selection_is_empty {
4495 return None;
4496 }
4497
4498 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4499 return None;
4500 }
4501
4502 let BlockCommentConfig {
4503 start: start_tag,
4504 end: end_tag,
4505 prefix: delimiter,
4506 tab_size: len,
4507 } = language.documentation_comment()?;
4508 let is_within_block_comment = buffer
4509 .language_scope_at(start_point)
4510 .is_some_and(|scope| scope.override_name() == Some("comment"));
4511 if !is_within_block_comment {
4512 return None;
4513 }
4514
4515 let (snapshot, range) =
4516 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4517
4518 let num_of_whitespaces = snapshot
4519 .chars_for_range(range.clone())
4520 .take_while(|c| c.is_whitespace())
4521 .count();
4522
4523 // 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.
4524 let column = start_point.column;
4525 let cursor_is_after_start_tag = {
4526 let start_tag_len = start_tag.len();
4527 let start_tag_line = snapshot
4528 .chars_for_range(range.clone())
4529 .skip(num_of_whitespaces)
4530 .take(start_tag_len)
4531 .collect::<String>();
4532 if start_tag_line.starts_with(start_tag.as_ref()) {
4533 num_of_whitespaces + start_tag_len <= column as usize
4534 } else {
4535 false
4536 }
4537 };
4538
4539 let cursor_is_after_delimiter = {
4540 let delimiter_trim = delimiter.trim_end();
4541 let delimiter_line = snapshot
4542 .chars_for_range(range.clone())
4543 .skip(num_of_whitespaces)
4544 .take(delimiter_trim.len())
4545 .collect::<String>();
4546 if delimiter_line.starts_with(delimiter_trim) {
4547 num_of_whitespaces + delimiter_trim.len() <= column as usize
4548 } else {
4549 false
4550 }
4551 };
4552
4553 let cursor_is_before_end_tag_if_exists = {
4554 let mut char_position = 0u32;
4555 let mut end_tag_offset = None;
4556
4557 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4558 if let Some(byte_pos) = chunk.find(&**end_tag) {
4559 let chars_before_match =
4560 chunk[..byte_pos].chars().count() as u32;
4561 end_tag_offset =
4562 Some(char_position + chars_before_match);
4563 break 'outer;
4564 }
4565 char_position += chunk.chars().count() as u32;
4566 }
4567
4568 if let Some(end_tag_offset) = end_tag_offset {
4569 let cursor_is_before_end_tag = column <= end_tag_offset;
4570 if cursor_is_after_start_tag {
4571 if cursor_is_before_end_tag {
4572 insert_extra_newline = true;
4573 }
4574 let cursor_is_at_start_of_end_tag =
4575 column == end_tag_offset;
4576 if cursor_is_at_start_of_end_tag {
4577 indent_on_extra_newline.len = *len;
4578 }
4579 }
4580 cursor_is_before_end_tag
4581 } else {
4582 true
4583 }
4584 };
4585
4586 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4587 && cursor_is_before_end_tag_if_exists
4588 {
4589 if cursor_is_after_start_tag {
4590 indent_on_newline.len = *len;
4591 }
4592 Some(delimiter.clone())
4593 } else {
4594 None
4595 }
4596 });
4597
4598 (
4599 comment_delimiter,
4600 doc_delimiter,
4601 insert_extra_newline,
4602 indent_on_newline,
4603 indent_on_extra_newline,
4604 )
4605 } else {
4606 (
4607 None,
4608 None,
4609 false,
4610 IndentSize::default(),
4611 IndentSize::default(),
4612 )
4613 };
4614
4615 let prevent_auto_indent = doc_delimiter.is_some();
4616 let delimiter = comment_delimiter.or(doc_delimiter);
4617
4618 let capacity_for_delimiter =
4619 delimiter.as_deref().map(str::len).unwrap_or_default();
4620 let mut new_text = String::with_capacity(
4621 1 + capacity_for_delimiter
4622 + existing_indent.len as usize
4623 + indent_on_newline.len as usize
4624 + indent_on_extra_newline.len as usize,
4625 );
4626 new_text.push('\n');
4627 new_text.extend(existing_indent.chars());
4628 new_text.extend(indent_on_newline.chars());
4629
4630 if let Some(delimiter) = &delimiter {
4631 new_text.push_str(delimiter);
4632 }
4633
4634 if insert_extra_newline {
4635 new_text.push('\n');
4636 new_text.extend(existing_indent.chars());
4637 new_text.extend(indent_on_extra_newline.chars());
4638 }
4639
4640 let anchor = buffer.anchor_after(end);
4641 let new_selection = selection.map(|_| anchor);
4642 (
4643 ((start..end, new_text), prevent_auto_indent),
4644 (insert_extra_newline, new_selection),
4645 )
4646 })
4647 .unzip()
4648 };
4649
4650 let mut auto_indent_edits = Vec::new();
4651 let mut edits = Vec::new();
4652 for (edit, prevent_auto_indent) in edits_with_flags {
4653 if prevent_auto_indent {
4654 edits.push(edit);
4655 } else {
4656 auto_indent_edits.push(edit);
4657 }
4658 }
4659 if !edits.is_empty() {
4660 this.edit(edits, cx);
4661 }
4662 if !auto_indent_edits.is_empty() {
4663 this.edit_with_autoindent(auto_indent_edits, cx);
4664 }
4665
4666 let buffer = this.buffer.read(cx).snapshot(cx);
4667 let new_selections = selection_info
4668 .into_iter()
4669 .map(|(extra_newline_inserted, new_selection)| {
4670 let mut cursor = new_selection.end.to_point(&buffer);
4671 if extra_newline_inserted {
4672 cursor.row -= 1;
4673 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4674 }
4675 new_selection.map(|_| cursor)
4676 })
4677 .collect();
4678
4679 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4680 this.refresh_edit_prediction(true, false, window, cx);
4681 });
4682 }
4683
4684 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4685 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4686
4687 let buffer = self.buffer.read(cx);
4688 let snapshot = buffer.snapshot(cx);
4689
4690 let mut edits = Vec::new();
4691 let mut rows = Vec::new();
4692
4693 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4694 let cursor = selection.head();
4695 let row = cursor.row;
4696
4697 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4698
4699 let newline = "\n".to_string();
4700 edits.push((start_of_line..start_of_line, newline));
4701
4702 rows.push(row + rows_inserted as u32);
4703 }
4704
4705 self.transact(window, cx, |editor, window, cx| {
4706 editor.edit(edits, cx);
4707
4708 editor.change_selections(Default::default(), window, cx, |s| {
4709 let mut index = 0;
4710 s.move_cursors_with(|map, _, _| {
4711 let row = rows[index];
4712 index += 1;
4713
4714 let point = Point::new(row, 0);
4715 let boundary = map.next_line_boundary(point).1;
4716 let clipped = map.clip_point(boundary, Bias::Left);
4717
4718 (clipped, SelectionGoal::None)
4719 });
4720 });
4721
4722 let mut indent_edits = Vec::new();
4723 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4724 for row in rows {
4725 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4726 for (row, indent) in indents {
4727 if indent.len == 0 {
4728 continue;
4729 }
4730
4731 let text = match indent.kind {
4732 IndentKind::Space => " ".repeat(indent.len as usize),
4733 IndentKind::Tab => "\t".repeat(indent.len as usize),
4734 };
4735 let point = Point::new(row.0, 0);
4736 indent_edits.push((point..point, text));
4737 }
4738 }
4739 editor.edit(indent_edits, cx);
4740 });
4741 }
4742
4743 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4744 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4745
4746 let buffer = self.buffer.read(cx);
4747 let snapshot = buffer.snapshot(cx);
4748
4749 let mut edits = Vec::new();
4750 let mut rows = Vec::new();
4751 let mut rows_inserted = 0;
4752
4753 for selection in self.selections.all_adjusted(cx) {
4754 let cursor = selection.head();
4755 let row = cursor.row;
4756
4757 let point = Point::new(row + 1, 0);
4758 let start_of_line = snapshot.clip_point(point, Bias::Left);
4759
4760 let newline = "\n".to_string();
4761 edits.push((start_of_line..start_of_line, newline));
4762
4763 rows_inserted += 1;
4764 rows.push(row + rows_inserted);
4765 }
4766
4767 self.transact(window, cx, |editor, window, cx| {
4768 editor.edit(edits, cx);
4769
4770 editor.change_selections(Default::default(), window, cx, |s| {
4771 let mut index = 0;
4772 s.move_cursors_with(|map, _, _| {
4773 let row = rows[index];
4774 index += 1;
4775
4776 let point = Point::new(row, 0);
4777 let boundary = map.next_line_boundary(point).1;
4778 let clipped = map.clip_point(boundary, Bias::Left);
4779
4780 (clipped, SelectionGoal::None)
4781 });
4782 });
4783
4784 let mut indent_edits = Vec::new();
4785 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4786 for row in rows {
4787 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4788 for (row, indent) in indents {
4789 if indent.len == 0 {
4790 continue;
4791 }
4792
4793 let text = match indent.kind {
4794 IndentKind::Space => " ".repeat(indent.len as usize),
4795 IndentKind::Tab => "\t".repeat(indent.len as usize),
4796 };
4797 let point = Point::new(row.0, 0);
4798 indent_edits.push((point..point, text));
4799 }
4800 }
4801 editor.edit(indent_edits, cx);
4802 });
4803 }
4804
4805 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4806 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4807 original_indent_columns: Vec::new(),
4808 });
4809 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4810 }
4811
4812 fn insert_with_autoindent_mode(
4813 &mut self,
4814 text: &str,
4815 autoindent_mode: Option<AutoindentMode>,
4816 window: &mut Window,
4817 cx: &mut Context<Self>,
4818 ) {
4819 if self.read_only(cx) {
4820 return;
4821 }
4822
4823 let text: Arc<str> = text.into();
4824 self.transact(window, cx, |this, window, cx| {
4825 let old_selections = this.selections.all_adjusted(cx);
4826 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4827 let anchors = {
4828 let snapshot = buffer.read(cx);
4829 old_selections
4830 .iter()
4831 .map(|s| {
4832 let anchor = snapshot.anchor_after(s.head());
4833 s.map(|_| anchor)
4834 })
4835 .collect::<Vec<_>>()
4836 };
4837 buffer.edit(
4838 old_selections
4839 .iter()
4840 .map(|s| (s.start..s.end, text.clone())),
4841 autoindent_mode,
4842 cx,
4843 );
4844 anchors
4845 });
4846
4847 this.change_selections(Default::default(), window, cx, |s| {
4848 s.select_anchors(selection_anchors);
4849 });
4850
4851 cx.notify();
4852 });
4853 }
4854
4855 fn trigger_completion_on_input(
4856 &mut self,
4857 text: &str,
4858 trigger_in_words: bool,
4859 window: &mut Window,
4860 cx: &mut Context<Self>,
4861 ) {
4862 let completions_source = self
4863 .context_menu
4864 .borrow()
4865 .as_ref()
4866 .and_then(|menu| match menu {
4867 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4868 CodeContextMenu::CodeActions(_) => None,
4869 });
4870
4871 match completions_source {
4872 Some(CompletionsMenuSource::Words) => {
4873 self.show_word_completions(&ShowWordCompletions, window, cx)
4874 }
4875 Some(CompletionsMenuSource::Normal)
4876 | Some(CompletionsMenuSource::SnippetChoices)
4877 | None
4878 if self.is_completion_trigger(
4879 text,
4880 trigger_in_words,
4881 completions_source.is_some(),
4882 cx,
4883 ) =>
4884 {
4885 self.show_completions(
4886 &ShowCompletions {
4887 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4888 },
4889 window,
4890 cx,
4891 )
4892 }
4893 _ => {
4894 self.hide_context_menu(window, cx);
4895 }
4896 }
4897 }
4898
4899 fn is_completion_trigger(
4900 &self,
4901 text: &str,
4902 trigger_in_words: bool,
4903 menu_is_open: bool,
4904 cx: &mut Context<Self>,
4905 ) -> bool {
4906 let position = self.selections.newest_anchor().head();
4907 let multibuffer = self.buffer.read(cx);
4908 let Some(buffer) = position
4909 .buffer_id
4910 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4911 else {
4912 return false;
4913 };
4914
4915 if let Some(completion_provider) = &self.completion_provider {
4916 completion_provider.is_completion_trigger(
4917 &buffer,
4918 position.text_anchor,
4919 text,
4920 trigger_in_words,
4921 menu_is_open,
4922 cx,
4923 )
4924 } else {
4925 false
4926 }
4927 }
4928
4929 /// If any empty selections is touching the start of its innermost containing autoclose
4930 /// region, expand it to select the brackets.
4931 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4932 let selections = self.selections.all::<usize>(cx);
4933 let buffer = self.buffer.read(cx).read(cx);
4934 let new_selections = self
4935 .selections_with_autoclose_regions(selections, &buffer)
4936 .map(|(mut selection, region)| {
4937 if !selection.is_empty() {
4938 return selection;
4939 }
4940
4941 if let Some(region) = region {
4942 let mut range = region.range.to_offset(&buffer);
4943 if selection.start == range.start && range.start >= region.pair.start.len() {
4944 range.start -= region.pair.start.len();
4945 if buffer.contains_str_at(range.start, ®ion.pair.start)
4946 && buffer.contains_str_at(range.end, ®ion.pair.end)
4947 {
4948 range.end += region.pair.end.len();
4949 selection.start = range.start;
4950 selection.end = range.end;
4951
4952 return selection;
4953 }
4954 }
4955 }
4956
4957 let always_treat_brackets_as_autoclosed = buffer
4958 .language_settings_at(selection.start, cx)
4959 .always_treat_brackets_as_autoclosed;
4960
4961 if !always_treat_brackets_as_autoclosed {
4962 return selection;
4963 }
4964
4965 if let Some(scope) = buffer.language_scope_at(selection.start) {
4966 for (pair, enabled) in scope.brackets() {
4967 if !enabled || !pair.close {
4968 continue;
4969 }
4970
4971 if buffer.contains_str_at(selection.start, &pair.end) {
4972 let pair_start_len = pair.start.len();
4973 if buffer.contains_str_at(
4974 selection.start.saturating_sub(pair_start_len),
4975 &pair.start,
4976 ) {
4977 selection.start -= pair_start_len;
4978 selection.end += pair.end.len();
4979
4980 return selection;
4981 }
4982 }
4983 }
4984 }
4985
4986 selection
4987 })
4988 .collect();
4989
4990 drop(buffer);
4991 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4992 selections.select(new_selections)
4993 });
4994 }
4995
4996 /// Iterate the given selections, and for each one, find the smallest surrounding
4997 /// autoclose region. This uses the ordering of the selections and the autoclose
4998 /// regions to avoid repeated comparisons.
4999 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5000 &'a self,
5001 selections: impl IntoIterator<Item = Selection<D>>,
5002 buffer: &'a MultiBufferSnapshot,
5003 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5004 let mut i = 0;
5005 let mut regions = self.autoclose_regions.as_slice();
5006 selections.into_iter().map(move |selection| {
5007 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5008
5009 let mut enclosing = None;
5010 while let Some(pair_state) = regions.get(i) {
5011 if pair_state.range.end.to_offset(buffer) < range.start {
5012 regions = ®ions[i + 1..];
5013 i = 0;
5014 } else if pair_state.range.start.to_offset(buffer) > range.end {
5015 break;
5016 } else {
5017 if pair_state.selection_id == selection.id {
5018 enclosing = Some(pair_state);
5019 }
5020 i += 1;
5021 }
5022 }
5023
5024 (selection, enclosing)
5025 })
5026 }
5027
5028 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5029 fn invalidate_autoclose_regions(
5030 &mut self,
5031 mut selections: &[Selection<Anchor>],
5032 buffer: &MultiBufferSnapshot,
5033 ) {
5034 self.autoclose_regions.retain(|state| {
5035 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5036 return false;
5037 }
5038
5039 let mut i = 0;
5040 while let Some(selection) = selections.get(i) {
5041 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5042 selections = &selections[1..];
5043 continue;
5044 }
5045 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5046 break;
5047 }
5048 if selection.id == state.selection_id {
5049 return true;
5050 } else {
5051 i += 1;
5052 }
5053 }
5054 false
5055 });
5056 }
5057
5058 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5059 let offset = position.to_offset(buffer);
5060 let (word_range, kind) = buffer.surrounding_word(offset, true);
5061 if offset > word_range.start && kind == Some(CharKind::Word) {
5062 Some(
5063 buffer
5064 .text_for_range(word_range.start..offset)
5065 .collect::<String>(),
5066 )
5067 } else {
5068 None
5069 }
5070 }
5071
5072 pub fn toggle_inline_values(
5073 &mut self,
5074 _: &ToggleInlineValues,
5075 _: &mut Window,
5076 cx: &mut Context<Self>,
5077 ) {
5078 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5079
5080 self.refresh_inline_values(cx);
5081 }
5082
5083 pub fn toggle_inlay_hints(
5084 &mut self,
5085 _: &ToggleInlayHints,
5086 _: &mut Window,
5087 cx: &mut Context<Self>,
5088 ) {
5089 self.refresh_inlay_hints(
5090 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5091 cx,
5092 );
5093 }
5094
5095 pub fn inlay_hints_enabled(&self) -> bool {
5096 self.inlay_hint_cache.enabled
5097 }
5098
5099 pub fn inline_values_enabled(&self) -> bool {
5100 self.inline_value_cache.enabled
5101 }
5102
5103 #[cfg(any(test, feature = "test-support"))]
5104 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5105 self.display_map
5106 .read(cx)
5107 .current_inlays()
5108 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5109 .cloned()
5110 .collect()
5111 }
5112
5113 #[cfg(any(test, feature = "test-support"))]
5114 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5115 self.display_map
5116 .read(cx)
5117 .current_inlays()
5118 .cloned()
5119 .collect()
5120 }
5121
5122 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5123 if self.semantics_provider.is_none() || !self.mode.is_full() {
5124 return;
5125 }
5126
5127 let reason_description = reason.description();
5128 let ignore_debounce = matches!(
5129 reason,
5130 InlayHintRefreshReason::SettingsChange(_)
5131 | InlayHintRefreshReason::Toggle(_)
5132 | InlayHintRefreshReason::ExcerptsRemoved(_)
5133 | InlayHintRefreshReason::ModifiersChanged(_)
5134 );
5135 let (invalidate_cache, required_languages) = match reason {
5136 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5137 match self.inlay_hint_cache.modifiers_override(enabled) {
5138 Some(enabled) => {
5139 if enabled {
5140 (InvalidationStrategy::RefreshRequested, None)
5141 } else {
5142 self.splice_inlays(
5143 &self
5144 .visible_inlay_hints(cx)
5145 .iter()
5146 .map(|inlay| inlay.id)
5147 .collect::<Vec<InlayId>>(),
5148 Vec::new(),
5149 cx,
5150 );
5151 return;
5152 }
5153 }
5154 None => return,
5155 }
5156 }
5157 InlayHintRefreshReason::Toggle(enabled) => {
5158 if self.inlay_hint_cache.toggle(enabled) {
5159 if enabled {
5160 (InvalidationStrategy::RefreshRequested, None)
5161 } else {
5162 self.splice_inlays(
5163 &self
5164 .visible_inlay_hints(cx)
5165 .iter()
5166 .map(|inlay| inlay.id)
5167 .collect::<Vec<InlayId>>(),
5168 Vec::new(),
5169 cx,
5170 );
5171 return;
5172 }
5173 } else {
5174 return;
5175 }
5176 }
5177 InlayHintRefreshReason::SettingsChange(new_settings) => {
5178 match self.inlay_hint_cache.update_settings(
5179 &self.buffer,
5180 new_settings,
5181 self.visible_inlay_hints(cx),
5182 cx,
5183 ) {
5184 ControlFlow::Break(Some(InlaySplice {
5185 to_remove,
5186 to_insert,
5187 })) => {
5188 self.splice_inlays(&to_remove, to_insert, cx);
5189 return;
5190 }
5191 ControlFlow::Break(None) => return,
5192 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5193 }
5194 }
5195 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5196 if let Some(InlaySplice {
5197 to_remove,
5198 to_insert,
5199 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5200 {
5201 self.splice_inlays(&to_remove, to_insert, cx);
5202 }
5203 self.display_map.update(cx, |display_map, _| {
5204 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5205 });
5206 return;
5207 }
5208 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5209 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5210 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5211 }
5212 InlayHintRefreshReason::RefreshRequested => {
5213 (InvalidationStrategy::RefreshRequested, None)
5214 }
5215 };
5216
5217 if let Some(InlaySplice {
5218 to_remove,
5219 to_insert,
5220 }) = self.inlay_hint_cache.spawn_hint_refresh(
5221 reason_description,
5222 self.visible_excerpts(required_languages.as_ref(), cx),
5223 invalidate_cache,
5224 ignore_debounce,
5225 cx,
5226 ) {
5227 self.splice_inlays(&to_remove, to_insert, cx);
5228 }
5229 }
5230
5231 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5232 self.display_map
5233 .read(cx)
5234 .current_inlays()
5235 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5236 .cloned()
5237 .collect()
5238 }
5239
5240 pub fn visible_excerpts(
5241 &self,
5242 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5243 cx: &mut Context<Editor>,
5244 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5245 let Some(project) = self.project() else {
5246 return HashMap::default();
5247 };
5248 let project = project.read(cx);
5249 let multi_buffer = self.buffer().read(cx);
5250 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5251 let multi_buffer_visible_start = self
5252 .scroll_manager
5253 .anchor()
5254 .anchor
5255 .to_point(&multi_buffer_snapshot);
5256 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5257 multi_buffer_visible_start
5258 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5259 Bias::Left,
5260 );
5261 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5262 multi_buffer_snapshot
5263 .range_to_buffer_ranges(multi_buffer_visible_range)
5264 .into_iter()
5265 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5266 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5267 let buffer_file = project::File::from_dyn(buffer.file())?;
5268 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5269 let worktree_entry = buffer_worktree
5270 .read(cx)
5271 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5272 if worktree_entry.is_ignored {
5273 return None;
5274 }
5275
5276 let language = buffer.language()?;
5277 if let Some(restrict_to_languages) = restrict_to_languages {
5278 if !restrict_to_languages.contains(language) {
5279 return None;
5280 }
5281 }
5282 Some((
5283 excerpt_id,
5284 (
5285 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5286 buffer.version().clone(),
5287 excerpt_visible_range,
5288 ),
5289 ))
5290 })
5291 .collect()
5292 }
5293
5294 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5295 TextLayoutDetails {
5296 text_system: window.text_system().clone(),
5297 editor_style: self.style.clone().unwrap(),
5298 rem_size: window.rem_size(),
5299 scroll_anchor: self.scroll_manager.anchor(),
5300 visible_rows: self.visible_line_count(),
5301 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5302 }
5303 }
5304
5305 pub fn splice_inlays(
5306 &self,
5307 to_remove: &[InlayId],
5308 to_insert: Vec<Inlay>,
5309 cx: &mut Context<Self>,
5310 ) {
5311 self.display_map.update(cx, |display_map, cx| {
5312 display_map.splice_inlays(to_remove, to_insert, cx)
5313 });
5314 cx.notify();
5315 }
5316
5317 fn trigger_on_type_formatting(
5318 &self,
5319 input: String,
5320 window: &mut Window,
5321 cx: &mut Context<Self>,
5322 ) -> Option<Task<Result<()>>> {
5323 if input.len() != 1 {
5324 return None;
5325 }
5326
5327 let project = self.project()?;
5328 let position = self.selections.newest_anchor().head();
5329 let (buffer, buffer_position) = self
5330 .buffer
5331 .read(cx)
5332 .text_anchor_for_position(position, cx)?;
5333
5334 let settings = language_settings::language_settings(
5335 buffer
5336 .read(cx)
5337 .language_at(buffer_position)
5338 .map(|l| l.name()),
5339 buffer.read(cx).file(),
5340 cx,
5341 );
5342 if !settings.use_on_type_format {
5343 return None;
5344 }
5345
5346 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5347 // hence we do LSP request & edit on host side only — add formats to host's history.
5348 let push_to_lsp_host_history = true;
5349 // If this is not the host, append its history with new edits.
5350 let push_to_client_history = project.read(cx).is_via_collab();
5351
5352 let on_type_formatting = project.update(cx, |project, cx| {
5353 project.on_type_format(
5354 buffer.clone(),
5355 buffer_position,
5356 input,
5357 push_to_lsp_host_history,
5358 cx,
5359 )
5360 });
5361 Some(cx.spawn_in(window, async move |editor, cx| {
5362 if let Some(transaction) = on_type_formatting.await? {
5363 if push_to_client_history {
5364 buffer
5365 .update(cx, |buffer, _| {
5366 buffer.push_transaction(transaction, Instant::now());
5367 buffer.finalize_last_transaction();
5368 })
5369 .ok();
5370 }
5371 editor.update(cx, |editor, cx| {
5372 editor.refresh_document_highlights(cx);
5373 })?;
5374 }
5375 Ok(())
5376 }))
5377 }
5378
5379 pub fn show_word_completions(
5380 &mut self,
5381 _: &ShowWordCompletions,
5382 window: &mut Window,
5383 cx: &mut Context<Self>,
5384 ) {
5385 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5386 }
5387
5388 pub fn show_completions(
5389 &mut self,
5390 options: &ShowCompletions,
5391 window: &mut Window,
5392 cx: &mut Context<Self>,
5393 ) {
5394 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5395 }
5396
5397 fn open_or_update_completions_menu(
5398 &mut self,
5399 requested_source: Option<CompletionsMenuSource>,
5400 trigger: Option<&str>,
5401 window: &mut Window,
5402 cx: &mut Context<Self>,
5403 ) {
5404 if self.pending_rename.is_some() {
5405 return;
5406 }
5407
5408 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5409
5410 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5411 // inserted and selected. To handle that case, the start of the selection is used so that
5412 // the menu starts with all choices.
5413 let position = self
5414 .selections
5415 .newest_anchor()
5416 .start
5417 .bias_right(&multibuffer_snapshot);
5418 if position.diff_base_anchor.is_some() {
5419 return;
5420 }
5421 let (buffer, buffer_position) =
5422 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5423 output
5424 } else {
5425 return;
5426 };
5427 let buffer_snapshot = buffer.read(cx).snapshot();
5428
5429 let query: Option<Arc<String>> =
5430 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5431
5432 drop(multibuffer_snapshot);
5433
5434 let provider = match requested_source {
5435 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5436 Some(CompletionsMenuSource::Words) => None,
5437 Some(CompletionsMenuSource::SnippetChoices) => {
5438 log::error!("bug: SnippetChoices requested_source is not handled");
5439 None
5440 }
5441 };
5442
5443 let sort_completions = provider
5444 .as_ref()
5445 .map_or(false, |provider| provider.sort_completions());
5446
5447 let filter_completions = provider
5448 .as_ref()
5449 .map_or(true, |provider| provider.filter_completions());
5450
5451 let trigger_kind = match trigger {
5452 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5453 CompletionTriggerKind::TRIGGER_CHARACTER
5454 }
5455 _ => CompletionTriggerKind::INVOKED,
5456 };
5457 let completion_context = CompletionContext {
5458 trigger_character: trigger.and_then(|trigger| {
5459 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5460 Some(String::from(trigger))
5461 } else {
5462 None
5463 }
5464 }),
5465 trigger_kind,
5466 };
5467
5468 // Hide the current completions menu when a trigger char is typed. Without this, cached
5469 // completions from before the trigger char may be reused (#32774). Snippet choices could
5470 // involve trigger chars, so this is skipped in that case.
5471 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5472 {
5473 let menu_is_open = matches!(
5474 self.context_menu.borrow().as_ref(),
5475 Some(CodeContextMenu::Completions(_))
5476 );
5477 if menu_is_open {
5478 self.hide_context_menu(window, cx);
5479 }
5480 }
5481
5482 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5483 if filter_completions {
5484 menu.filter(query.clone(), provider.clone(), window, cx);
5485 }
5486 // When `is_incomplete` is false, no need to re-query completions when the current query
5487 // is a suffix of the initial query.
5488 if !menu.is_incomplete {
5489 // If the new query is a suffix of the old query (typing more characters) and
5490 // the previous result was complete, the existing completions can be filtered.
5491 //
5492 // Note that this is always true for snippet completions.
5493 let query_matches = match (&menu.initial_query, &query) {
5494 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5495 (None, _) => true,
5496 _ => false,
5497 };
5498 if query_matches {
5499 let position_matches = if menu.initial_position == position {
5500 true
5501 } else {
5502 let snapshot = self.buffer.read(cx).read(cx);
5503 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5504 };
5505 if position_matches {
5506 return;
5507 }
5508 }
5509 }
5510 };
5511
5512 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5513 buffer_snapshot.surrounding_word(buffer_position, false)
5514 {
5515 let word_to_exclude = buffer_snapshot
5516 .text_for_range(word_range.clone())
5517 .collect::<String>();
5518 (
5519 buffer_snapshot.anchor_before(word_range.start)
5520 ..buffer_snapshot.anchor_after(buffer_position),
5521 Some(word_to_exclude),
5522 )
5523 } else {
5524 (buffer_position..buffer_position, None)
5525 };
5526
5527 let language = buffer_snapshot
5528 .language_at(buffer_position)
5529 .map(|language| language.name());
5530
5531 let completion_settings =
5532 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5533
5534 let show_completion_documentation = buffer_snapshot
5535 .settings_at(buffer_position, cx)
5536 .show_completion_documentation;
5537
5538 // The document can be large, so stay in reasonable bounds when searching for words,
5539 // otherwise completion pop-up might be slow to appear.
5540 const WORD_LOOKUP_ROWS: u32 = 5_000;
5541 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5542 let min_word_search = buffer_snapshot.clip_point(
5543 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5544 Bias::Left,
5545 );
5546 let max_word_search = buffer_snapshot.clip_point(
5547 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5548 Bias::Right,
5549 );
5550 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5551 ..buffer_snapshot.point_to_offset(max_word_search);
5552
5553 let skip_digits = query
5554 .as_ref()
5555 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5556
5557 let (mut words, provider_responses) = match &provider {
5558 Some(provider) => {
5559 let provider_responses = provider.completions(
5560 position.excerpt_id,
5561 &buffer,
5562 buffer_position,
5563 completion_context,
5564 window,
5565 cx,
5566 );
5567
5568 let words = match completion_settings.words {
5569 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5570 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5571 .background_spawn(async move {
5572 buffer_snapshot.words_in_range(WordsQuery {
5573 fuzzy_contents: None,
5574 range: word_search_range,
5575 skip_digits,
5576 })
5577 }),
5578 };
5579
5580 (words, provider_responses)
5581 }
5582 None => (
5583 cx.background_spawn(async move {
5584 buffer_snapshot.words_in_range(WordsQuery {
5585 fuzzy_contents: None,
5586 range: word_search_range,
5587 skip_digits,
5588 })
5589 }),
5590 Task::ready(Ok(Vec::new())),
5591 ),
5592 };
5593
5594 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5595
5596 let id = post_inc(&mut self.next_completion_id);
5597 let task = cx.spawn_in(window, async move |editor, cx| {
5598 let Ok(()) = editor.update(cx, |this, _| {
5599 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5600 }) else {
5601 return;
5602 };
5603
5604 // TODO: Ideally completions from different sources would be selectively re-queried, so
5605 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5606 let mut completions = Vec::new();
5607 let mut is_incomplete = false;
5608 if let Some(provider_responses) = provider_responses.await.log_err() {
5609 if !provider_responses.is_empty() {
5610 for response in provider_responses {
5611 completions.extend(response.completions);
5612 is_incomplete = is_incomplete || response.is_incomplete;
5613 }
5614 if completion_settings.words == WordsCompletionMode::Fallback {
5615 words = Task::ready(BTreeMap::default());
5616 }
5617 }
5618 }
5619
5620 let mut words = words.await;
5621 if let Some(word_to_exclude) = &word_to_exclude {
5622 words.remove(word_to_exclude);
5623 }
5624 for lsp_completion in &completions {
5625 words.remove(&lsp_completion.new_text);
5626 }
5627 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5628 replace_range: word_replace_range.clone(),
5629 new_text: word.clone(),
5630 label: CodeLabel::plain(word, None),
5631 icon_path: None,
5632 documentation: None,
5633 source: CompletionSource::BufferWord {
5634 word_range,
5635 resolved: false,
5636 },
5637 insert_text_mode: Some(InsertTextMode::AS_IS),
5638 confirm: None,
5639 }));
5640
5641 let menu = if completions.is_empty() {
5642 None
5643 } else {
5644 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5645 let languages = editor
5646 .workspace
5647 .as_ref()
5648 .and_then(|(workspace, _)| workspace.upgrade())
5649 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5650 let menu = CompletionsMenu::new(
5651 id,
5652 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5653 sort_completions,
5654 show_completion_documentation,
5655 position,
5656 query.clone(),
5657 is_incomplete,
5658 buffer.clone(),
5659 completions.into(),
5660 snippet_sort_order,
5661 languages,
5662 language,
5663 cx,
5664 );
5665
5666 let query = if filter_completions { query } else { None };
5667 let matches_task = if let Some(query) = query {
5668 menu.do_async_filtering(query, cx)
5669 } else {
5670 Task::ready(menu.unfiltered_matches())
5671 };
5672 (menu, matches_task)
5673 }) else {
5674 return;
5675 };
5676
5677 let matches = matches_task.await;
5678
5679 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5680 // Newer menu already set, so exit.
5681 match editor.context_menu.borrow().as_ref() {
5682 Some(CodeContextMenu::Completions(prev_menu)) => {
5683 if prev_menu.id > id {
5684 return;
5685 }
5686 }
5687 _ => {}
5688 };
5689
5690 // Only valid to take prev_menu because it the new menu is immediately set
5691 // below, or the menu is hidden.
5692 match editor.context_menu.borrow_mut().take() {
5693 Some(CodeContextMenu::Completions(prev_menu)) => {
5694 let position_matches =
5695 if prev_menu.initial_position == menu.initial_position {
5696 true
5697 } else {
5698 let snapshot = editor.buffer.read(cx).read(cx);
5699 prev_menu.initial_position.to_offset(&snapshot)
5700 == menu.initial_position.to_offset(&snapshot)
5701 };
5702 if position_matches {
5703 // Preserve markdown cache before `set_filter_results` because it will
5704 // try to populate the documentation cache.
5705 menu.preserve_markdown_cache(prev_menu);
5706 }
5707 }
5708 _ => {}
5709 };
5710
5711 menu.set_filter_results(matches, provider, window, cx);
5712 }) else {
5713 return;
5714 };
5715
5716 menu.visible().then_some(menu)
5717 };
5718
5719 editor
5720 .update_in(cx, |editor, window, cx| {
5721 if editor.focus_handle.is_focused(window) {
5722 if let Some(menu) = menu {
5723 *editor.context_menu.borrow_mut() =
5724 Some(CodeContextMenu::Completions(menu));
5725
5726 crate::hover_popover::hide_hover(editor, cx);
5727 if editor.show_edit_predictions_in_menu() {
5728 editor.update_visible_edit_prediction(window, cx);
5729 } else {
5730 editor.discard_edit_prediction(false, cx);
5731 }
5732
5733 cx.notify();
5734 return;
5735 }
5736 }
5737
5738 if editor.completion_tasks.len() <= 1 {
5739 // If there are no more completion tasks and the last menu was empty, we should hide it.
5740 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5741 // If it was already hidden and we don't show edit predictions in the menu,
5742 // we should also show the edit prediction when available.
5743 if was_hidden && editor.show_edit_predictions_in_menu() {
5744 editor.update_visible_edit_prediction(window, cx);
5745 }
5746 }
5747 })
5748 .ok();
5749 });
5750
5751 self.completion_tasks.push((id, task));
5752 }
5753
5754 #[cfg(feature = "test-support")]
5755 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5756 let menu = self.context_menu.borrow();
5757 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5758 let completions = menu.completions.borrow();
5759 Some(completions.to_vec())
5760 } else {
5761 None
5762 }
5763 }
5764
5765 pub fn with_completions_menu_matching_id<R>(
5766 &self,
5767 id: CompletionId,
5768 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5769 ) -> R {
5770 let mut context_menu = self.context_menu.borrow_mut();
5771 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5772 return f(None);
5773 };
5774 if completions_menu.id != id {
5775 return f(None);
5776 }
5777 f(Some(completions_menu))
5778 }
5779
5780 pub fn confirm_completion(
5781 &mut self,
5782 action: &ConfirmCompletion,
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::Complete, window, cx)
5788 }
5789
5790 pub fn confirm_completion_insert(
5791 &mut self,
5792 _: &ConfirmCompletionInsert,
5793 window: &mut Window,
5794 cx: &mut Context<Self>,
5795 ) -> Option<Task<Result<()>>> {
5796 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5797 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5798 }
5799
5800 pub fn confirm_completion_replace(
5801 &mut self,
5802 _: &ConfirmCompletionReplace,
5803 window: &mut Window,
5804 cx: &mut Context<Self>,
5805 ) -> Option<Task<Result<()>>> {
5806 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5807 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5808 }
5809
5810 pub fn compose_completion(
5811 &mut self,
5812 action: &ComposeCompletion,
5813 window: &mut Window,
5814 cx: &mut Context<Self>,
5815 ) -> Option<Task<Result<()>>> {
5816 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5817 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5818 }
5819
5820 fn do_completion(
5821 &mut self,
5822 item_ix: Option<usize>,
5823 intent: CompletionIntent,
5824 window: &mut Window,
5825 cx: &mut Context<Editor>,
5826 ) -> Option<Task<Result<()>>> {
5827 use language::ToOffset as _;
5828
5829 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5830 else {
5831 return None;
5832 };
5833
5834 let candidate_id = {
5835 let entries = completions_menu.entries.borrow();
5836 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5837 if self.show_edit_predictions_in_menu() {
5838 self.discard_edit_prediction(true, cx);
5839 }
5840 mat.candidate_id
5841 };
5842
5843 let completion = completions_menu
5844 .completions
5845 .borrow()
5846 .get(candidate_id)?
5847 .clone();
5848 cx.stop_propagation();
5849
5850 let buffer_handle = completions_menu.buffer.clone();
5851
5852 let CompletionEdit {
5853 new_text,
5854 snippet,
5855 replace_range,
5856 } = process_completion_for_edit(
5857 &completion,
5858 intent,
5859 &buffer_handle,
5860 &completions_menu.initial_position.text_anchor,
5861 cx,
5862 );
5863
5864 let buffer = buffer_handle.read(cx);
5865 let snapshot = self.buffer.read(cx).snapshot(cx);
5866 let newest_anchor = self.selections.newest_anchor();
5867 let replace_range_multibuffer = {
5868 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5869 let multibuffer_anchor = snapshot
5870 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5871 .unwrap()
5872 ..snapshot
5873 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5874 .unwrap();
5875 multibuffer_anchor.start.to_offset(&snapshot)
5876 ..multibuffer_anchor.end.to_offset(&snapshot)
5877 };
5878 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5879 return None;
5880 }
5881
5882 let old_text = buffer
5883 .text_for_range(replace_range.clone())
5884 .collect::<String>();
5885 let lookbehind = newest_anchor
5886 .start
5887 .text_anchor
5888 .to_offset(buffer)
5889 .saturating_sub(replace_range.start);
5890 let lookahead = replace_range
5891 .end
5892 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5893 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5894 let suffix = &old_text[lookbehind.min(old_text.len())..];
5895
5896 let selections = self.selections.all::<usize>(cx);
5897 let mut ranges = Vec::new();
5898 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5899
5900 for selection in &selections {
5901 let range = if selection.id == newest_anchor.id {
5902 replace_range_multibuffer.clone()
5903 } else {
5904 let mut range = selection.range();
5905
5906 // if prefix is present, don't duplicate it
5907 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5908 range.start = range.start.saturating_sub(lookbehind);
5909
5910 // if suffix is also present, mimic the newest cursor and replace it
5911 if selection.id != newest_anchor.id
5912 && snapshot.contains_str_at(range.end, suffix)
5913 {
5914 range.end += lookahead;
5915 }
5916 }
5917 range
5918 };
5919
5920 ranges.push(range.clone());
5921
5922 if !self.linked_edit_ranges.is_empty() {
5923 let start_anchor = snapshot.anchor_before(range.start);
5924 let end_anchor = snapshot.anchor_after(range.end);
5925 if let Some(ranges) = self
5926 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5927 {
5928 for (buffer, edits) in ranges {
5929 linked_edits
5930 .entry(buffer.clone())
5931 .or_default()
5932 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5933 }
5934 }
5935 }
5936 }
5937
5938 let common_prefix_len = old_text
5939 .chars()
5940 .zip(new_text.chars())
5941 .take_while(|(a, b)| a == b)
5942 .map(|(a, _)| a.len_utf8())
5943 .sum::<usize>();
5944
5945 cx.emit(EditorEvent::InputHandled {
5946 utf16_range_to_replace: None,
5947 text: new_text[common_prefix_len..].into(),
5948 });
5949
5950 self.transact(window, cx, |editor, window, cx| {
5951 if let Some(mut snippet) = snippet {
5952 snippet.text = new_text.to_string();
5953 editor
5954 .insert_snippet(&ranges, snippet, window, cx)
5955 .log_err();
5956 } else {
5957 editor.buffer.update(cx, |multi_buffer, cx| {
5958 let auto_indent = match completion.insert_text_mode {
5959 Some(InsertTextMode::AS_IS) => None,
5960 _ => editor.autoindent_mode.clone(),
5961 };
5962 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5963 multi_buffer.edit(edits, auto_indent, cx);
5964 });
5965 }
5966 for (buffer, edits) in linked_edits {
5967 buffer.update(cx, |buffer, cx| {
5968 let snapshot = buffer.snapshot();
5969 let edits = edits
5970 .into_iter()
5971 .map(|(range, text)| {
5972 use text::ToPoint as TP;
5973 let end_point = TP::to_point(&range.end, &snapshot);
5974 let start_point = TP::to_point(&range.start, &snapshot);
5975 (start_point..end_point, text)
5976 })
5977 .sorted_by_key(|(range, _)| range.start);
5978 buffer.edit(edits, None, cx);
5979 })
5980 }
5981
5982 editor.refresh_edit_prediction(true, false, window, cx);
5983 });
5984 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5985
5986 let show_new_completions_on_confirm = completion
5987 .confirm
5988 .as_ref()
5989 .map_or(false, |confirm| confirm(intent, window, cx));
5990 if show_new_completions_on_confirm {
5991 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5992 }
5993
5994 let provider = self.completion_provider.as_ref()?;
5995 drop(completion);
5996 let apply_edits = provider.apply_additional_edits_for_completion(
5997 buffer_handle,
5998 completions_menu.completions.clone(),
5999 candidate_id,
6000 true,
6001 cx,
6002 );
6003
6004 let editor_settings = EditorSettings::get_global(cx);
6005 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6006 // After the code completion is finished, users often want to know what signatures are needed.
6007 // so we should automatically call signature_help
6008 self.show_signature_help(&ShowSignatureHelp, window, cx);
6009 }
6010
6011 Some(cx.foreground_executor().spawn(async move {
6012 apply_edits.await?;
6013 Ok(())
6014 }))
6015 }
6016
6017 pub fn toggle_code_actions(
6018 &mut self,
6019 action: &ToggleCodeActions,
6020 window: &mut Window,
6021 cx: &mut Context<Self>,
6022 ) {
6023 let quick_launch = action.quick_launch;
6024 let mut context_menu = self.context_menu.borrow_mut();
6025 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6026 if code_actions.deployed_from == action.deployed_from {
6027 // Toggle if we're selecting the same one
6028 *context_menu = None;
6029 cx.notify();
6030 return;
6031 } else {
6032 // Otherwise, clear it and start a new one
6033 *context_menu = None;
6034 cx.notify();
6035 }
6036 }
6037 drop(context_menu);
6038 let snapshot = self.snapshot(window, cx);
6039 let deployed_from = action.deployed_from.clone();
6040 let action = action.clone();
6041 self.completion_tasks.clear();
6042 self.discard_edit_prediction(false, cx);
6043
6044 let multibuffer_point = match &action.deployed_from {
6045 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6046 DisplayPoint::new(*row, 0).to_point(&snapshot)
6047 }
6048 _ => self.selections.newest::<Point>(cx).head(),
6049 };
6050 let Some((buffer, buffer_row)) = snapshot
6051 .buffer_snapshot
6052 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6053 .and_then(|(buffer_snapshot, range)| {
6054 self.buffer()
6055 .read(cx)
6056 .buffer(buffer_snapshot.remote_id())
6057 .map(|buffer| (buffer, range.start.row))
6058 })
6059 else {
6060 return;
6061 };
6062 let buffer_id = buffer.read(cx).remote_id();
6063 let tasks = self
6064 .tasks
6065 .get(&(buffer_id, buffer_row))
6066 .map(|t| Arc::new(t.to_owned()));
6067
6068 if !self.focus_handle.is_focused(window) {
6069 return;
6070 }
6071 let project = self.project.clone();
6072
6073 let code_actions_task = match deployed_from {
6074 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6075 _ => self.code_actions(buffer_row, window, cx),
6076 };
6077
6078 let runnable_task = match deployed_from {
6079 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6080 _ => {
6081 let mut task_context_task = Task::ready(None);
6082 if let Some(tasks) = &tasks {
6083 if let Some(project) = project {
6084 task_context_task =
6085 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6086 }
6087 }
6088
6089 cx.spawn_in(window, {
6090 let buffer = buffer.clone();
6091 async move |editor, cx| {
6092 let task_context = task_context_task.await;
6093
6094 let resolved_tasks =
6095 tasks
6096 .zip(task_context.clone())
6097 .map(|(tasks, task_context)| ResolvedTasks {
6098 templates: tasks.resolve(&task_context).collect(),
6099 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6100 multibuffer_point.row,
6101 tasks.column,
6102 )),
6103 });
6104 let debug_scenarios = editor
6105 .update(cx, |editor, cx| {
6106 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6107 })?
6108 .await;
6109 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6110 }
6111 })
6112 }
6113 };
6114
6115 cx.spawn_in(window, async move |editor, cx| {
6116 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6117 let code_actions = code_actions_task.await;
6118 let spawn_straight_away = quick_launch
6119 && resolved_tasks
6120 .as_ref()
6121 .map_or(false, |tasks| tasks.templates.len() == 1)
6122 && code_actions
6123 .as_ref()
6124 .map_or(true, |actions| actions.is_empty())
6125 && debug_scenarios.is_empty();
6126
6127 editor.update_in(cx, |editor, window, cx| {
6128 crate::hover_popover::hide_hover(editor, cx);
6129 let actions = CodeActionContents::new(
6130 resolved_tasks,
6131 code_actions,
6132 debug_scenarios,
6133 task_context.unwrap_or_default(),
6134 );
6135
6136 // Don't show the menu if there are no actions available
6137 if actions.is_empty() {
6138 cx.notify();
6139 return Task::ready(Ok(()));
6140 }
6141
6142 *editor.context_menu.borrow_mut() =
6143 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6144 buffer,
6145 actions,
6146 selected_item: Default::default(),
6147 scroll_handle: UniformListScrollHandle::default(),
6148 deployed_from,
6149 }));
6150 cx.notify();
6151 if spawn_straight_away {
6152 if let Some(task) = editor.confirm_code_action(
6153 &ConfirmCodeAction { item_ix: Some(0) },
6154 window,
6155 cx,
6156 ) {
6157 return task;
6158 }
6159 }
6160
6161 Task::ready(Ok(()))
6162 })
6163 })
6164 .detach_and_log_err(cx);
6165 }
6166
6167 fn debug_scenarios(
6168 &mut self,
6169 resolved_tasks: &Option<ResolvedTasks>,
6170 buffer: &Entity<Buffer>,
6171 cx: &mut App,
6172 ) -> Task<Vec<task::DebugScenario>> {
6173 maybe!({
6174 let project = self.project()?;
6175 let dap_store = project.read(cx).dap_store();
6176 let mut scenarios = vec![];
6177 let resolved_tasks = resolved_tasks.as_ref()?;
6178 let buffer = buffer.read(cx);
6179 let language = buffer.language()?;
6180 let file = buffer.file();
6181 let debug_adapter = language_settings(language.name().into(), file, cx)
6182 .debuggers
6183 .first()
6184 .map(SharedString::from)
6185 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6186
6187 dap_store.update(cx, |dap_store, cx| {
6188 for (_, task) in &resolved_tasks.templates {
6189 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6190 task.original_task().clone(),
6191 debug_adapter.clone().into(),
6192 task.display_label().to_owned().into(),
6193 cx,
6194 );
6195 scenarios.push(maybe_scenario);
6196 }
6197 });
6198 Some(cx.background_spawn(async move {
6199 let scenarios = futures::future::join_all(scenarios)
6200 .await
6201 .into_iter()
6202 .flatten()
6203 .collect::<Vec<_>>();
6204 scenarios
6205 }))
6206 })
6207 .unwrap_or_else(|| Task::ready(vec![]))
6208 }
6209
6210 fn code_actions(
6211 &mut self,
6212 buffer_row: u32,
6213 window: &mut Window,
6214 cx: &mut Context<Self>,
6215 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6216 let mut task = self.code_actions_task.take();
6217 cx.spawn_in(window, async move |editor, cx| {
6218 while let Some(prev_task) = task {
6219 prev_task.await.log_err();
6220 task = editor
6221 .update(cx, |this, _| this.code_actions_task.take())
6222 .ok()?;
6223 }
6224
6225 editor
6226 .update(cx, |editor, cx| {
6227 editor
6228 .available_code_actions
6229 .clone()
6230 .and_then(|(location, code_actions)| {
6231 let snapshot = location.buffer.read(cx).snapshot();
6232 let point_range = location.range.to_point(&snapshot);
6233 let point_range = point_range.start.row..=point_range.end.row;
6234 if point_range.contains(&buffer_row) {
6235 Some(code_actions)
6236 } else {
6237 None
6238 }
6239 })
6240 })
6241 .ok()
6242 .flatten()
6243 })
6244 }
6245
6246 pub fn confirm_code_action(
6247 &mut self,
6248 action: &ConfirmCodeAction,
6249 window: &mut Window,
6250 cx: &mut Context<Self>,
6251 ) -> Option<Task<Result<()>>> {
6252 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6253
6254 let actions_menu =
6255 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6256 menu
6257 } else {
6258 return None;
6259 };
6260
6261 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6262 let action = actions_menu.actions.get(action_ix)?;
6263 let title = action.label();
6264 let buffer = actions_menu.buffer;
6265 let workspace = self.workspace()?;
6266
6267 match action {
6268 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6269 workspace.update(cx, |workspace, cx| {
6270 workspace.schedule_resolved_task(
6271 task_source_kind,
6272 resolved_task,
6273 false,
6274 window,
6275 cx,
6276 );
6277
6278 Some(Task::ready(Ok(())))
6279 })
6280 }
6281 CodeActionsItem::CodeAction {
6282 excerpt_id,
6283 action,
6284 provider,
6285 } => {
6286 let apply_code_action =
6287 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6288 let workspace = workspace.downgrade();
6289 Some(cx.spawn_in(window, async move |editor, cx| {
6290 let project_transaction = apply_code_action.await?;
6291 Self::open_project_transaction(
6292 &editor,
6293 workspace,
6294 project_transaction,
6295 title,
6296 cx,
6297 )
6298 .await
6299 }))
6300 }
6301 CodeActionsItem::DebugScenario(scenario) => {
6302 let context = actions_menu.actions.context.clone();
6303
6304 workspace.update(cx, |workspace, cx| {
6305 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6306 workspace.start_debug_session(
6307 scenario,
6308 context,
6309 Some(buffer),
6310 None,
6311 window,
6312 cx,
6313 );
6314 });
6315 Some(Task::ready(Ok(())))
6316 }
6317 }
6318 }
6319
6320 pub async fn open_project_transaction(
6321 this: &WeakEntity<Editor>,
6322 workspace: WeakEntity<Workspace>,
6323 transaction: ProjectTransaction,
6324 title: String,
6325 cx: &mut AsyncWindowContext,
6326 ) -> Result<()> {
6327 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6328 cx.update(|_, cx| {
6329 entries.sort_unstable_by_key(|(buffer, _)| {
6330 buffer.read(cx).file().map(|f| f.path().clone())
6331 });
6332 })?;
6333
6334 // If the project transaction's edits are all contained within this editor, then
6335 // avoid opening a new editor to display them.
6336
6337 if let Some((buffer, transaction)) = entries.first() {
6338 if entries.len() == 1 {
6339 let excerpt = this.update(cx, |editor, cx| {
6340 editor
6341 .buffer()
6342 .read(cx)
6343 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6344 })?;
6345 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6346 if excerpted_buffer == *buffer {
6347 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6348 let excerpt_range = excerpt_range.to_offset(buffer);
6349 buffer
6350 .edited_ranges_for_transaction::<usize>(transaction)
6351 .all(|range| {
6352 excerpt_range.start <= range.start
6353 && excerpt_range.end >= range.end
6354 })
6355 })?;
6356
6357 if all_edits_within_excerpt {
6358 return Ok(());
6359 }
6360 }
6361 }
6362 }
6363 } else {
6364 return Ok(());
6365 }
6366
6367 let mut ranges_to_highlight = Vec::new();
6368 let excerpt_buffer = cx.new(|cx| {
6369 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6370 for (buffer_handle, transaction) in &entries {
6371 let edited_ranges = buffer_handle
6372 .read(cx)
6373 .edited_ranges_for_transaction::<Point>(transaction)
6374 .collect::<Vec<_>>();
6375 let (ranges, _) = multibuffer.set_excerpts_for_path(
6376 PathKey::for_buffer(buffer_handle, cx),
6377 buffer_handle.clone(),
6378 edited_ranges,
6379 DEFAULT_MULTIBUFFER_CONTEXT,
6380 cx,
6381 );
6382
6383 ranges_to_highlight.extend(ranges);
6384 }
6385 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6386 multibuffer
6387 })?;
6388
6389 workspace.update_in(cx, |workspace, window, cx| {
6390 let project = workspace.project().clone();
6391 let editor =
6392 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6393 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6394 editor.update(cx, |editor, cx| {
6395 editor.highlight_background::<Self>(
6396 &ranges_to_highlight,
6397 |theme| theme.colors().editor_highlighted_line_background,
6398 cx,
6399 );
6400 });
6401 })?;
6402
6403 Ok(())
6404 }
6405
6406 pub fn clear_code_action_providers(&mut self) {
6407 self.code_action_providers.clear();
6408 self.available_code_actions.take();
6409 }
6410
6411 pub fn add_code_action_provider(
6412 &mut self,
6413 provider: Rc<dyn CodeActionProvider>,
6414 window: &mut Window,
6415 cx: &mut Context<Self>,
6416 ) {
6417 if self
6418 .code_action_providers
6419 .iter()
6420 .any(|existing_provider| existing_provider.id() == provider.id())
6421 {
6422 return;
6423 }
6424
6425 self.code_action_providers.push(provider);
6426 self.refresh_code_actions(window, cx);
6427 }
6428
6429 pub fn remove_code_action_provider(
6430 &mut self,
6431 id: Arc<str>,
6432 window: &mut Window,
6433 cx: &mut Context<Self>,
6434 ) {
6435 self.code_action_providers
6436 .retain(|provider| provider.id() != id);
6437 self.refresh_code_actions(window, cx);
6438 }
6439
6440 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6441 !self.code_action_providers.is_empty()
6442 && EditorSettings::get_global(cx).toolbar.code_actions
6443 }
6444
6445 pub fn has_available_code_actions(&self) -> bool {
6446 self.available_code_actions
6447 .as_ref()
6448 .is_some_and(|(_, actions)| !actions.is_empty())
6449 }
6450
6451 fn render_inline_code_actions(
6452 &self,
6453 icon_size: ui::IconSize,
6454 display_row: DisplayRow,
6455 is_active: bool,
6456 cx: &mut Context<Self>,
6457 ) -> AnyElement {
6458 let show_tooltip = !self.context_menu_visible();
6459 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6460 .icon_size(icon_size)
6461 .shape(ui::IconButtonShape::Square)
6462 .icon_color(ui::Color::Hidden)
6463 .toggle_state(is_active)
6464 .when(show_tooltip, |this| {
6465 this.tooltip({
6466 let focus_handle = self.focus_handle.clone();
6467 move |window, cx| {
6468 Tooltip::for_action_in(
6469 "Toggle Code Actions",
6470 &ToggleCodeActions {
6471 deployed_from: None,
6472 quick_launch: false,
6473 },
6474 &focus_handle,
6475 window,
6476 cx,
6477 )
6478 }
6479 })
6480 })
6481 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6482 window.focus(&editor.focus_handle(cx));
6483 editor.toggle_code_actions(
6484 &crate::actions::ToggleCodeActions {
6485 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6486 display_row,
6487 )),
6488 quick_launch: false,
6489 },
6490 window,
6491 cx,
6492 );
6493 }))
6494 .into_any_element()
6495 }
6496
6497 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6498 &self.context_menu
6499 }
6500
6501 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6502 let newest_selection = self.selections.newest_anchor().clone();
6503 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6504 let buffer = self.buffer.read(cx);
6505 if newest_selection.head().diff_base_anchor.is_some() {
6506 return None;
6507 }
6508 let (start_buffer, start) =
6509 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6510 let (end_buffer, end) =
6511 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6512 if start_buffer != end_buffer {
6513 return None;
6514 }
6515
6516 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6517 cx.background_executor()
6518 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6519 .await;
6520
6521 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6522 let providers = this.code_action_providers.clone();
6523 let tasks = this
6524 .code_action_providers
6525 .iter()
6526 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6527 .collect::<Vec<_>>();
6528 (providers, tasks)
6529 })?;
6530
6531 let mut actions = Vec::new();
6532 for (provider, provider_actions) in
6533 providers.into_iter().zip(future::join_all(tasks).await)
6534 {
6535 if let Some(provider_actions) = provider_actions.log_err() {
6536 actions.extend(provider_actions.into_iter().map(|action| {
6537 AvailableCodeAction {
6538 excerpt_id: newest_selection.start.excerpt_id,
6539 action,
6540 provider: provider.clone(),
6541 }
6542 }));
6543 }
6544 }
6545
6546 this.update(cx, |this, cx| {
6547 this.available_code_actions = if actions.is_empty() {
6548 None
6549 } else {
6550 Some((
6551 Location {
6552 buffer: start_buffer,
6553 range: start..end,
6554 },
6555 actions.into(),
6556 ))
6557 };
6558 cx.notify();
6559 })
6560 }));
6561 None
6562 }
6563
6564 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6565 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6566 self.show_git_blame_inline = false;
6567
6568 self.show_git_blame_inline_delay_task =
6569 Some(cx.spawn_in(window, async move |this, cx| {
6570 cx.background_executor().timer(delay).await;
6571
6572 this.update(cx, |this, cx| {
6573 this.show_git_blame_inline = true;
6574 cx.notify();
6575 })
6576 .log_err();
6577 }));
6578 }
6579 }
6580
6581 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6582 let snapshot = self.snapshot(window, cx);
6583 let cursor = self.selections.newest::<Point>(cx).head();
6584 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6585 else {
6586 return;
6587 };
6588
6589 let Some(blame) = self.blame.as_ref() else {
6590 return;
6591 };
6592
6593 let row_info = RowInfo {
6594 buffer_id: Some(buffer.remote_id()),
6595 buffer_row: Some(point.row),
6596 ..Default::default()
6597 };
6598 let Some(blame_entry) = blame
6599 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6600 .flatten()
6601 else {
6602 return;
6603 };
6604
6605 let anchor = self.selections.newest_anchor().head();
6606 let position = self.to_pixel_point(anchor, &snapshot, window);
6607 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6608 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6609 };
6610 }
6611
6612 fn show_blame_popover(
6613 &mut self,
6614 blame_entry: &BlameEntry,
6615 position: gpui::Point<Pixels>,
6616 ignore_timeout: bool,
6617 cx: &mut Context<Self>,
6618 ) {
6619 if let Some(state) = &mut self.inline_blame_popover {
6620 state.hide_task.take();
6621 } else {
6622 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6623 let blame_entry = blame_entry.clone();
6624 let show_task = cx.spawn(async move |editor, cx| {
6625 if !ignore_timeout {
6626 cx.background_executor()
6627 .timer(std::time::Duration::from_millis(blame_popover_delay))
6628 .await;
6629 }
6630 editor
6631 .update(cx, |editor, cx| {
6632 editor.inline_blame_popover_show_task.take();
6633 let Some(blame) = editor.blame.as_ref() else {
6634 return;
6635 };
6636 let blame = blame.read(cx);
6637 let details = blame.details_for_entry(&blame_entry);
6638 let markdown = cx.new(|cx| {
6639 Markdown::new(
6640 details
6641 .as_ref()
6642 .map(|message| message.message.clone())
6643 .unwrap_or_default(),
6644 None,
6645 None,
6646 cx,
6647 )
6648 });
6649 editor.inline_blame_popover = Some(InlineBlamePopover {
6650 position,
6651 hide_task: None,
6652 popover_bounds: None,
6653 popover_state: InlineBlamePopoverState {
6654 scroll_handle: ScrollHandle::new(),
6655 commit_message: details,
6656 markdown,
6657 },
6658 keyboard_grace: ignore_timeout,
6659 });
6660 cx.notify();
6661 })
6662 .ok();
6663 });
6664 self.inline_blame_popover_show_task = Some(show_task);
6665 }
6666 }
6667
6668 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6669 self.inline_blame_popover_show_task.take();
6670 if let Some(state) = &mut self.inline_blame_popover {
6671 let hide_task = cx.spawn(async move |editor, cx| {
6672 cx.background_executor()
6673 .timer(std::time::Duration::from_millis(100))
6674 .await;
6675 editor
6676 .update(cx, |editor, cx| {
6677 editor.inline_blame_popover.take();
6678 cx.notify();
6679 })
6680 .ok();
6681 });
6682 state.hide_task = Some(hide_task);
6683 }
6684 }
6685
6686 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6687 if self.pending_rename.is_some() {
6688 return None;
6689 }
6690
6691 let provider = self.semantics_provider.clone()?;
6692 let buffer = self.buffer.read(cx);
6693 let newest_selection = self.selections.newest_anchor().clone();
6694 let cursor_position = newest_selection.head();
6695 let (cursor_buffer, cursor_buffer_position) =
6696 buffer.text_anchor_for_position(cursor_position, cx)?;
6697 let (tail_buffer, tail_buffer_position) =
6698 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6699 if cursor_buffer != tail_buffer {
6700 return None;
6701 }
6702
6703 let snapshot = cursor_buffer.read(cx).snapshot();
6704 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6705 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6706 if start_word_range != end_word_range {
6707 self.document_highlights_task.take();
6708 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6709 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6710 return None;
6711 }
6712
6713 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6714 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6715 cx.background_executor()
6716 .timer(Duration::from_millis(debounce))
6717 .await;
6718
6719 let highlights = if let Some(highlights) = cx
6720 .update(|cx| {
6721 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6722 })
6723 .ok()
6724 .flatten()
6725 {
6726 highlights.await.log_err()
6727 } else {
6728 None
6729 };
6730
6731 if let Some(highlights) = highlights {
6732 this.update(cx, |this, cx| {
6733 if this.pending_rename.is_some() {
6734 return;
6735 }
6736
6737 let buffer_id = cursor_position.buffer_id;
6738 let buffer = this.buffer.read(cx);
6739 if !buffer
6740 .text_anchor_for_position(cursor_position, cx)
6741 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6742 {
6743 return;
6744 }
6745
6746 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6747 let mut write_ranges = Vec::new();
6748 let mut read_ranges = Vec::new();
6749 for highlight in highlights {
6750 for (excerpt_id, excerpt_range) in
6751 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6752 {
6753 let start = highlight
6754 .range
6755 .start
6756 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6757 let end = highlight
6758 .range
6759 .end
6760 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6761 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6762 continue;
6763 }
6764
6765 let range = Anchor {
6766 buffer_id,
6767 excerpt_id,
6768 text_anchor: start,
6769 diff_base_anchor: None,
6770 }..Anchor {
6771 buffer_id,
6772 excerpt_id,
6773 text_anchor: end,
6774 diff_base_anchor: None,
6775 };
6776 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6777 write_ranges.push(range);
6778 } else {
6779 read_ranges.push(range);
6780 }
6781 }
6782 }
6783
6784 this.highlight_background::<DocumentHighlightRead>(
6785 &read_ranges,
6786 |theme| theme.colors().editor_document_highlight_read_background,
6787 cx,
6788 );
6789 this.highlight_background::<DocumentHighlightWrite>(
6790 &write_ranges,
6791 |theme| theme.colors().editor_document_highlight_write_background,
6792 cx,
6793 );
6794 cx.notify();
6795 })
6796 .log_err();
6797 }
6798 }));
6799 None
6800 }
6801
6802 fn prepare_highlight_query_from_selection(
6803 &mut self,
6804 cx: &mut Context<Editor>,
6805 ) -> Option<(String, Range<Anchor>)> {
6806 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6807 return None;
6808 }
6809 if !EditorSettings::get_global(cx).selection_highlight {
6810 return None;
6811 }
6812 if self.selections.count() != 1 || self.selections.line_mode {
6813 return None;
6814 }
6815 let selection = self.selections.newest::<Point>(cx);
6816 if selection.is_empty() || selection.start.row != selection.end.row {
6817 return None;
6818 }
6819 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6820 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6821 let query = multi_buffer_snapshot
6822 .text_for_range(selection_anchor_range.clone())
6823 .collect::<String>();
6824 if query.trim().is_empty() {
6825 return None;
6826 }
6827 Some((query, selection_anchor_range))
6828 }
6829
6830 fn update_selection_occurrence_highlights(
6831 &mut self,
6832 query_text: String,
6833 query_range: Range<Anchor>,
6834 multi_buffer_range_to_query: Range<Point>,
6835 use_debounce: bool,
6836 window: &mut Window,
6837 cx: &mut Context<Editor>,
6838 ) -> Task<()> {
6839 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6840 cx.spawn_in(window, async move |editor, cx| {
6841 if use_debounce {
6842 cx.background_executor()
6843 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6844 .await;
6845 }
6846 let match_task = cx.background_spawn(async move {
6847 let buffer_ranges = multi_buffer_snapshot
6848 .range_to_buffer_ranges(multi_buffer_range_to_query)
6849 .into_iter()
6850 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6851 let mut match_ranges = Vec::new();
6852 let Ok(regex) = project::search::SearchQuery::text(
6853 query_text.clone(),
6854 false,
6855 false,
6856 false,
6857 Default::default(),
6858 Default::default(),
6859 false,
6860 None,
6861 ) else {
6862 return Vec::default();
6863 };
6864 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6865 match_ranges.extend(
6866 regex
6867 .search(buffer_snapshot, Some(search_range.clone()))
6868 .await
6869 .into_iter()
6870 .filter_map(|match_range| {
6871 let match_start = buffer_snapshot
6872 .anchor_after(search_range.start + match_range.start);
6873 let match_end = buffer_snapshot
6874 .anchor_before(search_range.start + match_range.end);
6875 let match_anchor_range = Anchor::range_in_buffer(
6876 excerpt_id,
6877 buffer_snapshot.remote_id(),
6878 match_start..match_end,
6879 );
6880 (match_anchor_range != query_range).then_some(match_anchor_range)
6881 }),
6882 );
6883 }
6884 match_ranges
6885 });
6886 let match_ranges = match_task.await;
6887 editor
6888 .update_in(cx, |editor, _, cx| {
6889 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6890 if !match_ranges.is_empty() {
6891 editor.highlight_background::<SelectedTextHighlight>(
6892 &match_ranges,
6893 |theme| theme.colors().editor_document_highlight_bracket_background,
6894 cx,
6895 )
6896 }
6897 })
6898 .log_err();
6899 })
6900 }
6901
6902 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6903 struct NewlineFold;
6904 let type_id = std::any::TypeId::of::<NewlineFold>();
6905 if !self.mode.is_single_line() {
6906 return;
6907 }
6908 let snapshot = self.snapshot(window, cx);
6909 if snapshot.buffer_snapshot.max_point().row == 0 {
6910 return;
6911 }
6912 let task = cx.background_spawn(async move {
6913 let new_newlines = snapshot
6914 .buffer_chars_at(0)
6915 .filter_map(|(c, i)| {
6916 if c == '\n' {
6917 Some(
6918 snapshot.buffer_snapshot.anchor_after(i)
6919 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6920 )
6921 } else {
6922 None
6923 }
6924 })
6925 .collect::<Vec<_>>();
6926 let existing_newlines = snapshot
6927 .folds_in_range(0..snapshot.buffer_snapshot.len())
6928 .filter_map(|fold| {
6929 if fold.placeholder.type_tag == Some(type_id) {
6930 Some(fold.range.start..fold.range.end)
6931 } else {
6932 None
6933 }
6934 })
6935 .collect::<Vec<_>>();
6936
6937 (new_newlines, existing_newlines)
6938 });
6939 self.folding_newlines = cx.spawn(async move |this, cx| {
6940 let (new_newlines, existing_newlines) = task.await;
6941 if new_newlines == existing_newlines {
6942 return;
6943 }
6944 let placeholder = FoldPlaceholder {
6945 render: Arc::new(move |_, _, cx| {
6946 div()
6947 .bg(cx.theme().status().hint_background)
6948 .border_b_1()
6949 .size_full()
6950 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6951 .border_color(cx.theme().status().hint)
6952 .child("\\n")
6953 .into_any()
6954 }),
6955 constrain_width: false,
6956 merge_adjacent: false,
6957 type_tag: Some(type_id),
6958 };
6959 let creases = new_newlines
6960 .into_iter()
6961 .map(|range| Crease::simple(range, placeholder.clone()))
6962 .collect();
6963 this.update(cx, |this, cx| {
6964 this.display_map.update(cx, |display_map, cx| {
6965 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6966 display_map.fold(creases, cx);
6967 });
6968 })
6969 .ok();
6970 });
6971 }
6972
6973 fn refresh_selected_text_highlights(
6974 &mut self,
6975 on_buffer_edit: bool,
6976 window: &mut Window,
6977 cx: &mut Context<Editor>,
6978 ) {
6979 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6980 else {
6981 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6982 self.quick_selection_highlight_task.take();
6983 self.debounced_selection_highlight_task.take();
6984 return;
6985 };
6986 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6987 if on_buffer_edit
6988 || self
6989 .quick_selection_highlight_task
6990 .as_ref()
6991 .map_or(true, |(prev_anchor_range, _)| {
6992 prev_anchor_range != &query_range
6993 })
6994 {
6995 let multi_buffer_visible_start = self
6996 .scroll_manager
6997 .anchor()
6998 .anchor
6999 .to_point(&multi_buffer_snapshot);
7000 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7001 multi_buffer_visible_start
7002 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7003 Bias::Left,
7004 );
7005 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7006 self.quick_selection_highlight_task = Some((
7007 query_range.clone(),
7008 self.update_selection_occurrence_highlights(
7009 query_text.clone(),
7010 query_range.clone(),
7011 multi_buffer_visible_range,
7012 false,
7013 window,
7014 cx,
7015 ),
7016 ));
7017 }
7018 if on_buffer_edit
7019 || self
7020 .debounced_selection_highlight_task
7021 .as_ref()
7022 .map_or(true, |(prev_anchor_range, _)| {
7023 prev_anchor_range != &query_range
7024 })
7025 {
7026 let multi_buffer_start = multi_buffer_snapshot
7027 .anchor_before(0)
7028 .to_point(&multi_buffer_snapshot);
7029 let multi_buffer_end = multi_buffer_snapshot
7030 .anchor_after(multi_buffer_snapshot.len())
7031 .to_point(&multi_buffer_snapshot);
7032 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7033 self.debounced_selection_highlight_task = Some((
7034 query_range.clone(),
7035 self.update_selection_occurrence_highlights(
7036 query_text,
7037 query_range,
7038 multi_buffer_full_range,
7039 true,
7040 window,
7041 cx,
7042 ),
7043 ));
7044 }
7045 }
7046
7047 pub fn refresh_edit_prediction(
7048 &mut self,
7049 debounce: bool,
7050 user_requested: bool,
7051 window: &mut Window,
7052 cx: &mut Context<Self>,
7053 ) -> Option<()> {
7054 if DisableAiSettings::get_global(cx).disable_ai {
7055 return None;
7056 }
7057
7058 let provider = self.edit_prediction_provider()?;
7059 let cursor = self.selections.newest_anchor().head();
7060 let (buffer, cursor_buffer_position) =
7061 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7062
7063 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7064 self.discard_edit_prediction(false, cx);
7065 return None;
7066 }
7067
7068 if !user_requested
7069 && (!self.should_show_edit_predictions()
7070 || !self.is_focused(window)
7071 || buffer.read(cx).is_empty())
7072 {
7073 self.discard_edit_prediction(false, cx);
7074 return None;
7075 }
7076
7077 self.update_visible_edit_prediction(window, cx);
7078 provider.refresh(
7079 self.project.clone(),
7080 buffer,
7081 cursor_buffer_position,
7082 debounce,
7083 cx,
7084 );
7085 Some(())
7086 }
7087
7088 fn show_edit_predictions_in_menu(&self) -> bool {
7089 match self.edit_prediction_settings {
7090 EditPredictionSettings::Disabled => false,
7091 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7092 }
7093 }
7094
7095 pub fn edit_predictions_enabled(&self) -> bool {
7096 match self.edit_prediction_settings {
7097 EditPredictionSettings::Disabled => false,
7098 EditPredictionSettings::Enabled { .. } => true,
7099 }
7100 }
7101
7102 fn edit_prediction_requires_modifier(&self) -> bool {
7103 match self.edit_prediction_settings {
7104 EditPredictionSettings::Disabled => false,
7105 EditPredictionSettings::Enabled {
7106 preview_requires_modifier,
7107 ..
7108 } => preview_requires_modifier,
7109 }
7110 }
7111
7112 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7113 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7114 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7115 self.discard_edit_prediction(false, cx);
7116 } else {
7117 let selection = self.selections.newest_anchor();
7118 let cursor = selection.head();
7119
7120 if let Some((buffer, cursor_buffer_position)) =
7121 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7122 {
7123 self.edit_prediction_settings =
7124 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7125 }
7126 }
7127 }
7128
7129 fn edit_prediction_settings_at_position(
7130 &self,
7131 buffer: &Entity<Buffer>,
7132 buffer_position: language::Anchor,
7133 cx: &App,
7134 ) -> EditPredictionSettings {
7135 if !self.mode.is_full()
7136 || !self.show_edit_predictions_override.unwrap_or(true)
7137 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7138 {
7139 return EditPredictionSettings::Disabled;
7140 }
7141
7142 let buffer = buffer.read(cx);
7143
7144 let file = buffer.file();
7145
7146 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7147 return EditPredictionSettings::Disabled;
7148 };
7149
7150 let by_provider = matches!(
7151 self.menu_edit_predictions_policy,
7152 MenuEditPredictionsPolicy::ByProvider
7153 );
7154
7155 let show_in_menu = by_provider
7156 && self
7157 .edit_prediction_provider
7158 .as_ref()
7159 .map_or(false, |provider| {
7160 provider.provider.show_completions_in_menu()
7161 });
7162
7163 let preview_requires_modifier =
7164 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7165
7166 EditPredictionSettings::Enabled {
7167 show_in_menu,
7168 preview_requires_modifier,
7169 }
7170 }
7171
7172 fn should_show_edit_predictions(&self) -> bool {
7173 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7174 }
7175
7176 pub fn edit_prediction_preview_is_active(&self) -> bool {
7177 matches!(
7178 self.edit_prediction_preview,
7179 EditPredictionPreview::Active { .. }
7180 )
7181 }
7182
7183 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7184 let cursor = self.selections.newest_anchor().head();
7185 if let Some((buffer, cursor_position)) =
7186 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7187 {
7188 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7189 } else {
7190 false
7191 }
7192 }
7193
7194 pub fn supports_minimap(&self, cx: &App) -> bool {
7195 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7196 }
7197
7198 fn edit_predictions_enabled_in_buffer(
7199 &self,
7200 buffer: &Entity<Buffer>,
7201 buffer_position: language::Anchor,
7202 cx: &App,
7203 ) -> bool {
7204 maybe!({
7205 if self.read_only(cx) {
7206 return Some(false);
7207 }
7208 let provider = self.edit_prediction_provider()?;
7209 if !provider.is_enabled(buffer, buffer_position, cx) {
7210 return Some(false);
7211 }
7212 let buffer = buffer.read(cx);
7213 let Some(file) = buffer.file() else {
7214 return Some(true);
7215 };
7216 let settings = all_language_settings(Some(file), cx);
7217 Some(settings.edit_predictions_enabled_for_file(file, cx))
7218 })
7219 .unwrap_or(false)
7220 }
7221
7222 fn cycle_edit_prediction(
7223 &mut self,
7224 direction: Direction,
7225 window: &mut Window,
7226 cx: &mut Context<Self>,
7227 ) -> Option<()> {
7228 let provider = self.edit_prediction_provider()?;
7229 let cursor = self.selections.newest_anchor().head();
7230 let (buffer, cursor_buffer_position) =
7231 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7232 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7233 return None;
7234 }
7235
7236 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7237 self.update_visible_edit_prediction(window, cx);
7238
7239 Some(())
7240 }
7241
7242 pub fn show_edit_prediction(
7243 &mut self,
7244 _: &ShowEditPrediction,
7245 window: &mut Window,
7246 cx: &mut Context<Self>,
7247 ) {
7248 if !self.has_active_edit_prediction() {
7249 self.refresh_edit_prediction(false, true, window, cx);
7250 return;
7251 }
7252
7253 self.update_visible_edit_prediction(window, cx);
7254 }
7255
7256 pub fn display_cursor_names(
7257 &mut self,
7258 _: &DisplayCursorNames,
7259 window: &mut Window,
7260 cx: &mut Context<Self>,
7261 ) {
7262 self.show_cursor_names(window, cx);
7263 }
7264
7265 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7266 self.show_cursor_names = true;
7267 cx.notify();
7268 cx.spawn_in(window, async move |this, cx| {
7269 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7270 this.update(cx, |this, cx| {
7271 this.show_cursor_names = false;
7272 cx.notify()
7273 })
7274 .ok()
7275 })
7276 .detach();
7277 }
7278
7279 pub fn next_edit_prediction(
7280 &mut self,
7281 _: &NextEditPrediction,
7282 window: &mut Window,
7283 cx: &mut Context<Self>,
7284 ) {
7285 if self.has_active_edit_prediction() {
7286 self.cycle_edit_prediction(Direction::Next, window, cx);
7287 } else {
7288 let is_copilot_disabled = self
7289 .refresh_edit_prediction(false, true, window, cx)
7290 .is_none();
7291 if is_copilot_disabled {
7292 cx.propagate();
7293 }
7294 }
7295 }
7296
7297 pub fn previous_edit_prediction(
7298 &mut self,
7299 _: &PreviousEditPrediction,
7300 window: &mut Window,
7301 cx: &mut Context<Self>,
7302 ) {
7303 if self.has_active_edit_prediction() {
7304 self.cycle_edit_prediction(Direction::Prev, window, cx);
7305 } else {
7306 let is_copilot_disabled = self
7307 .refresh_edit_prediction(false, true, window, cx)
7308 .is_none();
7309 if is_copilot_disabled {
7310 cx.propagate();
7311 }
7312 }
7313 }
7314
7315 pub fn accept_edit_prediction(
7316 &mut self,
7317 _: &AcceptEditPrediction,
7318 window: &mut Window,
7319 cx: &mut Context<Self>,
7320 ) {
7321 if self.show_edit_predictions_in_menu() {
7322 self.hide_context_menu(window, cx);
7323 }
7324
7325 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7326 return;
7327 };
7328
7329 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7330
7331 match &active_edit_prediction.completion {
7332 EditPrediction::Move { target, .. } => {
7333 let target = *target;
7334
7335 if let Some(position_map) = &self.last_position_map {
7336 if position_map
7337 .visible_row_range
7338 .contains(&target.to_display_point(&position_map.snapshot).row())
7339 || !self.edit_prediction_requires_modifier()
7340 {
7341 self.unfold_ranges(&[target..target], true, false, cx);
7342 // Note that this is also done in vim's handler of the Tab action.
7343 self.change_selections(
7344 SelectionEffects::scroll(Autoscroll::newest()),
7345 window,
7346 cx,
7347 |selections| {
7348 selections.select_anchor_ranges([target..target]);
7349 },
7350 );
7351 self.clear_row_highlights::<EditPredictionPreview>();
7352
7353 self.edit_prediction_preview
7354 .set_previous_scroll_position(None);
7355 } else {
7356 self.edit_prediction_preview
7357 .set_previous_scroll_position(Some(
7358 position_map.snapshot.scroll_anchor,
7359 ));
7360
7361 self.highlight_rows::<EditPredictionPreview>(
7362 target..target,
7363 cx.theme().colors().editor_highlighted_line_background,
7364 RowHighlightOptions {
7365 autoscroll: true,
7366 ..Default::default()
7367 },
7368 cx,
7369 );
7370 self.request_autoscroll(Autoscroll::fit(), cx);
7371 }
7372 }
7373 }
7374 EditPrediction::Edit { edits, .. } => {
7375 if let Some(provider) = self.edit_prediction_provider() {
7376 provider.accept(cx);
7377 }
7378
7379 // Store the transaction ID and selections before applying the edit
7380 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7381
7382 let snapshot = self.buffer.read(cx).snapshot(cx);
7383 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7384
7385 self.buffer.update(cx, |buffer, cx| {
7386 buffer.edit(edits.iter().cloned(), None, cx)
7387 });
7388
7389 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7390 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7391 });
7392
7393 let selections = self.selections.disjoint_anchors();
7394 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7395 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7396 if has_new_transaction {
7397 self.selection_history
7398 .insert_transaction(transaction_id_now, selections);
7399 }
7400 }
7401
7402 self.update_visible_edit_prediction(window, cx);
7403 if self.active_edit_prediction.is_none() {
7404 self.refresh_edit_prediction(true, true, window, cx);
7405 }
7406
7407 cx.notify();
7408 }
7409 }
7410
7411 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7412 }
7413
7414 pub fn accept_partial_edit_prediction(
7415 &mut self,
7416 _: &AcceptPartialEditPrediction,
7417 window: &mut Window,
7418 cx: &mut Context<Self>,
7419 ) {
7420 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7421 return;
7422 };
7423 if self.selections.count() != 1 {
7424 return;
7425 }
7426
7427 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7428
7429 match &active_edit_prediction.completion {
7430 EditPrediction::Move { target, .. } => {
7431 let target = *target;
7432 self.change_selections(
7433 SelectionEffects::scroll(Autoscroll::newest()),
7434 window,
7435 cx,
7436 |selections| {
7437 selections.select_anchor_ranges([target..target]);
7438 },
7439 );
7440 }
7441 EditPrediction::Edit { edits, .. } => {
7442 // Find an insertion that starts at the cursor position.
7443 let snapshot = self.buffer.read(cx).snapshot(cx);
7444 let cursor_offset = self.selections.newest::<usize>(cx).head();
7445 let insertion = edits.iter().find_map(|(range, text)| {
7446 let range = range.to_offset(&snapshot);
7447 if range.is_empty() && range.start == cursor_offset {
7448 Some(text)
7449 } else {
7450 None
7451 }
7452 });
7453
7454 if let Some(text) = insertion {
7455 let mut partial_completion = text
7456 .chars()
7457 .by_ref()
7458 .take_while(|c| c.is_alphabetic())
7459 .collect::<String>();
7460 if partial_completion.is_empty() {
7461 partial_completion = text
7462 .chars()
7463 .by_ref()
7464 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7465 .collect::<String>();
7466 }
7467
7468 cx.emit(EditorEvent::InputHandled {
7469 utf16_range_to_replace: None,
7470 text: partial_completion.clone().into(),
7471 });
7472
7473 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7474
7475 self.refresh_edit_prediction(true, true, window, cx);
7476 cx.notify();
7477 } else {
7478 self.accept_edit_prediction(&Default::default(), window, cx);
7479 }
7480 }
7481 }
7482 }
7483
7484 fn discard_edit_prediction(
7485 &mut self,
7486 should_report_edit_prediction_event: bool,
7487 cx: &mut Context<Self>,
7488 ) -> bool {
7489 if should_report_edit_prediction_event {
7490 let completion_id = self
7491 .active_edit_prediction
7492 .as_ref()
7493 .and_then(|active_completion| active_completion.completion_id.clone());
7494
7495 self.report_edit_prediction_event(completion_id, false, cx);
7496 }
7497
7498 if let Some(provider) = self.edit_prediction_provider() {
7499 provider.discard(cx);
7500 }
7501
7502 self.take_active_edit_prediction(cx)
7503 }
7504
7505 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7506 let Some(provider) = self.edit_prediction_provider() else {
7507 return;
7508 };
7509
7510 let Some((_, buffer, _)) = self
7511 .buffer
7512 .read(cx)
7513 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7514 else {
7515 return;
7516 };
7517
7518 let extension = buffer
7519 .read(cx)
7520 .file()
7521 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7522
7523 let event_type = match accepted {
7524 true => "Edit Prediction Accepted",
7525 false => "Edit Prediction Discarded",
7526 };
7527 telemetry::event!(
7528 event_type,
7529 provider = provider.name(),
7530 prediction_id = id,
7531 suggestion_accepted = accepted,
7532 file_extension = extension,
7533 );
7534 }
7535
7536 pub fn has_active_edit_prediction(&self) -> bool {
7537 self.active_edit_prediction.is_some()
7538 }
7539
7540 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7541 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7542 return false;
7543 };
7544
7545 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7546 self.clear_highlights::<EditPredictionHighlight>(cx);
7547 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7548 true
7549 }
7550
7551 /// Returns true when we're displaying the edit prediction popover below the cursor
7552 /// like we are not previewing and the LSP autocomplete menu is visible
7553 /// or we are in `when_holding_modifier` mode.
7554 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7555 if self.edit_prediction_preview_is_active()
7556 || !self.show_edit_predictions_in_menu()
7557 || !self.edit_predictions_enabled()
7558 {
7559 return false;
7560 }
7561
7562 if self.has_visible_completions_menu() {
7563 return true;
7564 }
7565
7566 has_completion && self.edit_prediction_requires_modifier()
7567 }
7568
7569 fn handle_modifiers_changed(
7570 &mut self,
7571 modifiers: Modifiers,
7572 position_map: &PositionMap,
7573 window: &mut Window,
7574 cx: &mut Context<Self>,
7575 ) {
7576 if self.show_edit_predictions_in_menu() {
7577 self.update_edit_prediction_preview(&modifiers, window, cx);
7578 }
7579
7580 self.update_selection_mode(&modifiers, position_map, window, cx);
7581
7582 let mouse_position = window.mouse_position();
7583 if !position_map.text_hitbox.is_hovered(window) {
7584 return;
7585 }
7586
7587 self.update_hovered_link(
7588 position_map.point_for_position(mouse_position),
7589 &position_map.snapshot,
7590 modifiers,
7591 window,
7592 cx,
7593 )
7594 }
7595
7596 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7597 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7598 if invert {
7599 match multi_cursor_setting {
7600 MultiCursorModifier::Alt => modifiers.alt,
7601 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7602 }
7603 } else {
7604 match multi_cursor_setting {
7605 MultiCursorModifier::Alt => modifiers.secondary(),
7606 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7607 }
7608 }
7609 }
7610
7611 fn columnar_selection_mode(
7612 modifiers: &Modifiers,
7613 cx: &mut Context<Self>,
7614 ) -> Option<ColumnarMode> {
7615 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7616 if Self::multi_cursor_modifier(false, modifiers, cx) {
7617 Some(ColumnarMode::FromMouse)
7618 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7619 Some(ColumnarMode::FromSelection)
7620 } else {
7621 None
7622 }
7623 } else {
7624 None
7625 }
7626 }
7627
7628 fn update_selection_mode(
7629 &mut self,
7630 modifiers: &Modifiers,
7631 position_map: &PositionMap,
7632 window: &mut Window,
7633 cx: &mut Context<Self>,
7634 ) {
7635 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7636 return;
7637 };
7638 if self.selections.pending.is_none() {
7639 return;
7640 }
7641
7642 let mouse_position = window.mouse_position();
7643 let point_for_position = position_map.point_for_position(mouse_position);
7644 let position = point_for_position.previous_valid;
7645
7646 self.select(
7647 SelectPhase::BeginColumnar {
7648 position,
7649 reset: false,
7650 mode,
7651 goal_column: point_for_position.exact_unclipped.column(),
7652 },
7653 window,
7654 cx,
7655 );
7656 }
7657
7658 fn update_edit_prediction_preview(
7659 &mut self,
7660 modifiers: &Modifiers,
7661 window: &mut Window,
7662 cx: &mut Context<Self>,
7663 ) {
7664 let mut modifiers_held = false;
7665 if let Some(accept_keystroke) = self
7666 .accept_edit_prediction_keybind(false, window, cx)
7667 .keystroke()
7668 {
7669 modifiers_held = modifiers_held
7670 || (&accept_keystroke.modifiers == modifiers
7671 && accept_keystroke.modifiers.modified());
7672 };
7673 if let Some(accept_partial_keystroke) = self
7674 .accept_edit_prediction_keybind(true, window, cx)
7675 .keystroke()
7676 {
7677 modifiers_held = modifiers_held
7678 || (&accept_partial_keystroke.modifiers == modifiers
7679 && accept_partial_keystroke.modifiers.modified());
7680 }
7681
7682 if modifiers_held {
7683 if matches!(
7684 self.edit_prediction_preview,
7685 EditPredictionPreview::Inactive { .. }
7686 ) {
7687 self.edit_prediction_preview = EditPredictionPreview::Active {
7688 previous_scroll_position: None,
7689 since: Instant::now(),
7690 };
7691
7692 self.update_visible_edit_prediction(window, cx);
7693 cx.notify();
7694 }
7695 } else if let EditPredictionPreview::Active {
7696 previous_scroll_position,
7697 since,
7698 } = self.edit_prediction_preview
7699 {
7700 if let (Some(previous_scroll_position), Some(position_map)) =
7701 (previous_scroll_position, self.last_position_map.as_ref())
7702 {
7703 self.set_scroll_position(
7704 previous_scroll_position
7705 .scroll_position(&position_map.snapshot.display_snapshot),
7706 window,
7707 cx,
7708 );
7709 }
7710
7711 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7712 released_too_fast: since.elapsed() < Duration::from_millis(200),
7713 };
7714 self.clear_row_highlights::<EditPredictionPreview>();
7715 self.update_visible_edit_prediction(window, cx);
7716 cx.notify();
7717 }
7718 }
7719
7720 fn update_visible_edit_prediction(
7721 &mut self,
7722 _window: &mut Window,
7723 cx: &mut Context<Self>,
7724 ) -> Option<()> {
7725 if DisableAiSettings::get_global(cx).disable_ai {
7726 return None;
7727 }
7728
7729 let selection = self.selections.newest_anchor();
7730 let cursor = selection.head();
7731 let multibuffer = self.buffer.read(cx).snapshot(cx);
7732 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7733 let excerpt_id = cursor.excerpt_id;
7734
7735 let show_in_menu = self.show_edit_predictions_in_menu();
7736 let completions_menu_has_precedence = !show_in_menu
7737 && (self.context_menu.borrow().is_some()
7738 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7739
7740 if completions_menu_has_precedence
7741 || !offset_selection.is_empty()
7742 || self
7743 .active_edit_prediction
7744 .as_ref()
7745 .map_or(false, |completion| {
7746 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7747 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7748 !invalidation_range.contains(&offset_selection.head())
7749 })
7750 {
7751 self.discard_edit_prediction(false, cx);
7752 return None;
7753 }
7754
7755 self.take_active_edit_prediction(cx);
7756 let Some(provider) = self.edit_prediction_provider() else {
7757 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7758 return None;
7759 };
7760
7761 let (buffer, cursor_buffer_position) =
7762 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7763
7764 self.edit_prediction_settings =
7765 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7766
7767 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7768
7769 if self.edit_prediction_indent_conflict {
7770 let cursor_point = cursor.to_point(&multibuffer);
7771
7772 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7773
7774 if let Some((_, indent)) = indents.iter().next() {
7775 if indent.len == cursor_point.column {
7776 self.edit_prediction_indent_conflict = false;
7777 }
7778 }
7779 }
7780
7781 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7782 let edits = edit_prediction
7783 .edits
7784 .into_iter()
7785 .flat_map(|(range, new_text)| {
7786 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7787 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7788 Some((start..end, new_text))
7789 })
7790 .collect::<Vec<_>>();
7791 if edits.is_empty() {
7792 return None;
7793 }
7794
7795 let first_edit_start = edits.first().unwrap().0.start;
7796 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7797 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7798
7799 let last_edit_end = edits.last().unwrap().0.end;
7800 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7801 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7802
7803 let cursor_row = cursor.to_point(&multibuffer).row;
7804
7805 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7806
7807 let mut inlay_ids = Vec::new();
7808 let invalidation_row_range;
7809 let move_invalidation_row_range = if cursor_row < edit_start_row {
7810 Some(cursor_row..edit_end_row)
7811 } else if cursor_row > edit_end_row {
7812 Some(edit_start_row..cursor_row)
7813 } else {
7814 None
7815 };
7816 let supports_jump = self
7817 .edit_prediction_provider
7818 .as_ref()
7819 .map(|provider| provider.provider.supports_jump_to_edit())
7820 .unwrap_or(true);
7821
7822 let is_move = supports_jump
7823 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7824 let completion = if is_move {
7825 invalidation_row_range =
7826 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7827 let target = first_edit_start;
7828 EditPrediction::Move { target, snapshot }
7829 } else {
7830 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7831 && !self.edit_predictions_hidden_for_vim_mode;
7832
7833 if show_completions_in_buffer {
7834 if edits
7835 .iter()
7836 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7837 {
7838 let mut inlays = Vec::new();
7839 for (range, new_text) in &edits {
7840 let inlay = Inlay::edit_prediction(
7841 post_inc(&mut self.next_inlay_id),
7842 range.start,
7843 new_text.as_str(),
7844 );
7845 inlay_ids.push(inlay.id);
7846 inlays.push(inlay);
7847 }
7848
7849 self.splice_inlays(&[], inlays, cx);
7850 } else {
7851 let background_color = cx.theme().status().deleted_background;
7852 self.highlight_text::<EditPredictionHighlight>(
7853 edits.iter().map(|(range, _)| range.clone()).collect(),
7854 HighlightStyle {
7855 background_color: Some(background_color),
7856 ..Default::default()
7857 },
7858 cx,
7859 );
7860 }
7861 }
7862
7863 invalidation_row_range = edit_start_row..edit_end_row;
7864
7865 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7866 if provider.show_tab_accept_marker() {
7867 EditDisplayMode::TabAccept
7868 } else {
7869 EditDisplayMode::Inline
7870 }
7871 } else {
7872 EditDisplayMode::DiffPopover
7873 };
7874
7875 EditPrediction::Edit {
7876 edits,
7877 edit_preview: edit_prediction.edit_preview,
7878 display_mode,
7879 snapshot,
7880 }
7881 };
7882
7883 let invalidation_range = multibuffer
7884 .anchor_before(Point::new(invalidation_row_range.start, 0))
7885 ..multibuffer.anchor_after(Point::new(
7886 invalidation_row_range.end,
7887 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7888 ));
7889
7890 self.stale_edit_prediction_in_menu = None;
7891 self.active_edit_prediction = Some(EditPredictionState {
7892 inlay_ids,
7893 completion,
7894 completion_id: edit_prediction.id,
7895 invalidation_range,
7896 });
7897
7898 cx.notify();
7899
7900 Some(())
7901 }
7902
7903 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7904 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7905 }
7906
7907 fn clear_tasks(&mut self) {
7908 self.tasks.clear()
7909 }
7910
7911 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7912 if self.tasks.insert(key, value).is_some() {
7913 // This case should hopefully be rare, but just in case...
7914 log::error!(
7915 "multiple different run targets found on a single line, only the last target will be rendered"
7916 )
7917 }
7918 }
7919
7920 /// Get all display points of breakpoints that will be rendered within editor
7921 ///
7922 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7923 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7924 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7925 fn active_breakpoints(
7926 &self,
7927 range: Range<DisplayRow>,
7928 window: &mut Window,
7929 cx: &mut Context<Self>,
7930 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7931 let mut breakpoint_display_points = HashMap::default();
7932
7933 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7934 return breakpoint_display_points;
7935 };
7936
7937 let snapshot = self.snapshot(window, cx);
7938
7939 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7940 let Some(project) = self.project() else {
7941 return breakpoint_display_points;
7942 };
7943
7944 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7945 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7946
7947 for (buffer_snapshot, range, excerpt_id) in
7948 multi_buffer_snapshot.range_to_buffer_ranges(range)
7949 {
7950 let Some(buffer) = project
7951 .read(cx)
7952 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7953 else {
7954 continue;
7955 };
7956 let breakpoints = breakpoint_store.read(cx).breakpoints(
7957 &buffer,
7958 Some(
7959 buffer_snapshot.anchor_before(range.start)
7960 ..buffer_snapshot.anchor_after(range.end),
7961 ),
7962 buffer_snapshot,
7963 cx,
7964 );
7965 for (breakpoint, state) in breakpoints {
7966 let multi_buffer_anchor =
7967 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7968 let position = multi_buffer_anchor
7969 .to_point(multi_buffer_snapshot)
7970 .to_display_point(&snapshot);
7971
7972 breakpoint_display_points.insert(
7973 position.row(),
7974 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7975 );
7976 }
7977 }
7978
7979 breakpoint_display_points
7980 }
7981
7982 fn breakpoint_context_menu(
7983 &self,
7984 anchor: Anchor,
7985 window: &mut Window,
7986 cx: &mut Context<Self>,
7987 ) -> Entity<ui::ContextMenu> {
7988 let weak_editor = cx.weak_entity();
7989 let focus_handle = self.focus_handle(cx);
7990
7991 let row = self
7992 .buffer
7993 .read(cx)
7994 .snapshot(cx)
7995 .summary_for_anchor::<Point>(&anchor)
7996 .row;
7997
7998 let breakpoint = self
7999 .breakpoint_at_row(row, window, cx)
8000 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8001
8002 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8003 "Edit Log Breakpoint"
8004 } else {
8005 "Set Log Breakpoint"
8006 };
8007
8008 let condition_breakpoint_msg = if breakpoint
8009 .as_ref()
8010 .is_some_and(|bp| bp.1.condition.is_some())
8011 {
8012 "Edit Condition Breakpoint"
8013 } else {
8014 "Set Condition Breakpoint"
8015 };
8016
8017 let hit_condition_breakpoint_msg = if breakpoint
8018 .as_ref()
8019 .is_some_and(|bp| bp.1.hit_condition.is_some())
8020 {
8021 "Edit Hit Condition Breakpoint"
8022 } else {
8023 "Set Hit Condition Breakpoint"
8024 };
8025
8026 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8027 "Unset Breakpoint"
8028 } else {
8029 "Set Breakpoint"
8030 };
8031
8032 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8033
8034 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8035 BreakpointState::Enabled => Some("Disable"),
8036 BreakpointState::Disabled => Some("Enable"),
8037 });
8038
8039 let (anchor, breakpoint) =
8040 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8041
8042 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8043 menu.on_blur_subscription(Subscription::new(|| {}))
8044 .context(focus_handle)
8045 .when(run_to_cursor, |this| {
8046 let weak_editor = weak_editor.clone();
8047 this.entry("Run to cursor", None, move |window, cx| {
8048 weak_editor
8049 .update(cx, |editor, cx| {
8050 editor.change_selections(
8051 SelectionEffects::no_scroll(),
8052 window,
8053 cx,
8054 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8055 );
8056 })
8057 .ok();
8058
8059 window.dispatch_action(Box::new(RunToCursor), cx);
8060 })
8061 .separator()
8062 })
8063 .when_some(toggle_state_msg, |this, msg| {
8064 this.entry(msg, None, {
8065 let weak_editor = weak_editor.clone();
8066 let breakpoint = breakpoint.clone();
8067 move |_window, cx| {
8068 weak_editor
8069 .update(cx, |this, cx| {
8070 this.edit_breakpoint_at_anchor(
8071 anchor,
8072 breakpoint.as_ref().clone(),
8073 BreakpointEditAction::InvertState,
8074 cx,
8075 );
8076 })
8077 .log_err();
8078 }
8079 })
8080 })
8081 .entry(set_breakpoint_msg, None, {
8082 let weak_editor = weak_editor.clone();
8083 let breakpoint = breakpoint.clone();
8084 move |_window, cx| {
8085 weak_editor
8086 .update(cx, |this, cx| {
8087 this.edit_breakpoint_at_anchor(
8088 anchor,
8089 breakpoint.as_ref().clone(),
8090 BreakpointEditAction::Toggle,
8091 cx,
8092 );
8093 })
8094 .log_err();
8095 }
8096 })
8097 .entry(log_breakpoint_msg, None, {
8098 let breakpoint = breakpoint.clone();
8099 let weak_editor = weak_editor.clone();
8100 move |window, cx| {
8101 weak_editor
8102 .update(cx, |this, cx| {
8103 this.add_edit_breakpoint_block(
8104 anchor,
8105 breakpoint.as_ref(),
8106 BreakpointPromptEditAction::Log,
8107 window,
8108 cx,
8109 );
8110 })
8111 .log_err();
8112 }
8113 })
8114 .entry(condition_breakpoint_msg, None, {
8115 let breakpoint = breakpoint.clone();
8116 let weak_editor = weak_editor.clone();
8117 move |window, cx| {
8118 weak_editor
8119 .update(cx, |this, cx| {
8120 this.add_edit_breakpoint_block(
8121 anchor,
8122 breakpoint.as_ref(),
8123 BreakpointPromptEditAction::Condition,
8124 window,
8125 cx,
8126 );
8127 })
8128 .log_err();
8129 }
8130 })
8131 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8132 weak_editor
8133 .update(cx, |this, cx| {
8134 this.add_edit_breakpoint_block(
8135 anchor,
8136 breakpoint.as_ref(),
8137 BreakpointPromptEditAction::HitCondition,
8138 window,
8139 cx,
8140 );
8141 })
8142 .log_err();
8143 })
8144 })
8145 }
8146
8147 fn render_breakpoint(
8148 &self,
8149 position: Anchor,
8150 row: DisplayRow,
8151 breakpoint: &Breakpoint,
8152 state: Option<BreakpointSessionState>,
8153 cx: &mut Context<Self>,
8154 ) -> IconButton {
8155 let is_rejected = state.is_some_and(|s| !s.verified);
8156 // Is it a breakpoint that shows up when hovering over gutter?
8157 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8158 (false, false),
8159 |PhantomBreakpointIndicator {
8160 is_active,
8161 display_row,
8162 collides_with_existing_breakpoint,
8163 }| {
8164 (
8165 is_active && display_row == row,
8166 collides_with_existing_breakpoint,
8167 )
8168 },
8169 );
8170
8171 let (color, icon) = {
8172 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8173 (false, false) => ui::IconName::DebugBreakpoint,
8174 (true, false) => ui::IconName::DebugLogBreakpoint,
8175 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8176 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8177 };
8178
8179 let color = if is_phantom {
8180 Color::Hint
8181 } else if is_rejected {
8182 Color::Disabled
8183 } else {
8184 Color::Debugger
8185 };
8186
8187 (color, icon)
8188 };
8189
8190 let breakpoint = Arc::from(breakpoint.clone());
8191
8192 let alt_as_text = gpui::Keystroke {
8193 modifiers: Modifiers::secondary_key(),
8194 ..Default::default()
8195 };
8196 let primary_action_text = if breakpoint.is_disabled() {
8197 "Enable breakpoint"
8198 } else if is_phantom && !collides_with_existing {
8199 "Set breakpoint"
8200 } else {
8201 "Unset breakpoint"
8202 };
8203 let focus_handle = self.focus_handle.clone();
8204
8205 let meta = if is_rejected {
8206 SharedString::from("No executable code is associated with this line.")
8207 } else if collides_with_existing && !breakpoint.is_disabled() {
8208 SharedString::from(format!(
8209 "{alt_as_text}-click to disable,\nright-click for more options."
8210 ))
8211 } else {
8212 SharedString::from("Right-click for more options.")
8213 };
8214 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8215 .icon_size(IconSize::XSmall)
8216 .size(ui::ButtonSize::None)
8217 .when(is_rejected, |this| {
8218 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8219 })
8220 .icon_color(color)
8221 .style(ButtonStyle::Transparent)
8222 .on_click(cx.listener({
8223 let breakpoint = breakpoint.clone();
8224
8225 move |editor, event: &ClickEvent, window, cx| {
8226 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8227 BreakpointEditAction::InvertState
8228 } else {
8229 BreakpointEditAction::Toggle
8230 };
8231
8232 window.focus(&editor.focus_handle(cx));
8233 editor.edit_breakpoint_at_anchor(
8234 position,
8235 breakpoint.as_ref().clone(),
8236 edit_action,
8237 cx,
8238 );
8239 }
8240 }))
8241 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8242 editor.set_breakpoint_context_menu(
8243 row,
8244 Some(position),
8245 event.position(),
8246 window,
8247 cx,
8248 );
8249 }))
8250 .tooltip(move |window, cx| {
8251 Tooltip::with_meta_in(
8252 primary_action_text,
8253 Some(&ToggleBreakpoint),
8254 meta.clone(),
8255 &focus_handle,
8256 window,
8257 cx,
8258 )
8259 })
8260 }
8261
8262 fn build_tasks_context(
8263 project: &Entity<Project>,
8264 buffer: &Entity<Buffer>,
8265 buffer_row: u32,
8266 tasks: &Arc<RunnableTasks>,
8267 cx: &mut Context<Self>,
8268 ) -> Task<Option<task::TaskContext>> {
8269 let position = Point::new(buffer_row, tasks.column);
8270 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8271 let location = Location {
8272 buffer: buffer.clone(),
8273 range: range_start..range_start,
8274 };
8275 // Fill in the environmental variables from the tree-sitter captures
8276 let mut captured_task_variables = TaskVariables::default();
8277 for (capture_name, value) in tasks.extra_variables.clone() {
8278 captured_task_variables.insert(
8279 task::VariableName::Custom(capture_name.into()),
8280 value.clone(),
8281 );
8282 }
8283 project.update(cx, |project, cx| {
8284 project.task_store().update(cx, |task_store, cx| {
8285 task_store.task_context_for_location(captured_task_variables, location, cx)
8286 })
8287 })
8288 }
8289
8290 pub fn spawn_nearest_task(
8291 &mut self,
8292 action: &SpawnNearestTask,
8293 window: &mut Window,
8294 cx: &mut Context<Self>,
8295 ) {
8296 let Some((workspace, _)) = self.workspace.clone() else {
8297 return;
8298 };
8299 let Some(project) = self.project.clone() else {
8300 return;
8301 };
8302
8303 // Try to find a closest, enclosing node using tree-sitter that has a task
8304 let Some((buffer, buffer_row, tasks)) = self
8305 .find_enclosing_node_task(cx)
8306 // Or find the task that's closest in row-distance.
8307 .or_else(|| self.find_closest_task(cx))
8308 else {
8309 return;
8310 };
8311
8312 let reveal_strategy = action.reveal;
8313 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8314 cx.spawn_in(window, async move |_, cx| {
8315 let context = task_context.await?;
8316 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8317
8318 let resolved = &mut resolved_task.resolved;
8319 resolved.reveal = reveal_strategy;
8320
8321 workspace
8322 .update_in(cx, |workspace, window, cx| {
8323 workspace.schedule_resolved_task(
8324 task_source_kind,
8325 resolved_task,
8326 false,
8327 window,
8328 cx,
8329 );
8330 })
8331 .ok()
8332 })
8333 .detach();
8334 }
8335
8336 fn find_closest_task(
8337 &mut self,
8338 cx: &mut Context<Self>,
8339 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8340 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8341
8342 let ((buffer_id, row), tasks) = self
8343 .tasks
8344 .iter()
8345 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8346
8347 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8348 let tasks = Arc::new(tasks.to_owned());
8349 Some((buffer, *row, tasks))
8350 }
8351
8352 fn find_enclosing_node_task(
8353 &mut self,
8354 cx: &mut Context<Self>,
8355 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8356 let snapshot = self.buffer.read(cx).snapshot(cx);
8357 let offset = self.selections.newest::<usize>(cx).head();
8358 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8359 let buffer_id = excerpt.buffer().remote_id();
8360
8361 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8362 let mut cursor = layer.node().walk();
8363
8364 while cursor.goto_first_child_for_byte(offset).is_some() {
8365 if cursor.node().end_byte() == offset {
8366 cursor.goto_next_sibling();
8367 }
8368 }
8369
8370 // Ascend to the smallest ancestor that contains the range and has a task.
8371 loop {
8372 let node = cursor.node();
8373 let node_range = node.byte_range();
8374 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8375
8376 // Check if this node contains our offset
8377 if node_range.start <= offset && node_range.end >= offset {
8378 // If it contains offset, check for task
8379 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8380 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8381 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8382 }
8383 }
8384
8385 if !cursor.goto_parent() {
8386 break;
8387 }
8388 }
8389 None
8390 }
8391
8392 fn render_run_indicator(
8393 &self,
8394 _style: &EditorStyle,
8395 is_active: bool,
8396 row: DisplayRow,
8397 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8398 cx: &mut Context<Self>,
8399 ) -> IconButton {
8400 let color = Color::Muted;
8401 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8402
8403 IconButton::new(
8404 ("run_indicator", row.0 as usize),
8405 ui::IconName::PlayOutlined,
8406 )
8407 .shape(ui::IconButtonShape::Square)
8408 .icon_size(IconSize::XSmall)
8409 .icon_color(color)
8410 .toggle_state(is_active)
8411 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8412 let quick_launch = match e {
8413 ClickEvent::Keyboard(_) => true,
8414 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8415 };
8416
8417 window.focus(&editor.focus_handle(cx));
8418 editor.toggle_code_actions(
8419 &ToggleCodeActions {
8420 deployed_from: Some(CodeActionSource::RunMenu(row)),
8421 quick_launch,
8422 },
8423 window,
8424 cx,
8425 );
8426 }))
8427 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8428 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8429 }))
8430 }
8431
8432 pub fn context_menu_visible(&self) -> bool {
8433 !self.edit_prediction_preview_is_active()
8434 && self
8435 .context_menu
8436 .borrow()
8437 .as_ref()
8438 .map_or(false, |menu| menu.visible())
8439 }
8440
8441 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8442 self.context_menu
8443 .borrow()
8444 .as_ref()
8445 .map(|menu| menu.origin())
8446 }
8447
8448 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8449 self.context_menu_options = Some(options);
8450 }
8451
8452 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8453 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8454
8455 fn render_edit_prediction_popover(
8456 &mut self,
8457 text_bounds: &Bounds<Pixels>,
8458 content_origin: gpui::Point<Pixels>,
8459 right_margin: Pixels,
8460 editor_snapshot: &EditorSnapshot,
8461 visible_row_range: Range<DisplayRow>,
8462 scroll_top: f32,
8463 scroll_bottom: f32,
8464 line_layouts: &[LineWithInvisibles],
8465 line_height: Pixels,
8466 scroll_pixel_position: gpui::Point<Pixels>,
8467 newest_selection_head: Option<DisplayPoint>,
8468 editor_width: Pixels,
8469 style: &EditorStyle,
8470 window: &mut Window,
8471 cx: &mut App,
8472 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8473 if self.mode().is_minimap() {
8474 return None;
8475 }
8476 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8477
8478 if self.edit_prediction_visible_in_cursor_popover(true) {
8479 return None;
8480 }
8481
8482 match &active_edit_prediction.completion {
8483 EditPrediction::Move { target, .. } => {
8484 let target_display_point = target.to_display_point(editor_snapshot);
8485
8486 if self.edit_prediction_requires_modifier() {
8487 if !self.edit_prediction_preview_is_active() {
8488 return None;
8489 }
8490
8491 self.render_edit_prediction_modifier_jump_popover(
8492 text_bounds,
8493 content_origin,
8494 visible_row_range,
8495 line_layouts,
8496 line_height,
8497 scroll_pixel_position,
8498 newest_selection_head,
8499 target_display_point,
8500 window,
8501 cx,
8502 )
8503 } else {
8504 self.render_edit_prediction_eager_jump_popover(
8505 text_bounds,
8506 content_origin,
8507 editor_snapshot,
8508 visible_row_range,
8509 scroll_top,
8510 scroll_bottom,
8511 line_height,
8512 scroll_pixel_position,
8513 target_display_point,
8514 editor_width,
8515 window,
8516 cx,
8517 )
8518 }
8519 }
8520 EditPrediction::Edit {
8521 display_mode: EditDisplayMode::Inline,
8522 ..
8523 } => None,
8524 EditPrediction::Edit {
8525 display_mode: EditDisplayMode::TabAccept,
8526 edits,
8527 ..
8528 } => {
8529 let range = &edits.first()?.0;
8530 let target_display_point = range.end.to_display_point(editor_snapshot);
8531
8532 self.render_edit_prediction_end_of_line_popover(
8533 "Accept",
8534 editor_snapshot,
8535 visible_row_range,
8536 target_display_point,
8537 line_height,
8538 scroll_pixel_position,
8539 content_origin,
8540 editor_width,
8541 window,
8542 cx,
8543 )
8544 }
8545 EditPrediction::Edit {
8546 edits,
8547 edit_preview,
8548 display_mode: EditDisplayMode::DiffPopover,
8549 snapshot,
8550 } => self.render_edit_prediction_diff_popover(
8551 text_bounds,
8552 content_origin,
8553 right_margin,
8554 editor_snapshot,
8555 visible_row_range,
8556 line_layouts,
8557 line_height,
8558 scroll_pixel_position,
8559 newest_selection_head,
8560 editor_width,
8561 style,
8562 edits,
8563 edit_preview,
8564 snapshot,
8565 window,
8566 cx,
8567 ),
8568 }
8569 }
8570
8571 fn render_edit_prediction_modifier_jump_popover(
8572 &mut self,
8573 text_bounds: &Bounds<Pixels>,
8574 content_origin: gpui::Point<Pixels>,
8575 visible_row_range: Range<DisplayRow>,
8576 line_layouts: &[LineWithInvisibles],
8577 line_height: Pixels,
8578 scroll_pixel_position: gpui::Point<Pixels>,
8579 newest_selection_head: Option<DisplayPoint>,
8580 target_display_point: DisplayPoint,
8581 window: &mut Window,
8582 cx: &mut App,
8583 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8584 let scrolled_content_origin =
8585 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8586
8587 const SCROLL_PADDING_Y: Pixels = px(12.);
8588
8589 if target_display_point.row() < visible_row_range.start {
8590 return self.render_edit_prediction_scroll_popover(
8591 |_| SCROLL_PADDING_Y,
8592 IconName::ArrowUp,
8593 visible_row_range,
8594 line_layouts,
8595 newest_selection_head,
8596 scrolled_content_origin,
8597 window,
8598 cx,
8599 );
8600 } else if target_display_point.row() >= visible_row_range.end {
8601 return self.render_edit_prediction_scroll_popover(
8602 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8603 IconName::ArrowDown,
8604 visible_row_range,
8605 line_layouts,
8606 newest_selection_head,
8607 scrolled_content_origin,
8608 window,
8609 cx,
8610 );
8611 }
8612
8613 const POLE_WIDTH: Pixels = px(2.);
8614
8615 let line_layout =
8616 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8617 let target_column = target_display_point.column() as usize;
8618
8619 let target_x = line_layout.x_for_index(target_column);
8620 let target_y =
8621 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8622
8623 let flag_on_right = target_x < text_bounds.size.width / 2.;
8624
8625 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8626 border_color.l += 0.001;
8627
8628 let mut element = v_flex()
8629 .items_end()
8630 .when(flag_on_right, |el| el.items_start())
8631 .child(if flag_on_right {
8632 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8633 .rounded_bl(px(0.))
8634 .rounded_tl(px(0.))
8635 .border_l_2()
8636 .border_color(border_color)
8637 } else {
8638 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8639 .rounded_br(px(0.))
8640 .rounded_tr(px(0.))
8641 .border_r_2()
8642 .border_color(border_color)
8643 })
8644 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8645 .into_any();
8646
8647 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8648
8649 let mut origin = scrolled_content_origin + point(target_x, target_y)
8650 - point(
8651 if flag_on_right {
8652 POLE_WIDTH
8653 } else {
8654 size.width - POLE_WIDTH
8655 },
8656 size.height - line_height,
8657 );
8658
8659 origin.x = origin.x.max(content_origin.x);
8660
8661 element.prepaint_at(origin, window, cx);
8662
8663 Some((element, origin))
8664 }
8665
8666 fn render_edit_prediction_scroll_popover(
8667 &mut self,
8668 to_y: impl Fn(Size<Pixels>) -> Pixels,
8669 scroll_icon: IconName,
8670 visible_row_range: Range<DisplayRow>,
8671 line_layouts: &[LineWithInvisibles],
8672 newest_selection_head: Option<DisplayPoint>,
8673 scrolled_content_origin: gpui::Point<Pixels>,
8674 window: &mut Window,
8675 cx: &mut App,
8676 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8677 let mut element = self
8678 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8679 .into_any();
8680
8681 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8682
8683 let cursor = newest_selection_head?;
8684 let cursor_row_layout =
8685 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8686 let cursor_column = cursor.column() as usize;
8687
8688 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8689
8690 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8691
8692 element.prepaint_at(origin, window, cx);
8693 Some((element, origin))
8694 }
8695
8696 fn render_edit_prediction_eager_jump_popover(
8697 &mut self,
8698 text_bounds: &Bounds<Pixels>,
8699 content_origin: gpui::Point<Pixels>,
8700 editor_snapshot: &EditorSnapshot,
8701 visible_row_range: Range<DisplayRow>,
8702 scroll_top: f32,
8703 scroll_bottom: f32,
8704 line_height: Pixels,
8705 scroll_pixel_position: gpui::Point<Pixels>,
8706 target_display_point: DisplayPoint,
8707 editor_width: Pixels,
8708 window: &mut Window,
8709 cx: &mut App,
8710 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8711 if target_display_point.row().as_f32() < scroll_top {
8712 let mut element = self
8713 .render_edit_prediction_line_popover(
8714 "Jump to Edit",
8715 Some(IconName::ArrowUp),
8716 window,
8717 cx,
8718 )?
8719 .into_any();
8720
8721 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8722 let offset = point(
8723 (text_bounds.size.width - size.width) / 2.,
8724 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8725 );
8726
8727 let origin = text_bounds.origin + offset;
8728 element.prepaint_at(origin, window, cx);
8729 Some((element, origin))
8730 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8731 let mut element = self
8732 .render_edit_prediction_line_popover(
8733 "Jump to Edit",
8734 Some(IconName::ArrowDown),
8735 window,
8736 cx,
8737 )?
8738 .into_any();
8739
8740 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8741 let offset = point(
8742 (text_bounds.size.width - size.width) / 2.,
8743 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8744 );
8745
8746 let origin = text_bounds.origin + offset;
8747 element.prepaint_at(origin, window, cx);
8748 Some((element, origin))
8749 } else {
8750 self.render_edit_prediction_end_of_line_popover(
8751 "Jump to Edit",
8752 editor_snapshot,
8753 visible_row_range,
8754 target_display_point,
8755 line_height,
8756 scroll_pixel_position,
8757 content_origin,
8758 editor_width,
8759 window,
8760 cx,
8761 )
8762 }
8763 }
8764
8765 fn render_edit_prediction_end_of_line_popover(
8766 self: &mut Editor,
8767 label: &'static str,
8768 editor_snapshot: &EditorSnapshot,
8769 visible_row_range: Range<DisplayRow>,
8770 target_display_point: DisplayPoint,
8771 line_height: Pixels,
8772 scroll_pixel_position: gpui::Point<Pixels>,
8773 content_origin: gpui::Point<Pixels>,
8774 editor_width: Pixels,
8775 window: &mut Window,
8776 cx: &mut App,
8777 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8778 let target_line_end = DisplayPoint::new(
8779 target_display_point.row(),
8780 editor_snapshot.line_len(target_display_point.row()),
8781 );
8782
8783 let mut element = self
8784 .render_edit_prediction_line_popover(label, None, window, cx)?
8785 .into_any();
8786
8787 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8788
8789 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8790
8791 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8792 let mut origin = start_point
8793 + line_origin
8794 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8795 origin.x = origin.x.max(content_origin.x);
8796
8797 let max_x = content_origin.x + editor_width - size.width;
8798
8799 if origin.x > max_x {
8800 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8801
8802 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8803 origin.y += offset;
8804 IconName::ArrowUp
8805 } else {
8806 origin.y -= offset;
8807 IconName::ArrowDown
8808 };
8809
8810 element = self
8811 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8812 .into_any();
8813
8814 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8815
8816 origin.x = content_origin.x + editor_width - size.width - px(2.);
8817 }
8818
8819 element.prepaint_at(origin, window, cx);
8820 Some((element, origin))
8821 }
8822
8823 fn render_edit_prediction_diff_popover(
8824 self: &Editor,
8825 text_bounds: &Bounds<Pixels>,
8826 content_origin: gpui::Point<Pixels>,
8827 right_margin: Pixels,
8828 editor_snapshot: &EditorSnapshot,
8829 visible_row_range: Range<DisplayRow>,
8830 line_layouts: &[LineWithInvisibles],
8831 line_height: Pixels,
8832 scroll_pixel_position: gpui::Point<Pixels>,
8833 newest_selection_head: Option<DisplayPoint>,
8834 editor_width: Pixels,
8835 style: &EditorStyle,
8836 edits: &Vec<(Range<Anchor>, String)>,
8837 edit_preview: &Option<language::EditPreview>,
8838 snapshot: &language::BufferSnapshot,
8839 window: &mut Window,
8840 cx: &mut App,
8841 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8842 let edit_start = edits
8843 .first()
8844 .unwrap()
8845 .0
8846 .start
8847 .to_display_point(editor_snapshot);
8848 let edit_end = edits
8849 .last()
8850 .unwrap()
8851 .0
8852 .end
8853 .to_display_point(editor_snapshot);
8854
8855 let is_visible = visible_row_range.contains(&edit_start.row())
8856 || visible_row_range.contains(&edit_end.row());
8857 if !is_visible {
8858 return None;
8859 }
8860
8861 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8862 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8863 } else {
8864 // Fallback for providers without edit_preview
8865 crate::edit_prediction_fallback_text(edits, cx)
8866 };
8867
8868 let styled_text = highlighted_edits.to_styled_text(&style.text);
8869 let line_count = highlighted_edits.text.lines().count();
8870
8871 const BORDER_WIDTH: Pixels = px(1.);
8872
8873 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8874 let has_keybind = keybind.is_some();
8875
8876 let mut element = h_flex()
8877 .items_start()
8878 .child(
8879 h_flex()
8880 .bg(cx.theme().colors().editor_background)
8881 .border(BORDER_WIDTH)
8882 .shadow_xs()
8883 .border_color(cx.theme().colors().border)
8884 .rounded_l_lg()
8885 .when(line_count > 1, |el| el.rounded_br_lg())
8886 .pr_1()
8887 .child(styled_text),
8888 )
8889 .child(
8890 h_flex()
8891 .h(line_height + BORDER_WIDTH * 2.)
8892 .px_1p5()
8893 .gap_1()
8894 // Workaround: For some reason, there's a gap if we don't do this
8895 .ml(-BORDER_WIDTH)
8896 .shadow(vec![gpui::BoxShadow {
8897 color: gpui::black().opacity(0.05),
8898 offset: point(px(1.), px(1.)),
8899 blur_radius: px(2.),
8900 spread_radius: px(0.),
8901 }])
8902 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8903 .border(BORDER_WIDTH)
8904 .border_color(cx.theme().colors().border)
8905 .rounded_r_lg()
8906 .id("edit_prediction_diff_popover_keybind")
8907 .when(!has_keybind, |el| {
8908 let status_colors = cx.theme().status();
8909
8910 el.bg(status_colors.error_background)
8911 .border_color(status_colors.error.opacity(0.6))
8912 .child(Icon::new(IconName::Info).color(Color::Error))
8913 .cursor_default()
8914 .hoverable_tooltip(move |_window, cx| {
8915 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8916 })
8917 })
8918 .children(keybind),
8919 )
8920 .into_any();
8921
8922 let longest_row =
8923 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8924 let longest_line_width = if visible_row_range.contains(&longest_row) {
8925 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8926 } else {
8927 layout_line(
8928 longest_row,
8929 editor_snapshot,
8930 style,
8931 editor_width,
8932 |_| false,
8933 window,
8934 cx,
8935 )
8936 .width
8937 };
8938
8939 let viewport_bounds =
8940 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8941 right: -right_margin,
8942 ..Default::default()
8943 });
8944
8945 let x_after_longest =
8946 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8947 - scroll_pixel_position.x;
8948
8949 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8950
8951 // Fully visible if it can be displayed within the window (allow overlapping other
8952 // panes). However, this is only allowed if the popover starts within text_bounds.
8953 let can_position_to_the_right = x_after_longest < text_bounds.right()
8954 && x_after_longest + element_bounds.width < viewport_bounds.right();
8955
8956 let mut origin = if can_position_to_the_right {
8957 point(
8958 x_after_longest,
8959 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8960 - scroll_pixel_position.y,
8961 )
8962 } else {
8963 let cursor_row = newest_selection_head.map(|head| head.row());
8964 let above_edit = edit_start
8965 .row()
8966 .0
8967 .checked_sub(line_count as u32)
8968 .map(DisplayRow);
8969 let below_edit = Some(edit_end.row() + 1);
8970 let above_cursor =
8971 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8972 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8973
8974 // Place the edit popover adjacent to the edit if there is a location
8975 // available that is onscreen and does not obscure the cursor. Otherwise,
8976 // place it adjacent to the cursor.
8977 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8978 .into_iter()
8979 .flatten()
8980 .find(|&start_row| {
8981 let end_row = start_row + line_count as u32;
8982 visible_row_range.contains(&start_row)
8983 && visible_row_range.contains(&end_row)
8984 && cursor_row.map_or(true, |cursor_row| {
8985 !((start_row..end_row).contains(&cursor_row))
8986 })
8987 })?;
8988
8989 content_origin
8990 + point(
8991 -scroll_pixel_position.x,
8992 row_target.as_f32() * line_height - scroll_pixel_position.y,
8993 )
8994 };
8995
8996 origin.x -= BORDER_WIDTH;
8997
8998 window.defer_draw(element, origin, 1);
8999
9000 // Do not return an element, since it will already be drawn due to defer_draw.
9001 None
9002 }
9003
9004 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9005 px(30.)
9006 }
9007
9008 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9009 if self.read_only(cx) {
9010 cx.theme().players().read_only()
9011 } else {
9012 self.style.as_ref().unwrap().local_player
9013 }
9014 }
9015
9016 fn render_edit_prediction_accept_keybind(
9017 &self,
9018 window: &mut Window,
9019 cx: &App,
9020 ) -> Option<AnyElement> {
9021 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9022 let accept_keystroke = accept_binding.keystroke()?;
9023
9024 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9025
9026 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
9027 Color::Accent
9028 } else {
9029 Color::Muted
9030 };
9031
9032 h_flex()
9033 .px_0p5()
9034 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9035 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9036 .text_size(TextSize::XSmall.rems(cx))
9037 .child(h_flex().children(ui::render_modifiers(
9038 &accept_keystroke.modifiers,
9039 PlatformStyle::platform(),
9040 Some(modifiers_color),
9041 Some(IconSize::XSmall.rems().into()),
9042 true,
9043 )))
9044 .when(is_platform_style_mac, |parent| {
9045 parent.child(accept_keystroke.key.clone())
9046 })
9047 .when(!is_platform_style_mac, |parent| {
9048 parent.child(
9049 Key::new(
9050 util::capitalize(&accept_keystroke.key),
9051 Some(Color::Default),
9052 )
9053 .size(Some(IconSize::XSmall.rems().into())),
9054 )
9055 })
9056 .into_any()
9057 .into()
9058 }
9059
9060 fn render_edit_prediction_line_popover(
9061 &self,
9062 label: impl Into<SharedString>,
9063 icon: Option<IconName>,
9064 window: &mut Window,
9065 cx: &App,
9066 ) -> Option<Stateful<Div>> {
9067 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9068
9069 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9070 let has_keybind = keybind.is_some();
9071
9072 let result = h_flex()
9073 .id("ep-line-popover")
9074 .py_0p5()
9075 .pl_1()
9076 .pr(padding_right)
9077 .gap_1()
9078 .rounded_md()
9079 .border_1()
9080 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9081 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9082 .shadow_xs()
9083 .when(!has_keybind, |el| {
9084 let status_colors = cx.theme().status();
9085
9086 el.bg(status_colors.error_background)
9087 .border_color(status_colors.error.opacity(0.6))
9088 .pl_2()
9089 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9090 .cursor_default()
9091 .hoverable_tooltip(move |_window, cx| {
9092 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9093 })
9094 })
9095 .children(keybind)
9096 .child(
9097 Label::new(label)
9098 .size(LabelSize::Small)
9099 .when(!has_keybind, |el| {
9100 el.color(cx.theme().status().error.into()).strikethrough()
9101 }),
9102 )
9103 .when(!has_keybind, |el| {
9104 el.child(
9105 h_flex().ml_1().child(
9106 Icon::new(IconName::Info)
9107 .size(IconSize::Small)
9108 .color(cx.theme().status().error.into()),
9109 ),
9110 )
9111 })
9112 .when_some(icon, |element, icon| {
9113 element.child(
9114 div()
9115 .mt(px(1.5))
9116 .child(Icon::new(icon).size(IconSize::Small)),
9117 )
9118 });
9119
9120 Some(result)
9121 }
9122
9123 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9124 let accent_color = cx.theme().colors().text_accent;
9125 let editor_bg_color = cx.theme().colors().editor_background;
9126 editor_bg_color.blend(accent_color.opacity(0.1))
9127 }
9128
9129 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9130 let accent_color = cx.theme().colors().text_accent;
9131 let editor_bg_color = cx.theme().colors().editor_background;
9132 editor_bg_color.blend(accent_color.opacity(0.6))
9133 }
9134 fn get_prediction_provider_icon_name(
9135 provider: &Option<RegisteredEditPredictionProvider>,
9136 ) -> IconName {
9137 match provider {
9138 Some(provider) => match provider.provider.name() {
9139 "copilot" => IconName::Copilot,
9140 "supermaven" => IconName::Supermaven,
9141 _ => IconName::ZedPredict,
9142 },
9143 None => IconName::ZedPredict,
9144 }
9145 }
9146
9147 fn render_edit_prediction_cursor_popover(
9148 &self,
9149 min_width: Pixels,
9150 max_width: Pixels,
9151 cursor_point: Point,
9152 style: &EditorStyle,
9153 accept_keystroke: Option<&gpui::Keystroke>,
9154 _window: &Window,
9155 cx: &mut Context<Editor>,
9156 ) -> Option<AnyElement> {
9157 let provider = self.edit_prediction_provider.as_ref()?;
9158 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9159
9160 if provider.provider.needs_terms_acceptance(cx) {
9161 return Some(
9162 h_flex()
9163 .min_w(min_width)
9164 .flex_1()
9165 .px_2()
9166 .py_1()
9167 .gap_3()
9168 .elevation_2(cx)
9169 .hover(|style| style.bg(cx.theme().colors().element_hover))
9170 .id("accept-terms")
9171 .cursor_pointer()
9172 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9173 .on_click(cx.listener(|this, _event, window, cx| {
9174 cx.stop_propagation();
9175 this.report_editor_event(ReportEditorEvent::ZetaTosClicked, None, cx);
9176 window.dispatch_action(
9177 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9178 cx,
9179 );
9180 }))
9181 .child(
9182 h_flex()
9183 .flex_1()
9184 .gap_2()
9185 .child(Icon::new(provider_icon))
9186 .child(Label::new("Accept Terms of Service"))
9187 .child(div().w_full())
9188 .child(
9189 Icon::new(IconName::ArrowUpRight)
9190 .color(Color::Muted)
9191 .size(IconSize::Small),
9192 )
9193 .into_any_element(),
9194 )
9195 .into_any(),
9196 );
9197 }
9198
9199 let is_refreshing = provider.provider.is_refreshing(cx);
9200
9201 fn pending_completion_container(icon: IconName) -> Div {
9202 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9203 }
9204
9205 let completion = match &self.active_edit_prediction {
9206 Some(prediction) => {
9207 if !self.has_visible_completions_menu() {
9208 const RADIUS: Pixels = px(6.);
9209 const BORDER_WIDTH: Pixels = px(1.);
9210
9211 return Some(
9212 h_flex()
9213 .elevation_2(cx)
9214 .border(BORDER_WIDTH)
9215 .border_color(cx.theme().colors().border)
9216 .when(accept_keystroke.is_none(), |el| {
9217 el.border_color(cx.theme().status().error)
9218 })
9219 .rounded(RADIUS)
9220 .rounded_tl(px(0.))
9221 .overflow_hidden()
9222 .child(div().px_1p5().child(match &prediction.completion {
9223 EditPrediction::Move { target, snapshot } => {
9224 use text::ToPoint as _;
9225 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9226 {
9227 Icon::new(IconName::ZedPredictDown)
9228 } else {
9229 Icon::new(IconName::ZedPredictUp)
9230 }
9231 }
9232 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9233 }))
9234 .child(
9235 h_flex()
9236 .gap_1()
9237 .py_1()
9238 .px_2()
9239 .rounded_r(RADIUS - BORDER_WIDTH)
9240 .border_l_1()
9241 .border_color(cx.theme().colors().border)
9242 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9243 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9244 el.child(
9245 Label::new("Hold")
9246 .size(LabelSize::Small)
9247 .when(accept_keystroke.is_none(), |el| {
9248 el.strikethrough()
9249 })
9250 .line_height_style(LineHeightStyle::UiLabel),
9251 )
9252 })
9253 .id("edit_prediction_cursor_popover_keybind")
9254 .when(accept_keystroke.is_none(), |el| {
9255 let status_colors = cx.theme().status();
9256
9257 el.bg(status_colors.error_background)
9258 .border_color(status_colors.error.opacity(0.6))
9259 .child(Icon::new(IconName::Info).color(Color::Error))
9260 .cursor_default()
9261 .hoverable_tooltip(move |_window, cx| {
9262 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9263 .into()
9264 })
9265 })
9266 .when_some(
9267 accept_keystroke.as_ref(),
9268 |el, accept_keystroke| {
9269 el.child(h_flex().children(ui::render_modifiers(
9270 &accept_keystroke.modifiers,
9271 PlatformStyle::platform(),
9272 Some(Color::Default),
9273 Some(IconSize::XSmall.rems().into()),
9274 false,
9275 )))
9276 },
9277 ),
9278 )
9279 .into_any(),
9280 );
9281 }
9282
9283 self.render_edit_prediction_cursor_popover_preview(
9284 prediction,
9285 cursor_point,
9286 style,
9287 cx,
9288 )?
9289 }
9290
9291 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9292 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9293 stale_completion,
9294 cursor_point,
9295 style,
9296 cx,
9297 )?,
9298
9299 None => pending_completion_container(provider_icon)
9300 .child(Label::new("...").size(LabelSize::Small)),
9301 },
9302
9303 None => pending_completion_container(provider_icon)
9304 .child(Label::new("...").size(LabelSize::Small)),
9305 };
9306
9307 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9308 completion
9309 .with_animation(
9310 "loading-completion",
9311 Animation::new(Duration::from_secs(2))
9312 .repeat()
9313 .with_easing(pulsating_between(0.4, 0.8)),
9314 |label, delta| label.opacity(delta),
9315 )
9316 .into_any_element()
9317 } else {
9318 completion.into_any_element()
9319 };
9320
9321 let has_completion = self.active_edit_prediction.is_some();
9322
9323 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9324 Some(
9325 h_flex()
9326 .min_w(min_width)
9327 .max_w(max_width)
9328 .flex_1()
9329 .elevation_2(cx)
9330 .border_color(cx.theme().colors().border)
9331 .child(
9332 div()
9333 .flex_1()
9334 .py_1()
9335 .px_2()
9336 .overflow_hidden()
9337 .child(completion),
9338 )
9339 .when_some(accept_keystroke, |el, accept_keystroke| {
9340 if !accept_keystroke.modifiers.modified() {
9341 return el;
9342 }
9343
9344 el.child(
9345 h_flex()
9346 .h_full()
9347 .border_l_1()
9348 .rounded_r_lg()
9349 .border_color(cx.theme().colors().border)
9350 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9351 .gap_1()
9352 .py_1()
9353 .px_2()
9354 .child(
9355 h_flex()
9356 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9357 .when(is_platform_style_mac, |parent| parent.gap_1())
9358 .child(h_flex().children(ui::render_modifiers(
9359 &accept_keystroke.modifiers,
9360 PlatformStyle::platform(),
9361 Some(if !has_completion {
9362 Color::Muted
9363 } else {
9364 Color::Default
9365 }),
9366 None,
9367 false,
9368 ))),
9369 )
9370 .child(Label::new("Preview").into_any_element())
9371 .opacity(if has_completion { 1.0 } else { 0.4 }),
9372 )
9373 })
9374 .into_any(),
9375 )
9376 }
9377
9378 fn render_edit_prediction_cursor_popover_preview(
9379 &self,
9380 completion: &EditPredictionState,
9381 cursor_point: Point,
9382 style: &EditorStyle,
9383 cx: &mut Context<Editor>,
9384 ) -> Option<Div> {
9385 use text::ToPoint as _;
9386
9387 fn render_relative_row_jump(
9388 prefix: impl Into<String>,
9389 current_row: u32,
9390 target_row: u32,
9391 ) -> Div {
9392 let (row_diff, arrow) = if target_row < current_row {
9393 (current_row - target_row, IconName::ArrowUp)
9394 } else {
9395 (target_row - current_row, IconName::ArrowDown)
9396 };
9397
9398 h_flex()
9399 .child(
9400 Label::new(format!("{}{}", prefix.into(), row_diff))
9401 .color(Color::Muted)
9402 .size(LabelSize::Small),
9403 )
9404 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9405 }
9406
9407 let supports_jump = self
9408 .edit_prediction_provider
9409 .as_ref()
9410 .map(|provider| provider.provider.supports_jump_to_edit())
9411 .unwrap_or(true);
9412
9413 match &completion.completion {
9414 EditPrediction::Move {
9415 target, snapshot, ..
9416 } => {
9417 if !supports_jump {
9418 return None;
9419 }
9420
9421 Some(
9422 h_flex()
9423 .px_2()
9424 .gap_2()
9425 .flex_1()
9426 .child(
9427 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9428 Icon::new(IconName::ZedPredictDown)
9429 } else {
9430 Icon::new(IconName::ZedPredictUp)
9431 },
9432 )
9433 .child(Label::new("Jump to Edit")),
9434 )
9435 }
9436
9437 EditPrediction::Edit {
9438 edits,
9439 edit_preview,
9440 snapshot,
9441 display_mode: _,
9442 } => {
9443 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9444
9445 let (highlighted_edits, has_more_lines) =
9446 if let Some(edit_preview) = edit_preview.as_ref() {
9447 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9448 .first_line_preview()
9449 } else {
9450 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9451 };
9452
9453 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9454 .with_default_highlights(&style.text, highlighted_edits.highlights);
9455
9456 let preview = h_flex()
9457 .gap_1()
9458 .min_w_16()
9459 .child(styled_text)
9460 .when(has_more_lines, |parent| parent.child("…"));
9461
9462 let left = if supports_jump && first_edit_row != cursor_point.row {
9463 render_relative_row_jump("", cursor_point.row, first_edit_row)
9464 .into_any_element()
9465 } else {
9466 let icon_name =
9467 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9468 Icon::new(icon_name).into_any_element()
9469 };
9470
9471 Some(
9472 h_flex()
9473 .h_full()
9474 .flex_1()
9475 .gap_2()
9476 .pr_1()
9477 .overflow_x_hidden()
9478 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9479 .child(left)
9480 .child(preview),
9481 )
9482 }
9483 }
9484 }
9485
9486 pub fn render_context_menu(
9487 &self,
9488 style: &EditorStyle,
9489 max_height_in_lines: u32,
9490 window: &mut Window,
9491 cx: &mut Context<Editor>,
9492 ) -> Option<AnyElement> {
9493 let menu = self.context_menu.borrow();
9494 let menu = menu.as_ref()?;
9495 if !menu.visible() {
9496 return None;
9497 };
9498 Some(menu.render(style, max_height_in_lines, window, cx))
9499 }
9500
9501 fn render_context_menu_aside(
9502 &mut self,
9503 max_size: Size<Pixels>,
9504 window: &mut Window,
9505 cx: &mut Context<Editor>,
9506 ) -> Option<AnyElement> {
9507 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9508 if menu.visible() {
9509 menu.render_aside(max_size, window, cx)
9510 } else {
9511 None
9512 }
9513 })
9514 }
9515
9516 fn hide_context_menu(
9517 &mut self,
9518 window: &mut Window,
9519 cx: &mut Context<Self>,
9520 ) -> Option<CodeContextMenu> {
9521 cx.notify();
9522 self.completion_tasks.clear();
9523 let context_menu = self.context_menu.borrow_mut().take();
9524 self.stale_edit_prediction_in_menu.take();
9525 self.update_visible_edit_prediction(window, cx);
9526 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9527 if let Some(completion_provider) = &self.completion_provider {
9528 completion_provider.selection_changed(None, window, cx);
9529 }
9530 }
9531 context_menu
9532 }
9533
9534 fn show_snippet_choices(
9535 &mut self,
9536 choices: &Vec<String>,
9537 selection: Range<Anchor>,
9538 cx: &mut Context<Self>,
9539 ) {
9540 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9541 (Some(a), Some(b)) if a == b => a,
9542 _ => {
9543 log::error!("expected anchor range to have matching buffer IDs");
9544 return;
9545 }
9546 };
9547 let multi_buffer = self.buffer().read(cx);
9548 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9549 return;
9550 };
9551
9552 let id = post_inc(&mut self.next_completion_id);
9553 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9554 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9555 CompletionsMenu::new_snippet_choices(
9556 id,
9557 true,
9558 choices,
9559 selection,
9560 buffer,
9561 snippet_sort_order,
9562 ),
9563 ));
9564 }
9565
9566 pub fn insert_snippet(
9567 &mut self,
9568 insertion_ranges: &[Range<usize>],
9569 snippet: Snippet,
9570 window: &mut Window,
9571 cx: &mut Context<Self>,
9572 ) -> Result<()> {
9573 struct Tabstop<T> {
9574 is_end_tabstop: bool,
9575 ranges: Vec<Range<T>>,
9576 choices: Option<Vec<String>>,
9577 }
9578
9579 let tabstops = self.buffer.update(cx, |buffer, cx| {
9580 let snippet_text: Arc<str> = snippet.text.clone().into();
9581 let edits = insertion_ranges
9582 .iter()
9583 .cloned()
9584 .map(|range| (range, snippet_text.clone()));
9585 let autoindent_mode = AutoindentMode::Block {
9586 original_indent_columns: Vec::new(),
9587 };
9588 buffer.edit(edits, Some(autoindent_mode), cx);
9589
9590 let snapshot = &*buffer.read(cx);
9591 let snippet = &snippet;
9592 snippet
9593 .tabstops
9594 .iter()
9595 .map(|tabstop| {
9596 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9597 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9598 });
9599 let mut tabstop_ranges = tabstop
9600 .ranges
9601 .iter()
9602 .flat_map(|tabstop_range| {
9603 let mut delta = 0_isize;
9604 insertion_ranges.iter().map(move |insertion_range| {
9605 let insertion_start = insertion_range.start as isize + delta;
9606 delta +=
9607 snippet.text.len() as isize - insertion_range.len() as isize;
9608
9609 let start = ((insertion_start + tabstop_range.start) as usize)
9610 .min(snapshot.len());
9611 let end = ((insertion_start + tabstop_range.end) as usize)
9612 .min(snapshot.len());
9613 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9614 })
9615 })
9616 .collect::<Vec<_>>();
9617 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9618
9619 Tabstop {
9620 is_end_tabstop,
9621 ranges: tabstop_ranges,
9622 choices: tabstop.choices.clone(),
9623 }
9624 })
9625 .collect::<Vec<_>>()
9626 });
9627 if let Some(tabstop) = tabstops.first() {
9628 self.change_selections(Default::default(), window, cx, |s| {
9629 // Reverse order so that the first range is the newest created selection.
9630 // Completions will use it and autoscroll will prioritize it.
9631 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9632 });
9633
9634 if let Some(choices) = &tabstop.choices {
9635 if let Some(selection) = tabstop.ranges.first() {
9636 self.show_snippet_choices(choices, selection.clone(), cx)
9637 }
9638 }
9639
9640 // If we're already at the last tabstop and it's at the end of the snippet,
9641 // we're done, we don't need to keep the state around.
9642 if !tabstop.is_end_tabstop {
9643 let choices = tabstops
9644 .iter()
9645 .map(|tabstop| tabstop.choices.clone())
9646 .collect();
9647
9648 let ranges = tabstops
9649 .into_iter()
9650 .map(|tabstop| tabstop.ranges)
9651 .collect::<Vec<_>>();
9652
9653 self.snippet_stack.push(SnippetState {
9654 active_index: 0,
9655 ranges,
9656 choices,
9657 });
9658 }
9659
9660 // Check whether the just-entered snippet ends with an auto-closable bracket.
9661 if self.autoclose_regions.is_empty() {
9662 let snapshot = self.buffer.read(cx).snapshot(cx);
9663 let mut all_selections = self.selections.all::<Point>(cx);
9664 for selection in &mut all_selections {
9665 let selection_head = selection.head();
9666 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9667 continue;
9668 };
9669
9670 let mut bracket_pair = None;
9671 let max_lookup_length = scope
9672 .brackets()
9673 .map(|(pair, _)| {
9674 pair.start
9675 .as_str()
9676 .chars()
9677 .count()
9678 .max(pair.end.as_str().chars().count())
9679 })
9680 .max();
9681 if let Some(max_lookup_length) = max_lookup_length {
9682 let next_text = snapshot
9683 .chars_at(selection_head)
9684 .take(max_lookup_length)
9685 .collect::<String>();
9686 let prev_text = snapshot
9687 .reversed_chars_at(selection_head)
9688 .take(max_lookup_length)
9689 .collect::<String>();
9690
9691 for (pair, enabled) in scope.brackets() {
9692 if enabled
9693 && pair.close
9694 && prev_text.starts_with(pair.start.as_str())
9695 && next_text.starts_with(pair.end.as_str())
9696 {
9697 bracket_pair = Some(pair.clone());
9698 break;
9699 }
9700 }
9701 }
9702
9703 if let Some(pair) = bracket_pair {
9704 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9705 let autoclose_enabled =
9706 self.use_autoclose && snapshot_settings.use_autoclose;
9707 if autoclose_enabled {
9708 let start = snapshot.anchor_after(selection_head);
9709 let end = snapshot.anchor_after(selection_head);
9710 self.autoclose_regions.push(AutocloseRegion {
9711 selection_id: selection.id,
9712 range: start..end,
9713 pair,
9714 });
9715 }
9716 }
9717 }
9718 }
9719 }
9720 Ok(())
9721 }
9722
9723 pub fn move_to_next_snippet_tabstop(
9724 &mut self,
9725 window: &mut Window,
9726 cx: &mut Context<Self>,
9727 ) -> bool {
9728 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9729 }
9730
9731 pub fn move_to_prev_snippet_tabstop(
9732 &mut self,
9733 window: &mut Window,
9734 cx: &mut Context<Self>,
9735 ) -> bool {
9736 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9737 }
9738
9739 pub fn move_to_snippet_tabstop(
9740 &mut self,
9741 bias: Bias,
9742 window: &mut Window,
9743 cx: &mut Context<Self>,
9744 ) -> bool {
9745 if let Some(mut snippet) = self.snippet_stack.pop() {
9746 match bias {
9747 Bias::Left => {
9748 if snippet.active_index > 0 {
9749 snippet.active_index -= 1;
9750 } else {
9751 self.snippet_stack.push(snippet);
9752 return false;
9753 }
9754 }
9755 Bias::Right => {
9756 if snippet.active_index + 1 < snippet.ranges.len() {
9757 snippet.active_index += 1;
9758 } else {
9759 self.snippet_stack.push(snippet);
9760 return false;
9761 }
9762 }
9763 }
9764 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9765 self.change_selections(Default::default(), window, cx, |s| {
9766 // Reverse order so that the first range is the newest created selection.
9767 // Completions will use it and autoscroll will prioritize it.
9768 s.select_ranges(current_ranges.iter().rev().cloned())
9769 });
9770
9771 if let Some(choices) = &snippet.choices[snippet.active_index] {
9772 if let Some(selection) = current_ranges.first() {
9773 self.show_snippet_choices(choices, selection.clone(), cx);
9774 }
9775 }
9776
9777 // If snippet state is not at the last tabstop, push it back on the stack
9778 if snippet.active_index + 1 < snippet.ranges.len() {
9779 self.snippet_stack.push(snippet);
9780 }
9781 return true;
9782 }
9783 }
9784
9785 false
9786 }
9787
9788 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9789 self.transact(window, cx, |this, window, cx| {
9790 this.select_all(&SelectAll, window, cx);
9791 this.insert("", window, cx);
9792 });
9793 }
9794
9795 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9796 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9797 self.transact(window, cx, |this, window, cx| {
9798 this.select_autoclose_pair(window, cx);
9799 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9800 if !this.linked_edit_ranges.is_empty() {
9801 let selections = this.selections.all::<MultiBufferPoint>(cx);
9802 let snapshot = this.buffer.read(cx).snapshot(cx);
9803
9804 for selection in selections.iter() {
9805 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9806 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9807 if selection_start.buffer_id != selection_end.buffer_id {
9808 continue;
9809 }
9810 if let Some(ranges) =
9811 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9812 {
9813 for (buffer, entries) in ranges {
9814 linked_ranges.entry(buffer).or_default().extend(entries);
9815 }
9816 }
9817 }
9818 }
9819
9820 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9821 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9822 for selection in &mut selections {
9823 if selection.is_empty() {
9824 let old_head = selection.head();
9825 let mut new_head =
9826 movement::left(&display_map, old_head.to_display_point(&display_map))
9827 .to_point(&display_map);
9828 if let Some((buffer, line_buffer_range)) = display_map
9829 .buffer_snapshot
9830 .buffer_line_for_row(MultiBufferRow(old_head.row))
9831 {
9832 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9833 let indent_len = match indent_size.kind {
9834 IndentKind::Space => {
9835 buffer.settings_at(line_buffer_range.start, cx).tab_size
9836 }
9837 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9838 };
9839 if old_head.column <= indent_size.len && old_head.column > 0 {
9840 let indent_len = indent_len.get();
9841 new_head = cmp::min(
9842 new_head,
9843 MultiBufferPoint::new(
9844 old_head.row,
9845 ((old_head.column - 1) / indent_len) * indent_len,
9846 ),
9847 );
9848 }
9849 }
9850
9851 selection.set_head(new_head, SelectionGoal::None);
9852 }
9853 }
9854
9855 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9856 this.insert("", window, cx);
9857 let empty_str: Arc<str> = Arc::from("");
9858 for (buffer, edits) in linked_ranges {
9859 let snapshot = buffer.read(cx).snapshot();
9860 use text::ToPoint as TP;
9861
9862 let edits = edits
9863 .into_iter()
9864 .map(|range| {
9865 let end_point = TP::to_point(&range.end, &snapshot);
9866 let mut start_point = TP::to_point(&range.start, &snapshot);
9867
9868 if end_point == start_point {
9869 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9870 .saturating_sub(1);
9871 start_point =
9872 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9873 };
9874
9875 (start_point..end_point, empty_str.clone())
9876 })
9877 .sorted_by_key(|(range, _)| range.start)
9878 .collect::<Vec<_>>();
9879 buffer.update(cx, |this, cx| {
9880 this.edit(edits, None, cx);
9881 })
9882 }
9883 this.refresh_edit_prediction(true, false, window, cx);
9884 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9885 });
9886 }
9887
9888 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9889 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9890 self.transact(window, cx, |this, window, cx| {
9891 this.change_selections(Default::default(), window, cx, |s| {
9892 s.move_with(|map, selection| {
9893 if selection.is_empty() {
9894 let cursor = movement::right(map, selection.head());
9895 selection.end = cursor;
9896 selection.reversed = true;
9897 selection.goal = SelectionGoal::None;
9898 }
9899 })
9900 });
9901 this.insert("", window, cx);
9902 this.refresh_edit_prediction(true, false, window, cx);
9903 });
9904 }
9905
9906 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9907 if self.mode.is_single_line() {
9908 cx.propagate();
9909 return;
9910 }
9911
9912 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9913 if self.move_to_prev_snippet_tabstop(window, cx) {
9914 return;
9915 }
9916 self.outdent(&Outdent, window, cx);
9917 }
9918
9919 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9920 if self.mode.is_single_line() {
9921 cx.propagate();
9922 return;
9923 }
9924
9925 if self.move_to_next_snippet_tabstop(window, cx) {
9926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9927 return;
9928 }
9929 if self.read_only(cx) {
9930 return;
9931 }
9932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9933 let mut selections = self.selections.all_adjusted(cx);
9934 let buffer = self.buffer.read(cx);
9935 let snapshot = buffer.snapshot(cx);
9936 let rows_iter = selections.iter().map(|s| s.head().row);
9937 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9938
9939 let has_some_cursor_in_whitespace = selections
9940 .iter()
9941 .filter(|selection| selection.is_empty())
9942 .any(|selection| {
9943 let cursor = selection.head();
9944 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9945 cursor.column < current_indent.len
9946 });
9947
9948 let mut edits = Vec::new();
9949 let mut prev_edited_row = 0;
9950 let mut row_delta = 0;
9951 for selection in &mut selections {
9952 if selection.start.row != prev_edited_row {
9953 row_delta = 0;
9954 }
9955 prev_edited_row = selection.end.row;
9956
9957 // If the selection is non-empty, then increase the indentation of the selected lines.
9958 if !selection.is_empty() {
9959 row_delta =
9960 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9961 continue;
9962 }
9963
9964 let cursor = selection.head();
9965 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9966 if let Some(suggested_indent) =
9967 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9968 {
9969 // Don't do anything if already at suggested indent
9970 // and there is any other cursor which is not
9971 if has_some_cursor_in_whitespace
9972 && cursor.column == current_indent.len
9973 && current_indent.len == suggested_indent.len
9974 {
9975 continue;
9976 }
9977
9978 // Adjust line and move cursor to suggested indent
9979 // if cursor is not at suggested indent
9980 if cursor.column < suggested_indent.len
9981 && cursor.column <= current_indent.len
9982 && current_indent.len <= suggested_indent.len
9983 {
9984 selection.start = Point::new(cursor.row, suggested_indent.len);
9985 selection.end = selection.start;
9986 if row_delta == 0 {
9987 edits.extend(Buffer::edit_for_indent_size_adjustment(
9988 cursor.row,
9989 current_indent,
9990 suggested_indent,
9991 ));
9992 row_delta = suggested_indent.len - current_indent.len;
9993 }
9994 continue;
9995 }
9996
9997 // If current indent is more than suggested indent
9998 // only move cursor to current indent and skip indent
9999 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10000 selection.start = Point::new(cursor.row, current_indent.len);
10001 selection.end = selection.start;
10002 continue;
10003 }
10004 }
10005
10006 // Otherwise, insert a hard or soft tab.
10007 let settings = buffer.language_settings_at(cursor, cx);
10008 let tab_size = if settings.hard_tabs {
10009 IndentSize::tab()
10010 } else {
10011 let tab_size = settings.tab_size.get();
10012 let indent_remainder = snapshot
10013 .text_for_range(Point::new(cursor.row, 0)..cursor)
10014 .flat_map(str::chars)
10015 .fold(row_delta % tab_size, |counter: u32, c| {
10016 if c == '\t' {
10017 0
10018 } else {
10019 (counter + 1) % tab_size
10020 }
10021 });
10022
10023 let chars_to_next_tab_stop = tab_size - indent_remainder;
10024 IndentSize::spaces(chars_to_next_tab_stop)
10025 };
10026 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10027 selection.end = selection.start;
10028 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10029 row_delta += tab_size.len;
10030 }
10031
10032 self.transact(window, cx, |this, window, cx| {
10033 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10034 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10035 this.refresh_edit_prediction(true, false, window, cx);
10036 });
10037 }
10038
10039 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10040 if self.read_only(cx) {
10041 return;
10042 }
10043 if self.mode.is_single_line() {
10044 cx.propagate();
10045 return;
10046 }
10047
10048 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10049 let mut selections = self.selections.all::<Point>(cx);
10050 let mut prev_edited_row = 0;
10051 let mut row_delta = 0;
10052 let mut edits = Vec::new();
10053 let buffer = self.buffer.read(cx);
10054 let snapshot = buffer.snapshot(cx);
10055 for selection in &mut selections {
10056 if selection.start.row != prev_edited_row {
10057 row_delta = 0;
10058 }
10059 prev_edited_row = selection.end.row;
10060
10061 row_delta =
10062 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10063 }
10064
10065 self.transact(window, cx, |this, window, cx| {
10066 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10067 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10068 });
10069 }
10070
10071 fn indent_selection(
10072 buffer: &MultiBuffer,
10073 snapshot: &MultiBufferSnapshot,
10074 selection: &mut Selection<Point>,
10075 edits: &mut Vec<(Range<Point>, String)>,
10076 delta_for_start_row: u32,
10077 cx: &App,
10078 ) -> u32 {
10079 let settings = buffer.language_settings_at(selection.start, cx);
10080 let tab_size = settings.tab_size.get();
10081 let indent_kind = if settings.hard_tabs {
10082 IndentKind::Tab
10083 } else {
10084 IndentKind::Space
10085 };
10086 let mut start_row = selection.start.row;
10087 let mut end_row = selection.end.row + 1;
10088
10089 // If a selection ends at the beginning of a line, don't indent
10090 // that last line.
10091 if selection.end.column == 0 && selection.end.row > selection.start.row {
10092 end_row -= 1;
10093 }
10094
10095 // Avoid re-indenting a row that has already been indented by a
10096 // previous selection, but still update this selection's column
10097 // to reflect that indentation.
10098 if delta_for_start_row > 0 {
10099 start_row += 1;
10100 selection.start.column += delta_for_start_row;
10101 if selection.end.row == selection.start.row {
10102 selection.end.column += delta_for_start_row;
10103 }
10104 }
10105
10106 let mut delta_for_end_row = 0;
10107 let has_multiple_rows = start_row + 1 != end_row;
10108 for row in start_row..end_row {
10109 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10110 let indent_delta = match (current_indent.kind, indent_kind) {
10111 (IndentKind::Space, IndentKind::Space) => {
10112 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10113 IndentSize::spaces(columns_to_next_tab_stop)
10114 }
10115 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10116 (_, IndentKind::Tab) => IndentSize::tab(),
10117 };
10118
10119 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10120 0
10121 } else {
10122 selection.start.column
10123 };
10124 let row_start = Point::new(row, start);
10125 edits.push((
10126 row_start..row_start,
10127 indent_delta.chars().collect::<String>(),
10128 ));
10129
10130 // Update this selection's endpoints to reflect the indentation.
10131 if row == selection.start.row {
10132 selection.start.column += indent_delta.len;
10133 }
10134 if row == selection.end.row {
10135 selection.end.column += indent_delta.len;
10136 delta_for_end_row = indent_delta.len;
10137 }
10138 }
10139
10140 if selection.start.row == selection.end.row {
10141 delta_for_start_row + delta_for_end_row
10142 } else {
10143 delta_for_end_row
10144 }
10145 }
10146
10147 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10148 if self.read_only(cx) {
10149 return;
10150 }
10151 if self.mode.is_single_line() {
10152 cx.propagate();
10153 return;
10154 }
10155
10156 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10157 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10158 let selections = self.selections.all::<Point>(cx);
10159 let mut deletion_ranges = Vec::new();
10160 let mut last_outdent = None;
10161 {
10162 let buffer = self.buffer.read(cx);
10163 let snapshot = buffer.snapshot(cx);
10164 for selection in &selections {
10165 let settings = buffer.language_settings_at(selection.start, cx);
10166 let tab_size = settings.tab_size.get();
10167 let mut rows = selection.spanned_rows(false, &display_map);
10168
10169 // Avoid re-outdenting a row that has already been outdented by a
10170 // previous selection.
10171 if let Some(last_row) = last_outdent {
10172 if last_row == rows.start {
10173 rows.start = rows.start.next_row();
10174 }
10175 }
10176 let has_multiple_rows = rows.len() > 1;
10177 for row in rows.iter_rows() {
10178 let indent_size = snapshot.indent_size_for_line(row);
10179 if indent_size.len > 0 {
10180 let deletion_len = match indent_size.kind {
10181 IndentKind::Space => {
10182 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10183 if columns_to_prev_tab_stop == 0 {
10184 tab_size
10185 } else {
10186 columns_to_prev_tab_stop
10187 }
10188 }
10189 IndentKind::Tab => 1,
10190 };
10191 let start = if has_multiple_rows
10192 || deletion_len > selection.start.column
10193 || indent_size.len < selection.start.column
10194 {
10195 0
10196 } else {
10197 selection.start.column - deletion_len
10198 };
10199 deletion_ranges.push(
10200 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10201 );
10202 last_outdent = Some(row);
10203 }
10204 }
10205 }
10206 }
10207
10208 self.transact(window, cx, |this, window, cx| {
10209 this.buffer.update(cx, |buffer, cx| {
10210 let empty_str: Arc<str> = Arc::default();
10211 buffer.edit(
10212 deletion_ranges
10213 .into_iter()
10214 .map(|range| (range, empty_str.clone())),
10215 None,
10216 cx,
10217 );
10218 });
10219 let selections = this.selections.all::<usize>(cx);
10220 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10221 });
10222 }
10223
10224 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10225 if self.read_only(cx) {
10226 return;
10227 }
10228 if self.mode.is_single_line() {
10229 cx.propagate();
10230 return;
10231 }
10232
10233 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10234 let selections = self
10235 .selections
10236 .all::<usize>(cx)
10237 .into_iter()
10238 .map(|s| s.range());
10239
10240 self.transact(window, cx, |this, window, cx| {
10241 this.buffer.update(cx, |buffer, cx| {
10242 buffer.autoindent_ranges(selections, cx);
10243 });
10244 let selections = this.selections.all::<usize>(cx);
10245 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10246 });
10247 }
10248
10249 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10250 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10251 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10252 let selections = self.selections.all::<Point>(cx);
10253
10254 let mut new_cursors = Vec::new();
10255 let mut edit_ranges = Vec::new();
10256 let mut selections = selections.iter().peekable();
10257 while let Some(selection) = selections.next() {
10258 let mut rows = selection.spanned_rows(false, &display_map);
10259 let goal_display_column = selection.head().to_display_point(&display_map).column();
10260
10261 // Accumulate contiguous regions of rows that we want to delete.
10262 while let Some(next_selection) = selections.peek() {
10263 let next_rows = next_selection.spanned_rows(false, &display_map);
10264 if next_rows.start <= rows.end {
10265 rows.end = next_rows.end;
10266 selections.next().unwrap();
10267 } else {
10268 break;
10269 }
10270 }
10271
10272 let buffer = &display_map.buffer_snapshot;
10273 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10274 let edit_end;
10275 let cursor_buffer_row;
10276 if buffer.max_point().row >= rows.end.0 {
10277 // If there's a line after the range, delete the \n from the end of the row range
10278 // and position the cursor on the next line.
10279 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10280 cursor_buffer_row = rows.end;
10281 } else {
10282 // If there isn't a line after the range, delete the \n from the line before the
10283 // start of the row range and position the cursor there.
10284 edit_start = edit_start.saturating_sub(1);
10285 edit_end = buffer.len();
10286 cursor_buffer_row = rows.start.previous_row();
10287 }
10288
10289 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10290 *cursor.column_mut() =
10291 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10292
10293 new_cursors.push((
10294 selection.id,
10295 buffer.anchor_after(cursor.to_point(&display_map)),
10296 ));
10297 edit_ranges.push(edit_start..edit_end);
10298 }
10299
10300 self.transact(window, cx, |this, window, cx| {
10301 let buffer = this.buffer.update(cx, |buffer, cx| {
10302 let empty_str: Arc<str> = Arc::default();
10303 buffer.edit(
10304 edit_ranges
10305 .into_iter()
10306 .map(|range| (range, empty_str.clone())),
10307 None,
10308 cx,
10309 );
10310 buffer.snapshot(cx)
10311 });
10312 let new_selections = new_cursors
10313 .into_iter()
10314 .map(|(id, cursor)| {
10315 let cursor = cursor.to_point(&buffer);
10316 Selection {
10317 id,
10318 start: cursor,
10319 end: cursor,
10320 reversed: false,
10321 goal: SelectionGoal::None,
10322 }
10323 })
10324 .collect();
10325
10326 this.change_selections(Default::default(), window, cx, |s| {
10327 s.select(new_selections);
10328 });
10329 });
10330 }
10331
10332 pub fn join_lines_impl(
10333 &mut self,
10334 insert_whitespace: bool,
10335 window: &mut Window,
10336 cx: &mut Context<Self>,
10337 ) {
10338 if self.read_only(cx) {
10339 return;
10340 }
10341 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10342 for selection in self.selections.all::<Point>(cx) {
10343 let start = MultiBufferRow(selection.start.row);
10344 // Treat single line selections as if they include the next line. Otherwise this action
10345 // would do nothing for single line selections individual cursors.
10346 let end = if selection.start.row == selection.end.row {
10347 MultiBufferRow(selection.start.row + 1)
10348 } else {
10349 MultiBufferRow(selection.end.row)
10350 };
10351
10352 if let Some(last_row_range) = row_ranges.last_mut() {
10353 if start <= last_row_range.end {
10354 last_row_range.end = end;
10355 continue;
10356 }
10357 }
10358 row_ranges.push(start..end);
10359 }
10360
10361 let snapshot = self.buffer.read(cx).snapshot(cx);
10362 let mut cursor_positions = Vec::new();
10363 for row_range in &row_ranges {
10364 let anchor = snapshot.anchor_before(Point::new(
10365 row_range.end.previous_row().0,
10366 snapshot.line_len(row_range.end.previous_row()),
10367 ));
10368 cursor_positions.push(anchor..anchor);
10369 }
10370
10371 self.transact(window, cx, |this, window, cx| {
10372 for row_range in row_ranges.into_iter().rev() {
10373 for row in row_range.iter_rows().rev() {
10374 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10375 let next_line_row = row.next_row();
10376 let indent = snapshot.indent_size_for_line(next_line_row);
10377 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10378
10379 let replace =
10380 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10381 " "
10382 } else {
10383 ""
10384 };
10385
10386 this.buffer.update(cx, |buffer, cx| {
10387 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10388 });
10389 }
10390 }
10391
10392 this.change_selections(Default::default(), window, cx, |s| {
10393 s.select_anchor_ranges(cursor_positions)
10394 });
10395 });
10396 }
10397
10398 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10399 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10400 self.join_lines_impl(true, window, cx);
10401 }
10402
10403 pub fn sort_lines_case_sensitive(
10404 &mut self,
10405 _: &SortLinesCaseSensitive,
10406 window: &mut Window,
10407 cx: &mut Context<Self>,
10408 ) {
10409 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10410 }
10411
10412 pub fn sort_lines_by_length(
10413 &mut self,
10414 _: &SortLinesByLength,
10415 window: &mut Window,
10416 cx: &mut Context<Self>,
10417 ) {
10418 self.manipulate_immutable_lines(window, cx, |lines| {
10419 lines.sort_by_key(|&line| line.chars().count())
10420 })
10421 }
10422
10423 pub fn sort_lines_case_insensitive(
10424 &mut self,
10425 _: &SortLinesCaseInsensitive,
10426 window: &mut Window,
10427 cx: &mut Context<Self>,
10428 ) {
10429 self.manipulate_immutable_lines(window, cx, |lines| {
10430 lines.sort_by_key(|line| line.to_lowercase())
10431 })
10432 }
10433
10434 pub fn unique_lines_case_insensitive(
10435 &mut self,
10436 _: &UniqueLinesCaseInsensitive,
10437 window: &mut Window,
10438 cx: &mut Context<Self>,
10439 ) {
10440 self.manipulate_immutable_lines(window, cx, |lines| {
10441 let mut seen = HashSet::default();
10442 lines.retain(|line| seen.insert(line.to_lowercase()));
10443 })
10444 }
10445
10446 pub fn unique_lines_case_sensitive(
10447 &mut self,
10448 _: &UniqueLinesCaseSensitive,
10449 window: &mut Window,
10450 cx: &mut Context<Self>,
10451 ) {
10452 self.manipulate_immutable_lines(window, cx, |lines| {
10453 let mut seen = HashSet::default();
10454 lines.retain(|line| seen.insert(*line));
10455 })
10456 }
10457
10458 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10459 let Some(project) = self.project.clone() else {
10460 return;
10461 };
10462 self.reload(project, window, cx)
10463 .detach_and_notify_err(window, cx);
10464 }
10465
10466 pub fn restore_file(
10467 &mut self,
10468 _: &::git::RestoreFile,
10469 window: &mut Window,
10470 cx: &mut Context<Self>,
10471 ) {
10472 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10473 let mut buffer_ids = HashSet::default();
10474 let snapshot = self.buffer().read(cx).snapshot(cx);
10475 for selection in self.selections.all::<usize>(cx) {
10476 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10477 }
10478
10479 let buffer = self.buffer().read(cx);
10480 let ranges = buffer_ids
10481 .into_iter()
10482 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10483 .collect::<Vec<_>>();
10484
10485 self.restore_hunks_in_ranges(ranges, window, cx);
10486 }
10487
10488 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10489 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10490 let selections = self
10491 .selections
10492 .all(cx)
10493 .into_iter()
10494 .map(|s| s.range())
10495 .collect();
10496 self.restore_hunks_in_ranges(selections, window, cx);
10497 }
10498
10499 pub fn restore_hunks_in_ranges(
10500 &mut self,
10501 ranges: Vec<Range<Point>>,
10502 window: &mut Window,
10503 cx: &mut Context<Editor>,
10504 ) {
10505 let mut revert_changes = HashMap::default();
10506 let chunk_by = self
10507 .snapshot(window, cx)
10508 .hunks_for_ranges(ranges)
10509 .into_iter()
10510 .chunk_by(|hunk| hunk.buffer_id);
10511 for (buffer_id, hunks) in &chunk_by {
10512 let hunks = hunks.collect::<Vec<_>>();
10513 for hunk in &hunks {
10514 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10515 }
10516 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10517 }
10518 drop(chunk_by);
10519 if !revert_changes.is_empty() {
10520 self.transact(window, cx, |editor, window, cx| {
10521 editor.restore(revert_changes, window, cx);
10522 });
10523 }
10524 }
10525
10526 pub fn open_active_item_in_terminal(
10527 &mut self,
10528 _: &OpenInTerminal,
10529 window: &mut Window,
10530 cx: &mut Context<Self>,
10531 ) {
10532 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10533 let project_path = buffer.read(cx).project_path(cx)?;
10534 let project = self.project()?.read(cx);
10535 let entry = project.entry_for_path(&project_path, cx)?;
10536 let parent = match &entry.canonical_path {
10537 Some(canonical_path) => canonical_path.to_path_buf(),
10538 None => project.absolute_path(&project_path, cx)?,
10539 }
10540 .parent()?
10541 .to_path_buf();
10542 Some(parent)
10543 }) {
10544 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10545 }
10546 }
10547
10548 fn set_breakpoint_context_menu(
10549 &mut self,
10550 display_row: DisplayRow,
10551 position: Option<Anchor>,
10552 clicked_point: gpui::Point<Pixels>,
10553 window: &mut Window,
10554 cx: &mut Context<Self>,
10555 ) {
10556 let source = self
10557 .buffer
10558 .read(cx)
10559 .snapshot(cx)
10560 .anchor_before(Point::new(display_row.0, 0u32));
10561
10562 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10563
10564 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10565 self,
10566 source,
10567 clicked_point,
10568 context_menu,
10569 window,
10570 cx,
10571 );
10572 }
10573
10574 fn add_edit_breakpoint_block(
10575 &mut self,
10576 anchor: Anchor,
10577 breakpoint: &Breakpoint,
10578 edit_action: BreakpointPromptEditAction,
10579 window: &mut Window,
10580 cx: &mut Context<Self>,
10581 ) {
10582 let weak_editor = cx.weak_entity();
10583 let bp_prompt = cx.new(|cx| {
10584 BreakpointPromptEditor::new(
10585 weak_editor,
10586 anchor,
10587 breakpoint.clone(),
10588 edit_action,
10589 window,
10590 cx,
10591 )
10592 });
10593
10594 let height = bp_prompt.update(cx, |this, cx| {
10595 this.prompt
10596 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10597 });
10598 let cloned_prompt = bp_prompt.clone();
10599 let blocks = vec![BlockProperties {
10600 style: BlockStyle::Sticky,
10601 placement: BlockPlacement::Above(anchor),
10602 height: Some(height),
10603 render: Arc::new(move |cx| {
10604 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10605 cloned_prompt.clone().into_any_element()
10606 }),
10607 priority: 0,
10608 }];
10609
10610 let focus_handle = bp_prompt.focus_handle(cx);
10611 window.focus(&focus_handle);
10612
10613 let block_ids = self.insert_blocks(blocks, None, cx);
10614 bp_prompt.update(cx, |prompt, _| {
10615 prompt.add_block_ids(block_ids);
10616 });
10617 }
10618
10619 pub(crate) fn breakpoint_at_row(
10620 &self,
10621 row: u32,
10622 window: &mut Window,
10623 cx: &mut Context<Self>,
10624 ) -> Option<(Anchor, Breakpoint)> {
10625 let snapshot = self.snapshot(window, cx);
10626 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10627
10628 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10629 }
10630
10631 pub(crate) fn breakpoint_at_anchor(
10632 &self,
10633 breakpoint_position: Anchor,
10634 snapshot: &EditorSnapshot,
10635 cx: &mut Context<Self>,
10636 ) -> Option<(Anchor, Breakpoint)> {
10637 let project = self.project.clone()?;
10638
10639 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10640 snapshot
10641 .buffer_snapshot
10642 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10643 })?;
10644
10645 let enclosing_excerpt = breakpoint_position.excerpt_id;
10646 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10647 let buffer_snapshot = buffer.read(cx).snapshot();
10648
10649 let row = buffer_snapshot
10650 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10651 .row;
10652
10653 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10654 let anchor_end = snapshot
10655 .buffer_snapshot
10656 .anchor_after(Point::new(row, line_len));
10657
10658 let bp = self
10659 .breakpoint_store
10660 .as_ref()?
10661 .read_with(cx, |breakpoint_store, cx| {
10662 breakpoint_store
10663 .breakpoints(
10664 &buffer,
10665 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10666 &buffer_snapshot,
10667 cx,
10668 )
10669 .next()
10670 .and_then(|(bp, _)| {
10671 let breakpoint_row = buffer_snapshot
10672 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10673 .row;
10674
10675 if breakpoint_row == row {
10676 snapshot
10677 .buffer_snapshot
10678 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10679 .map(|position| (position, bp.bp.clone()))
10680 } else {
10681 None
10682 }
10683 })
10684 });
10685 bp
10686 }
10687
10688 pub fn edit_log_breakpoint(
10689 &mut self,
10690 _: &EditLogBreakpoint,
10691 window: &mut Window,
10692 cx: &mut Context<Self>,
10693 ) {
10694 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10695 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10696 message: None,
10697 state: BreakpointState::Enabled,
10698 condition: None,
10699 hit_condition: None,
10700 });
10701
10702 self.add_edit_breakpoint_block(
10703 anchor,
10704 &breakpoint,
10705 BreakpointPromptEditAction::Log,
10706 window,
10707 cx,
10708 );
10709 }
10710 }
10711
10712 fn breakpoints_at_cursors(
10713 &self,
10714 window: &mut Window,
10715 cx: &mut Context<Self>,
10716 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10717 let snapshot = self.snapshot(window, cx);
10718 let cursors = self
10719 .selections
10720 .disjoint_anchors()
10721 .into_iter()
10722 .map(|selection| {
10723 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10724
10725 let breakpoint_position = self
10726 .breakpoint_at_row(cursor_position.row, window, cx)
10727 .map(|bp| bp.0)
10728 .unwrap_or_else(|| {
10729 snapshot
10730 .display_snapshot
10731 .buffer_snapshot
10732 .anchor_after(Point::new(cursor_position.row, 0))
10733 });
10734
10735 let breakpoint = self
10736 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10737 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10738
10739 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10740 })
10741 // 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.
10742 .collect::<HashMap<Anchor, _>>();
10743
10744 cursors.into_iter().collect()
10745 }
10746
10747 pub fn enable_breakpoint(
10748 &mut self,
10749 _: &crate::actions::EnableBreakpoint,
10750 window: &mut Window,
10751 cx: &mut Context<Self>,
10752 ) {
10753 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10754 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10755 continue;
10756 };
10757 self.edit_breakpoint_at_anchor(
10758 anchor,
10759 breakpoint,
10760 BreakpointEditAction::InvertState,
10761 cx,
10762 );
10763 }
10764 }
10765
10766 pub fn disable_breakpoint(
10767 &mut self,
10768 _: &crate::actions::DisableBreakpoint,
10769 window: &mut Window,
10770 cx: &mut Context<Self>,
10771 ) {
10772 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10773 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10774 continue;
10775 };
10776 self.edit_breakpoint_at_anchor(
10777 anchor,
10778 breakpoint,
10779 BreakpointEditAction::InvertState,
10780 cx,
10781 );
10782 }
10783 }
10784
10785 pub fn toggle_breakpoint(
10786 &mut self,
10787 _: &crate::actions::ToggleBreakpoint,
10788 window: &mut Window,
10789 cx: &mut Context<Self>,
10790 ) {
10791 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10792 if let Some(breakpoint) = breakpoint {
10793 self.edit_breakpoint_at_anchor(
10794 anchor,
10795 breakpoint,
10796 BreakpointEditAction::Toggle,
10797 cx,
10798 );
10799 } else {
10800 self.edit_breakpoint_at_anchor(
10801 anchor,
10802 Breakpoint::new_standard(),
10803 BreakpointEditAction::Toggle,
10804 cx,
10805 );
10806 }
10807 }
10808 }
10809
10810 pub fn edit_breakpoint_at_anchor(
10811 &mut self,
10812 breakpoint_position: Anchor,
10813 breakpoint: Breakpoint,
10814 edit_action: BreakpointEditAction,
10815 cx: &mut Context<Self>,
10816 ) {
10817 let Some(breakpoint_store) = &self.breakpoint_store else {
10818 return;
10819 };
10820
10821 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10822 if breakpoint_position == Anchor::min() {
10823 self.buffer()
10824 .read(cx)
10825 .excerpt_buffer_ids()
10826 .into_iter()
10827 .next()
10828 } else {
10829 None
10830 }
10831 }) else {
10832 return;
10833 };
10834
10835 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10836 return;
10837 };
10838
10839 breakpoint_store.update(cx, |breakpoint_store, cx| {
10840 breakpoint_store.toggle_breakpoint(
10841 buffer,
10842 BreakpointWithPosition {
10843 position: breakpoint_position.text_anchor,
10844 bp: breakpoint,
10845 },
10846 edit_action,
10847 cx,
10848 );
10849 });
10850
10851 cx.notify();
10852 }
10853
10854 #[cfg(any(test, feature = "test-support"))]
10855 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10856 self.breakpoint_store.clone()
10857 }
10858
10859 pub fn prepare_restore_change(
10860 &self,
10861 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10862 hunk: &MultiBufferDiffHunk,
10863 cx: &mut App,
10864 ) -> Option<()> {
10865 if hunk.is_created_file() {
10866 return None;
10867 }
10868 let buffer = self.buffer.read(cx);
10869 let diff = buffer.diff_for(hunk.buffer_id)?;
10870 let buffer = buffer.buffer(hunk.buffer_id)?;
10871 let buffer = buffer.read(cx);
10872 let original_text = diff
10873 .read(cx)
10874 .base_text()
10875 .as_rope()
10876 .slice(hunk.diff_base_byte_range.clone());
10877 let buffer_snapshot = buffer.snapshot();
10878 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10879 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10880 probe
10881 .0
10882 .start
10883 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10884 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10885 }) {
10886 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10887 Some(())
10888 } else {
10889 None
10890 }
10891 }
10892
10893 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10894 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10895 }
10896
10897 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10898 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10899 }
10900
10901 fn manipulate_lines<M>(
10902 &mut self,
10903 window: &mut Window,
10904 cx: &mut Context<Self>,
10905 mut manipulate: M,
10906 ) where
10907 M: FnMut(&str) -> LineManipulationResult,
10908 {
10909 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10910
10911 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10912 let buffer = self.buffer.read(cx).snapshot(cx);
10913
10914 let mut edits = Vec::new();
10915
10916 let selections = self.selections.all::<Point>(cx);
10917 let mut selections = selections.iter().peekable();
10918 let mut contiguous_row_selections = Vec::new();
10919 let mut new_selections = Vec::new();
10920 let mut added_lines = 0;
10921 let mut removed_lines = 0;
10922
10923 while let Some(selection) = selections.next() {
10924 let (start_row, end_row) = consume_contiguous_rows(
10925 &mut contiguous_row_selections,
10926 selection,
10927 &display_map,
10928 &mut selections,
10929 );
10930
10931 let start_point = Point::new(start_row.0, 0);
10932 let end_point = Point::new(
10933 end_row.previous_row().0,
10934 buffer.line_len(end_row.previous_row()),
10935 );
10936 let text = buffer
10937 .text_for_range(start_point..end_point)
10938 .collect::<String>();
10939
10940 let LineManipulationResult {
10941 new_text,
10942 line_count_before,
10943 line_count_after,
10944 } = manipulate(&text);
10945
10946 edits.push((start_point..end_point, new_text));
10947
10948 // Selections must change based on added and removed line count
10949 let start_row =
10950 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10951 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10952 new_selections.push(Selection {
10953 id: selection.id,
10954 start: start_row,
10955 end: end_row,
10956 goal: SelectionGoal::None,
10957 reversed: selection.reversed,
10958 });
10959
10960 if line_count_after > line_count_before {
10961 added_lines += line_count_after - line_count_before;
10962 } else if line_count_before > line_count_after {
10963 removed_lines += line_count_before - line_count_after;
10964 }
10965 }
10966
10967 self.transact(window, cx, |this, window, cx| {
10968 let buffer = this.buffer.update(cx, |buffer, cx| {
10969 buffer.edit(edits, None, cx);
10970 buffer.snapshot(cx)
10971 });
10972
10973 // Recalculate offsets on newly edited buffer
10974 let new_selections = new_selections
10975 .iter()
10976 .map(|s| {
10977 let start_point = Point::new(s.start.0, 0);
10978 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10979 Selection {
10980 id: s.id,
10981 start: buffer.point_to_offset(start_point),
10982 end: buffer.point_to_offset(end_point),
10983 goal: s.goal,
10984 reversed: s.reversed,
10985 }
10986 })
10987 .collect();
10988
10989 this.change_selections(Default::default(), window, cx, |s| {
10990 s.select(new_selections);
10991 });
10992
10993 this.request_autoscroll(Autoscroll::fit(), cx);
10994 });
10995 }
10996
10997 fn manipulate_immutable_lines<Fn>(
10998 &mut self,
10999 window: &mut Window,
11000 cx: &mut Context<Self>,
11001 mut callback: Fn,
11002 ) where
11003 Fn: FnMut(&mut Vec<&str>),
11004 {
11005 self.manipulate_lines(window, cx, |text| {
11006 let mut lines: Vec<&str> = text.split('\n').collect();
11007 let line_count_before = lines.len();
11008
11009 callback(&mut lines);
11010
11011 LineManipulationResult {
11012 new_text: lines.join("\n"),
11013 line_count_before,
11014 line_count_after: lines.len(),
11015 }
11016 });
11017 }
11018
11019 fn manipulate_mutable_lines<Fn>(
11020 &mut self,
11021 window: &mut Window,
11022 cx: &mut Context<Self>,
11023 mut callback: Fn,
11024 ) where
11025 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11026 {
11027 self.manipulate_lines(window, cx, |text| {
11028 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11029 let line_count_before = lines.len();
11030
11031 callback(&mut lines);
11032
11033 LineManipulationResult {
11034 new_text: lines.join("\n"),
11035 line_count_before,
11036 line_count_after: lines.len(),
11037 }
11038 });
11039 }
11040
11041 pub fn convert_indentation_to_spaces(
11042 &mut self,
11043 _: &ConvertIndentationToSpaces,
11044 window: &mut Window,
11045 cx: &mut Context<Self>,
11046 ) {
11047 let settings = self.buffer.read(cx).language_settings(cx);
11048 let tab_size = settings.tab_size.get() as usize;
11049
11050 self.manipulate_mutable_lines(window, cx, |lines| {
11051 // Allocates a reasonably sized scratch buffer once for the whole loop
11052 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11053 // Avoids recomputing spaces that could be inserted many times
11054 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11055 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11056 .collect();
11057
11058 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11059 let mut chars = line.as_ref().chars();
11060 let mut col = 0;
11061 let mut changed = false;
11062
11063 while let Some(ch) = chars.next() {
11064 match ch {
11065 ' ' => {
11066 reindented_line.push(' ');
11067 col += 1;
11068 }
11069 '\t' => {
11070 // \t are converted to spaces depending on the current column
11071 let spaces_len = tab_size - (col % tab_size);
11072 reindented_line.extend(&space_cache[spaces_len - 1]);
11073 col += spaces_len;
11074 changed = true;
11075 }
11076 _ => {
11077 // If we dont append before break, the character is consumed
11078 reindented_line.push(ch);
11079 break;
11080 }
11081 }
11082 }
11083
11084 if !changed {
11085 reindented_line.clear();
11086 continue;
11087 }
11088 // Append the rest of the line and replace old reference with new one
11089 reindented_line.extend(chars);
11090 *line = Cow::Owned(reindented_line.clone());
11091 reindented_line.clear();
11092 }
11093 });
11094 }
11095
11096 pub fn convert_indentation_to_tabs(
11097 &mut self,
11098 _: &ConvertIndentationToTabs,
11099 window: &mut Window,
11100 cx: &mut Context<Self>,
11101 ) {
11102 let settings = self.buffer.read(cx).language_settings(cx);
11103 let tab_size = settings.tab_size.get() as usize;
11104
11105 self.manipulate_mutable_lines(window, cx, |lines| {
11106 // Allocates a reasonably sized buffer once for the whole loop
11107 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11108 // Avoids recomputing spaces that could be inserted many times
11109 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11110 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11111 .collect();
11112
11113 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11114 let mut chars = line.chars();
11115 let mut spaces_count = 0;
11116 let mut first_non_indent_char = None;
11117 let mut changed = false;
11118
11119 while let Some(ch) = chars.next() {
11120 match ch {
11121 ' ' => {
11122 // Keep track of spaces. Append \t when we reach tab_size
11123 spaces_count += 1;
11124 changed = true;
11125 if spaces_count == tab_size {
11126 reindented_line.push('\t');
11127 spaces_count = 0;
11128 }
11129 }
11130 '\t' => {
11131 reindented_line.push('\t');
11132 spaces_count = 0;
11133 }
11134 _ => {
11135 // Dont append it yet, we might have remaining spaces
11136 first_non_indent_char = Some(ch);
11137 break;
11138 }
11139 }
11140 }
11141
11142 if !changed {
11143 reindented_line.clear();
11144 continue;
11145 }
11146 // Remaining spaces that didn't make a full tab stop
11147 if spaces_count > 0 {
11148 reindented_line.extend(&space_cache[spaces_count - 1]);
11149 }
11150 // If we consume an extra character that was not indentation, add it back
11151 if let Some(extra_char) = first_non_indent_char {
11152 reindented_line.push(extra_char);
11153 }
11154 // Append the rest of the line and replace old reference with new one
11155 reindented_line.extend(chars);
11156 *line = Cow::Owned(reindented_line.clone());
11157 reindented_line.clear();
11158 }
11159 });
11160 }
11161
11162 pub fn convert_to_upper_case(
11163 &mut self,
11164 _: &ConvertToUpperCase,
11165 window: &mut Window,
11166 cx: &mut Context<Self>,
11167 ) {
11168 self.manipulate_text(window, cx, |text| text.to_uppercase())
11169 }
11170
11171 pub fn convert_to_lower_case(
11172 &mut self,
11173 _: &ConvertToLowerCase,
11174 window: &mut Window,
11175 cx: &mut Context<Self>,
11176 ) {
11177 self.manipulate_text(window, cx, |text| text.to_lowercase())
11178 }
11179
11180 pub fn convert_to_title_case(
11181 &mut self,
11182 _: &ConvertToTitleCase,
11183 window: &mut Window,
11184 cx: &mut Context<Self>,
11185 ) {
11186 self.manipulate_text(window, cx, |text| {
11187 text.split('\n')
11188 .map(|line| line.to_case(Case::Title))
11189 .join("\n")
11190 })
11191 }
11192
11193 pub fn convert_to_snake_case(
11194 &mut self,
11195 _: &ConvertToSnakeCase,
11196 window: &mut Window,
11197 cx: &mut Context<Self>,
11198 ) {
11199 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11200 }
11201
11202 pub fn convert_to_kebab_case(
11203 &mut self,
11204 _: &ConvertToKebabCase,
11205 window: &mut Window,
11206 cx: &mut Context<Self>,
11207 ) {
11208 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11209 }
11210
11211 pub fn convert_to_upper_camel_case(
11212 &mut self,
11213 _: &ConvertToUpperCamelCase,
11214 window: &mut Window,
11215 cx: &mut Context<Self>,
11216 ) {
11217 self.manipulate_text(window, cx, |text| {
11218 text.split('\n')
11219 .map(|line| line.to_case(Case::UpperCamel))
11220 .join("\n")
11221 })
11222 }
11223
11224 pub fn convert_to_lower_camel_case(
11225 &mut self,
11226 _: &ConvertToLowerCamelCase,
11227 window: &mut Window,
11228 cx: &mut Context<Self>,
11229 ) {
11230 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11231 }
11232
11233 pub fn convert_to_opposite_case(
11234 &mut self,
11235 _: &ConvertToOppositeCase,
11236 window: &mut Window,
11237 cx: &mut Context<Self>,
11238 ) {
11239 self.manipulate_text(window, cx, |text| {
11240 text.chars()
11241 .fold(String::with_capacity(text.len()), |mut t, c| {
11242 if c.is_uppercase() {
11243 t.extend(c.to_lowercase());
11244 } else {
11245 t.extend(c.to_uppercase());
11246 }
11247 t
11248 })
11249 })
11250 }
11251
11252 pub fn convert_to_sentence_case(
11253 &mut self,
11254 _: &ConvertToSentenceCase,
11255 window: &mut Window,
11256 cx: &mut Context<Self>,
11257 ) {
11258 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11259 }
11260
11261 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11262 self.manipulate_text(window, cx, |text| {
11263 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11264 if has_upper_case_characters {
11265 text.to_lowercase()
11266 } else {
11267 text.to_uppercase()
11268 }
11269 })
11270 }
11271
11272 pub fn convert_to_rot13(
11273 &mut self,
11274 _: &ConvertToRot13,
11275 window: &mut Window,
11276 cx: &mut Context<Self>,
11277 ) {
11278 self.manipulate_text(window, cx, |text| {
11279 text.chars()
11280 .map(|c| match c {
11281 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11282 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11283 _ => c,
11284 })
11285 .collect()
11286 })
11287 }
11288
11289 pub fn convert_to_rot47(
11290 &mut self,
11291 _: &ConvertToRot47,
11292 window: &mut Window,
11293 cx: &mut Context<Self>,
11294 ) {
11295 self.manipulate_text(window, cx, |text| {
11296 text.chars()
11297 .map(|c| {
11298 let code_point = c as u32;
11299 if code_point >= 33 && code_point <= 126 {
11300 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11301 }
11302 c
11303 })
11304 .collect()
11305 })
11306 }
11307
11308 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11309 where
11310 Fn: FnMut(&str) -> String,
11311 {
11312 let buffer = self.buffer.read(cx).snapshot(cx);
11313
11314 let mut new_selections = Vec::new();
11315 let mut edits = Vec::new();
11316 let mut selection_adjustment = 0i32;
11317
11318 for selection in self.selections.all::<usize>(cx) {
11319 let selection_is_empty = selection.is_empty();
11320
11321 let (start, end) = if selection_is_empty {
11322 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11323 (word_range.start, word_range.end)
11324 } else {
11325 (selection.start, selection.end)
11326 };
11327
11328 let text = buffer.text_for_range(start..end).collect::<String>();
11329 let old_length = text.len() as i32;
11330 let text = callback(&text);
11331
11332 new_selections.push(Selection {
11333 start: (start as i32 - selection_adjustment) as usize,
11334 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11335 goal: SelectionGoal::None,
11336 ..selection
11337 });
11338
11339 selection_adjustment += old_length - text.len() as i32;
11340
11341 edits.push((start..end, text));
11342 }
11343
11344 self.transact(window, cx, |this, window, cx| {
11345 this.buffer.update(cx, |buffer, cx| {
11346 buffer.edit(edits, None, cx);
11347 });
11348
11349 this.change_selections(Default::default(), window, cx, |s| {
11350 s.select(new_selections);
11351 });
11352
11353 this.request_autoscroll(Autoscroll::fit(), cx);
11354 });
11355 }
11356
11357 pub fn move_selection_on_drop(
11358 &mut self,
11359 selection: &Selection<Anchor>,
11360 target: DisplayPoint,
11361 is_cut: bool,
11362 window: &mut Window,
11363 cx: &mut Context<Self>,
11364 ) {
11365 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11366 let buffer = &display_map.buffer_snapshot;
11367 let mut edits = Vec::new();
11368 let insert_point = display_map
11369 .clip_point(target, Bias::Left)
11370 .to_point(&display_map);
11371 let text = buffer
11372 .text_for_range(selection.start..selection.end)
11373 .collect::<String>();
11374 if is_cut {
11375 edits.push(((selection.start..selection.end), String::new()));
11376 }
11377 let insert_anchor = buffer.anchor_before(insert_point);
11378 edits.push(((insert_anchor..insert_anchor), text));
11379 let last_edit_start = insert_anchor.bias_left(buffer);
11380 let last_edit_end = insert_anchor.bias_right(buffer);
11381 self.transact(window, cx, |this, window, cx| {
11382 this.buffer.update(cx, |buffer, cx| {
11383 buffer.edit(edits, None, cx);
11384 });
11385 this.change_selections(Default::default(), window, cx, |s| {
11386 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11387 });
11388 });
11389 }
11390
11391 pub fn clear_selection_drag_state(&mut self) {
11392 self.selection_drag_state = SelectionDragState::None;
11393 }
11394
11395 pub fn duplicate(
11396 &mut self,
11397 upwards: bool,
11398 whole_lines: bool,
11399 window: &mut Window,
11400 cx: &mut Context<Self>,
11401 ) {
11402 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11403
11404 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11405 let buffer = &display_map.buffer_snapshot;
11406 let selections = self.selections.all::<Point>(cx);
11407
11408 let mut edits = Vec::new();
11409 let mut selections_iter = selections.iter().peekable();
11410 while let Some(selection) = selections_iter.next() {
11411 let mut rows = selection.spanned_rows(false, &display_map);
11412 // duplicate line-wise
11413 if whole_lines || selection.start == selection.end {
11414 // Avoid duplicating the same lines twice.
11415 while let Some(next_selection) = selections_iter.peek() {
11416 let next_rows = next_selection.spanned_rows(false, &display_map);
11417 if next_rows.start < rows.end {
11418 rows.end = next_rows.end;
11419 selections_iter.next().unwrap();
11420 } else {
11421 break;
11422 }
11423 }
11424
11425 // Copy the text from the selected row region and splice it either at the start
11426 // or end of the region.
11427 let start = Point::new(rows.start.0, 0);
11428 let end = Point::new(
11429 rows.end.previous_row().0,
11430 buffer.line_len(rows.end.previous_row()),
11431 );
11432 let text = buffer
11433 .text_for_range(start..end)
11434 .chain(Some("\n"))
11435 .collect::<String>();
11436 let insert_location = if upwards {
11437 Point::new(rows.end.0, 0)
11438 } else {
11439 start
11440 };
11441 edits.push((insert_location..insert_location, text));
11442 } else {
11443 // duplicate character-wise
11444 let start = selection.start;
11445 let end = selection.end;
11446 let text = buffer.text_for_range(start..end).collect::<String>();
11447 edits.push((selection.end..selection.end, text));
11448 }
11449 }
11450
11451 self.transact(window, cx, |this, _, cx| {
11452 this.buffer.update(cx, |buffer, cx| {
11453 buffer.edit(edits, None, cx);
11454 });
11455
11456 this.request_autoscroll(Autoscroll::fit(), cx);
11457 });
11458 }
11459
11460 pub fn duplicate_line_up(
11461 &mut self,
11462 _: &DuplicateLineUp,
11463 window: &mut Window,
11464 cx: &mut Context<Self>,
11465 ) {
11466 self.duplicate(true, true, window, cx);
11467 }
11468
11469 pub fn duplicate_line_down(
11470 &mut self,
11471 _: &DuplicateLineDown,
11472 window: &mut Window,
11473 cx: &mut Context<Self>,
11474 ) {
11475 self.duplicate(false, true, window, cx);
11476 }
11477
11478 pub fn duplicate_selection(
11479 &mut self,
11480 _: &DuplicateSelection,
11481 window: &mut Window,
11482 cx: &mut Context<Self>,
11483 ) {
11484 self.duplicate(false, false, window, cx);
11485 }
11486
11487 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11488 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11489 if self.mode.is_single_line() {
11490 cx.propagate();
11491 return;
11492 }
11493
11494 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11495 let buffer = self.buffer.read(cx).snapshot(cx);
11496
11497 let mut edits = Vec::new();
11498 let mut unfold_ranges = Vec::new();
11499 let mut refold_creases = Vec::new();
11500
11501 let selections = self.selections.all::<Point>(cx);
11502 let mut selections = selections.iter().peekable();
11503 let mut contiguous_row_selections = Vec::new();
11504 let mut new_selections = Vec::new();
11505
11506 while let Some(selection) = selections.next() {
11507 // Find all the selections that span a contiguous row range
11508 let (start_row, end_row) = consume_contiguous_rows(
11509 &mut contiguous_row_selections,
11510 selection,
11511 &display_map,
11512 &mut selections,
11513 );
11514
11515 // Move the text spanned by the row range to be before the line preceding the row range
11516 if start_row.0 > 0 {
11517 let range_to_move = Point::new(
11518 start_row.previous_row().0,
11519 buffer.line_len(start_row.previous_row()),
11520 )
11521 ..Point::new(
11522 end_row.previous_row().0,
11523 buffer.line_len(end_row.previous_row()),
11524 );
11525 let insertion_point = display_map
11526 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11527 .0;
11528
11529 // Don't move lines across excerpts
11530 if buffer
11531 .excerpt_containing(insertion_point..range_to_move.end)
11532 .is_some()
11533 {
11534 let text = buffer
11535 .text_for_range(range_to_move.clone())
11536 .flat_map(|s| s.chars())
11537 .skip(1)
11538 .chain(['\n'])
11539 .collect::<String>();
11540
11541 edits.push((
11542 buffer.anchor_after(range_to_move.start)
11543 ..buffer.anchor_before(range_to_move.end),
11544 String::new(),
11545 ));
11546 let insertion_anchor = buffer.anchor_after(insertion_point);
11547 edits.push((insertion_anchor..insertion_anchor, text));
11548
11549 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11550
11551 // Move selections up
11552 new_selections.extend(contiguous_row_selections.drain(..).map(
11553 |mut selection| {
11554 selection.start.row -= row_delta;
11555 selection.end.row -= row_delta;
11556 selection
11557 },
11558 ));
11559
11560 // Move folds up
11561 unfold_ranges.push(range_to_move.clone());
11562 for fold in display_map.folds_in_range(
11563 buffer.anchor_before(range_to_move.start)
11564 ..buffer.anchor_after(range_to_move.end),
11565 ) {
11566 let mut start = fold.range.start.to_point(&buffer);
11567 let mut end = fold.range.end.to_point(&buffer);
11568 start.row -= row_delta;
11569 end.row -= row_delta;
11570 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11571 }
11572 }
11573 }
11574
11575 // If we didn't move line(s), preserve the existing selections
11576 new_selections.append(&mut contiguous_row_selections);
11577 }
11578
11579 self.transact(window, cx, |this, window, cx| {
11580 this.unfold_ranges(&unfold_ranges, true, true, cx);
11581 this.buffer.update(cx, |buffer, cx| {
11582 for (range, text) in edits {
11583 buffer.edit([(range, text)], None, cx);
11584 }
11585 });
11586 this.fold_creases(refold_creases, true, window, cx);
11587 this.change_selections(Default::default(), window, cx, |s| {
11588 s.select(new_selections);
11589 })
11590 });
11591 }
11592
11593 pub fn move_line_down(
11594 &mut self,
11595 _: &MoveLineDown,
11596 window: &mut Window,
11597 cx: &mut Context<Self>,
11598 ) {
11599 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11600 if self.mode.is_single_line() {
11601 cx.propagate();
11602 return;
11603 }
11604
11605 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11606 let buffer = self.buffer.read(cx).snapshot(cx);
11607
11608 let mut edits = Vec::new();
11609 let mut unfold_ranges = Vec::new();
11610 let mut refold_creases = Vec::new();
11611
11612 let selections = self.selections.all::<Point>(cx);
11613 let mut selections = selections.iter().peekable();
11614 let mut contiguous_row_selections = Vec::new();
11615 let mut new_selections = Vec::new();
11616
11617 while let Some(selection) = selections.next() {
11618 // Find all the selections that span a contiguous row range
11619 let (start_row, end_row) = consume_contiguous_rows(
11620 &mut contiguous_row_selections,
11621 selection,
11622 &display_map,
11623 &mut selections,
11624 );
11625
11626 // Move the text spanned by the row range to be after the last line of the row range
11627 if end_row.0 <= buffer.max_point().row {
11628 let range_to_move =
11629 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11630 let insertion_point = display_map
11631 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11632 .0;
11633
11634 // Don't move lines across excerpt boundaries
11635 if buffer
11636 .excerpt_containing(range_to_move.start..insertion_point)
11637 .is_some()
11638 {
11639 let mut text = String::from("\n");
11640 text.extend(buffer.text_for_range(range_to_move.clone()));
11641 text.pop(); // Drop trailing newline
11642 edits.push((
11643 buffer.anchor_after(range_to_move.start)
11644 ..buffer.anchor_before(range_to_move.end),
11645 String::new(),
11646 ));
11647 let insertion_anchor = buffer.anchor_after(insertion_point);
11648 edits.push((insertion_anchor..insertion_anchor, text));
11649
11650 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11651
11652 // Move selections down
11653 new_selections.extend(contiguous_row_selections.drain(..).map(
11654 |mut selection| {
11655 selection.start.row += row_delta;
11656 selection.end.row += row_delta;
11657 selection
11658 },
11659 ));
11660
11661 // Move folds down
11662 unfold_ranges.push(range_to_move.clone());
11663 for fold in display_map.folds_in_range(
11664 buffer.anchor_before(range_to_move.start)
11665 ..buffer.anchor_after(range_to_move.end),
11666 ) {
11667 let mut start = fold.range.start.to_point(&buffer);
11668 let mut end = fold.range.end.to_point(&buffer);
11669 start.row += row_delta;
11670 end.row += row_delta;
11671 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11672 }
11673 }
11674 }
11675
11676 // If we didn't move line(s), preserve the existing selections
11677 new_selections.append(&mut contiguous_row_selections);
11678 }
11679
11680 self.transact(window, cx, |this, window, cx| {
11681 this.unfold_ranges(&unfold_ranges, true, true, cx);
11682 this.buffer.update(cx, |buffer, cx| {
11683 for (range, text) in edits {
11684 buffer.edit([(range, text)], None, cx);
11685 }
11686 });
11687 this.fold_creases(refold_creases, true, window, cx);
11688 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11689 });
11690 }
11691
11692 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11693 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11694 let text_layout_details = &self.text_layout_details(window);
11695 self.transact(window, cx, |this, window, cx| {
11696 let edits = this.change_selections(Default::default(), window, cx, |s| {
11697 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11698 s.move_with(|display_map, selection| {
11699 if !selection.is_empty() {
11700 return;
11701 }
11702
11703 let mut head = selection.head();
11704 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11705 if head.column() == display_map.line_len(head.row()) {
11706 transpose_offset = display_map
11707 .buffer_snapshot
11708 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11709 }
11710
11711 if transpose_offset == 0 {
11712 return;
11713 }
11714
11715 *head.column_mut() += 1;
11716 head = display_map.clip_point(head, Bias::Right);
11717 let goal = SelectionGoal::HorizontalPosition(
11718 display_map
11719 .x_for_display_point(head, text_layout_details)
11720 .into(),
11721 );
11722 selection.collapse_to(head, goal);
11723
11724 let transpose_start = display_map
11725 .buffer_snapshot
11726 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11727 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11728 let transpose_end = display_map
11729 .buffer_snapshot
11730 .clip_offset(transpose_offset + 1, Bias::Right);
11731 if let Some(ch) =
11732 display_map.buffer_snapshot.chars_at(transpose_start).next()
11733 {
11734 edits.push((transpose_start..transpose_offset, String::new()));
11735 edits.push((transpose_end..transpose_end, ch.to_string()));
11736 }
11737 }
11738 });
11739 edits
11740 });
11741 this.buffer
11742 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11743 let selections = this.selections.all::<usize>(cx);
11744 this.change_selections(Default::default(), window, cx, |s| {
11745 s.select(selections);
11746 });
11747 });
11748 }
11749
11750 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11751 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11752 if self.mode.is_single_line() {
11753 cx.propagate();
11754 return;
11755 }
11756
11757 self.rewrap_impl(RewrapOptions::default(), cx)
11758 }
11759
11760 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11761 let buffer = self.buffer.read(cx).snapshot(cx);
11762 let selections = self.selections.all::<Point>(cx);
11763
11764 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11765 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11766 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11767 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11768 .peekable();
11769
11770 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11771 row
11772 } else {
11773 return Vec::new();
11774 };
11775
11776 let language_settings = buffer.language_settings_at(selection.head(), cx);
11777 let language_scope = buffer.language_scope_at(selection.head());
11778
11779 let indent_and_prefix_for_row =
11780 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11781 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11782 let (comment_prefix, rewrap_prefix) =
11783 if let Some(language_scope) = &language_scope {
11784 let indent_end = Point::new(row, indent.len);
11785 let comment_prefix = language_scope
11786 .line_comment_prefixes()
11787 .iter()
11788 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11789 .map(|prefix| prefix.to_string());
11790 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11791 let line_text_after_indent = buffer
11792 .text_for_range(indent_end..line_end)
11793 .collect::<String>();
11794 let rewrap_prefix = language_scope
11795 .rewrap_prefixes()
11796 .iter()
11797 .find_map(|prefix_regex| {
11798 prefix_regex.find(&line_text_after_indent).map(|mat| {
11799 if mat.start() == 0 {
11800 Some(mat.as_str().to_string())
11801 } else {
11802 None
11803 }
11804 })
11805 })
11806 .flatten();
11807 (comment_prefix, rewrap_prefix)
11808 } else {
11809 (None, None)
11810 };
11811 (indent, comment_prefix, rewrap_prefix)
11812 };
11813
11814 let mut ranges = Vec::new();
11815 let from_empty_selection = selection.is_empty();
11816
11817 let mut current_range_start = first_row;
11818 let mut prev_row = first_row;
11819 let (
11820 mut current_range_indent,
11821 mut current_range_comment_prefix,
11822 mut current_range_rewrap_prefix,
11823 ) = indent_and_prefix_for_row(first_row);
11824
11825 for row in non_blank_rows_iter.skip(1) {
11826 let has_paragraph_break = row > prev_row + 1;
11827
11828 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11829 indent_and_prefix_for_row(row);
11830
11831 let has_indent_change = row_indent != current_range_indent;
11832 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11833
11834 let has_boundary_change = has_comment_change
11835 || row_rewrap_prefix.is_some()
11836 || (has_indent_change && current_range_comment_prefix.is_some());
11837
11838 if has_paragraph_break || has_boundary_change {
11839 ranges.push((
11840 language_settings.clone(),
11841 Point::new(current_range_start, 0)
11842 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11843 current_range_indent,
11844 current_range_comment_prefix.clone(),
11845 current_range_rewrap_prefix.clone(),
11846 from_empty_selection,
11847 ));
11848 current_range_start = row;
11849 current_range_indent = row_indent;
11850 current_range_comment_prefix = row_comment_prefix;
11851 current_range_rewrap_prefix = row_rewrap_prefix;
11852 }
11853 prev_row = row;
11854 }
11855
11856 ranges.push((
11857 language_settings.clone(),
11858 Point::new(current_range_start, 0)
11859 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11860 current_range_indent,
11861 current_range_comment_prefix,
11862 current_range_rewrap_prefix,
11863 from_empty_selection,
11864 ));
11865
11866 ranges
11867 });
11868
11869 let mut edits = Vec::new();
11870 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11871
11872 for (
11873 language_settings,
11874 wrap_range,
11875 indent_size,
11876 comment_prefix,
11877 rewrap_prefix,
11878 from_empty_selection,
11879 ) in wrap_ranges
11880 {
11881 let mut start_row = wrap_range.start.row;
11882 let mut end_row = wrap_range.end.row;
11883
11884 // Skip selections that overlap with a range that has already been rewrapped.
11885 let selection_range = start_row..end_row;
11886 if rewrapped_row_ranges
11887 .iter()
11888 .any(|range| range.overlaps(&selection_range))
11889 {
11890 continue;
11891 }
11892
11893 let tab_size = language_settings.tab_size;
11894
11895 let indent_prefix = indent_size.chars().collect::<String>();
11896 let mut line_prefix = indent_prefix.clone();
11897 let mut inside_comment = false;
11898 if let Some(prefix) = &comment_prefix {
11899 line_prefix.push_str(prefix);
11900 inside_comment = true;
11901 }
11902 if let Some(prefix) = &rewrap_prefix {
11903 line_prefix.push_str(prefix);
11904 }
11905
11906 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11907 RewrapBehavior::InComments => inside_comment,
11908 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11909 RewrapBehavior::Anywhere => true,
11910 };
11911
11912 let should_rewrap = options.override_language_settings
11913 || allow_rewrap_based_on_language
11914 || self.hard_wrap.is_some();
11915 if !should_rewrap {
11916 continue;
11917 }
11918
11919 if from_empty_selection {
11920 'expand_upwards: while start_row > 0 {
11921 let prev_row = start_row - 1;
11922 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11923 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11924 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11925 {
11926 start_row = prev_row;
11927 } else {
11928 break 'expand_upwards;
11929 }
11930 }
11931
11932 'expand_downwards: while end_row < buffer.max_point().row {
11933 let next_row = end_row + 1;
11934 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11935 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11936 && !buffer.is_line_blank(MultiBufferRow(next_row))
11937 {
11938 end_row = next_row;
11939 } else {
11940 break 'expand_downwards;
11941 }
11942 }
11943 }
11944
11945 let start = Point::new(start_row, 0);
11946 let start_offset = start.to_offset(&buffer);
11947 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11948 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11949 let Some(lines_without_prefixes) = selection_text
11950 .lines()
11951 .enumerate()
11952 .map(|(ix, line)| {
11953 let line_trimmed = line.trim_start();
11954 if rewrap_prefix.is_some() && ix > 0 {
11955 Ok(line_trimmed)
11956 } else {
11957 line_trimmed
11958 .strip_prefix(&line_prefix.trim_start())
11959 .with_context(|| {
11960 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11961 })
11962 }
11963 })
11964 .collect::<Result<Vec<_>, _>>()
11965 .log_err()
11966 else {
11967 continue;
11968 };
11969
11970 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11971 buffer
11972 .language_settings_at(Point::new(start_row, 0), cx)
11973 .preferred_line_length as usize
11974 });
11975
11976 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11977 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11978 } else {
11979 line_prefix.clone()
11980 };
11981
11982 let wrapped_text = wrap_with_prefix(
11983 line_prefix,
11984 subsequent_lines_prefix,
11985 lines_without_prefixes.join("\n"),
11986 wrap_column,
11987 tab_size,
11988 options.preserve_existing_whitespace,
11989 );
11990
11991 // TODO: should always use char-based diff while still supporting cursor behavior that
11992 // matches vim.
11993 let mut diff_options = DiffOptions::default();
11994 if options.override_language_settings {
11995 diff_options.max_word_diff_len = 0;
11996 diff_options.max_word_diff_line_count = 0;
11997 } else {
11998 diff_options.max_word_diff_len = usize::MAX;
11999 diff_options.max_word_diff_line_count = usize::MAX;
12000 }
12001
12002 for (old_range, new_text) in
12003 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12004 {
12005 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12006 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12007 edits.push((edit_start..edit_end, new_text));
12008 }
12009
12010 rewrapped_row_ranges.push(start_row..=end_row);
12011 }
12012
12013 self.buffer
12014 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12015 }
12016
12017 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
12018 let mut text = String::new();
12019 let buffer = self.buffer.read(cx).snapshot(cx);
12020 let mut selections = self.selections.all::<Point>(cx);
12021 let mut clipboard_selections = Vec::with_capacity(selections.len());
12022 {
12023 let max_point = buffer.max_point();
12024 let mut is_first = true;
12025 for selection in &mut selections {
12026 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12027 if is_entire_line {
12028 selection.start = Point::new(selection.start.row, 0);
12029 if !selection.is_empty() && selection.end.column == 0 {
12030 selection.end = cmp::min(max_point, selection.end);
12031 } else {
12032 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12033 }
12034 selection.goal = SelectionGoal::None;
12035 }
12036 if is_first {
12037 is_first = false;
12038 } else {
12039 text += "\n";
12040 }
12041 let mut len = 0;
12042 for chunk in buffer.text_for_range(selection.start..selection.end) {
12043 text.push_str(chunk);
12044 len += chunk.len();
12045 }
12046 clipboard_selections.push(ClipboardSelection {
12047 len,
12048 is_entire_line,
12049 first_line_indent: buffer
12050 .indent_size_for_line(MultiBufferRow(selection.start.row))
12051 .len,
12052 });
12053 }
12054 }
12055
12056 self.transact(window, cx, |this, window, cx| {
12057 this.change_selections(Default::default(), window, cx, |s| {
12058 s.select(selections);
12059 });
12060 this.insert("", window, cx);
12061 });
12062 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12063 }
12064
12065 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12066 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12067 let item = self.cut_common(window, cx);
12068 cx.write_to_clipboard(item);
12069 }
12070
12071 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12072 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12073 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12074 s.move_with(|snapshot, sel| {
12075 if sel.is_empty() {
12076 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12077 }
12078 });
12079 });
12080 let item = self.cut_common(window, cx);
12081 cx.set_global(KillRing(item))
12082 }
12083
12084 pub fn kill_ring_yank(
12085 &mut self,
12086 _: &KillRingYank,
12087 window: &mut Window,
12088 cx: &mut Context<Self>,
12089 ) {
12090 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12091 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12092 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12093 (kill_ring.text().to_string(), kill_ring.metadata_json())
12094 } else {
12095 return;
12096 }
12097 } else {
12098 return;
12099 };
12100 self.do_paste(&text, metadata, false, window, cx);
12101 }
12102
12103 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12104 self.do_copy(true, cx);
12105 }
12106
12107 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12108 self.do_copy(false, cx);
12109 }
12110
12111 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12112 let selections = self.selections.all::<Point>(cx);
12113 let buffer = self.buffer.read(cx).read(cx);
12114 let mut text = String::new();
12115
12116 let mut clipboard_selections = Vec::with_capacity(selections.len());
12117 {
12118 let max_point = buffer.max_point();
12119 let mut is_first = true;
12120 for selection in &selections {
12121 let mut start = selection.start;
12122 let mut end = selection.end;
12123 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12124 if is_entire_line {
12125 start = Point::new(start.row, 0);
12126 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12127 }
12128
12129 let mut trimmed_selections = Vec::new();
12130 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12131 let row = MultiBufferRow(start.row);
12132 let first_indent = buffer.indent_size_for_line(row);
12133 if first_indent.len == 0 || start.column > first_indent.len {
12134 trimmed_selections.push(start..end);
12135 } else {
12136 trimmed_selections.push(
12137 Point::new(row.0, first_indent.len)
12138 ..Point::new(row.0, buffer.line_len(row)),
12139 );
12140 for row in start.row + 1..=end.row {
12141 let mut line_len = buffer.line_len(MultiBufferRow(row));
12142 if row == end.row {
12143 line_len = end.column;
12144 }
12145 if line_len == 0 {
12146 trimmed_selections
12147 .push(Point::new(row, 0)..Point::new(row, line_len));
12148 continue;
12149 }
12150 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12151 if row_indent_size.len >= first_indent.len {
12152 trimmed_selections.push(
12153 Point::new(row, first_indent.len)..Point::new(row, line_len),
12154 );
12155 } else {
12156 trimmed_selections.clear();
12157 trimmed_selections.push(start..end);
12158 break;
12159 }
12160 }
12161 }
12162 } else {
12163 trimmed_selections.push(start..end);
12164 }
12165
12166 for trimmed_range in trimmed_selections {
12167 if is_first {
12168 is_first = false;
12169 } else {
12170 text += "\n";
12171 }
12172 let mut len = 0;
12173 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12174 text.push_str(chunk);
12175 len += chunk.len();
12176 }
12177 clipboard_selections.push(ClipboardSelection {
12178 len,
12179 is_entire_line,
12180 first_line_indent: buffer
12181 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12182 .len,
12183 });
12184 }
12185 }
12186 }
12187
12188 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12189 text,
12190 clipboard_selections,
12191 ));
12192 }
12193
12194 pub fn do_paste(
12195 &mut self,
12196 text: &String,
12197 clipboard_selections: Option<Vec<ClipboardSelection>>,
12198 handle_entire_lines: bool,
12199 window: &mut Window,
12200 cx: &mut Context<Self>,
12201 ) {
12202 if self.read_only(cx) {
12203 return;
12204 }
12205
12206 let clipboard_text = Cow::Borrowed(text);
12207
12208 self.transact(window, cx, |this, window, cx| {
12209 let had_active_edit_prediction = this.has_active_edit_prediction();
12210
12211 if let Some(mut clipboard_selections) = clipboard_selections {
12212 let old_selections = this.selections.all::<usize>(cx);
12213 let all_selections_were_entire_line =
12214 clipboard_selections.iter().all(|s| s.is_entire_line);
12215 let first_selection_indent_column =
12216 clipboard_selections.first().map(|s| s.first_line_indent);
12217 if clipboard_selections.len() != old_selections.len() {
12218 clipboard_selections.drain(..);
12219 }
12220 let cursor_offset = this.selections.last::<usize>(cx).head();
12221 let mut auto_indent_on_paste = true;
12222
12223 this.buffer.update(cx, |buffer, cx| {
12224 let snapshot = buffer.read(cx);
12225 auto_indent_on_paste = snapshot
12226 .language_settings_at(cursor_offset, cx)
12227 .auto_indent_on_paste;
12228
12229 let mut start_offset = 0;
12230 let mut edits = Vec::new();
12231 let mut original_indent_columns = Vec::new();
12232 for (ix, selection) in old_selections.iter().enumerate() {
12233 let to_insert;
12234 let entire_line;
12235 let original_indent_column;
12236 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12237 let end_offset = start_offset + clipboard_selection.len;
12238 to_insert = &clipboard_text[start_offset..end_offset];
12239 entire_line = clipboard_selection.is_entire_line;
12240 start_offset = end_offset + 1;
12241 original_indent_column = Some(clipboard_selection.first_line_indent);
12242 } else {
12243 to_insert = clipboard_text.as_str();
12244 entire_line = all_selections_were_entire_line;
12245 original_indent_column = first_selection_indent_column
12246 }
12247
12248 // If the corresponding selection was empty when this slice of the
12249 // clipboard text was written, then the entire line containing the
12250 // selection was copied. If this selection is also currently empty,
12251 // then paste the line before the current line of the buffer.
12252 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12253 let column = selection.start.to_point(&snapshot).column as usize;
12254 let line_start = selection.start - column;
12255 line_start..line_start
12256 } else {
12257 selection.range()
12258 };
12259
12260 edits.push((range, to_insert));
12261 original_indent_columns.push(original_indent_column);
12262 }
12263 drop(snapshot);
12264
12265 buffer.edit(
12266 edits,
12267 if auto_indent_on_paste {
12268 Some(AutoindentMode::Block {
12269 original_indent_columns,
12270 })
12271 } else {
12272 None
12273 },
12274 cx,
12275 );
12276 });
12277
12278 let selections = this.selections.all::<usize>(cx);
12279 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12280 } else {
12281 this.insert(&clipboard_text, window, cx);
12282 }
12283
12284 let trigger_in_words =
12285 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12286
12287 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12288 });
12289 }
12290
12291 pub fn diff_clipboard_with_selection(
12292 &mut self,
12293 _: &DiffClipboardWithSelection,
12294 window: &mut Window,
12295 cx: &mut Context<Self>,
12296 ) {
12297 let selections = self.selections.all::<usize>(cx);
12298
12299 if selections.is_empty() {
12300 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12301 return;
12302 };
12303
12304 let clipboard_text = match cx.read_from_clipboard() {
12305 Some(item) => match item.entries().first() {
12306 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12307 _ => None,
12308 },
12309 None => None,
12310 };
12311
12312 let Some(clipboard_text) = clipboard_text else {
12313 log::warn!("Clipboard doesn't contain text.");
12314 return;
12315 };
12316
12317 window.dispatch_action(
12318 Box::new(DiffClipboardWithSelectionData {
12319 clipboard_text,
12320 editor: cx.entity(),
12321 }),
12322 cx,
12323 );
12324 }
12325
12326 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12327 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12328 if let Some(item) = cx.read_from_clipboard() {
12329 let entries = item.entries();
12330
12331 match entries.first() {
12332 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12333 // of all the pasted entries.
12334 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12335 .do_paste(
12336 clipboard_string.text(),
12337 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12338 true,
12339 window,
12340 cx,
12341 ),
12342 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12343 }
12344 }
12345 }
12346
12347 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12348 if self.read_only(cx) {
12349 return;
12350 }
12351
12352 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12353
12354 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12355 if let Some((selections, _)) =
12356 self.selection_history.transaction(transaction_id).cloned()
12357 {
12358 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12359 s.select_anchors(selections.to_vec());
12360 });
12361 } else {
12362 log::error!(
12363 "No entry in selection_history found for undo. \
12364 This may correspond to a bug where undo does not update the selection. \
12365 If this is occurring, please add details to \
12366 https://github.com/zed-industries/zed/issues/22692"
12367 );
12368 }
12369 self.request_autoscroll(Autoscroll::fit(), cx);
12370 self.unmark_text(window, cx);
12371 self.refresh_edit_prediction(true, false, window, cx);
12372 cx.emit(EditorEvent::Edited { transaction_id });
12373 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12374 }
12375 }
12376
12377 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12378 if self.read_only(cx) {
12379 return;
12380 }
12381
12382 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12383
12384 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12385 if let Some((_, Some(selections))) =
12386 self.selection_history.transaction(transaction_id).cloned()
12387 {
12388 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12389 s.select_anchors(selections.to_vec());
12390 });
12391 } else {
12392 log::error!(
12393 "No entry in selection_history found for redo. \
12394 This may correspond to a bug where undo does not update the selection. \
12395 If this is occurring, please add details to \
12396 https://github.com/zed-industries/zed/issues/22692"
12397 );
12398 }
12399 self.request_autoscroll(Autoscroll::fit(), cx);
12400 self.unmark_text(window, cx);
12401 self.refresh_edit_prediction(true, false, window, cx);
12402 cx.emit(EditorEvent::Edited { transaction_id });
12403 }
12404 }
12405
12406 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12407 self.buffer
12408 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12409 }
12410
12411 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12412 self.buffer
12413 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12414 }
12415
12416 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12417 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12418 self.change_selections(Default::default(), window, cx, |s| {
12419 s.move_with(|map, selection| {
12420 let cursor = if selection.is_empty() {
12421 movement::left(map, selection.start)
12422 } else {
12423 selection.start
12424 };
12425 selection.collapse_to(cursor, SelectionGoal::None);
12426 });
12427 })
12428 }
12429
12430 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12431 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12432 self.change_selections(Default::default(), window, cx, |s| {
12433 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12434 })
12435 }
12436
12437 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12439 self.change_selections(Default::default(), window, cx, |s| {
12440 s.move_with(|map, selection| {
12441 let cursor = if selection.is_empty() {
12442 movement::right(map, selection.end)
12443 } else {
12444 selection.end
12445 };
12446 selection.collapse_to(cursor, SelectionGoal::None)
12447 });
12448 })
12449 }
12450
12451 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12453 self.change_selections(Default::default(), window, cx, |s| {
12454 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12455 })
12456 }
12457
12458 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12459 if self.take_rename(true, window, cx).is_some() {
12460 return;
12461 }
12462
12463 if self.mode.is_single_line() {
12464 cx.propagate();
12465 return;
12466 }
12467
12468 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12469
12470 let text_layout_details = &self.text_layout_details(window);
12471 let selection_count = self.selections.count();
12472 let first_selection = self.selections.first_anchor();
12473
12474 self.change_selections(Default::default(), window, cx, |s| {
12475 s.move_with(|map, selection| {
12476 if !selection.is_empty() {
12477 selection.goal = SelectionGoal::None;
12478 }
12479 let (cursor, goal) = movement::up(
12480 map,
12481 selection.start,
12482 selection.goal,
12483 false,
12484 text_layout_details,
12485 );
12486 selection.collapse_to(cursor, goal);
12487 });
12488 });
12489
12490 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12491 {
12492 cx.propagate();
12493 }
12494 }
12495
12496 pub fn move_up_by_lines(
12497 &mut self,
12498 action: &MoveUpByLines,
12499 window: &mut Window,
12500 cx: &mut Context<Self>,
12501 ) {
12502 if self.take_rename(true, window, cx).is_some() {
12503 return;
12504 }
12505
12506 if self.mode.is_single_line() {
12507 cx.propagate();
12508 return;
12509 }
12510
12511 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12512
12513 let text_layout_details = &self.text_layout_details(window);
12514
12515 self.change_selections(Default::default(), window, cx, |s| {
12516 s.move_with(|map, selection| {
12517 if !selection.is_empty() {
12518 selection.goal = SelectionGoal::None;
12519 }
12520 let (cursor, goal) = movement::up_by_rows(
12521 map,
12522 selection.start,
12523 action.lines,
12524 selection.goal,
12525 false,
12526 text_layout_details,
12527 );
12528 selection.collapse_to(cursor, goal);
12529 });
12530 })
12531 }
12532
12533 pub fn move_down_by_lines(
12534 &mut self,
12535 action: &MoveDownByLines,
12536 window: &mut Window,
12537 cx: &mut Context<Self>,
12538 ) {
12539 if self.take_rename(true, window, cx).is_some() {
12540 return;
12541 }
12542
12543 if self.mode.is_single_line() {
12544 cx.propagate();
12545 return;
12546 }
12547
12548 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12549
12550 let text_layout_details = &self.text_layout_details(window);
12551
12552 self.change_selections(Default::default(), window, cx, |s| {
12553 s.move_with(|map, selection| {
12554 if !selection.is_empty() {
12555 selection.goal = SelectionGoal::None;
12556 }
12557 let (cursor, goal) = movement::down_by_rows(
12558 map,
12559 selection.start,
12560 action.lines,
12561 selection.goal,
12562 false,
12563 text_layout_details,
12564 );
12565 selection.collapse_to(cursor, goal);
12566 });
12567 })
12568 }
12569
12570 pub fn select_down_by_lines(
12571 &mut self,
12572 action: &SelectDownByLines,
12573 window: &mut Window,
12574 cx: &mut Context<Self>,
12575 ) {
12576 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12577 let text_layout_details = &self.text_layout_details(window);
12578 self.change_selections(Default::default(), window, cx, |s| {
12579 s.move_heads_with(|map, head, goal| {
12580 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12581 })
12582 })
12583 }
12584
12585 pub fn select_up_by_lines(
12586 &mut self,
12587 action: &SelectUpByLines,
12588 window: &mut Window,
12589 cx: &mut Context<Self>,
12590 ) {
12591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12592 let text_layout_details = &self.text_layout_details(window);
12593 self.change_selections(Default::default(), window, cx, |s| {
12594 s.move_heads_with(|map, head, goal| {
12595 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12596 })
12597 })
12598 }
12599
12600 pub fn select_page_up(
12601 &mut self,
12602 _: &SelectPageUp,
12603 window: &mut Window,
12604 cx: &mut Context<Self>,
12605 ) {
12606 let Some(row_count) = self.visible_row_count() else {
12607 return;
12608 };
12609
12610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12611
12612 let text_layout_details = &self.text_layout_details(window);
12613
12614 self.change_selections(Default::default(), window, cx, |s| {
12615 s.move_heads_with(|map, head, goal| {
12616 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12617 })
12618 })
12619 }
12620
12621 pub fn move_page_up(
12622 &mut self,
12623 action: &MovePageUp,
12624 window: &mut Window,
12625 cx: &mut Context<Self>,
12626 ) {
12627 if self.take_rename(true, window, cx).is_some() {
12628 return;
12629 }
12630
12631 if self
12632 .context_menu
12633 .borrow_mut()
12634 .as_mut()
12635 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12636 .unwrap_or(false)
12637 {
12638 return;
12639 }
12640
12641 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12642 cx.propagate();
12643 return;
12644 }
12645
12646 let Some(row_count) = self.visible_row_count() else {
12647 return;
12648 };
12649
12650 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12651
12652 let effects = if action.center_cursor {
12653 SelectionEffects::scroll(Autoscroll::center())
12654 } else {
12655 SelectionEffects::default()
12656 };
12657
12658 let text_layout_details = &self.text_layout_details(window);
12659
12660 self.change_selections(effects, window, cx, |s| {
12661 s.move_with(|map, selection| {
12662 if !selection.is_empty() {
12663 selection.goal = SelectionGoal::None;
12664 }
12665 let (cursor, goal) = movement::up_by_rows(
12666 map,
12667 selection.end,
12668 row_count,
12669 selection.goal,
12670 false,
12671 text_layout_details,
12672 );
12673 selection.collapse_to(cursor, goal);
12674 });
12675 });
12676 }
12677
12678 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12680 let text_layout_details = &self.text_layout_details(window);
12681 self.change_selections(Default::default(), window, cx, |s| {
12682 s.move_heads_with(|map, head, goal| {
12683 movement::up(map, head, goal, false, text_layout_details)
12684 })
12685 })
12686 }
12687
12688 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12689 self.take_rename(true, window, cx);
12690
12691 if self.mode.is_single_line() {
12692 cx.propagate();
12693 return;
12694 }
12695
12696 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12697
12698 let text_layout_details = &self.text_layout_details(window);
12699 let selection_count = self.selections.count();
12700 let first_selection = self.selections.first_anchor();
12701
12702 self.change_selections(Default::default(), window, cx, |s| {
12703 s.move_with(|map, selection| {
12704 if !selection.is_empty() {
12705 selection.goal = SelectionGoal::None;
12706 }
12707 let (cursor, goal) = movement::down(
12708 map,
12709 selection.end,
12710 selection.goal,
12711 false,
12712 text_layout_details,
12713 );
12714 selection.collapse_to(cursor, goal);
12715 });
12716 });
12717
12718 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12719 {
12720 cx.propagate();
12721 }
12722 }
12723
12724 pub fn select_page_down(
12725 &mut self,
12726 _: &SelectPageDown,
12727 window: &mut Window,
12728 cx: &mut Context<Self>,
12729 ) {
12730 let Some(row_count) = self.visible_row_count() else {
12731 return;
12732 };
12733
12734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12735
12736 let text_layout_details = &self.text_layout_details(window);
12737
12738 self.change_selections(Default::default(), window, cx, |s| {
12739 s.move_heads_with(|map, head, goal| {
12740 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12741 })
12742 })
12743 }
12744
12745 pub fn move_page_down(
12746 &mut self,
12747 action: &MovePageDown,
12748 window: &mut Window,
12749 cx: &mut Context<Self>,
12750 ) {
12751 if self.take_rename(true, window, cx).is_some() {
12752 return;
12753 }
12754
12755 if self
12756 .context_menu
12757 .borrow_mut()
12758 .as_mut()
12759 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12760 .unwrap_or(false)
12761 {
12762 return;
12763 }
12764
12765 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12766 cx.propagate();
12767 return;
12768 }
12769
12770 let Some(row_count) = self.visible_row_count() else {
12771 return;
12772 };
12773
12774 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12775
12776 let effects = if action.center_cursor {
12777 SelectionEffects::scroll(Autoscroll::center())
12778 } else {
12779 SelectionEffects::default()
12780 };
12781
12782 let text_layout_details = &self.text_layout_details(window);
12783 self.change_selections(effects, window, cx, |s| {
12784 s.move_with(|map, selection| {
12785 if !selection.is_empty() {
12786 selection.goal = SelectionGoal::None;
12787 }
12788 let (cursor, goal) = movement::down_by_rows(
12789 map,
12790 selection.end,
12791 row_count,
12792 selection.goal,
12793 false,
12794 text_layout_details,
12795 );
12796 selection.collapse_to(cursor, goal);
12797 });
12798 });
12799 }
12800
12801 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12802 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12803 let text_layout_details = &self.text_layout_details(window);
12804 self.change_selections(Default::default(), window, cx, |s| {
12805 s.move_heads_with(|map, head, goal| {
12806 movement::down(map, head, goal, false, text_layout_details)
12807 })
12808 });
12809 }
12810
12811 pub fn context_menu_first(
12812 &mut self,
12813 _: &ContextMenuFirst,
12814 window: &mut Window,
12815 cx: &mut Context<Self>,
12816 ) {
12817 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12818 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12819 }
12820 }
12821
12822 pub fn context_menu_prev(
12823 &mut self,
12824 _: &ContextMenuPrevious,
12825 window: &mut Window,
12826 cx: &mut Context<Self>,
12827 ) {
12828 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12829 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12830 }
12831 }
12832
12833 pub fn context_menu_next(
12834 &mut self,
12835 _: &ContextMenuNext,
12836 window: &mut Window,
12837 cx: &mut Context<Self>,
12838 ) {
12839 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12840 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12841 }
12842 }
12843
12844 pub fn context_menu_last(
12845 &mut self,
12846 _: &ContextMenuLast,
12847 window: &mut Window,
12848 cx: &mut Context<Self>,
12849 ) {
12850 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12851 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12852 }
12853 }
12854
12855 pub fn signature_help_prev(
12856 &mut self,
12857 _: &SignatureHelpPrevious,
12858 _: &mut Window,
12859 cx: &mut Context<Self>,
12860 ) {
12861 if let Some(popover) = self.signature_help_state.popover_mut() {
12862 if popover.current_signature == 0 {
12863 popover.current_signature = popover.signatures.len() - 1;
12864 } else {
12865 popover.current_signature -= 1;
12866 }
12867 cx.notify();
12868 }
12869 }
12870
12871 pub fn signature_help_next(
12872 &mut self,
12873 _: &SignatureHelpNext,
12874 _: &mut Window,
12875 cx: &mut Context<Self>,
12876 ) {
12877 if let Some(popover) = self.signature_help_state.popover_mut() {
12878 if popover.current_signature + 1 == popover.signatures.len() {
12879 popover.current_signature = 0;
12880 } else {
12881 popover.current_signature += 1;
12882 }
12883 cx.notify();
12884 }
12885 }
12886
12887 pub fn move_to_previous_word_start(
12888 &mut self,
12889 _: &MoveToPreviousWordStart,
12890 window: &mut Window,
12891 cx: &mut Context<Self>,
12892 ) {
12893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12894 self.change_selections(Default::default(), window, cx, |s| {
12895 s.move_cursors_with(|map, head, _| {
12896 (
12897 movement::previous_word_start(map, head),
12898 SelectionGoal::None,
12899 )
12900 });
12901 })
12902 }
12903
12904 pub fn move_to_previous_subword_start(
12905 &mut self,
12906 _: &MoveToPreviousSubwordStart,
12907 window: &mut Window,
12908 cx: &mut Context<Self>,
12909 ) {
12910 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12911 self.change_selections(Default::default(), window, cx, |s| {
12912 s.move_cursors_with(|map, head, _| {
12913 (
12914 movement::previous_subword_start(map, head),
12915 SelectionGoal::None,
12916 )
12917 });
12918 })
12919 }
12920
12921 pub fn select_to_previous_word_start(
12922 &mut self,
12923 _: &SelectToPreviousWordStart,
12924 window: &mut Window,
12925 cx: &mut Context<Self>,
12926 ) {
12927 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12928 self.change_selections(Default::default(), window, cx, |s| {
12929 s.move_heads_with(|map, head, _| {
12930 (
12931 movement::previous_word_start(map, head),
12932 SelectionGoal::None,
12933 )
12934 });
12935 })
12936 }
12937
12938 pub fn select_to_previous_subword_start(
12939 &mut self,
12940 _: &SelectToPreviousSubwordStart,
12941 window: &mut Window,
12942 cx: &mut Context<Self>,
12943 ) {
12944 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12945 self.change_selections(Default::default(), window, cx, |s| {
12946 s.move_heads_with(|map, head, _| {
12947 (
12948 movement::previous_subword_start(map, head),
12949 SelectionGoal::None,
12950 )
12951 });
12952 })
12953 }
12954
12955 pub fn delete_to_previous_word_start(
12956 &mut self,
12957 action: &DeleteToPreviousWordStart,
12958 window: &mut Window,
12959 cx: &mut Context<Self>,
12960 ) {
12961 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12962 self.transact(window, cx, |this, window, cx| {
12963 this.select_autoclose_pair(window, cx);
12964 this.change_selections(Default::default(), window, cx, |s| {
12965 s.move_with(|map, selection| {
12966 if selection.is_empty() {
12967 let cursor = if action.ignore_newlines {
12968 movement::previous_word_start(map, selection.head())
12969 } else {
12970 movement::previous_word_start_or_newline(map, selection.head())
12971 };
12972 selection.set_head(cursor, SelectionGoal::None);
12973 }
12974 });
12975 });
12976 this.insert("", window, cx);
12977 });
12978 }
12979
12980 pub fn delete_to_previous_subword_start(
12981 &mut self,
12982 _: &DeleteToPreviousSubwordStart,
12983 window: &mut Window,
12984 cx: &mut Context<Self>,
12985 ) {
12986 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12987 self.transact(window, cx, |this, window, cx| {
12988 this.select_autoclose_pair(window, cx);
12989 this.change_selections(Default::default(), window, cx, |s| {
12990 s.move_with(|map, selection| {
12991 if selection.is_empty() {
12992 let cursor = movement::previous_subword_start(map, selection.head());
12993 selection.set_head(cursor, SelectionGoal::None);
12994 }
12995 });
12996 });
12997 this.insert("", window, cx);
12998 });
12999 }
13000
13001 pub fn move_to_next_word_end(
13002 &mut self,
13003 _: &MoveToNextWordEnd,
13004 window: &mut Window,
13005 cx: &mut Context<Self>,
13006 ) {
13007 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13008 self.change_selections(Default::default(), window, cx, |s| {
13009 s.move_cursors_with(|map, head, _| {
13010 (movement::next_word_end(map, head), SelectionGoal::None)
13011 });
13012 })
13013 }
13014
13015 pub fn move_to_next_subword_end(
13016 &mut self,
13017 _: &MoveToNextSubwordEnd,
13018 window: &mut Window,
13019 cx: &mut Context<Self>,
13020 ) {
13021 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13022 self.change_selections(Default::default(), window, cx, |s| {
13023 s.move_cursors_with(|map, head, _| {
13024 (movement::next_subword_end(map, head), SelectionGoal::None)
13025 });
13026 })
13027 }
13028
13029 pub fn select_to_next_word_end(
13030 &mut self,
13031 _: &SelectToNextWordEnd,
13032 window: &mut Window,
13033 cx: &mut Context<Self>,
13034 ) {
13035 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13036 self.change_selections(Default::default(), window, cx, |s| {
13037 s.move_heads_with(|map, head, _| {
13038 (movement::next_word_end(map, head), SelectionGoal::None)
13039 });
13040 })
13041 }
13042
13043 pub fn select_to_next_subword_end(
13044 &mut self,
13045 _: &SelectToNextSubwordEnd,
13046 window: &mut Window,
13047 cx: &mut Context<Self>,
13048 ) {
13049 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13050 self.change_selections(Default::default(), window, cx, |s| {
13051 s.move_heads_with(|map, head, _| {
13052 (movement::next_subword_end(map, head), SelectionGoal::None)
13053 });
13054 })
13055 }
13056
13057 pub fn delete_to_next_word_end(
13058 &mut self,
13059 action: &DeleteToNextWordEnd,
13060 window: &mut Window,
13061 cx: &mut Context<Self>,
13062 ) {
13063 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13064 self.transact(window, cx, |this, window, cx| {
13065 this.change_selections(Default::default(), window, cx, |s| {
13066 s.move_with(|map, selection| {
13067 if selection.is_empty() {
13068 let cursor = if action.ignore_newlines {
13069 movement::next_word_end(map, selection.head())
13070 } else {
13071 movement::next_word_end_or_newline(map, selection.head())
13072 };
13073 selection.set_head(cursor, SelectionGoal::None);
13074 }
13075 });
13076 });
13077 this.insert("", window, cx);
13078 });
13079 }
13080
13081 pub fn delete_to_next_subword_end(
13082 &mut self,
13083 _: &DeleteToNextSubwordEnd,
13084 window: &mut Window,
13085 cx: &mut Context<Self>,
13086 ) {
13087 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13088 self.transact(window, cx, |this, window, cx| {
13089 this.change_selections(Default::default(), window, cx, |s| {
13090 s.move_with(|map, selection| {
13091 if selection.is_empty() {
13092 let cursor = movement::next_subword_end(map, selection.head());
13093 selection.set_head(cursor, SelectionGoal::None);
13094 }
13095 });
13096 });
13097 this.insert("", window, cx);
13098 });
13099 }
13100
13101 pub fn move_to_beginning_of_line(
13102 &mut self,
13103 action: &MoveToBeginningOfLine,
13104 window: &mut Window,
13105 cx: &mut Context<Self>,
13106 ) {
13107 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13108 self.change_selections(Default::default(), window, cx, |s| {
13109 s.move_cursors_with(|map, head, _| {
13110 (
13111 movement::indented_line_beginning(
13112 map,
13113 head,
13114 action.stop_at_soft_wraps,
13115 action.stop_at_indent,
13116 ),
13117 SelectionGoal::None,
13118 )
13119 });
13120 })
13121 }
13122
13123 pub fn select_to_beginning_of_line(
13124 &mut self,
13125 action: &SelectToBeginningOfLine,
13126 window: &mut Window,
13127 cx: &mut Context<Self>,
13128 ) {
13129 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13130 self.change_selections(Default::default(), window, cx, |s| {
13131 s.move_heads_with(|map, head, _| {
13132 (
13133 movement::indented_line_beginning(
13134 map,
13135 head,
13136 action.stop_at_soft_wraps,
13137 action.stop_at_indent,
13138 ),
13139 SelectionGoal::None,
13140 )
13141 });
13142 });
13143 }
13144
13145 pub fn delete_to_beginning_of_line(
13146 &mut self,
13147 action: &DeleteToBeginningOfLine,
13148 window: &mut Window,
13149 cx: &mut Context<Self>,
13150 ) {
13151 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13152 self.transact(window, cx, |this, window, cx| {
13153 this.change_selections(Default::default(), window, cx, |s| {
13154 s.move_with(|_, selection| {
13155 selection.reversed = true;
13156 });
13157 });
13158
13159 this.select_to_beginning_of_line(
13160 &SelectToBeginningOfLine {
13161 stop_at_soft_wraps: false,
13162 stop_at_indent: action.stop_at_indent,
13163 },
13164 window,
13165 cx,
13166 );
13167 this.backspace(&Backspace, window, cx);
13168 });
13169 }
13170
13171 pub fn move_to_end_of_line(
13172 &mut self,
13173 action: &MoveToEndOfLine,
13174 window: &mut Window,
13175 cx: &mut Context<Self>,
13176 ) {
13177 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13178 self.change_selections(Default::default(), window, cx, |s| {
13179 s.move_cursors_with(|map, head, _| {
13180 (
13181 movement::line_end(map, head, action.stop_at_soft_wraps),
13182 SelectionGoal::None,
13183 )
13184 });
13185 })
13186 }
13187
13188 pub fn select_to_end_of_line(
13189 &mut self,
13190 action: &SelectToEndOfLine,
13191 window: &mut Window,
13192 cx: &mut Context<Self>,
13193 ) {
13194 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13195 self.change_selections(Default::default(), window, cx, |s| {
13196 s.move_heads_with(|map, head, _| {
13197 (
13198 movement::line_end(map, head, action.stop_at_soft_wraps),
13199 SelectionGoal::None,
13200 )
13201 });
13202 })
13203 }
13204
13205 pub fn delete_to_end_of_line(
13206 &mut self,
13207 _: &DeleteToEndOfLine,
13208 window: &mut Window,
13209 cx: &mut Context<Self>,
13210 ) {
13211 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13212 self.transact(window, cx, |this, window, cx| {
13213 this.select_to_end_of_line(
13214 &SelectToEndOfLine {
13215 stop_at_soft_wraps: false,
13216 },
13217 window,
13218 cx,
13219 );
13220 this.delete(&Delete, window, cx);
13221 });
13222 }
13223
13224 pub fn cut_to_end_of_line(
13225 &mut self,
13226 _: &CutToEndOfLine,
13227 window: &mut Window,
13228 cx: &mut Context<Self>,
13229 ) {
13230 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13231 self.transact(window, cx, |this, window, cx| {
13232 this.select_to_end_of_line(
13233 &SelectToEndOfLine {
13234 stop_at_soft_wraps: false,
13235 },
13236 window,
13237 cx,
13238 );
13239 this.cut(&Cut, window, cx);
13240 });
13241 }
13242
13243 pub fn move_to_start_of_paragraph(
13244 &mut self,
13245 _: &MoveToStartOfParagraph,
13246 window: &mut Window,
13247 cx: &mut Context<Self>,
13248 ) {
13249 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13250 cx.propagate();
13251 return;
13252 }
13253 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13254 self.change_selections(Default::default(), window, cx, |s| {
13255 s.move_with(|map, selection| {
13256 selection.collapse_to(
13257 movement::start_of_paragraph(map, selection.head(), 1),
13258 SelectionGoal::None,
13259 )
13260 });
13261 })
13262 }
13263
13264 pub fn move_to_end_of_paragraph(
13265 &mut self,
13266 _: &MoveToEndOfParagraph,
13267 window: &mut Window,
13268 cx: &mut Context<Self>,
13269 ) {
13270 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13271 cx.propagate();
13272 return;
13273 }
13274 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13275 self.change_selections(Default::default(), window, cx, |s| {
13276 s.move_with(|map, selection| {
13277 selection.collapse_to(
13278 movement::end_of_paragraph(map, selection.head(), 1),
13279 SelectionGoal::None,
13280 )
13281 });
13282 })
13283 }
13284
13285 pub fn select_to_start_of_paragraph(
13286 &mut self,
13287 _: &SelectToStartOfParagraph,
13288 window: &mut Window,
13289 cx: &mut Context<Self>,
13290 ) {
13291 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13292 cx.propagate();
13293 return;
13294 }
13295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13296 self.change_selections(Default::default(), window, cx, |s| {
13297 s.move_heads_with(|map, head, _| {
13298 (
13299 movement::start_of_paragraph(map, head, 1),
13300 SelectionGoal::None,
13301 )
13302 });
13303 })
13304 }
13305
13306 pub fn select_to_end_of_paragraph(
13307 &mut self,
13308 _: &SelectToEndOfParagraph,
13309 window: &mut Window,
13310 cx: &mut Context<Self>,
13311 ) {
13312 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13313 cx.propagate();
13314 return;
13315 }
13316 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13317 self.change_selections(Default::default(), window, cx, |s| {
13318 s.move_heads_with(|map, head, _| {
13319 (
13320 movement::end_of_paragraph(map, head, 1),
13321 SelectionGoal::None,
13322 )
13323 });
13324 })
13325 }
13326
13327 pub fn move_to_start_of_excerpt(
13328 &mut self,
13329 _: &MoveToStartOfExcerpt,
13330 window: &mut Window,
13331 cx: &mut Context<Self>,
13332 ) {
13333 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13334 cx.propagate();
13335 return;
13336 }
13337 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13338 self.change_selections(Default::default(), window, cx, |s| {
13339 s.move_with(|map, selection| {
13340 selection.collapse_to(
13341 movement::start_of_excerpt(
13342 map,
13343 selection.head(),
13344 workspace::searchable::Direction::Prev,
13345 ),
13346 SelectionGoal::None,
13347 )
13348 });
13349 })
13350 }
13351
13352 pub fn move_to_start_of_next_excerpt(
13353 &mut self,
13354 _: &MoveToStartOfNextExcerpt,
13355 window: &mut Window,
13356 cx: &mut Context<Self>,
13357 ) {
13358 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13359 cx.propagate();
13360 return;
13361 }
13362
13363 self.change_selections(Default::default(), window, cx, |s| {
13364 s.move_with(|map, selection| {
13365 selection.collapse_to(
13366 movement::start_of_excerpt(
13367 map,
13368 selection.head(),
13369 workspace::searchable::Direction::Next,
13370 ),
13371 SelectionGoal::None,
13372 )
13373 });
13374 })
13375 }
13376
13377 pub fn move_to_end_of_excerpt(
13378 &mut self,
13379 _: &MoveToEndOfExcerpt,
13380 window: &mut Window,
13381 cx: &mut Context<Self>,
13382 ) {
13383 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13384 cx.propagate();
13385 return;
13386 }
13387 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13388 self.change_selections(Default::default(), window, cx, |s| {
13389 s.move_with(|map, selection| {
13390 selection.collapse_to(
13391 movement::end_of_excerpt(
13392 map,
13393 selection.head(),
13394 workspace::searchable::Direction::Next,
13395 ),
13396 SelectionGoal::None,
13397 )
13398 });
13399 })
13400 }
13401
13402 pub fn move_to_end_of_previous_excerpt(
13403 &mut self,
13404 _: &MoveToEndOfPreviousExcerpt,
13405 window: &mut Window,
13406 cx: &mut Context<Self>,
13407 ) {
13408 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13409 cx.propagate();
13410 return;
13411 }
13412 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13413 self.change_selections(Default::default(), window, cx, |s| {
13414 s.move_with(|map, selection| {
13415 selection.collapse_to(
13416 movement::end_of_excerpt(
13417 map,
13418 selection.head(),
13419 workspace::searchable::Direction::Prev,
13420 ),
13421 SelectionGoal::None,
13422 )
13423 });
13424 })
13425 }
13426
13427 pub fn select_to_start_of_excerpt(
13428 &mut self,
13429 _: &SelectToStartOfExcerpt,
13430 window: &mut Window,
13431 cx: &mut Context<Self>,
13432 ) {
13433 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13434 cx.propagate();
13435 return;
13436 }
13437 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13438 self.change_selections(Default::default(), window, cx, |s| {
13439 s.move_heads_with(|map, head, _| {
13440 (
13441 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13442 SelectionGoal::None,
13443 )
13444 });
13445 })
13446 }
13447
13448 pub fn select_to_start_of_next_excerpt(
13449 &mut self,
13450 _: &SelectToStartOfNextExcerpt,
13451 window: &mut Window,
13452 cx: &mut Context<Self>,
13453 ) {
13454 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13455 cx.propagate();
13456 return;
13457 }
13458 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13459 self.change_selections(Default::default(), window, cx, |s| {
13460 s.move_heads_with(|map, head, _| {
13461 (
13462 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13463 SelectionGoal::None,
13464 )
13465 });
13466 })
13467 }
13468
13469 pub fn select_to_end_of_excerpt(
13470 &mut self,
13471 _: &SelectToEndOfExcerpt,
13472 window: &mut Window,
13473 cx: &mut Context<Self>,
13474 ) {
13475 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13476 cx.propagate();
13477 return;
13478 }
13479 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13480 self.change_selections(Default::default(), window, cx, |s| {
13481 s.move_heads_with(|map, head, _| {
13482 (
13483 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13484 SelectionGoal::None,
13485 )
13486 });
13487 })
13488 }
13489
13490 pub fn select_to_end_of_previous_excerpt(
13491 &mut self,
13492 _: &SelectToEndOfPreviousExcerpt,
13493 window: &mut Window,
13494 cx: &mut Context<Self>,
13495 ) {
13496 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13497 cx.propagate();
13498 return;
13499 }
13500 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13501 self.change_selections(Default::default(), window, cx, |s| {
13502 s.move_heads_with(|map, head, _| {
13503 (
13504 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13505 SelectionGoal::None,
13506 )
13507 });
13508 })
13509 }
13510
13511 pub fn move_to_beginning(
13512 &mut self,
13513 _: &MoveToBeginning,
13514 window: &mut Window,
13515 cx: &mut Context<Self>,
13516 ) {
13517 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13518 cx.propagate();
13519 return;
13520 }
13521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13522 self.change_selections(Default::default(), window, cx, |s| {
13523 s.select_ranges(vec![0..0]);
13524 });
13525 }
13526
13527 pub fn select_to_beginning(
13528 &mut self,
13529 _: &SelectToBeginning,
13530 window: &mut Window,
13531 cx: &mut Context<Self>,
13532 ) {
13533 let mut selection = self.selections.last::<Point>(cx);
13534 selection.set_head(Point::zero(), SelectionGoal::None);
13535 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13536 self.change_selections(Default::default(), window, cx, |s| {
13537 s.select(vec![selection]);
13538 });
13539 }
13540
13541 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13542 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13543 cx.propagate();
13544 return;
13545 }
13546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13547 let cursor = self.buffer.read(cx).read(cx).len();
13548 self.change_selections(Default::default(), window, cx, |s| {
13549 s.select_ranges(vec![cursor..cursor])
13550 });
13551 }
13552
13553 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13554 self.nav_history = nav_history;
13555 }
13556
13557 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13558 self.nav_history.as_ref()
13559 }
13560
13561 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13562 self.push_to_nav_history(
13563 self.selections.newest_anchor().head(),
13564 None,
13565 false,
13566 true,
13567 cx,
13568 );
13569 }
13570
13571 fn push_to_nav_history(
13572 &mut self,
13573 cursor_anchor: Anchor,
13574 new_position: Option<Point>,
13575 is_deactivate: bool,
13576 always: bool,
13577 cx: &mut Context<Self>,
13578 ) {
13579 if let Some(nav_history) = self.nav_history.as_mut() {
13580 let buffer = self.buffer.read(cx).read(cx);
13581 let cursor_position = cursor_anchor.to_point(&buffer);
13582 let scroll_state = self.scroll_manager.anchor();
13583 let scroll_top_row = scroll_state.top_row(&buffer);
13584 drop(buffer);
13585
13586 if let Some(new_position) = new_position {
13587 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13588 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13589 return;
13590 }
13591 }
13592
13593 nav_history.push(
13594 Some(NavigationData {
13595 cursor_anchor,
13596 cursor_position,
13597 scroll_anchor: scroll_state,
13598 scroll_top_row,
13599 }),
13600 cx,
13601 );
13602 cx.emit(EditorEvent::PushedToNavHistory {
13603 anchor: cursor_anchor,
13604 is_deactivate,
13605 })
13606 }
13607 }
13608
13609 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13611 let buffer = self.buffer.read(cx).snapshot(cx);
13612 let mut selection = self.selections.first::<usize>(cx);
13613 selection.set_head(buffer.len(), SelectionGoal::None);
13614 self.change_selections(Default::default(), window, cx, |s| {
13615 s.select(vec![selection]);
13616 });
13617 }
13618
13619 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13621 let end = self.buffer.read(cx).read(cx).len();
13622 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13623 s.select_ranges(vec![0..end]);
13624 });
13625 }
13626
13627 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13628 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13629 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13630 let mut selections = self.selections.all::<Point>(cx);
13631 let max_point = display_map.buffer_snapshot.max_point();
13632 for selection in &mut selections {
13633 let rows = selection.spanned_rows(true, &display_map);
13634 selection.start = Point::new(rows.start.0, 0);
13635 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13636 selection.reversed = false;
13637 }
13638 self.change_selections(Default::default(), window, cx, |s| {
13639 s.select(selections);
13640 });
13641 }
13642
13643 pub fn split_selection_into_lines(
13644 &mut self,
13645 action: &SplitSelectionIntoLines,
13646 window: &mut Window,
13647 cx: &mut Context<Self>,
13648 ) {
13649 let selections = self
13650 .selections
13651 .all::<Point>(cx)
13652 .into_iter()
13653 .map(|selection| selection.start..selection.end)
13654 .collect::<Vec<_>>();
13655 self.unfold_ranges(&selections, true, true, cx);
13656
13657 let mut new_selection_ranges = Vec::new();
13658 {
13659 let buffer = self.buffer.read(cx).read(cx);
13660 for selection in selections {
13661 for row in selection.start.row..selection.end.row {
13662 let line_start = Point::new(row, 0);
13663 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13664
13665 if action.keep_selections {
13666 // Keep the selection range for each line
13667 let selection_start = if row == selection.start.row {
13668 selection.start
13669 } else {
13670 line_start
13671 };
13672 new_selection_ranges.push(selection_start..line_end);
13673 } else {
13674 // Collapse to cursor at end of line
13675 new_selection_ranges.push(line_end..line_end);
13676 }
13677 }
13678
13679 let is_multiline_selection = selection.start.row != selection.end.row;
13680 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13681 // so this action feels more ergonomic when paired with other selection operations
13682 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13683 if !should_skip_last {
13684 if action.keep_selections {
13685 if is_multiline_selection {
13686 let line_start = Point::new(selection.end.row, 0);
13687 new_selection_ranges.push(line_start..selection.end);
13688 } else {
13689 new_selection_ranges.push(selection.start..selection.end);
13690 }
13691 } else {
13692 new_selection_ranges.push(selection.end..selection.end);
13693 }
13694 }
13695 }
13696 }
13697 self.change_selections(Default::default(), window, cx, |s| {
13698 s.select_ranges(new_selection_ranges);
13699 });
13700 }
13701
13702 pub fn add_selection_above(
13703 &mut self,
13704 _: &AddSelectionAbove,
13705 window: &mut Window,
13706 cx: &mut Context<Self>,
13707 ) {
13708 self.add_selection(true, window, cx);
13709 }
13710
13711 pub fn add_selection_below(
13712 &mut self,
13713 _: &AddSelectionBelow,
13714 window: &mut Window,
13715 cx: &mut Context<Self>,
13716 ) {
13717 self.add_selection(false, window, cx);
13718 }
13719
13720 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13722
13723 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13724 let all_selections = self.selections.all::<Point>(cx);
13725 let text_layout_details = self.text_layout_details(window);
13726
13727 let (mut columnar_selections, new_selections_to_columnarize) = {
13728 if let Some(state) = self.add_selections_state.as_ref() {
13729 let columnar_selection_ids: HashSet<_> = state
13730 .groups
13731 .iter()
13732 .flat_map(|group| group.stack.iter())
13733 .copied()
13734 .collect();
13735
13736 all_selections
13737 .into_iter()
13738 .partition(|s| columnar_selection_ids.contains(&s.id))
13739 } else {
13740 (Vec::new(), all_selections)
13741 }
13742 };
13743
13744 let mut state = self
13745 .add_selections_state
13746 .take()
13747 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13748
13749 for selection in new_selections_to_columnarize {
13750 let range = selection.display_range(&display_map).sorted();
13751 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13752 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13753 let positions = start_x.min(end_x)..start_x.max(end_x);
13754 let mut stack = Vec::new();
13755 for row in range.start.row().0..=range.end.row().0 {
13756 if let Some(selection) = self.selections.build_columnar_selection(
13757 &display_map,
13758 DisplayRow(row),
13759 &positions,
13760 selection.reversed,
13761 &text_layout_details,
13762 ) {
13763 stack.push(selection.id);
13764 columnar_selections.push(selection);
13765 }
13766 }
13767 if !stack.is_empty() {
13768 if above {
13769 stack.reverse();
13770 }
13771 state.groups.push(AddSelectionsGroup { above, stack });
13772 }
13773 }
13774
13775 let mut final_selections = Vec::new();
13776 let end_row = if above {
13777 DisplayRow(0)
13778 } else {
13779 display_map.max_point().row()
13780 };
13781
13782 let mut last_added_item_per_group = HashMap::default();
13783 for group in state.groups.iter_mut() {
13784 if let Some(last_id) = group.stack.last() {
13785 last_added_item_per_group.insert(*last_id, group);
13786 }
13787 }
13788
13789 for selection in columnar_selections {
13790 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13791 if above == group.above {
13792 let range = selection.display_range(&display_map).sorted();
13793 debug_assert_eq!(range.start.row(), range.end.row());
13794 let mut row = range.start.row();
13795 let positions =
13796 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13797 px(start)..px(end)
13798 } else {
13799 let start_x =
13800 display_map.x_for_display_point(range.start, &text_layout_details);
13801 let end_x =
13802 display_map.x_for_display_point(range.end, &text_layout_details);
13803 start_x.min(end_x)..start_x.max(end_x)
13804 };
13805
13806 let mut maybe_new_selection = None;
13807 while row != end_row {
13808 if above {
13809 row.0 -= 1;
13810 } else {
13811 row.0 += 1;
13812 }
13813 if let Some(new_selection) = self.selections.build_columnar_selection(
13814 &display_map,
13815 row,
13816 &positions,
13817 selection.reversed,
13818 &text_layout_details,
13819 ) {
13820 maybe_new_selection = Some(new_selection);
13821 break;
13822 }
13823 }
13824
13825 if let Some(new_selection) = maybe_new_selection {
13826 group.stack.push(new_selection.id);
13827 if above {
13828 final_selections.push(new_selection);
13829 final_selections.push(selection);
13830 } else {
13831 final_selections.push(selection);
13832 final_selections.push(new_selection);
13833 }
13834 } else {
13835 final_selections.push(selection);
13836 }
13837 } else {
13838 group.stack.pop();
13839 }
13840 } else {
13841 final_selections.push(selection);
13842 }
13843 }
13844
13845 self.change_selections(Default::default(), window, cx, |s| {
13846 s.select(final_selections);
13847 });
13848
13849 let final_selection_ids: HashSet<_> = self
13850 .selections
13851 .all::<Point>(cx)
13852 .iter()
13853 .map(|s| s.id)
13854 .collect();
13855 state.groups.retain_mut(|group| {
13856 // selections might get merged above so we remove invalid items from stacks
13857 group.stack.retain(|id| final_selection_ids.contains(id));
13858
13859 // single selection in stack can be treated as initial state
13860 group.stack.len() > 1
13861 });
13862
13863 if !state.groups.is_empty() {
13864 self.add_selections_state = Some(state);
13865 }
13866 }
13867
13868 fn select_match_ranges(
13869 &mut self,
13870 range: Range<usize>,
13871 reversed: bool,
13872 replace_newest: bool,
13873 auto_scroll: Option<Autoscroll>,
13874 window: &mut Window,
13875 cx: &mut Context<Editor>,
13876 ) {
13877 self.unfold_ranges(
13878 std::slice::from_ref(&range),
13879 false,
13880 auto_scroll.is_some(),
13881 cx,
13882 );
13883 let effects = if let Some(scroll) = auto_scroll {
13884 SelectionEffects::scroll(scroll)
13885 } else {
13886 SelectionEffects::no_scroll()
13887 };
13888 self.change_selections(effects, window, cx, |s| {
13889 if replace_newest {
13890 s.delete(s.newest_anchor().id);
13891 }
13892 if reversed {
13893 s.insert_range(range.end..range.start);
13894 } else {
13895 s.insert_range(range);
13896 }
13897 });
13898 }
13899
13900 pub fn select_next_match_internal(
13901 &mut self,
13902 display_map: &DisplaySnapshot,
13903 replace_newest: bool,
13904 autoscroll: Option<Autoscroll>,
13905 window: &mut Window,
13906 cx: &mut Context<Self>,
13907 ) -> Result<()> {
13908 let buffer = &display_map.buffer_snapshot;
13909 let mut selections = self.selections.all::<usize>(cx);
13910 if let Some(mut select_next_state) = self.select_next_state.take() {
13911 let query = &select_next_state.query;
13912 if !select_next_state.done {
13913 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13914 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13915 let mut next_selected_range = None;
13916
13917 let bytes_after_last_selection =
13918 buffer.bytes_in_range(last_selection.end..buffer.len());
13919 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13920 let query_matches = query
13921 .stream_find_iter(bytes_after_last_selection)
13922 .map(|result| (last_selection.end, result))
13923 .chain(
13924 query
13925 .stream_find_iter(bytes_before_first_selection)
13926 .map(|result| (0, result)),
13927 );
13928
13929 for (start_offset, query_match) in query_matches {
13930 let query_match = query_match.unwrap(); // can only fail due to I/O
13931 let offset_range =
13932 start_offset + query_match.start()..start_offset + query_match.end();
13933
13934 if !select_next_state.wordwise
13935 || (!buffer.is_inside_word(offset_range.start, false)
13936 && !buffer.is_inside_word(offset_range.end, false))
13937 {
13938 // TODO: This is n^2, because we might check all the selections
13939 if !selections
13940 .iter()
13941 .any(|selection| selection.range().overlaps(&offset_range))
13942 {
13943 next_selected_range = Some(offset_range);
13944 break;
13945 }
13946 }
13947 }
13948
13949 if let Some(next_selected_range) = next_selected_range {
13950 self.select_match_ranges(
13951 next_selected_range,
13952 last_selection.reversed,
13953 replace_newest,
13954 autoscroll,
13955 window,
13956 cx,
13957 );
13958 } else {
13959 select_next_state.done = true;
13960 }
13961 }
13962
13963 self.select_next_state = Some(select_next_state);
13964 } else {
13965 let mut only_carets = true;
13966 let mut same_text_selected = true;
13967 let mut selected_text = None;
13968
13969 let mut selections_iter = selections.iter().peekable();
13970 while let Some(selection) = selections_iter.next() {
13971 if selection.start != selection.end {
13972 only_carets = false;
13973 }
13974
13975 if same_text_selected {
13976 if selected_text.is_none() {
13977 selected_text =
13978 Some(buffer.text_for_range(selection.range()).collect::<String>());
13979 }
13980
13981 if let Some(next_selection) = selections_iter.peek() {
13982 if next_selection.range().len() == selection.range().len() {
13983 let next_selected_text = buffer
13984 .text_for_range(next_selection.range())
13985 .collect::<String>();
13986 if Some(next_selected_text) != selected_text {
13987 same_text_selected = false;
13988 selected_text = None;
13989 }
13990 } else {
13991 same_text_selected = false;
13992 selected_text = None;
13993 }
13994 }
13995 }
13996 }
13997
13998 if only_carets {
13999 for selection in &mut selections {
14000 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14001 selection.start = word_range.start;
14002 selection.end = word_range.end;
14003 selection.goal = SelectionGoal::None;
14004 selection.reversed = false;
14005 self.select_match_ranges(
14006 selection.start..selection.end,
14007 selection.reversed,
14008 replace_newest,
14009 autoscroll,
14010 window,
14011 cx,
14012 );
14013 }
14014
14015 if selections.len() == 1 {
14016 let selection = selections
14017 .last()
14018 .expect("ensured that there's only one selection");
14019 let query = buffer
14020 .text_for_range(selection.start..selection.end)
14021 .collect::<String>();
14022 let is_empty = query.is_empty();
14023 let select_state = SelectNextState {
14024 query: AhoCorasick::new(&[query])?,
14025 wordwise: true,
14026 done: is_empty,
14027 };
14028 self.select_next_state = Some(select_state);
14029 } else {
14030 self.select_next_state = None;
14031 }
14032 } else if let Some(selected_text) = selected_text {
14033 self.select_next_state = Some(SelectNextState {
14034 query: AhoCorasick::new(&[selected_text])?,
14035 wordwise: false,
14036 done: false,
14037 });
14038 self.select_next_match_internal(
14039 display_map,
14040 replace_newest,
14041 autoscroll,
14042 window,
14043 cx,
14044 )?;
14045 }
14046 }
14047 Ok(())
14048 }
14049
14050 pub fn select_all_matches(
14051 &mut self,
14052 _action: &SelectAllMatches,
14053 window: &mut Window,
14054 cx: &mut Context<Self>,
14055 ) -> Result<()> {
14056 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14057
14058 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14059
14060 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14061 let Some(select_next_state) = self.select_next_state.as_mut() else {
14062 return Ok(());
14063 };
14064 if select_next_state.done {
14065 return Ok(());
14066 }
14067
14068 let mut new_selections = Vec::new();
14069
14070 let reversed = self.selections.oldest::<usize>(cx).reversed;
14071 let buffer = &display_map.buffer_snapshot;
14072 let query_matches = select_next_state
14073 .query
14074 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14075
14076 for query_match in query_matches.into_iter() {
14077 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14078 let offset_range = if reversed {
14079 query_match.end()..query_match.start()
14080 } else {
14081 query_match.start()..query_match.end()
14082 };
14083
14084 if !select_next_state.wordwise
14085 || (!buffer.is_inside_word(offset_range.start, false)
14086 && !buffer.is_inside_word(offset_range.end, false))
14087 {
14088 new_selections.push(offset_range.start..offset_range.end);
14089 }
14090 }
14091
14092 select_next_state.done = true;
14093
14094 if new_selections.is_empty() {
14095 log::error!("bug: new_selections is empty in select_all_matches");
14096 return Ok(());
14097 }
14098
14099 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14100 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14101 selections.select_ranges(new_selections)
14102 });
14103
14104 Ok(())
14105 }
14106
14107 pub fn select_next(
14108 &mut self,
14109 action: &SelectNext,
14110 window: &mut Window,
14111 cx: &mut Context<Self>,
14112 ) -> Result<()> {
14113 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14114 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14115 self.select_next_match_internal(
14116 &display_map,
14117 action.replace_newest,
14118 Some(Autoscroll::newest()),
14119 window,
14120 cx,
14121 )?;
14122 Ok(())
14123 }
14124
14125 pub fn select_previous(
14126 &mut self,
14127 action: &SelectPrevious,
14128 window: &mut Window,
14129 cx: &mut Context<Self>,
14130 ) -> Result<()> {
14131 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14132 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14133 let buffer = &display_map.buffer_snapshot;
14134 let mut selections = self.selections.all::<usize>(cx);
14135 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14136 let query = &select_prev_state.query;
14137 if !select_prev_state.done {
14138 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14139 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14140 let mut next_selected_range = None;
14141 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14142 let bytes_before_last_selection =
14143 buffer.reversed_bytes_in_range(0..last_selection.start);
14144 let bytes_after_first_selection =
14145 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14146 let query_matches = query
14147 .stream_find_iter(bytes_before_last_selection)
14148 .map(|result| (last_selection.start, result))
14149 .chain(
14150 query
14151 .stream_find_iter(bytes_after_first_selection)
14152 .map(|result| (buffer.len(), result)),
14153 );
14154 for (end_offset, query_match) in query_matches {
14155 let query_match = query_match.unwrap(); // can only fail due to I/O
14156 let offset_range =
14157 end_offset - query_match.end()..end_offset - query_match.start();
14158
14159 if !select_prev_state.wordwise
14160 || (!buffer.is_inside_word(offset_range.start, false)
14161 && !buffer.is_inside_word(offset_range.end, false))
14162 {
14163 next_selected_range = Some(offset_range);
14164 break;
14165 }
14166 }
14167
14168 if let Some(next_selected_range) = next_selected_range {
14169 self.select_match_ranges(
14170 next_selected_range,
14171 last_selection.reversed,
14172 action.replace_newest,
14173 Some(Autoscroll::newest()),
14174 window,
14175 cx,
14176 );
14177 } else {
14178 select_prev_state.done = true;
14179 }
14180 }
14181
14182 self.select_prev_state = Some(select_prev_state);
14183 } else {
14184 let mut only_carets = true;
14185 let mut same_text_selected = true;
14186 let mut selected_text = None;
14187
14188 let mut selections_iter = selections.iter().peekable();
14189 while let Some(selection) = selections_iter.next() {
14190 if selection.start != selection.end {
14191 only_carets = false;
14192 }
14193
14194 if same_text_selected {
14195 if selected_text.is_none() {
14196 selected_text =
14197 Some(buffer.text_for_range(selection.range()).collect::<String>());
14198 }
14199
14200 if let Some(next_selection) = selections_iter.peek() {
14201 if next_selection.range().len() == selection.range().len() {
14202 let next_selected_text = buffer
14203 .text_for_range(next_selection.range())
14204 .collect::<String>();
14205 if Some(next_selected_text) != selected_text {
14206 same_text_selected = false;
14207 selected_text = None;
14208 }
14209 } else {
14210 same_text_selected = false;
14211 selected_text = None;
14212 }
14213 }
14214 }
14215 }
14216
14217 if only_carets {
14218 for selection in &mut selections {
14219 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14220 selection.start = word_range.start;
14221 selection.end = word_range.end;
14222 selection.goal = SelectionGoal::None;
14223 selection.reversed = false;
14224 self.select_match_ranges(
14225 selection.start..selection.end,
14226 selection.reversed,
14227 action.replace_newest,
14228 Some(Autoscroll::newest()),
14229 window,
14230 cx,
14231 );
14232 }
14233 if selections.len() == 1 {
14234 let selection = selections
14235 .last()
14236 .expect("ensured that there's only one selection");
14237 let query = buffer
14238 .text_for_range(selection.start..selection.end)
14239 .collect::<String>();
14240 let is_empty = query.is_empty();
14241 let select_state = SelectNextState {
14242 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14243 wordwise: true,
14244 done: is_empty,
14245 };
14246 self.select_prev_state = Some(select_state);
14247 } else {
14248 self.select_prev_state = None;
14249 }
14250 } else if let Some(selected_text) = selected_text {
14251 self.select_prev_state = Some(SelectNextState {
14252 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14253 wordwise: false,
14254 done: false,
14255 });
14256 self.select_previous(action, window, cx)?;
14257 }
14258 }
14259 Ok(())
14260 }
14261
14262 pub fn find_next_match(
14263 &mut self,
14264 _: &FindNextMatch,
14265 window: &mut Window,
14266 cx: &mut Context<Self>,
14267 ) -> Result<()> {
14268 let selections = self.selections.disjoint_anchors();
14269 match selections.first() {
14270 Some(first) if selections.len() >= 2 => {
14271 self.change_selections(Default::default(), window, cx, |s| {
14272 s.select_ranges([first.range()]);
14273 });
14274 }
14275 _ => self.select_next(
14276 &SelectNext {
14277 replace_newest: true,
14278 },
14279 window,
14280 cx,
14281 )?,
14282 }
14283 Ok(())
14284 }
14285
14286 pub fn find_previous_match(
14287 &mut self,
14288 _: &FindPreviousMatch,
14289 window: &mut Window,
14290 cx: &mut Context<Self>,
14291 ) -> Result<()> {
14292 let selections = self.selections.disjoint_anchors();
14293 match selections.last() {
14294 Some(last) if selections.len() >= 2 => {
14295 self.change_selections(Default::default(), window, cx, |s| {
14296 s.select_ranges([last.range()]);
14297 });
14298 }
14299 _ => self.select_previous(
14300 &SelectPrevious {
14301 replace_newest: true,
14302 },
14303 window,
14304 cx,
14305 )?,
14306 }
14307 Ok(())
14308 }
14309
14310 pub fn toggle_comments(
14311 &mut self,
14312 action: &ToggleComments,
14313 window: &mut Window,
14314 cx: &mut Context<Self>,
14315 ) {
14316 if self.read_only(cx) {
14317 return;
14318 }
14319 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14320 let text_layout_details = &self.text_layout_details(window);
14321 self.transact(window, cx, |this, window, cx| {
14322 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14323 let mut edits = Vec::new();
14324 let mut selection_edit_ranges = Vec::new();
14325 let mut last_toggled_row = None;
14326 let snapshot = this.buffer.read(cx).read(cx);
14327 let empty_str: Arc<str> = Arc::default();
14328 let mut suffixes_inserted = Vec::new();
14329 let ignore_indent = action.ignore_indent;
14330
14331 fn comment_prefix_range(
14332 snapshot: &MultiBufferSnapshot,
14333 row: MultiBufferRow,
14334 comment_prefix: &str,
14335 comment_prefix_whitespace: &str,
14336 ignore_indent: bool,
14337 ) -> Range<Point> {
14338 let indent_size = if ignore_indent {
14339 0
14340 } else {
14341 snapshot.indent_size_for_line(row).len
14342 };
14343
14344 let start = Point::new(row.0, indent_size);
14345
14346 let mut line_bytes = snapshot
14347 .bytes_in_range(start..snapshot.max_point())
14348 .flatten()
14349 .copied();
14350
14351 // If this line currently begins with the line comment prefix, then record
14352 // the range containing the prefix.
14353 if line_bytes
14354 .by_ref()
14355 .take(comment_prefix.len())
14356 .eq(comment_prefix.bytes())
14357 {
14358 // Include any whitespace that matches the comment prefix.
14359 let matching_whitespace_len = line_bytes
14360 .zip(comment_prefix_whitespace.bytes())
14361 .take_while(|(a, b)| a == b)
14362 .count() as u32;
14363 let end = Point::new(
14364 start.row,
14365 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14366 );
14367 start..end
14368 } else {
14369 start..start
14370 }
14371 }
14372
14373 fn comment_suffix_range(
14374 snapshot: &MultiBufferSnapshot,
14375 row: MultiBufferRow,
14376 comment_suffix: &str,
14377 comment_suffix_has_leading_space: bool,
14378 ) -> Range<Point> {
14379 let end = Point::new(row.0, snapshot.line_len(row));
14380 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14381
14382 let mut line_end_bytes = snapshot
14383 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14384 .flatten()
14385 .copied();
14386
14387 let leading_space_len = if suffix_start_column > 0
14388 && line_end_bytes.next() == Some(b' ')
14389 && comment_suffix_has_leading_space
14390 {
14391 1
14392 } else {
14393 0
14394 };
14395
14396 // If this line currently begins with the line comment prefix, then record
14397 // the range containing the prefix.
14398 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14399 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14400 start..end
14401 } else {
14402 end..end
14403 }
14404 }
14405
14406 // TODO: Handle selections that cross excerpts
14407 for selection in &mut selections {
14408 let start_column = snapshot
14409 .indent_size_for_line(MultiBufferRow(selection.start.row))
14410 .len;
14411 let language = if let Some(language) =
14412 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14413 {
14414 language
14415 } else {
14416 continue;
14417 };
14418
14419 selection_edit_ranges.clear();
14420
14421 // If multiple selections contain a given row, avoid processing that
14422 // row more than once.
14423 let mut start_row = MultiBufferRow(selection.start.row);
14424 if last_toggled_row == Some(start_row) {
14425 start_row = start_row.next_row();
14426 }
14427 let end_row =
14428 if selection.end.row > selection.start.row && selection.end.column == 0 {
14429 MultiBufferRow(selection.end.row - 1)
14430 } else {
14431 MultiBufferRow(selection.end.row)
14432 };
14433 last_toggled_row = Some(end_row);
14434
14435 if start_row > end_row {
14436 continue;
14437 }
14438
14439 // If the language has line comments, toggle those.
14440 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14441
14442 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14443 if ignore_indent {
14444 full_comment_prefixes = full_comment_prefixes
14445 .into_iter()
14446 .map(|s| Arc::from(s.trim_end()))
14447 .collect();
14448 }
14449
14450 if !full_comment_prefixes.is_empty() {
14451 let first_prefix = full_comment_prefixes
14452 .first()
14453 .expect("prefixes is non-empty");
14454 let prefix_trimmed_lengths = full_comment_prefixes
14455 .iter()
14456 .map(|p| p.trim_end_matches(' ').len())
14457 .collect::<SmallVec<[usize; 4]>>();
14458
14459 let mut all_selection_lines_are_comments = true;
14460
14461 for row in start_row.0..=end_row.0 {
14462 let row = MultiBufferRow(row);
14463 if start_row < end_row && snapshot.is_line_blank(row) {
14464 continue;
14465 }
14466
14467 let prefix_range = full_comment_prefixes
14468 .iter()
14469 .zip(prefix_trimmed_lengths.iter().copied())
14470 .map(|(prefix, trimmed_prefix_len)| {
14471 comment_prefix_range(
14472 snapshot.deref(),
14473 row,
14474 &prefix[..trimmed_prefix_len],
14475 &prefix[trimmed_prefix_len..],
14476 ignore_indent,
14477 )
14478 })
14479 .max_by_key(|range| range.end.column - range.start.column)
14480 .expect("prefixes is non-empty");
14481
14482 if prefix_range.is_empty() {
14483 all_selection_lines_are_comments = false;
14484 }
14485
14486 selection_edit_ranges.push(prefix_range);
14487 }
14488
14489 if all_selection_lines_are_comments {
14490 edits.extend(
14491 selection_edit_ranges
14492 .iter()
14493 .cloned()
14494 .map(|range| (range, empty_str.clone())),
14495 );
14496 } else {
14497 let min_column = selection_edit_ranges
14498 .iter()
14499 .map(|range| range.start.column)
14500 .min()
14501 .unwrap_or(0);
14502 edits.extend(selection_edit_ranges.iter().map(|range| {
14503 let position = Point::new(range.start.row, min_column);
14504 (position..position, first_prefix.clone())
14505 }));
14506 }
14507 } else if let Some(BlockCommentConfig {
14508 start: full_comment_prefix,
14509 end: comment_suffix,
14510 ..
14511 }) = language.block_comment()
14512 {
14513 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14514 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14515 let prefix_range = comment_prefix_range(
14516 snapshot.deref(),
14517 start_row,
14518 comment_prefix,
14519 comment_prefix_whitespace,
14520 ignore_indent,
14521 );
14522 let suffix_range = comment_suffix_range(
14523 snapshot.deref(),
14524 end_row,
14525 comment_suffix.trim_start_matches(' '),
14526 comment_suffix.starts_with(' '),
14527 );
14528
14529 if prefix_range.is_empty() || suffix_range.is_empty() {
14530 edits.push((
14531 prefix_range.start..prefix_range.start,
14532 full_comment_prefix.clone(),
14533 ));
14534 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14535 suffixes_inserted.push((end_row, comment_suffix.len()));
14536 } else {
14537 edits.push((prefix_range, empty_str.clone()));
14538 edits.push((suffix_range, empty_str.clone()));
14539 }
14540 } else {
14541 continue;
14542 }
14543 }
14544
14545 drop(snapshot);
14546 this.buffer.update(cx, |buffer, cx| {
14547 buffer.edit(edits, None, cx);
14548 });
14549
14550 // Adjust selections so that they end before any comment suffixes that
14551 // were inserted.
14552 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14553 let mut selections = this.selections.all::<Point>(cx);
14554 let snapshot = this.buffer.read(cx).read(cx);
14555 for selection in &mut selections {
14556 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14557 match row.cmp(&MultiBufferRow(selection.end.row)) {
14558 Ordering::Less => {
14559 suffixes_inserted.next();
14560 continue;
14561 }
14562 Ordering::Greater => break,
14563 Ordering::Equal => {
14564 if selection.end.column == snapshot.line_len(row) {
14565 if selection.is_empty() {
14566 selection.start.column -= suffix_len as u32;
14567 }
14568 selection.end.column -= suffix_len as u32;
14569 }
14570 break;
14571 }
14572 }
14573 }
14574 }
14575
14576 drop(snapshot);
14577 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14578
14579 let selections = this.selections.all::<Point>(cx);
14580 let selections_on_single_row = selections.windows(2).all(|selections| {
14581 selections[0].start.row == selections[1].start.row
14582 && selections[0].end.row == selections[1].end.row
14583 && selections[0].start.row == selections[0].end.row
14584 });
14585 let selections_selecting = selections
14586 .iter()
14587 .any(|selection| selection.start != selection.end);
14588 let advance_downwards = action.advance_downwards
14589 && selections_on_single_row
14590 && !selections_selecting
14591 && !matches!(this.mode, EditorMode::SingleLine { .. });
14592
14593 if advance_downwards {
14594 let snapshot = this.buffer.read(cx).snapshot(cx);
14595
14596 this.change_selections(Default::default(), window, cx, |s| {
14597 s.move_cursors_with(|display_snapshot, display_point, _| {
14598 let mut point = display_point.to_point(display_snapshot);
14599 point.row += 1;
14600 point = snapshot.clip_point(point, Bias::Left);
14601 let display_point = point.to_display_point(display_snapshot);
14602 let goal = SelectionGoal::HorizontalPosition(
14603 display_snapshot
14604 .x_for_display_point(display_point, text_layout_details)
14605 .into(),
14606 );
14607 (display_point, goal)
14608 })
14609 });
14610 }
14611 });
14612 }
14613
14614 pub fn select_enclosing_symbol(
14615 &mut self,
14616 _: &SelectEnclosingSymbol,
14617 window: &mut Window,
14618 cx: &mut Context<Self>,
14619 ) {
14620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14621
14622 let buffer = self.buffer.read(cx).snapshot(cx);
14623 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14624
14625 fn update_selection(
14626 selection: &Selection<usize>,
14627 buffer_snap: &MultiBufferSnapshot,
14628 ) -> Option<Selection<usize>> {
14629 let cursor = selection.head();
14630 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14631 for symbol in symbols.iter().rev() {
14632 let start = symbol.range.start.to_offset(buffer_snap);
14633 let end = symbol.range.end.to_offset(buffer_snap);
14634 let new_range = start..end;
14635 if start < selection.start || end > selection.end {
14636 return Some(Selection {
14637 id: selection.id,
14638 start: new_range.start,
14639 end: new_range.end,
14640 goal: SelectionGoal::None,
14641 reversed: selection.reversed,
14642 });
14643 }
14644 }
14645 None
14646 }
14647
14648 let mut selected_larger_symbol = false;
14649 let new_selections = old_selections
14650 .iter()
14651 .map(|selection| match update_selection(selection, &buffer) {
14652 Some(new_selection) => {
14653 if new_selection.range() != selection.range() {
14654 selected_larger_symbol = true;
14655 }
14656 new_selection
14657 }
14658 None => selection.clone(),
14659 })
14660 .collect::<Vec<_>>();
14661
14662 if selected_larger_symbol {
14663 self.change_selections(Default::default(), window, cx, |s| {
14664 s.select(new_selections);
14665 });
14666 }
14667 }
14668
14669 pub fn select_larger_syntax_node(
14670 &mut self,
14671 _: &SelectLargerSyntaxNode,
14672 window: &mut Window,
14673 cx: &mut Context<Self>,
14674 ) {
14675 let Some(visible_row_count) = self.visible_row_count() else {
14676 return;
14677 };
14678 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14679 if old_selections.is_empty() {
14680 return;
14681 }
14682
14683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14684
14685 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14686 let buffer = self.buffer.read(cx).snapshot(cx);
14687
14688 let mut selected_larger_node = false;
14689 let mut new_selections = old_selections
14690 .iter()
14691 .map(|selection| {
14692 let old_range = selection.start..selection.end;
14693
14694 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14695 // manually select word at selection
14696 if ["string_content", "inline"].contains(&node.kind()) {
14697 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14698 // ignore if word is already selected
14699 if !word_range.is_empty() && old_range != word_range {
14700 let (last_word_range, _) =
14701 buffer.surrounding_word(old_range.end, false);
14702 // only select word if start and end point belongs to same word
14703 if word_range == last_word_range {
14704 selected_larger_node = true;
14705 return Selection {
14706 id: selection.id,
14707 start: word_range.start,
14708 end: word_range.end,
14709 goal: SelectionGoal::None,
14710 reversed: selection.reversed,
14711 };
14712 }
14713 }
14714 }
14715 }
14716
14717 let mut new_range = old_range.clone();
14718 while let Some((_node, containing_range)) =
14719 buffer.syntax_ancestor(new_range.clone())
14720 {
14721 new_range = match containing_range {
14722 MultiOrSingleBufferOffsetRange::Single(_) => break,
14723 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14724 };
14725 if !display_map.intersects_fold(new_range.start)
14726 && !display_map.intersects_fold(new_range.end)
14727 {
14728 break;
14729 }
14730 }
14731
14732 selected_larger_node |= new_range != old_range;
14733 Selection {
14734 id: selection.id,
14735 start: new_range.start,
14736 end: new_range.end,
14737 goal: SelectionGoal::None,
14738 reversed: selection.reversed,
14739 }
14740 })
14741 .collect::<Vec<_>>();
14742
14743 if !selected_larger_node {
14744 return; // don't put this call in the history
14745 }
14746
14747 // scroll based on transformation done to the last selection created by the user
14748 let (last_old, last_new) = old_selections
14749 .last()
14750 .zip(new_selections.last().cloned())
14751 .expect("old_selections isn't empty");
14752
14753 // revert selection
14754 let is_selection_reversed = {
14755 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14756 new_selections.last_mut().expect("checked above").reversed =
14757 should_newest_selection_be_reversed;
14758 should_newest_selection_be_reversed
14759 };
14760
14761 if selected_larger_node {
14762 self.select_syntax_node_history.disable_clearing = true;
14763 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14764 s.select(new_selections.clone());
14765 });
14766 self.select_syntax_node_history.disable_clearing = false;
14767 }
14768
14769 let start_row = last_new.start.to_display_point(&display_map).row().0;
14770 let end_row = last_new.end.to_display_point(&display_map).row().0;
14771 let selection_height = end_row - start_row + 1;
14772 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14773
14774 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14775 let scroll_behavior = if fits_on_the_screen {
14776 self.request_autoscroll(Autoscroll::fit(), cx);
14777 SelectSyntaxNodeScrollBehavior::FitSelection
14778 } else if is_selection_reversed {
14779 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14780 SelectSyntaxNodeScrollBehavior::CursorTop
14781 } else {
14782 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14783 SelectSyntaxNodeScrollBehavior::CursorBottom
14784 };
14785
14786 self.select_syntax_node_history.push((
14787 old_selections,
14788 scroll_behavior,
14789 is_selection_reversed,
14790 ));
14791 }
14792
14793 pub fn select_smaller_syntax_node(
14794 &mut self,
14795 _: &SelectSmallerSyntaxNode,
14796 window: &mut Window,
14797 cx: &mut Context<Self>,
14798 ) {
14799 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14800
14801 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14802 self.select_syntax_node_history.pop()
14803 {
14804 if let Some(selection) = selections.last_mut() {
14805 selection.reversed = is_selection_reversed;
14806 }
14807
14808 self.select_syntax_node_history.disable_clearing = true;
14809 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14810 s.select(selections.to_vec());
14811 });
14812 self.select_syntax_node_history.disable_clearing = false;
14813
14814 match scroll_behavior {
14815 SelectSyntaxNodeScrollBehavior::CursorTop => {
14816 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14817 }
14818 SelectSyntaxNodeScrollBehavior::FitSelection => {
14819 self.request_autoscroll(Autoscroll::fit(), cx);
14820 }
14821 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14822 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14823 }
14824 }
14825 }
14826 }
14827
14828 pub fn unwrap_syntax_node(
14829 &mut self,
14830 _: &UnwrapSyntaxNode,
14831 window: &mut Window,
14832 cx: &mut Context<Self>,
14833 ) {
14834 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14835
14836 let buffer = self.buffer.read(cx).snapshot(cx);
14837 let selections = self
14838 .selections
14839 .all::<usize>(cx)
14840 .into_iter()
14841 // subtracting the offset requires sorting
14842 .sorted_by_key(|i| i.start);
14843
14844 let full_edits = selections
14845 .into_iter()
14846 .filter_map(|selection| {
14847 // Only requires two branches once if-let-chains stabilize (#53667)
14848 let child = if !selection.is_empty() {
14849 selection.range()
14850 } else if let Some((_, ancestor_range)) =
14851 buffer.syntax_ancestor(selection.start..selection.end)
14852 {
14853 match ancestor_range {
14854 MultiOrSingleBufferOffsetRange::Single(range) => range,
14855 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14856 }
14857 } else {
14858 selection.range()
14859 };
14860
14861 let mut parent = child.clone();
14862 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
14863 parent = match ancestor_range {
14864 MultiOrSingleBufferOffsetRange::Single(range) => range,
14865 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14866 };
14867 if parent.start < child.start || parent.end > child.end {
14868 break;
14869 }
14870 }
14871
14872 if parent == child {
14873 return None;
14874 }
14875 let text = buffer.text_for_range(child.clone()).collect::<String>();
14876 Some((selection.id, parent, text))
14877 })
14878 .collect::<Vec<_>>();
14879
14880 self.transact(window, cx, |this, window, cx| {
14881 this.buffer.update(cx, |buffer, cx| {
14882 buffer.edit(
14883 full_edits
14884 .iter()
14885 .map(|(_, p, t)| (p.clone(), t.clone()))
14886 .collect::<Vec<_>>(),
14887 None,
14888 cx,
14889 );
14890 });
14891 this.change_selections(Default::default(), window, cx, |s| {
14892 let mut offset = 0;
14893 let mut selections = vec![];
14894 for (id, parent, text) in full_edits {
14895 let start = parent.start - offset;
14896 offset += parent.len() - text.len();
14897 selections.push(Selection {
14898 id: id,
14899 start,
14900 end: start + text.len(),
14901 reversed: false,
14902 goal: Default::default(),
14903 });
14904 }
14905 s.select(selections);
14906 });
14907 });
14908 }
14909
14910 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14911 if !EditorSettings::get_global(cx).gutter.runnables {
14912 self.clear_tasks();
14913 return Task::ready(());
14914 }
14915 let project = self.project().map(Entity::downgrade);
14916 let task_sources = self.lsp_task_sources(cx);
14917 let multi_buffer = self.buffer.downgrade();
14918 cx.spawn_in(window, async move |editor, cx| {
14919 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14920 let Some(project) = project.and_then(|p| p.upgrade()) else {
14921 return;
14922 };
14923 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14924 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14925 }) else {
14926 return;
14927 };
14928
14929 let hide_runnables = project
14930 .update(cx, |project, cx| {
14931 // Do not display any test indicators in non-dev server remote projects.
14932 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14933 })
14934 .unwrap_or(true);
14935 if hide_runnables {
14936 return;
14937 }
14938 let new_rows =
14939 cx.background_spawn({
14940 let snapshot = display_snapshot.clone();
14941 async move {
14942 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14943 }
14944 })
14945 .await;
14946 let Ok(lsp_tasks) =
14947 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14948 else {
14949 return;
14950 };
14951 let lsp_tasks = lsp_tasks.await;
14952
14953 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14954 lsp_tasks
14955 .into_iter()
14956 .flat_map(|(kind, tasks)| {
14957 tasks.into_iter().filter_map(move |(location, task)| {
14958 Some((kind.clone(), location?, task))
14959 })
14960 })
14961 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14962 let buffer = location.target.buffer;
14963 let buffer_snapshot = buffer.read(cx).snapshot();
14964 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14965 |(excerpt_id, snapshot, _)| {
14966 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14967 display_snapshot
14968 .buffer_snapshot
14969 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14970 } else {
14971 None
14972 }
14973 },
14974 );
14975 if let Some(offset) = offset {
14976 let task_buffer_range =
14977 location.target.range.to_point(&buffer_snapshot);
14978 let context_buffer_range =
14979 task_buffer_range.to_offset(&buffer_snapshot);
14980 let context_range = BufferOffset(context_buffer_range.start)
14981 ..BufferOffset(context_buffer_range.end);
14982
14983 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14984 .or_insert_with(|| RunnableTasks {
14985 templates: Vec::new(),
14986 offset,
14987 column: task_buffer_range.start.column,
14988 extra_variables: HashMap::default(),
14989 context_range,
14990 })
14991 .templates
14992 .push((kind, task.original_task().clone()));
14993 }
14994
14995 acc
14996 })
14997 }) else {
14998 return;
14999 };
15000
15001 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15002 buffer.language_settings(cx).tasks.prefer_lsp
15003 }) else {
15004 return;
15005 };
15006
15007 let rows = Self::runnable_rows(
15008 project,
15009 display_snapshot,
15010 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15011 new_rows,
15012 cx.clone(),
15013 )
15014 .await;
15015 editor
15016 .update(cx, |editor, _| {
15017 editor.clear_tasks();
15018 for (key, mut value) in rows {
15019 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15020 value.templates.extend(lsp_tasks.templates);
15021 }
15022
15023 editor.insert_tasks(key, value);
15024 }
15025 for (key, value) in lsp_tasks_by_rows {
15026 editor.insert_tasks(key, value);
15027 }
15028 })
15029 .ok();
15030 })
15031 }
15032 fn fetch_runnable_ranges(
15033 snapshot: &DisplaySnapshot,
15034 range: Range<Anchor>,
15035 ) -> Vec<language::RunnableRange> {
15036 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15037 }
15038
15039 fn runnable_rows(
15040 project: Entity<Project>,
15041 snapshot: DisplaySnapshot,
15042 prefer_lsp: bool,
15043 runnable_ranges: Vec<RunnableRange>,
15044 cx: AsyncWindowContext,
15045 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15046 cx.spawn(async move |cx| {
15047 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15048 for mut runnable in runnable_ranges {
15049 let Some(tasks) = cx
15050 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15051 .ok()
15052 else {
15053 continue;
15054 };
15055 let mut tasks = tasks.await;
15056
15057 if prefer_lsp {
15058 tasks.retain(|(task_kind, _)| {
15059 !matches!(task_kind, TaskSourceKind::Language { .. })
15060 });
15061 }
15062 if tasks.is_empty() {
15063 continue;
15064 }
15065
15066 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15067 let Some(row) = snapshot
15068 .buffer_snapshot
15069 .buffer_line_for_row(MultiBufferRow(point.row))
15070 .map(|(_, range)| range.start.row)
15071 else {
15072 continue;
15073 };
15074
15075 let context_range =
15076 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15077 runnable_rows.push((
15078 (runnable.buffer_id, row),
15079 RunnableTasks {
15080 templates: tasks,
15081 offset: snapshot
15082 .buffer_snapshot
15083 .anchor_before(runnable.run_range.start),
15084 context_range,
15085 column: point.column,
15086 extra_variables: runnable.extra_captures,
15087 },
15088 ));
15089 }
15090 runnable_rows
15091 })
15092 }
15093
15094 fn templates_with_tags(
15095 project: &Entity<Project>,
15096 runnable: &mut Runnable,
15097 cx: &mut App,
15098 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15099 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15100 let (worktree_id, file) = project
15101 .buffer_for_id(runnable.buffer, cx)
15102 .and_then(|buffer| buffer.read(cx).file())
15103 .map(|file| (file.worktree_id(cx), file.clone()))
15104 .unzip();
15105
15106 (
15107 project.task_store().read(cx).task_inventory().cloned(),
15108 worktree_id,
15109 file,
15110 )
15111 });
15112
15113 let tags = mem::take(&mut runnable.tags);
15114 let language = runnable.language.clone();
15115 cx.spawn(async move |cx| {
15116 let mut templates_with_tags = Vec::new();
15117 if let Some(inventory) = inventory {
15118 for RunnableTag(tag) in tags {
15119 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15120 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15121 }) else {
15122 return templates_with_tags;
15123 };
15124 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15125 move |(_, template)| {
15126 template.tags.iter().any(|source_tag| source_tag == &tag)
15127 },
15128 ));
15129 }
15130 }
15131 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15132
15133 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15134 // Strongest source wins; if we have worktree tag binding, prefer that to
15135 // global and language bindings;
15136 // if we have a global binding, prefer that to language binding.
15137 let first_mismatch = templates_with_tags
15138 .iter()
15139 .position(|(tag_source, _)| tag_source != leading_tag_source);
15140 if let Some(index) = first_mismatch {
15141 templates_with_tags.truncate(index);
15142 }
15143 }
15144
15145 templates_with_tags
15146 })
15147 }
15148
15149 pub fn move_to_enclosing_bracket(
15150 &mut self,
15151 _: &MoveToEnclosingBracket,
15152 window: &mut Window,
15153 cx: &mut Context<Self>,
15154 ) {
15155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15156 self.change_selections(Default::default(), window, cx, |s| {
15157 s.move_offsets_with(|snapshot, selection| {
15158 let Some(enclosing_bracket_ranges) =
15159 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15160 else {
15161 return;
15162 };
15163
15164 let mut best_length = usize::MAX;
15165 let mut best_inside = false;
15166 let mut best_in_bracket_range = false;
15167 let mut best_destination = None;
15168 for (open, close) in enclosing_bracket_ranges {
15169 let close = close.to_inclusive();
15170 let length = close.end() - open.start;
15171 let inside = selection.start >= open.end && selection.end <= *close.start();
15172 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15173 || close.contains(&selection.head());
15174
15175 // If best is next to a bracket and current isn't, skip
15176 if !in_bracket_range && best_in_bracket_range {
15177 continue;
15178 }
15179
15180 // Prefer smaller lengths unless best is inside and current isn't
15181 if length > best_length && (best_inside || !inside) {
15182 continue;
15183 }
15184
15185 best_length = length;
15186 best_inside = inside;
15187 best_in_bracket_range = in_bracket_range;
15188 best_destination = Some(
15189 if close.contains(&selection.start) && close.contains(&selection.end) {
15190 if inside { open.end } else { open.start }
15191 } else if inside {
15192 *close.start()
15193 } else {
15194 *close.end()
15195 },
15196 );
15197 }
15198
15199 if let Some(destination) = best_destination {
15200 selection.collapse_to(destination, SelectionGoal::None);
15201 }
15202 })
15203 });
15204 }
15205
15206 pub fn undo_selection(
15207 &mut self,
15208 _: &UndoSelection,
15209 window: &mut Window,
15210 cx: &mut Context<Self>,
15211 ) {
15212 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15213 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15214 self.selection_history.mode = SelectionHistoryMode::Undoing;
15215 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15216 this.end_selection(window, cx);
15217 this.change_selections(
15218 SelectionEffects::scroll(Autoscroll::newest()),
15219 window,
15220 cx,
15221 |s| s.select_anchors(entry.selections.to_vec()),
15222 );
15223 });
15224 self.selection_history.mode = SelectionHistoryMode::Normal;
15225
15226 self.select_next_state = entry.select_next_state;
15227 self.select_prev_state = entry.select_prev_state;
15228 self.add_selections_state = entry.add_selections_state;
15229 }
15230 }
15231
15232 pub fn redo_selection(
15233 &mut self,
15234 _: &RedoSelection,
15235 window: &mut Window,
15236 cx: &mut Context<Self>,
15237 ) {
15238 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15239 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15240 self.selection_history.mode = SelectionHistoryMode::Redoing;
15241 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15242 this.end_selection(window, cx);
15243 this.change_selections(
15244 SelectionEffects::scroll(Autoscroll::newest()),
15245 window,
15246 cx,
15247 |s| s.select_anchors(entry.selections.to_vec()),
15248 );
15249 });
15250 self.selection_history.mode = SelectionHistoryMode::Normal;
15251
15252 self.select_next_state = entry.select_next_state;
15253 self.select_prev_state = entry.select_prev_state;
15254 self.add_selections_state = entry.add_selections_state;
15255 }
15256 }
15257
15258 pub fn expand_excerpts(
15259 &mut self,
15260 action: &ExpandExcerpts,
15261 _: &mut Window,
15262 cx: &mut Context<Self>,
15263 ) {
15264 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15265 }
15266
15267 pub fn expand_excerpts_down(
15268 &mut self,
15269 action: &ExpandExcerptsDown,
15270 _: &mut Window,
15271 cx: &mut Context<Self>,
15272 ) {
15273 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15274 }
15275
15276 pub fn expand_excerpts_up(
15277 &mut self,
15278 action: &ExpandExcerptsUp,
15279 _: &mut Window,
15280 cx: &mut Context<Self>,
15281 ) {
15282 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15283 }
15284
15285 pub fn expand_excerpts_for_direction(
15286 &mut self,
15287 lines: u32,
15288 direction: ExpandExcerptDirection,
15289
15290 cx: &mut Context<Self>,
15291 ) {
15292 let selections = self.selections.disjoint_anchors();
15293
15294 let lines = if lines == 0 {
15295 EditorSettings::get_global(cx).expand_excerpt_lines
15296 } else {
15297 lines
15298 };
15299
15300 self.buffer.update(cx, |buffer, cx| {
15301 let snapshot = buffer.snapshot(cx);
15302 let mut excerpt_ids = selections
15303 .iter()
15304 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15305 .collect::<Vec<_>>();
15306 excerpt_ids.sort();
15307 excerpt_ids.dedup();
15308 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15309 })
15310 }
15311
15312 pub fn expand_excerpt(
15313 &mut self,
15314 excerpt: ExcerptId,
15315 direction: ExpandExcerptDirection,
15316 window: &mut Window,
15317 cx: &mut Context<Self>,
15318 ) {
15319 let current_scroll_position = self.scroll_position(cx);
15320 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15321 let mut should_scroll_up = false;
15322
15323 if direction == ExpandExcerptDirection::Down {
15324 let multi_buffer = self.buffer.read(cx);
15325 let snapshot = multi_buffer.snapshot(cx);
15326 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15327 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15328 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15329 let buffer_snapshot = buffer.read(cx).snapshot();
15330 let excerpt_end_row =
15331 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15332 let last_row = buffer_snapshot.max_point().row;
15333 let lines_below = last_row.saturating_sub(excerpt_end_row);
15334 should_scroll_up = lines_below >= lines_to_expand;
15335 }
15336 }
15337 }
15338 }
15339
15340 self.buffer.update(cx, |buffer, cx| {
15341 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15342 });
15343
15344 if should_scroll_up {
15345 let new_scroll_position =
15346 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15347 self.set_scroll_position(new_scroll_position, window, cx);
15348 }
15349 }
15350
15351 pub fn go_to_singleton_buffer_point(
15352 &mut self,
15353 point: Point,
15354 window: &mut Window,
15355 cx: &mut Context<Self>,
15356 ) {
15357 self.go_to_singleton_buffer_range(point..point, window, cx);
15358 }
15359
15360 pub fn go_to_singleton_buffer_range(
15361 &mut self,
15362 range: Range<Point>,
15363 window: &mut Window,
15364 cx: &mut Context<Self>,
15365 ) {
15366 let multibuffer = self.buffer().read(cx);
15367 let Some(buffer) = multibuffer.as_singleton() else {
15368 return;
15369 };
15370 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15371 return;
15372 };
15373 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15374 return;
15375 };
15376 self.change_selections(
15377 SelectionEffects::default().nav_history(true),
15378 window,
15379 cx,
15380 |s| s.select_anchor_ranges([start..end]),
15381 );
15382 }
15383
15384 pub fn go_to_diagnostic(
15385 &mut self,
15386 action: &GoToDiagnostic,
15387 window: &mut Window,
15388 cx: &mut Context<Self>,
15389 ) {
15390 if !self.diagnostics_enabled() {
15391 return;
15392 }
15393 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15394 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15395 }
15396
15397 pub fn go_to_prev_diagnostic(
15398 &mut self,
15399 action: &GoToPreviousDiagnostic,
15400 window: &mut Window,
15401 cx: &mut Context<Self>,
15402 ) {
15403 if !self.diagnostics_enabled() {
15404 return;
15405 }
15406 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15407 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15408 }
15409
15410 pub fn go_to_diagnostic_impl(
15411 &mut self,
15412 direction: Direction,
15413 severity: GoToDiagnosticSeverityFilter,
15414 window: &mut Window,
15415 cx: &mut Context<Self>,
15416 ) {
15417 let buffer = self.buffer.read(cx).snapshot(cx);
15418 let selection = self.selections.newest::<usize>(cx);
15419
15420 let mut active_group_id = None;
15421 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15422 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15423 active_group_id = Some(active_group.group_id);
15424 }
15425 }
15426
15427 fn filtered(
15428 snapshot: EditorSnapshot,
15429 severity: GoToDiagnosticSeverityFilter,
15430 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15431 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15432 diagnostics
15433 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15434 .filter(|entry| entry.range.start != entry.range.end)
15435 .filter(|entry| !entry.diagnostic.is_unnecessary)
15436 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15437 }
15438
15439 let snapshot = self.snapshot(window, cx);
15440 let before = filtered(
15441 snapshot.clone(),
15442 severity,
15443 buffer
15444 .diagnostics_in_range(0..selection.start)
15445 .filter(|entry| entry.range.start <= selection.start),
15446 );
15447 let after = filtered(
15448 snapshot,
15449 severity,
15450 buffer
15451 .diagnostics_in_range(selection.start..buffer.len())
15452 .filter(|entry| entry.range.start >= selection.start),
15453 );
15454
15455 let mut found: Option<DiagnosticEntry<usize>> = None;
15456 if direction == Direction::Prev {
15457 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15458 {
15459 for diagnostic in prev_diagnostics.into_iter().rev() {
15460 if diagnostic.range.start != selection.start
15461 || active_group_id
15462 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15463 {
15464 found = Some(diagnostic);
15465 break 'outer;
15466 }
15467 }
15468 }
15469 } else {
15470 for diagnostic in after.chain(before) {
15471 if diagnostic.range.start != selection.start
15472 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15473 {
15474 found = Some(diagnostic);
15475 break;
15476 }
15477 }
15478 }
15479 let Some(next_diagnostic) = found else {
15480 return;
15481 };
15482
15483 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15484 return;
15485 };
15486 self.change_selections(Default::default(), window, cx, |s| {
15487 s.select_ranges(vec![
15488 next_diagnostic.range.start..next_diagnostic.range.start,
15489 ])
15490 });
15491 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15492 self.refresh_edit_prediction(false, true, window, cx);
15493 }
15494
15495 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15496 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15497 let snapshot = self.snapshot(window, cx);
15498 let selection = self.selections.newest::<Point>(cx);
15499 self.go_to_hunk_before_or_after_position(
15500 &snapshot,
15501 selection.head(),
15502 Direction::Next,
15503 window,
15504 cx,
15505 );
15506 }
15507
15508 pub fn go_to_hunk_before_or_after_position(
15509 &mut self,
15510 snapshot: &EditorSnapshot,
15511 position: Point,
15512 direction: Direction,
15513 window: &mut Window,
15514 cx: &mut Context<Editor>,
15515 ) {
15516 let row = if direction == Direction::Next {
15517 self.hunk_after_position(snapshot, position)
15518 .map(|hunk| hunk.row_range.start)
15519 } else {
15520 self.hunk_before_position(snapshot, position)
15521 };
15522
15523 if let Some(row) = row {
15524 let destination = Point::new(row.0, 0);
15525 let autoscroll = Autoscroll::center();
15526
15527 self.unfold_ranges(&[destination..destination], false, false, cx);
15528 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15529 s.select_ranges([destination..destination]);
15530 });
15531 }
15532 }
15533
15534 fn hunk_after_position(
15535 &mut self,
15536 snapshot: &EditorSnapshot,
15537 position: Point,
15538 ) -> Option<MultiBufferDiffHunk> {
15539 snapshot
15540 .buffer_snapshot
15541 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15542 .find(|hunk| hunk.row_range.start.0 > position.row)
15543 .or_else(|| {
15544 snapshot
15545 .buffer_snapshot
15546 .diff_hunks_in_range(Point::zero()..position)
15547 .find(|hunk| hunk.row_range.end.0 < position.row)
15548 })
15549 }
15550
15551 fn go_to_prev_hunk(
15552 &mut self,
15553 _: &GoToPreviousHunk,
15554 window: &mut Window,
15555 cx: &mut Context<Self>,
15556 ) {
15557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15558 let snapshot = self.snapshot(window, cx);
15559 let selection = self.selections.newest::<Point>(cx);
15560 self.go_to_hunk_before_or_after_position(
15561 &snapshot,
15562 selection.head(),
15563 Direction::Prev,
15564 window,
15565 cx,
15566 );
15567 }
15568
15569 fn hunk_before_position(
15570 &mut self,
15571 snapshot: &EditorSnapshot,
15572 position: Point,
15573 ) -> Option<MultiBufferRow> {
15574 snapshot
15575 .buffer_snapshot
15576 .diff_hunk_before(position)
15577 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15578 }
15579
15580 fn go_to_next_change(
15581 &mut self,
15582 _: &GoToNextChange,
15583 window: &mut Window,
15584 cx: &mut Context<Self>,
15585 ) {
15586 if let Some(selections) = self
15587 .change_list
15588 .next_change(1, Direction::Next)
15589 .map(|s| s.to_vec())
15590 {
15591 self.change_selections(Default::default(), window, cx, |s| {
15592 let map = s.display_map();
15593 s.select_display_ranges(selections.iter().map(|a| {
15594 let point = a.to_display_point(&map);
15595 point..point
15596 }))
15597 })
15598 }
15599 }
15600
15601 fn go_to_previous_change(
15602 &mut self,
15603 _: &GoToPreviousChange,
15604 window: &mut Window,
15605 cx: &mut Context<Self>,
15606 ) {
15607 if let Some(selections) = self
15608 .change_list
15609 .next_change(1, Direction::Prev)
15610 .map(|s| s.to_vec())
15611 {
15612 self.change_selections(Default::default(), window, cx, |s| {
15613 let map = s.display_map();
15614 s.select_display_ranges(selections.iter().map(|a| {
15615 let point = a.to_display_point(&map);
15616 point..point
15617 }))
15618 })
15619 }
15620 }
15621
15622 fn go_to_line<T: 'static>(
15623 &mut self,
15624 position: Anchor,
15625 highlight_color: Option<Hsla>,
15626 window: &mut Window,
15627 cx: &mut Context<Self>,
15628 ) {
15629 let snapshot = self.snapshot(window, cx).display_snapshot;
15630 let position = position.to_point(&snapshot.buffer_snapshot);
15631 let start = snapshot
15632 .buffer_snapshot
15633 .clip_point(Point::new(position.row, 0), Bias::Left);
15634 let end = start + Point::new(1, 0);
15635 let start = snapshot.buffer_snapshot.anchor_before(start);
15636 let end = snapshot.buffer_snapshot.anchor_before(end);
15637
15638 self.highlight_rows::<T>(
15639 start..end,
15640 highlight_color
15641 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15642 Default::default(),
15643 cx,
15644 );
15645
15646 if self.buffer.read(cx).is_singleton() {
15647 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15648 }
15649 }
15650
15651 pub fn go_to_definition(
15652 &mut self,
15653 _: &GoToDefinition,
15654 window: &mut Window,
15655 cx: &mut Context<Self>,
15656 ) -> Task<Result<Navigated>> {
15657 let definition =
15658 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15659 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15660 cx.spawn_in(window, async move |editor, cx| {
15661 if definition.await? == Navigated::Yes {
15662 return Ok(Navigated::Yes);
15663 }
15664 match fallback_strategy {
15665 GoToDefinitionFallback::None => Ok(Navigated::No),
15666 GoToDefinitionFallback::FindAllReferences => {
15667 match editor.update_in(cx, |editor, window, cx| {
15668 editor.find_all_references(&FindAllReferences, window, cx)
15669 })? {
15670 Some(references) => references.await,
15671 None => Ok(Navigated::No),
15672 }
15673 }
15674 }
15675 })
15676 }
15677
15678 pub fn go_to_declaration(
15679 &mut self,
15680 _: &GoToDeclaration,
15681 window: &mut Window,
15682 cx: &mut Context<Self>,
15683 ) -> Task<Result<Navigated>> {
15684 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15685 }
15686
15687 pub fn go_to_declaration_split(
15688 &mut self,
15689 _: &GoToDeclaration,
15690 window: &mut Window,
15691 cx: &mut Context<Self>,
15692 ) -> Task<Result<Navigated>> {
15693 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15694 }
15695
15696 pub fn go_to_implementation(
15697 &mut self,
15698 _: &GoToImplementation,
15699 window: &mut Window,
15700 cx: &mut Context<Self>,
15701 ) -> Task<Result<Navigated>> {
15702 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15703 }
15704
15705 pub fn go_to_implementation_split(
15706 &mut self,
15707 _: &GoToImplementationSplit,
15708 window: &mut Window,
15709 cx: &mut Context<Self>,
15710 ) -> Task<Result<Navigated>> {
15711 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15712 }
15713
15714 pub fn go_to_type_definition(
15715 &mut self,
15716 _: &GoToTypeDefinition,
15717 window: &mut Window,
15718 cx: &mut Context<Self>,
15719 ) -> Task<Result<Navigated>> {
15720 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15721 }
15722
15723 pub fn go_to_definition_split(
15724 &mut self,
15725 _: &GoToDefinitionSplit,
15726 window: &mut Window,
15727 cx: &mut Context<Self>,
15728 ) -> Task<Result<Navigated>> {
15729 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15730 }
15731
15732 pub fn go_to_type_definition_split(
15733 &mut self,
15734 _: &GoToTypeDefinitionSplit,
15735 window: &mut Window,
15736 cx: &mut Context<Self>,
15737 ) -> Task<Result<Navigated>> {
15738 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15739 }
15740
15741 fn go_to_definition_of_kind(
15742 &mut self,
15743 kind: GotoDefinitionKind,
15744 split: bool,
15745 window: &mut Window,
15746 cx: &mut Context<Self>,
15747 ) -> Task<Result<Navigated>> {
15748 let Some(provider) = self.semantics_provider.clone() else {
15749 return Task::ready(Ok(Navigated::No));
15750 };
15751 let head = self.selections.newest::<usize>(cx).head();
15752 let buffer = self.buffer.read(cx);
15753 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15754 return Task::ready(Ok(Navigated::No));
15755 };
15756 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15757 return Task::ready(Ok(Navigated::No));
15758 };
15759
15760 cx.spawn_in(window, async move |editor, cx| {
15761 let definitions = definitions.await?;
15762 let navigated = editor
15763 .update_in(cx, |editor, window, cx| {
15764 editor.navigate_to_hover_links(
15765 Some(kind),
15766 definitions
15767 .into_iter()
15768 .filter(|location| {
15769 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15770 })
15771 .map(HoverLink::Text)
15772 .collect::<Vec<_>>(),
15773 split,
15774 window,
15775 cx,
15776 )
15777 })?
15778 .await?;
15779 anyhow::Ok(navigated)
15780 })
15781 }
15782
15783 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15784 let selection = self.selections.newest_anchor();
15785 let head = selection.head();
15786 let tail = selection.tail();
15787
15788 let Some((buffer, start_position)) =
15789 self.buffer.read(cx).text_anchor_for_position(head, cx)
15790 else {
15791 return;
15792 };
15793
15794 let end_position = if head != tail {
15795 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15796 return;
15797 };
15798 Some(pos)
15799 } else {
15800 None
15801 };
15802
15803 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15804 let url = if let Some(end_pos) = end_position {
15805 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15806 } else {
15807 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15808 };
15809
15810 if let Some(url) = url {
15811 editor.update(cx, |_, cx| {
15812 cx.open_url(&url);
15813 })
15814 } else {
15815 Ok(())
15816 }
15817 });
15818
15819 url_finder.detach();
15820 }
15821
15822 pub fn open_selected_filename(
15823 &mut self,
15824 _: &OpenSelectedFilename,
15825 window: &mut Window,
15826 cx: &mut Context<Self>,
15827 ) {
15828 let Some(workspace) = self.workspace() else {
15829 return;
15830 };
15831
15832 let position = self.selections.newest_anchor().head();
15833
15834 let Some((buffer, buffer_position)) =
15835 self.buffer.read(cx).text_anchor_for_position(position, cx)
15836 else {
15837 return;
15838 };
15839
15840 let project = self.project.clone();
15841
15842 cx.spawn_in(window, async move |_, cx| {
15843 let result = find_file(&buffer, project, buffer_position, cx).await;
15844
15845 if let Some((_, path)) = result {
15846 workspace
15847 .update_in(cx, |workspace, window, cx| {
15848 workspace.open_resolved_path(path, window, cx)
15849 })?
15850 .await?;
15851 }
15852 anyhow::Ok(())
15853 })
15854 .detach();
15855 }
15856
15857 pub(crate) fn navigate_to_hover_links(
15858 &mut self,
15859 kind: Option<GotoDefinitionKind>,
15860 definitions: Vec<HoverLink>,
15861 split: bool,
15862 window: &mut Window,
15863 cx: &mut Context<Editor>,
15864 ) -> Task<Result<Navigated>> {
15865 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15866 let mut first_url_or_file = None;
15867 let definitions: Vec<_> = definitions
15868 .into_iter()
15869 .filter_map(|def| match def {
15870 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15871 HoverLink::InlayHint(lsp_location, server_id) => {
15872 let computation =
15873 self.compute_target_location(lsp_location, server_id, window, cx);
15874 Some(cx.background_spawn(computation))
15875 }
15876 HoverLink::Url(url) => {
15877 first_url_or_file = Some(Either::Left(url));
15878 None
15879 }
15880 HoverLink::File(path) => {
15881 first_url_or_file = Some(Either::Right(path));
15882 None
15883 }
15884 })
15885 .collect();
15886
15887 let workspace = self.workspace();
15888
15889 cx.spawn_in(window, async move |editor, acx| {
15890 let mut locations: Vec<Location> = future::join_all(definitions)
15891 .await
15892 .into_iter()
15893 .filter_map(|location| location.transpose())
15894 .collect::<Result<_>>()
15895 .context("location tasks")?;
15896
15897 if locations.len() > 1 {
15898 let Some(workspace) = workspace else {
15899 return Ok(Navigated::No);
15900 };
15901
15902 let tab_kind = match kind {
15903 Some(GotoDefinitionKind::Implementation) => "Implementations",
15904 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15905 Some(GotoDefinitionKind::Declaration) => "Declarations",
15906 Some(GotoDefinitionKind::Type) => "Types",
15907 };
15908 let title = editor
15909 .update_in(acx, |_, _, cx| {
15910 let target = locations
15911 .iter()
15912 .map(|location| {
15913 location
15914 .buffer
15915 .read(cx)
15916 .text_for_range(location.range.clone())
15917 .collect::<String>()
15918 })
15919 .filter(|text| !text.contains('\n'))
15920 .unique()
15921 .take(3)
15922 .join(", ");
15923 if target.is_empty() {
15924 tab_kind.to_owned()
15925 } else {
15926 format!("{tab_kind} for {target}")
15927 }
15928 })
15929 .context("buffer title")?;
15930
15931 let opened = workspace
15932 .update_in(acx, |workspace, window, cx| {
15933 Self::open_locations_in_multibuffer(
15934 workspace,
15935 locations,
15936 title,
15937 split,
15938 MultibufferSelectionMode::First,
15939 window,
15940 cx,
15941 )
15942 })
15943 .is_ok();
15944
15945 anyhow::Ok(Navigated::from_bool(opened))
15946 } else if locations.is_empty() {
15947 // If there is one definition, just open it directly
15948 match first_url_or_file {
15949 Some(Either::Left(url)) => {
15950 acx.update(|_, cx| cx.open_url(&url))?;
15951 Ok(Navigated::Yes)
15952 }
15953 Some(Either::Right(path)) => {
15954 let Some(workspace) = workspace else {
15955 return Ok(Navigated::No);
15956 };
15957
15958 workspace
15959 .update_in(acx, |workspace, window, cx| {
15960 workspace.open_resolved_path(path, window, cx)
15961 })?
15962 .await?;
15963 Ok(Navigated::Yes)
15964 }
15965 None => Ok(Navigated::No),
15966 }
15967 } else {
15968 let Some(workspace) = workspace else {
15969 return Ok(Navigated::No);
15970 };
15971
15972 let target = locations.pop().unwrap();
15973 editor.update_in(acx, |editor, window, cx| {
15974 let pane = workspace.read(cx).active_pane().clone();
15975
15976 let range = target.range.to_point(target.buffer.read(cx));
15977 let range = editor.range_for_match(&range);
15978 let range = collapse_multiline_range(range);
15979
15980 if !split
15981 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15982 {
15983 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15984 } else {
15985 window.defer(cx, move |window, cx| {
15986 let target_editor: Entity<Self> =
15987 workspace.update(cx, |workspace, cx| {
15988 let pane = if split {
15989 workspace.adjacent_pane(window, cx)
15990 } else {
15991 workspace.active_pane().clone()
15992 };
15993
15994 workspace.open_project_item(
15995 pane,
15996 target.buffer.clone(),
15997 true,
15998 true,
15999 window,
16000 cx,
16001 )
16002 });
16003 target_editor.update(cx, |target_editor, cx| {
16004 // When selecting a definition in a different buffer, disable the nav history
16005 // to avoid creating a history entry at the previous cursor location.
16006 pane.update(cx, |pane, _| pane.disable_history());
16007 target_editor.go_to_singleton_buffer_range(range, window, cx);
16008 pane.update(cx, |pane, _| pane.enable_history());
16009 });
16010 });
16011 }
16012 Navigated::Yes
16013 })
16014 }
16015 })
16016 }
16017
16018 fn compute_target_location(
16019 &self,
16020 lsp_location: lsp::Location,
16021 server_id: LanguageServerId,
16022 window: &mut Window,
16023 cx: &mut Context<Self>,
16024 ) -> Task<anyhow::Result<Option<Location>>> {
16025 let Some(project) = self.project.clone() else {
16026 return Task::ready(Ok(None));
16027 };
16028
16029 cx.spawn_in(window, async move |editor, cx| {
16030 let location_task = editor.update(cx, |_, cx| {
16031 project.update(cx, |project, cx| {
16032 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16033 })
16034 })?;
16035 let location = Some({
16036 let target_buffer_handle = location_task.await.context("open local buffer")?;
16037 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16038 let target_start = target_buffer
16039 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16040 let target_end = target_buffer
16041 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16042 target_buffer.anchor_after(target_start)
16043 ..target_buffer.anchor_before(target_end)
16044 })?;
16045 Location {
16046 buffer: target_buffer_handle,
16047 range,
16048 }
16049 });
16050 Ok(location)
16051 })
16052 }
16053
16054 pub fn find_all_references(
16055 &mut self,
16056 _: &FindAllReferences,
16057 window: &mut Window,
16058 cx: &mut Context<Self>,
16059 ) -> Option<Task<Result<Navigated>>> {
16060 let selection = self.selections.newest::<usize>(cx);
16061 let multi_buffer = self.buffer.read(cx);
16062 let head = selection.head();
16063
16064 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16065 let head_anchor = multi_buffer_snapshot.anchor_at(
16066 head,
16067 if head < selection.tail() {
16068 Bias::Right
16069 } else {
16070 Bias::Left
16071 },
16072 );
16073
16074 match self
16075 .find_all_references_task_sources
16076 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16077 {
16078 Ok(_) => {
16079 log::info!(
16080 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16081 );
16082 return None;
16083 }
16084 Err(i) => {
16085 self.find_all_references_task_sources.insert(i, head_anchor);
16086 }
16087 }
16088
16089 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16090 let workspace = self.workspace()?;
16091 let project = workspace.read(cx).project().clone();
16092 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16093 Some(cx.spawn_in(window, async move |editor, cx| {
16094 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16095 if let Ok(i) = editor
16096 .find_all_references_task_sources
16097 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16098 {
16099 editor.find_all_references_task_sources.remove(i);
16100 }
16101 });
16102
16103 let locations = references.await?;
16104 if locations.is_empty() {
16105 return anyhow::Ok(Navigated::No);
16106 }
16107
16108 workspace.update_in(cx, |workspace, window, cx| {
16109 let target = locations
16110 .iter()
16111 .map(|location| {
16112 location
16113 .buffer
16114 .read(cx)
16115 .text_for_range(location.range.clone())
16116 .collect::<String>()
16117 })
16118 .filter(|text| !text.contains('\n'))
16119 .unique()
16120 .take(3)
16121 .join(", ");
16122 let title = if target.is_empty() {
16123 "References".to_owned()
16124 } else {
16125 format!("References to {target}")
16126 };
16127 Self::open_locations_in_multibuffer(
16128 workspace,
16129 locations,
16130 title,
16131 false,
16132 MultibufferSelectionMode::First,
16133 window,
16134 cx,
16135 );
16136 Navigated::Yes
16137 })
16138 }))
16139 }
16140
16141 /// Opens a multibuffer with the given project locations in it
16142 pub fn open_locations_in_multibuffer(
16143 workspace: &mut Workspace,
16144 mut locations: Vec<Location>,
16145 title: String,
16146 split: bool,
16147 multibuffer_selection_mode: MultibufferSelectionMode,
16148 window: &mut Window,
16149 cx: &mut Context<Workspace>,
16150 ) {
16151 if locations.is_empty() {
16152 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16153 return;
16154 }
16155
16156 // If there are multiple definitions, open them in a multibuffer
16157 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16158 let mut locations = locations.into_iter().peekable();
16159 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16160 let capability = workspace.project().read(cx).capability();
16161
16162 let excerpt_buffer = cx.new(|cx| {
16163 let mut multibuffer = MultiBuffer::new(capability);
16164 while let Some(location) = locations.next() {
16165 let buffer = location.buffer.read(cx);
16166 let mut ranges_for_buffer = Vec::new();
16167 let range = location.range.to_point(buffer);
16168 ranges_for_buffer.push(range.clone());
16169
16170 while let Some(next_location) = locations.peek() {
16171 if next_location.buffer == location.buffer {
16172 ranges_for_buffer.push(next_location.range.to_point(buffer));
16173 locations.next();
16174 } else {
16175 break;
16176 }
16177 }
16178
16179 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16180 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16181 PathKey::for_buffer(&location.buffer, cx),
16182 location.buffer.clone(),
16183 ranges_for_buffer,
16184 DEFAULT_MULTIBUFFER_CONTEXT,
16185 cx,
16186 );
16187 ranges.extend(new_ranges)
16188 }
16189
16190 multibuffer.with_title(title)
16191 });
16192
16193 let editor = cx.new(|cx| {
16194 Editor::for_multibuffer(
16195 excerpt_buffer,
16196 Some(workspace.project().clone()),
16197 window,
16198 cx,
16199 )
16200 });
16201 editor.update(cx, |editor, cx| {
16202 match multibuffer_selection_mode {
16203 MultibufferSelectionMode::First => {
16204 if let Some(first_range) = ranges.first() {
16205 editor.change_selections(
16206 SelectionEffects::no_scroll(),
16207 window,
16208 cx,
16209 |selections| {
16210 selections.clear_disjoint();
16211 selections
16212 .select_anchor_ranges(std::iter::once(first_range.clone()));
16213 },
16214 );
16215 }
16216 editor.highlight_background::<Self>(
16217 &ranges,
16218 |theme| theme.colors().editor_highlighted_line_background,
16219 cx,
16220 );
16221 }
16222 MultibufferSelectionMode::All => {
16223 editor.change_selections(
16224 SelectionEffects::no_scroll(),
16225 window,
16226 cx,
16227 |selections| {
16228 selections.clear_disjoint();
16229 selections.select_anchor_ranges(ranges);
16230 },
16231 );
16232 }
16233 }
16234 editor.register_buffers_with_language_servers(cx);
16235 });
16236
16237 let item = Box::new(editor);
16238 let item_id = item.item_id();
16239
16240 if split {
16241 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16242 } else {
16243 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16244 let (preview_item_id, preview_item_idx) =
16245 workspace.active_pane().read_with(cx, |pane, _| {
16246 (pane.preview_item_id(), pane.preview_item_idx())
16247 });
16248
16249 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16250
16251 if let Some(preview_item_id) = preview_item_id {
16252 workspace.active_pane().update(cx, |pane, cx| {
16253 pane.remove_item(preview_item_id, false, false, window, cx);
16254 });
16255 }
16256 } else {
16257 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16258 }
16259 }
16260 workspace.active_pane().update(cx, |pane, cx| {
16261 pane.set_preview_item_id(Some(item_id), cx);
16262 });
16263 }
16264
16265 pub fn rename(
16266 &mut self,
16267 _: &Rename,
16268 window: &mut Window,
16269 cx: &mut Context<Self>,
16270 ) -> Option<Task<Result<()>>> {
16271 use language::ToOffset as _;
16272
16273 let provider = self.semantics_provider.clone()?;
16274 let selection = self.selections.newest_anchor().clone();
16275 let (cursor_buffer, cursor_buffer_position) = self
16276 .buffer
16277 .read(cx)
16278 .text_anchor_for_position(selection.head(), cx)?;
16279 let (tail_buffer, cursor_buffer_position_end) = self
16280 .buffer
16281 .read(cx)
16282 .text_anchor_for_position(selection.tail(), cx)?;
16283 if tail_buffer != cursor_buffer {
16284 return None;
16285 }
16286
16287 let snapshot = cursor_buffer.read(cx).snapshot();
16288 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16289 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16290 let prepare_rename = provider
16291 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16292 .unwrap_or_else(|| Task::ready(Ok(None)));
16293 drop(snapshot);
16294
16295 Some(cx.spawn_in(window, async move |this, cx| {
16296 let rename_range = if let Some(range) = prepare_rename.await? {
16297 Some(range)
16298 } else {
16299 this.update(cx, |this, cx| {
16300 let buffer = this.buffer.read(cx).snapshot(cx);
16301 let mut buffer_highlights = this
16302 .document_highlights_for_position(selection.head(), &buffer)
16303 .filter(|highlight| {
16304 highlight.start.excerpt_id == selection.head().excerpt_id
16305 && highlight.end.excerpt_id == selection.head().excerpt_id
16306 });
16307 buffer_highlights
16308 .next()
16309 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16310 })?
16311 };
16312 if let Some(rename_range) = rename_range {
16313 this.update_in(cx, |this, window, cx| {
16314 let snapshot = cursor_buffer.read(cx).snapshot();
16315 let rename_buffer_range = rename_range.to_offset(&snapshot);
16316 let cursor_offset_in_rename_range =
16317 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16318 let cursor_offset_in_rename_range_end =
16319 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16320
16321 this.take_rename(false, window, cx);
16322 let buffer = this.buffer.read(cx).read(cx);
16323 let cursor_offset = selection.head().to_offset(&buffer);
16324 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16325 let rename_end = rename_start + rename_buffer_range.len();
16326 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16327 let mut old_highlight_id = None;
16328 let old_name: Arc<str> = buffer
16329 .chunks(rename_start..rename_end, true)
16330 .map(|chunk| {
16331 if old_highlight_id.is_none() {
16332 old_highlight_id = chunk.syntax_highlight_id;
16333 }
16334 chunk.text
16335 })
16336 .collect::<String>()
16337 .into();
16338
16339 drop(buffer);
16340
16341 // Position the selection in the rename editor so that it matches the current selection.
16342 this.show_local_selections = false;
16343 let rename_editor = cx.new(|cx| {
16344 let mut editor = Editor::single_line(window, cx);
16345 editor.buffer.update(cx, |buffer, cx| {
16346 buffer.edit([(0..0, old_name.clone())], None, cx)
16347 });
16348 let rename_selection_range = match cursor_offset_in_rename_range
16349 .cmp(&cursor_offset_in_rename_range_end)
16350 {
16351 Ordering::Equal => {
16352 editor.select_all(&SelectAll, window, cx);
16353 return editor;
16354 }
16355 Ordering::Less => {
16356 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16357 }
16358 Ordering::Greater => {
16359 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16360 }
16361 };
16362 if rename_selection_range.end > old_name.len() {
16363 editor.select_all(&SelectAll, window, cx);
16364 } else {
16365 editor.change_selections(Default::default(), window, cx, |s| {
16366 s.select_ranges([rename_selection_range]);
16367 });
16368 }
16369 editor
16370 });
16371 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16372 if e == &EditorEvent::Focused {
16373 cx.emit(EditorEvent::FocusedIn)
16374 }
16375 })
16376 .detach();
16377
16378 let write_highlights =
16379 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16380 let read_highlights =
16381 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16382 let ranges = write_highlights
16383 .iter()
16384 .flat_map(|(_, ranges)| ranges.iter())
16385 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16386 .cloned()
16387 .collect();
16388
16389 this.highlight_text::<Rename>(
16390 ranges,
16391 HighlightStyle {
16392 fade_out: Some(0.6),
16393 ..Default::default()
16394 },
16395 cx,
16396 );
16397 let rename_focus_handle = rename_editor.focus_handle(cx);
16398 window.focus(&rename_focus_handle);
16399 let block_id = this.insert_blocks(
16400 [BlockProperties {
16401 style: BlockStyle::Flex,
16402 placement: BlockPlacement::Below(range.start),
16403 height: Some(1),
16404 render: Arc::new({
16405 let rename_editor = rename_editor.clone();
16406 move |cx: &mut BlockContext| {
16407 let mut text_style = cx.editor_style.text.clone();
16408 if let Some(highlight_style) = old_highlight_id
16409 .and_then(|h| h.style(&cx.editor_style.syntax))
16410 {
16411 text_style = text_style.highlight(highlight_style);
16412 }
16413 div()
16414 .block_mouse_except_scroll()
16415 .pl(cx.anchor_x)
16416 .child(EditorElement::new(
16417 &rename_editor,
16418 EditorStyle {
16419 background: cx.theme().system().transparent,
16420 local_player: cx.editor_style.local_player,
16421 text: text_style,
16422 scrollbar_width: cx.editor_style.scrollbar_width,
16423 syntax: cx.editor_style.syntax.clone(),
16424 status: cx.editor_style.status.clone(),
16425 inlay_hints_style: HighlightStyle {
16426 font_weight: Some(FontWeight::BOLD),
16427 ..make_inlay_hints_style(cx.app)
16428 },
16429 edit_prediction_styles: make_suggestion_styles(
16430 cx.app,
16431 ),
16432 ..EditorStyle::default()
16433 },
16434 ))
16435 .into_any_element()
16436 }
16437 }),
16438 priority: 0,
16439 }],
16440 Some(Autoscroll::fit()),
16441 cx,
16442 )[0];
16443 this.pending_rename = Some(RenameState {
16444 range,
16445 old_name,
16446 editor: rename_editor,
16447 block_id,
16448 });
16449 })?;
16450 }
16451
16452 Ok(())
16453 }))
16454 }
16455
16456 pub fn confirm_rename(
16457 &mut self,
16458 _: &ConfirmRename,
16459 window: &mut Window,
16460 cx: &mut Context<Self>,
16461 ) -> Option<Task<Result<()>>> {
16462 let rename = self.take_rename(false, window, cx)?;
16463 let workspace = self.workspace()?.downgrade();
16464 let (buffer, start) = self
16465 .buffer
16466 .read(cx)
16467 .text_anchor_for_position(rename.range.start, cx)?;
16468 let (end_buffer, _) = self
16469 .buffer
16470 .read(cx)
16471 .text_anchor_for_position(rename.range.end, cx)?;
16472 if buffer != end_buffer {
16473 return None;
16474 }
16475
16476 let old_name = rename.old_name;
16477 let new_name = rename.editor.read(cx).text(cx);
16478
16479 let rename = self.semantics_provider.as_ref()?.perform_rename(
16480 &buffer,
16481 start,
16482 new_name.clone(),
16483 cx,
16484 )?;
16485
16486 Some(cx.spawn_in(window, async move |editor, cx| {
16487 let project_transaction = rename.await?;
16488 Self::open_project_transaction(
16489 &editor,
16490 workspace,
16491 project_transaction,
16492 format!("Rename: {} → {}", old_name, new_name),
16493 cx,
16494 )
16495 .await?;
16496
16497 editor.update(cx, |editor, cx| {
16498 editor.refresh_document_highlights(cx);
16499 })?;
16500 Ok(())
16501 }))
16502 }
16503
16504 fn take_rename(
16505 &mut self,
16506 moving_cursor: bool,
16507 window: &mut Window,
16508 cx: &mut Context<Self>,
16509 ) -> Option<RenameState> {
16510 let rename = self.pending_rename.take()?;
16511 if rename.editor.focus_handle(cx).is_focused(window) {
16512 window.focus(&self.focus_handle);
16513 }
16514
16515 self.remove_blocks(
16516 [rename.block_id].into_iter().collect(),
16517 Some(Autoscroll::fit()),
16518 cx,
16519 );
16520 self.clear_highlights::<Rename>(cx);
16521 self.show_local_selections = true;
16522
16523 if moving_cursor {
16524 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16525 editor.selections.newest::<usize>(cx).head()
16526 });
16527
16528 // Update the selection to match the position of the selection inside
16529 // the rename editor.
16530 let snapshot = self.buffer.read(cx).read(cx);
16531 let rename_range = rename.range.to_offset(&snapshot);
16532 let cursor_in_editor = snapshot
16533 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16534 .min(rename_range.end);
16535 drop(snapshot);
16536
16537 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16538 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16539 });
16540 } else {
16541 self.refresh_document_highlights(cx);
16542 }
16543
16544 Some(rename)
16545 }
16546
16547 pub fn pending_rename(&self) -> Option<&RenameState> {
16548 self.pending_rename.as_ref()
16549 }
16550
16551 fn format(
16552 &mut self,
16553 _: &Format,
16554 window: &mut Window,
16555 cx: &mut Context<Self>,
16556 ) -> Option<Task<Result<()>>> {
16557 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16558
16559 let project = match &self.project {
16560 Some(project) => project.clone(),
16561 None => return None,
16562 };
16563
16564 Some(self.perform_format(
16565 project,
16566 FormatTrigger::Manual,
16567 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16568 window,
16569 cx,
16570 ))
16571 }
16572
16573 fn format_selections(
16574 &mut self,
16575 _: &FormatSelections,
16576 window: &mut Window,
16577 cx: &mut Context<Self>,
16578 ) -> Option<Task<Result<()>>> {
16579 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16580
16581 let project = match &self.project {
16582 Some(project) => project.clone(),
16583 None => return None,
16584 };
16585
16586 let ranges = self
16587 .selections
16588 .all_adjusted(cx)
16589 .into_iter()
16590 .map(|selection| selection.range())
16591 .collect_vec();
16592
16593 Some(self.perform_format(
16594 project,
16595 FormatTrigger::Manual,
16596 FormatTarget::Ranges(ranges),
16597 window,
16598 cx,
16599 ))
16600 }
16601
16602 fn perform_format(
16603 &mut self,
16604 project: Entity<Project>,
16605 trigger: FormatTrigger,
16606 target: FormatTarget,
16607 window: &mut Window,
16608 cx: &mut Context<Self>,
16609 ) -> Task<Result<()>> {
16610 let buffer = self.buffer.clone();
16611 let (buffers, target) = match target {
16612 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16613 FormatTarget::Ranges(selection_ranges) => {
16614 let multi_buffer = buffer.read(cx);
16615 let snapshot = multi_buffer.read(cx);
16616 let mut buffers = HashSet::default();
16617 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16618 BTreeMap::new();
16619 for selection_range in selection_ranges {
16620 for (buffer, buffer_range, _) in
16621 snapshot.range_to_buffer_ranges(selection_range)
16622 {
16623 let buffer_id = buffer.remote_id();
16624 let start = buffer.anchor_before(buffer_range.start);
16625 let end = buffer.anchor_after(buffer_range.end);
16626 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16627 buffer_id_to_ranges
16628 .entry(buffer_id)
16629 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16630 .or_insert_with(|| vec![start..end]);
16631 }
16632 }
16633 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16634 }
16635 };
16636
16637 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16638 let selections_prev = transaction_id_prev
16639 .and_then(|transaction_id_prev| {
16640 // default to selections as they were after the last edit, if we have them,
16641 // instead of how they are now.
16642 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16643 // will take you back to where you made the last edit, instead of staying where you scrolled
16644 self.selection_history
16645 .transaction(transaction_id_prev)
16646 .map(|t| t.0.clone())
16647 })
16648 .unwrap_or_else(|| {
16649 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16650 self.selections.disjoint_anchors()
16651 });
16652
16653 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16654 let format = project.update(cx, |project, cx| {
16655 project.format(buffers, target, true, trigger, cx)
16656 });
16657
16658 cx.spawn_in(window, async move |editor, cx| {
16659 let transaction = futures::select_biased! {
16660 transaction = format.log_err().fuse() => transaction,
16661 () = timeout => {
16662 log::warn!("timed out waiting for formatting");
16663 None
16664 }
16665 };
16666
16667 buffer
16668 .update(cx, |buffer, cx| {
16669 if let Some(transaction) = transaction {
16670 if !buffer.is_singleton() {
16671 buffer.push_transaction(&transaction.0, cx);
16672 }
16673 }
16674 cx.notify();
16675 })
16676 .ok();
16677
16678 if let Some(transaction_id_now) =
16679 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16680 {
16681 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16682 if has_new_transaction {
16683 _ = editor.update(cx, |editor, _| {
16684 editor
16685 .selection_history
16686 .insert_transaction(transaction_id_now, selections_prev);
16687 });
16688 }
16689 }
16690
16691 Ok(())
16692 })
16693 }
16694
16695 fn organize_imports(
16696 &mut self,
16697 _: &OrganizeImports,
16698 window: &mut Window,
16699 cx: &mut Context<Self>,
16700 ) -> Option<Task<Result<()>>> {
16701 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16702 let project = match &self.project {
16703 Some(project) => project.clone(),
16704 None => return None,
16705 };
16706 Some(self.perform_code_action_kind(
16707 project,
16708 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16709 window,
16710 cx,
16711 ))
16712 }
16713
16714 fn perform_code_action_kind(
16715 &mut self,
16716 project: Entity<Project>,
16717 kind: CodeActionKind,
16718 window: &mut Window,
16719 cx: &mut Context<Self>,
16720 ) -> Task<Result<()>> {
16721 let buffer = self.buffer.clone();
16722 let buffers = buffer.read(cx).all_buffers();
16723 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16724 let apply_action = project.update(cx, |project, cx| {
16725 project.apply_code_action_kind(buffers, kind, true, cx)
16726 });
16727 cx.spawn_in(window, async move |_, cx| {
16728 let transaction = futures::select_biased! {
16729 () = timeout => {
16730 log::warn!("timed out waiting for executing code action");
16731 None
16732 }
16733 transaction = apply_action.log_err().fuse() => transaction,
16734 };
16735 buffer
16736 .update(cx, |buffer, cx| {
16737 // check if we need this
16738 if let Some(transaction) = transaction {
16739 if !buffer.is_singleton() {
16740 buffer.push_transaction(&transaction.0, cx);
16741 }
16742 }
16743 cx.notify();
16744 })
16745 .ok();
16746 Ok(())
16747 })
16748 }
16749
16750 pub fn restart_language_server(
16751 &mut self,
16752 _: &RestartLanguageServer,
16753 _: &mut Window,
16754 cx: &mut Context<Self>,
16755 ) {
16756 if let Some(project) = self.project.clone() {
16757 self.buffer.update(cx, |multi_buffer, cx| {
16758 project.update(cx, |project, cx| {
16759 project.restart_language_servers_for_buffers(
16760 multi_buffer.all_buffers().into_iter().collect(),
16761 HashSet::default(),
16762 cx,
16763 );
16764 });
16765 })
16766 }
16767 }
16768
16769 pub fn stop_language_server(
16770 &mut self,
16771 _: &StopLanguageServer,
16772 _: &mut Window,
16773 cx: &mut Context<Self>,
16774 ) {
16775 if let Some(project) = self.project.clone() {
16776 self.buffer.update(cx, |multi_buffer, cx| {
16777 project.update(cx, |project, cx| {
16778 project.stop_language_servers_for_buffers(
16779 multi_buffer.all_buffers().into_iter().collect(),
16780 HashSet::default(),
16781 cx,
16782 );
16783 cx.emit(project::Event::RefreshInlayHints);
16784 });
16785 });
16786 }
16787 }
16788
16789 fn cancel_language_server_work(
16790 workspace: &mut Workspace,
16791 _: &actions::CancelLanguageServerWork,
16792 _: &mut Window,
16793 cx: &mut Context<Workspace>,
16794 ) {
16795 let project = workspace.project();
16796 let buffers = workspace
16797 .active_item(cx)
16798 .and_then(|item| item.act_as::<Editor>(cx))
16799 .map_or(HashSet::default(), |editor| {
16800 editor.read(cx).buffer.read(cx).all_buffers()
16801 });
16802 project.update(cx, |project, cx| {
16803 project.cancel_language_server_work_for_buffers(buffers, cx);
16804 });
16805 }
16806
16807 fn show_character_palette(
16808 &mut self,
16809 _: &ShowCharacterPalette,
16810 window: &mut Window,
16811 _: &mut Context<Self>,
16812 ) {
16813 window.show_character_palette();
16814 }
16815
16816 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16817 if !self.diagnostics_enabled() {
16818 return;
16819 }
16820
16821 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16822 let buffer = self.buffer.read(cx).snapshot(cx);
16823 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16824 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16825 let is_valid = buffer
16826 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16827 .any(|entry| {
16828 entry.diagnostic.is_primary
16829 && !entry.range.is_empty()
16830 && entry.range.start == primary_range_start
16831 && entry.diagnostic.message == active_diagnostics.active_message
16832 });
16833
16834 if !is_valid {
16835 self.dismiss_diagnostics(cx);
16836 }
16837 }
16838 }
16839
16840 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16841 match &self.active_diagnostics {
16842 ActiveDiagnostic::Group(group) => Some(group),
16843 _ => None,
16844 }
16845 }
16846
16847 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16848 if !self.diagnostics_enabled() {
16849 return;
16850 }
16851 self.dismiss_diagnostics(cx);
16852 self.active_diagnostics = ActiveDiagnostic::All;
16853 }
16854
16855 fn activate_diagnostics(
16856 &mut self,
16857 buffer_id: BufferId,
16858 diagnostic: DiagnosticEntry<usize>,
16859 window: &mut Window,
16860 cx: &mut Context<Self>,
16861 ) {
16862 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16863 return;
16864 }
16865 self.dismiss_diagnostics(cx);
16866 let snapshot = self.snapshot(window, cx);
16867 let buffer = self.buffer.read(cx).snapshot(cx);
16868 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16869 return;
16870 };
16871
16872 let diagnostic_group = buffer
16873 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16874 .collect::<Vec<_>>();
16875
16876 let blocks =
16877 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16878
16879 let blocks = self.display_map.update(cx, |display_map, cx| {
16880 display_map.insert_blocks(blocks, cx).into_iter().collect()
16881 });
16882 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16883 active_range: buffer.anchor_before(diagnostic.range.start)
16884 ..buffer.anchor_after(diagnostic.range.end),
16885 active_message: diagnostic.diagnostic.message.clone(),
16886 group_id: diagnostic.diagnostic.group_id,
16887 blocks,
16888 });
16889 cx.notify();
16890 }
16891
16892 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16893 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16894 return;
16895 };
16896
16897 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16898 if let ActiveDiagnostic::Group(group) = prev {
16899 self.display_map.update(cx, |display_map, cx| {
16900 display_map.remove_blocks(group.blocks, cx);
16901 });
16902 cx.notify();
16903 }
16904 }
16905
16906 /// Disable inline diagnostics rendering for this editor.
16907 pub fn disable_inline_diagnostics(&mut self) {
16908 self.inline_diagnostics_enabled = false;
16909 self.inline_diagnostics_update = Task::ready(());
16910 self.inline_diagnostics.clear();
16911 }
16912
16913 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16914 self.diagnostics_enabled = false;
16915 self.dismiss_diagnostics(cx);
16916 self.inline_diagnostics_update = Task::ready(());
16917 self.inline_diagnostics.clear();
16918 }
16919
16920 pub fn diagnostics_enabled(&self) -> bool {
16921 self.diagnostics_enabled && self.mode.is_full()
16922 }
16923
16924 pub fn inline_diagnostics_enabled(&self) -> bool {
16925 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16926 }
16927
16928 pub fn show_inline_diagnostics(&self) -> bool {
16929 self.show_inline_diagnostics
16930 }
16931
16932 pub fn toggle_inline_diagnostics(
16933 &mut self,
16934 _: &ToggleInlineDiagnostics,
16935 window: &mut Window,
16936 cx: &mut Context<Editor>,
16937 ) {
16938 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16939 self.refresh_inline_diagnostics(false, window, cx);
16940 }
16941
16942 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16943 self.diagnostics_max_severity = severity;
16944 self.display_map.update(cx, |display_map, _| {
16945 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16946 });
16947 }
16948
16949 pub fn toggle_diagnostics(
16950 &mut self,
16951 _: &ToggleDiagnostics,
16952 window: &mut Window,
16953 cx: &mut Context<Editor>,
16954 ) {
16955 if !self.diagnostics_enabled() {
16956 return;
16957 }
16958
16959 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16960 EditorSettings::get_global(cx)
16961 .diagnostics_max_severity
16962 .filter(|severity| severity != &DiagnosticSeverity::Off)
16963 .unwrap_or(DiagnosticSeverity::Hint)
16964 } else {
16965 DiagnosticSeverity::Off
16966 };
16967 self.set_max_diagnostics_severity(new_severity, cx);
16968 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16969 self.active_diagnostics = ActiveDiagnostic::None;
16970 self.inline_diagnostics_update = Task::ready(());
16971 self.inline_diagnostics.clear();
16972 } else {
16973 self.refresh_inline_diagnostics(false, window, cx);
16974 }
16975
16976 cx.notify();
16977 }
16978
16979 pub fn toggle_minimap(
16980 &mut self,
16981 _: &ToggleMinimap,
16982 window: &mut Window,
16983 cx: &mut Context<Editor>,
16984 ) {
16985 if self.supports_minimap(cx) {
16986 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16987 }
16988 }
16989
16990 fn refresh_inline_diagnostics(
16991 &mut self,
16992 debounce: bool,
16993 window: &mut Window,
16994 cx: &mut Context<Self>,
16995 ) {
16996 let max_severity = ProjectSettings::get_global(cx)
16997 .diagnostics
16998 .inline
16999 .max_severity
17000 .unwrap_or(self.diagnostics_max_severity);
17001
17002 if !self.inline_diagnostics_enabled()
17003 || !self.show_inline_diagnostics
17004 || max_severity == DiagnosticSeverity::Off
17005 {
17006 self.inline_diagnostics_update = Task::ready(());
17007 self.inline_diagnostics.clear();
17008 return;
17009 }
17010
17011 let debounce_ms = ProjectSettings::get_global(cx)
17012 .diagnostics
17013 .inline
17014 .update_debounce_ms;
17015 let debounce = if debounce && debounce_ms > 0 {
17016 Some(Duration::from_millis(debounce_ms))
17017 } else {
17018 None
17019 };
17020 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17021 if let Some(debounce) = debounce {
17022 cx.background_executor().timer(debounce).await;
17023 }
17024 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17025 editor
17026 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17027 .ok()
17028 }) else {
17029 return;
17030 };
17031
17032 let new_inline_diagnostics = cx
17033 .background_spawn(async move {
17034 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17035 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17036 let message = diagnostic_entry
17037 .diagnostic
17038 .message
17039 .split_once('\n')
17040 .map(|(line, _)| line)
17041 .map(SharedString::new)
17042 .unwrap_or_else(|| {
17043 SharedString::from(diagnostic_entry.diagnostic.message)
17044 });
17045 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17046 let (Ok(i) | Err(i)) = inline_diagnostics
17047 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17048 inline_diagnostics.insert(
17049 i,
17050 (
17051 start_anchor,
17052 InlineDiagnostic {
17053 message,
17054 group_id: diagnostic_entry.diagnostic.group_id,
17055 start: diagnostic_entry.range.start.to_point(&snapshot),
17056 is_primary: diagnostic_entry.diagnostic.is_primary,
17057 severity: diagnostic_entry.diagnostic.severity,
17058 },
17059 ),
17060 );
17061 }
17062 inline_diagnostics
17063 })
17064 .await;
17065
17066 editor
17067 .update(cx, |editor, cx| {
17068 editor.inline_diagnostics = new_inline_diagnostics;
17069 cx.notify();
17070 })
17071 .ok();
17072 });
17073 }
17074
17075 fn pull_diagnostics(
17076 &mut self,
17077 buffer_id: Option<BufferId>,
17078 window: &Window,
17079 cx: &mut Context<Self>,
17080 ) -> Option<()> {
17081 if !self.mode().is_full() {
17082 return None;
17083 }
17084 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17085 .diagnostics
17086 .lsp_pull_diagnostics;
17087 if !pull_diagnostics_settings.enabled {
17088 return None;
17089 }
17090 let project = self.project()?.downgrade();
17091 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17092 let mut buffers = self.buffer.read(cx).all_buffers();
17093 if let Some(buffer_id) = buffer_id {
17094 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17095 }
17096
17097 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17098 cx.background_executor().timer(debounce).await;
17099
17100 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17101 buffers
17102 .into_iter()
17103 .filter_map(|buffer| {
17104 project
17105 .update(cx, |project, cx| {
17106 project.lsp_store().update(cx, |lsp_store, cx| {
17107 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17108 })
17109 })
17110 .ok()
17111 })
17112 .collect::<FuturesUnordered<_>>()
17113 }) else {
17114 return;
17115 };
17116
17117 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17118 match pull_task {
17119 Ok(()) => {
17120 if editor
17121 .update_in(cx, |editor, window, cx| {
17122 editor.update_diagnostics_state(window, cx);
17123 })
17124 .is_err()
17125 {
17126 return;
17127 }
17128 }
17129 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17130 }
17131 }
17132 });
17133
17134 Some(())
17135 }
17136
17137 pub fn set_selections_from_remote(
17138 &mut self,
17139 selections: Vec<Selection<Anchor>>,
17140 pending_selection: Option<Selection<Anchor>>,
17141 window: &mut Window,
17142 cx: &mut Context<Self>,
17143 ) {
17144 let old_cursor_position = self.selections.newest_anchor().head();
17145 self.selections.change_with(cx, |s| {
17146 s.select_anchors(selections);
17147 if let Some(pending_selection) = pending_selection {
17148 s.set_pending(pending_selection, SelectMode::Character);
17149 } else {
17150 s.clear_pending();
17151 }
17152 });
17153 self.selections_did_change(
17154 false,
17155 &old_cursor_position,
17156 SelectionEffects::default(),
17157 window,
17158 cx,
17159 );
17160 }
17161
17162 pub fn transact(
17163 &mut self,
17164 window: &mut Window,
17165 cx: &mut Context<Self>,
17166 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17167 ) -> Option<TransactionId> {
17168 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17169 this.start_transaction_at(Instant::now(), window, cx);
17170 update(this, window, cx);
17171 this.end_transaction_at(Instant::now(), cx)
17172 })
17173 }
17174
17175 pub fn start_transaction_at(
17176 &mut self,
17177 now: Instant,
17178 window: &mut Window,
17179 cx: &mut Context<Self>,
17180 ) -> Option<TransactionId> {
17181 self.end_selection(window, cx);
17182 if let Some(tx_id) = self
17183 .buffer
17184 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17185 {
17186 self.selection_history
17187 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17188 cx.emit(EditorEvent::TransactionBegun {
17189 transaction_id: tx_id,
17190 });
17191 Some(tx_id)
17192 } else {
17193 None
17194 }
17195 }
17196
17197 pub fn end_transaction_at(
17198 &mut self,
17199 now: Instant,
17200 cx: &mut Context<Self>,
17201 ) -> Option<TransactionId> {
17202 if let Some(transaction_id) = self
17203 .buffer
17204 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17205 {
17206 if let Some((_, end_selections)) =
17207 self.selection_history.transaction_mut(transaction_id)
17208 {
17209 *end_selections = Some(self.selections.disjoint_anchors());
17210 } else {
17211 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17212 }
17213
17214 cx.emit(EditorEvent::Edited { transaction_id });
17215 Some(transaction_id)
17216 } else {
17217 None
17218 }
17219 }
17220
17221 pub fn modify_transaction_selection_history(
17222 &mut self,
17223 transaction_id: TransactionId,
17224 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17225 ) -> bool {
17226 self.selection_history
17227 .transaction_mut(transaction_id)
17228 .map(modify)
17229 .is_some()
17230 }
17231
17232 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17233 if self.selection_mark_mode {
17234 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17235 s.move_with(|_, sel| {
17236 sel.collapse_to(sel.head(), SelectionGoal::None);
17237 });
17238 })
17239 }
17240 self.selection_mark_mode = true;
17241 cx.notify();
17242 }
17243
17244 pub fn swap_selection_ends(
17245 &mut self,
17246 _: &actions::SwapSelectionEnds,
17247 window: &mut Window,
17248 cx: &mut Context<Self>,
17249 ) {
17250 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17251 s.move_with(|_, sel| {
17252 if sel.start != sel.end {
17253 sel.reversed = !sel.reversed
17254 }
17255 });
17256 });
17257 self.request_autoscroll(Autoscroll::newest(), cx);
17258 cx.notify();
17259 }
17260
17261 pub fn toggle_focus(
17262 workspace: &mut Workspace,
17263 _: &actions::ToggleFocus,
17264 window: &mut Window,
17265 cx: &mut Context<Workspace>,
17266 ) {
17267 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17268 return;
17269 };
17270 workspace.activate_item(&item, true, true, window, cx);
17271 }
17272
17273 pub fn toggle_fold(
17274 &mut self,
17275 _: &actions::ToggleFold,
17276 window: &mut Window,
17277 cx: &mut Context<Self>,
17278 ) {
17279 if self.is_singleton(cx) {
17280 let selection = self.selections.newest::<Point>(cx);
17281
17282 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17283 let range = if selection.is_empty() {
17284 let point = selection.head().to_display_point(&display_map);
17285 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17286 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17287 .to_point(&display_map);
17288 start..end
17289 } else {
17290 selection.range()
17291 };
17292 if display_map.folds_in_range(range).next().is_some() {
17293 self.unfold_lines(&Default::default(), window, cx)
17294 } else {
17295 self.fold(&Default::default(), window, cx)
17296 }
17297 } else {
17298 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17299 let buffer_ids: HashSet<_> = self
17300 .selections
17301 .disjoint_anchor_ranges()
17302 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17303 .collect();
17304
17305 let should_unfold = buffer_ids
17306 .iter()
17307 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17308
17309 for buffer_id in buffer_ids {
17310 if should_unfold {
17311 self.unfold_buffer(buffer_id, cx);
17312 } else {
17313 self.fold_buffer(buffer_id, cx);
17314 }
17315 }
17316 }
17317 }
17318
17319 pub fn toggle_fold_recursive(
17320 &mut self,
17321 _: &actions::ToggleFoldRecursive,
17322 window: &mut Window,
17323 cx: &mut Context<Self>,
17324 ) {
17325 let selection = self.selections.newest::<Point>(cx);
17326
17327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17328 let range = if selection.is_empty() {
17329 let point = selection.head().to_display_point(&display_map);
17330 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17331 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17332 .to_point(&display_map);
17333 start..end
17334 } else {
17335 selection.range()
17336 };
17337 if display_map.folds_in_range(range).next().is_some() {
17338 self.unfold_recursive(&Default::default(), window, cx)
17339 } else {
17340 self.fold_recursive(&Default::default(), window, cx)
17341 }
17342 }
17343
17344 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17345 if self.is_singleton(cx) {
17346 let mut to_fold = Vec::new();
17347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17348 let selections = self.selections.all_adjusted(cx);
17349
17350 for selection in selections {
17351 let range = selection.range().sorted();
17352 let buffer_start_row = range.start.row;
17353
17354 if range.start.row != range.end.row {
17355 let mut found = false;
17356 let mut row = range.start.row;
17357 while row <= range.end.row {
17358 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17359 {
17360 found = true;
17361 row = crease.range().end.row + 1;
17362 to_fold.push(crease);
17363 } else {
17364 row += 1
17365 }
17366 }
17367 if found {
17368 continue;
17369 }
17370 }
17371
17372 for row in (0..=range.start.row).rev() {
17373 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17374 if crease.range().end.row >= buffer_start_row {
17375 to_fold.push(crease);
17376 if row <= range.start.row {
17377 break;
17378 }
17379 }
17380 }
17381 }
17382 }
17383
17384 self.fold_creases(to_fold, true, window, cx);
17385 } else {
17386 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17387 let buffer_ids = self
17388 .selections
17389 .disjoint_anchor_ranges()
17390 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17391 .collect::<HashSet<_>>();
17392 for buffer_id in buffer_ids {
17393 self.fold_buffer(buffer_id, cx);
17394 }
17395 }
17396 }
17397
17398 pub fn toggle_fold_all(
17399 &mut self,
17400 _: &actions::ToggleFoldAll,
17401 window: &mut Window,
17402 cx: &mut Context<Self>,
17403 ) {
17404 if self.buffer.read(cx).is_singleton() {
17405 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17406 let has_folds = display_map
17407 .folds_in_range(0..display_map.buffer_snapshot.len())
17408 .next()
17409 .is_some();
17410
17411 if has_folds {
17412 self.unfold_all(&actions::UnfoldAll, window, cx);
17413 } else {
17414 self.fold_all(&actions::FoldAll, window, cx);
17415 }
17416 } else {
17417 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17418 let should_unfold = buffer_ids
17419 .iter()
17420 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17421
17422 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17423 editor
17424 .update_in(cx, |editor, _, cx| {
17425 for buffer_id in buffer_ids {
17426 if should_unfold {
17427 editor.unfold_buffer(buffer_id, cx);
17428 } else {
17429 editor.fold_buffer(buffer_id, cx);
17430 }
17431 }
17432 })
17433 .ok();
17434 });
17435 }
17436 }
17437
17438 fn fold_at_level(
17439 &mut self,
17440 fold_at: &FoldAtLevel,
17441 window: &mut Window,
17442 cx: &mut Context<Self>,
17443 ) {
17444 if !self.buffer.read(cx).is_singleton() {
17445 return;
17446 }
17447
17448 let fold_at_level = fold_at.0;
17449 let snapshot = self.buffer.read(cx).snapshot(cx);
17450 let mut to_fold = Vec::new();
17451 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17452
17453 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17454 while start_row < end_row {
17455 match self
17456 .snapshot(window, cx)
17457 .crease_for_buffer_row(MultiBufferRow(start_row))
17458 {
17459 Some(crease) => {
17460 let nested_start_row = crease.range().start.row + 1;
17461 let nested_end_row = crease.range().end.row;
17462
17463 if current_level < fold_at_level {
17464 stack.push((nested_start_row, nested_end_row, current_level + 1));
17465 } else if current_level == fold_at_level {
17466 to_fold.push(crease);
17467 }
17468
17469 start_row = nested_end_row + 1;
17470 }
17471 None => start_row += 1,
17472 }
17473 }
17474 }
17475
17476 self.fold_creases(to_fold, true, window, cx);
17477 }
17478
17479 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17480 if self.buffer.read(cx).is_singleton() {
17481 let mut fold_ranges = Vec::new();
17482 let snapshot = self.buffer.read(cx).snapshot(cx);
17483
17484 for row in 0..snapshot.max_row().0 {
17485 if let Some(foldable_range) = self
17486 .snapshot(window, cx)
17487 .crease_for_buffer_row(MultiBufferRow(row))
17488 {
17489 fold_ranges.push(foldable_range);
17490 }
17491 }
17492
17493 self.fold_creases(fold_ranges, true, window, cx);
17494 } else {
17495 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17496 editor
17497 .update_in(cx, |editor, _, cx| {
17498 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17499 editor.fold_buffer(buffer_id, cx);
17500 }
17501 })
17502 .ok();
17503 });
17504 }
17505 }
17506
17507 pub fn fold_function_bodies(
17508 &mut self,
17509 _: &actions::FoldFunctionBodies,
17510 window: &mut Window,
17511 cx: &mut Context<Self>,
17512 ) {
17513 let snapshot = self.buffer.read(cx).snapshot(cx);
17514
17515 let ranges = snapshot
17516 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17517 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17518 .collect::<Vec<_>>();
17519
17520 let creases = ranges
17521 .into_iter()
17522 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17523 .collect();
17524
17525 self.fold_creases(creases, true, window, cx);
17526 }
17527
17528 pub fn fold_recursive(
17529 &mut self,
17530 _: &actions::FoldRecursive,
17531 window: &mut Window,
17532 cx: &mut Context<Self>,
17533 ) {
17534 let mut to_fold = Vec::new();
17535 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17536 let selections = self.selections.all_adjusted(cx);
17537
17538 for selection in selections {
17539 let range = selection.range().sorted();
17540 let buffer_start_row = range.start.row;
17541
17542 if range.start.row != range.end.row {
17543 let mut found = false;
17544 for row in range.start.row..=range.end.row {
17545 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17546 found = true;
17547 to_fold.push(crease);
17548 }
17549 }
17550 if found {
17551 continue;
17552 }
17553 }
17554
17555 for row in (0..=range.start.row).rev() {
17556 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17557 if crease.range().end.row >= buffer_start_row {
17558 to_fold.push(crease);
17559 } else {
17560 break;
17561 }
17562 }
17563 }
17564 }
17565
17566 self.fold_creases(to_fold, true, window, cx);
17567 }
17568
17569 pub fn fold_at(
17570 &mut self,
17571 buffer_row: MultiBufferRow,
17572 window: &mut Window,
17573 cx: &mut Context<Self>,
17574 ) {
17575 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17576
17577 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17578 let autoscroll = self
17579 .selections
17580 .all::<Point>(cx)
17581 .iter()
17582 .any(|selection| crease.range().overlaps(&selection.range()));
17583
17584 self.fold_creases(vec![crease], autoscroll, window, cx);
17585 }
17586 }
17587
17588 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17589 if self.is_singleton(cx) {
17590 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17591 let buffer = &display_map.buffer_snapshot;
17592 let selections = self.selections.all::<Point>(cx);
17593 let ranges = selections
17594 .iter()
17595 .map(|s| {
17596 let range = s.display_range(&display_map).sorted();
17597 let mut start = range.start.to_point(&display_map);
17598 let mut end = range.end.to_point(&display_map);
17599 start.column = 0;
17600 end.column = buffer.line_len(MultiBufferRow(end.row));
17601 start..end
17602 })
17603 .collect::<Vec<_>>();
17604
17605 self.unfold_ranges(&ranges, true, true, cx);
17606 } else {
17607 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17608 let buffer_ids = self
17609 .selections
17610 .disjoint_anchor_ranges()
17611 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17612 .collect::<HashSet<_>>();
17613 for buffer_id in buffer_ids {
17614 self.unfold_buffer(buffer_id, cx);
17615 }
17616 }
17617 }
17618
17619 pub fn unfold_recursive(
17620 &mut self,
17621 _: &UnfoldRecursive,
17622 _window: &mut Window,
17623 cx: &mut Context<Self>,
17624 ) {
17625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17626 let selections = self.selections.all::<Point>(cx);
17627 let ranges = selections
17628 .iter()
17629 .map(|s| {
17630 let mut range = s.display_range(&display_map).sorted();
17631 *range.start.column_mut() = 0;
17632 *range.end.column_mut() = display_map.line_len(range.end.row());
17633 let start = range.start.to_point(&display_map);
17634 let end = range.end.to_point(&display_map);
17635 start..end
17636 })
17637 .collect::<Vec<_>>();
17638
17639 self.unfold_ranges(&ranges, true, true, cx);
17640 }
17641
17642 pub fn unfold_at(
17643 &mut self,
17644 buffer_row: MultiBufferRow,
17645 _window: &mut Window,
17646 cx: &mut Context<Self>,
17647 ) {
17648 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17649
17650 let intersection_range = Point::new(buffer_row.0, 0)
17651 ..Point::new(
17652 buffer_row.0,
17653 display_map.buffer_snapshot.line_len(buffer_row),
17654 );
17655
17656 let autoscroll = self
17657 .selections
17658 .all::<Point>(cx)
17659 .iter()
17660 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17661
17662 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17663 }
17664
17665 pub fn unfold_all(
17666 &mut self,
17667 _: &actions::UnfoldAll,
17668 _window: &mut Window,
17669 cx: &mut Context<Self>,
17670 ) {
17671 if self.buffer.read(cx).is_singleton() {
17672 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17673 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17674 } else {
17675 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17676 editor
17677 .update(cx, |editor, cx| {
17678 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17679 editor.unfold_buffer(buffer_id, cx);
17680 }
17681 })
17682 .ok();
17683 });
17684 }
17685 }
17686
17687 pub fn fold_selected_ranges(
17688 &mut self,
17689 _: &FoldSelectedRanges,
17690 window: &mut Window,
17691 cx: &mut Context<Self>,
17692 ) {
17693 let selections = self.selections.all_adjusted(cx);
17694 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17695 let ranges = selections
17696 .into_iter()
17697 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17698 .collect::<Vec<_>>();
17699 self.fold_creases(ranges, true, window, cx);
17700 }
17701
17702 pub fn fold_ranges<T: ToOffset + Clone>(
17703 &mut self,
17704 ranges: Vec<Range<T>>,
17705 auto_scroll: bool,
17706 window: &mut Window,
17707 cx: &mut Context<Self>,
17708 ) {
17709 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17710 let ranges = ranges
17711 .into_iter()
17712 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17713 .collect::<Vec<_>>();
17714 self.fold_creases(ranges, auto_scroll, window, cx);
17715 }
17716
17717 pub fn fold_creases<T: ToOffset + Clone>(
17718 &mut self,
17719 creases: Vec<Crease<T>>,
17720 auto_scroll: bool,
17721 _window: &mut Window,
17722 cx: &mut Context<Self>,
17723 ) {
17724 if creases.is_empty() {
17725 return;
17726 }
17727
17728 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17729
17730 if auto_scroll {
17731 self.request_autoscroll(Autoscroll::fit(), cx);
17732 }
17733
17734 cx.notify();
17735
17736 self.scrollbar_marker_state.dirty = true;
17737 self.folds_did_change(cx);
17738 }
17739
17740 /// Removes any folds whose ranges intersect any of the given ranges.
17741 pub fn unfold_ranges<T: ToOffset + Clone>(
17742 &mut self,
17743 ranges: &[Range<T>],
17744 inclusive: bool,
17745 auto_scroll: bool,
17746 cx: &mut Context<Self>,
17747 ) {
17748 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17749 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17750 });
17751 self.folds_did_change(cx);
17752 }
17753
17754 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17755 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17756 return;
17757 }
17758 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17759 self.display_map.update(cx, |display_map, cx| {
17760 display_map.fold_buffers([buffer_id], cx)
17761 });
17762 cx.emit(EditorEvent::BufferFoldToggled {
17763 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17764 folded: true,
17765 });
17766 cx.notify();
17767 }
17768
17769 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17770 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17771 return;
17772 }
17773 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17774 self.display_map.update(cx, |display_map, cx| {
17775 display_map.unfold_buffers([buffer_id], cx);
17776 });
17777 cx.emit(EditorEvent::BufferFoldToggled {
17778 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17779 folded: false,
17780 });
17781 cx.notify();
17782 }
17783
17784 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17785 self.display_map.read(cx).is_buffer_folded(buffer)
17786 }
17787
17788 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17789 self.display_map.read(cx).folded_buffers()
17790 }
17791
17792 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17793 self.display_map.update(cx, |display_map, cx| {
17794 display_map.disable_header_for_buffer(buffer_id, cx);
17795 });
17796 cx.notify();
17797 }
17798
17799 /// Removes any folds with the given ranges.
17800 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17801 &mut self,
17802 ranges: &[Range<T>],
17803 type_id: TypeId,
17804 auto_scroll: bool,
17805 cx: &mut Context<Self>,
17806 ) {
17807 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17808 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17809 });
17810 self.folds_did_change(cx);
17811 }
17812
17813 fn remove_folds_with<T: ToOffset + Clone>(
17814 &mut self,
17815 ranges: &[Range<T>],
17816 auto_scroll: bool,
17817 cx: &mut Context<Self>,
17818 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17819 ) {
17820 if ranges.is_empty() {
17821 return;
17822 }
17823
17824 let mut buffers_affected = HashSet::default();
17825 let multi_buffer = self.buffer().read(cx);
17826 for range in ranges {
17827 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17828 buffers_affected.insert(buffer.read(cx).remote_id());
17829 };
17830 }
17831
17832 self.display_map.update(cx, update);
17833
17834 if auto_scroll {
17835 self.request_autoscroll(Autoscroll::fit(), cx);
17836 }
17837
17838 cx.notify();
17839 self.scrollbar_marker_state.dirty = true;
17840 self.active_indent_guides_state.dirty = true;
17841 }
17842
17843 pub fn update_renderer_widths(
17844 &mut self,
17845 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17846 cx: &mut Context<Self>,
17847 ) -> bool {
17848 self.display_map
17849 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17850 }
17851
17852 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17853 self.display_map.read(cx).fold_placeholder.clone()
17854 }
17855
17856 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17857 self.buffer.update(cx, |buffer, cx| {
17858 buffer.set_all_diff_hunks_expanded(cx);
17859 });
17860 }
17861
17862 pub fn expand_all_diff_hunks(
17863 &mut self,
17864 _: &ExpandAllDiffHunks,
17865 _window: &mut Window,
17866 cx: &mut Context<Self>,
17867 ) {
17868 self.buffer.update(cx, |buffer, cx| {
17869 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17870 });
17871 }
17872
17873 pub fn toggle_selected_diff_hunks(
17874 &mut self,
17875 _: &ToggleSelectedDiffHunks,
17876 _window: &mut Window,
17877 cx: &mut Context<Self>,
17878 ) {
17879 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17880 self.toggle_diff_hunks_in_ranges(ranges, cx);
17881 }
17882
17883 pub fn diff_hunks_in_ranges<'a>(
17884 &'a self,
17885 ranges: &'a [Range<Anchor>],
17886 buffer: &'a MultiBufferSnapshot,
17887 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17888 ranges.iter().flat_map(move |range| {
17889 let end_excerpt_id = range.end.excerpt_id;
17890 let range = range.to_point(buffer);
17891 let mut peek_end = range.end;
17892 if range.end.row < buffer.max_row().0 {
17893 peek_end = Point::new(range.end.row + 1, 0);
17894 }
17895 buffer
17896 .diff_hunks_in_range(range.start..peek_end)
17897 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17898 })
17899 }
17900
17901 pub fn has_stageable_diff_hunks_in_ranges(
17902 &self,
17903 ranges: &[Range<Anchor>],
17904 snapshot: &MultiBufferSnapshot,
17905 ) -> bool {
17906 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
17907 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17908 }
17909
17910 pub fn toggle_staged_selected_diff_hunks(
17911 &mut self,
17912 _: &::git::ToggleStaged,
17913 _: &mut Window,
17914 cx: &mut Context<Self>,
17915 ) {
17916 let snapshot = self.buffer.read(cx).snapshot(cx);
17917 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17918 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17919 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17920 }
17921
17922 pub fn set_render_diff_hunk_controls(
17923 &mut self,
17924 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17925 cx: &mut Context<Self>,
17926 ) {
17927 self.render_diff_hunk_controls = render_diff_hunk_controls;
17928 cx.notify();
17929 }
17930
17931 pub fn stage_and_next(
17932 &mut self,
17933 _: &::git::StageAndNext,
17934 window: &mut Window,
17935 cx: &mut Context<Self>,
17936 ) {
17937 self.do_stage_or_unstage_and_next(true, window, cx);
17938 }
17939
17940 pub fn unstage_and_next(
17941 &mut self,
17942 _: &::git::UnstageAndNext,
17943 window: &mut Window,
17944 cx: &mut Context<Self>,
17945 ) {
17946 self.do_stage_or_unstage_and_next(false, window, cx);
17947 }
17948
17949 pub fn stage_or_unstage_diff_hunks(
17950 &mut self,
17951 stage: bool,
17952 ranges: Vec<Range<Anchor>>,
17953 cx: &mut Context<Self>,
17954 ) {
17955 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17956 cx.spawn(async move |this, cx| {
17957 task.await?;
17958 this.update(cx, |this, cx| {
17959 let snapshot = this.buffer.read(cx).snapshot(cx);
17960 let chunk_by = this
17961 .diff_hunks_in_ranges(&ranges, &snapshot)
17962 .chunk_by(|hunk| hunk.buffer_id);
17963 for (buffer_id, hunks) in &chunk_by {
17964 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17965 }
17966 })
17967 })
17968 .detach_and_log_err(cx);
17969 }
17970
17971 fn save_buffers_for_ranges_if_needed(
17972 &mut self,
17973 ranges: &[Range<Anchor>],
17974 cx: &mut Context<Editor>,
17975 ) -> Task<Result<()>> {
17976 let multibuffer = self.buffer.read(cx);
17977 let snapshot = multibuffer.read(cx);
17978 let buffer_ids: HashSet<_> = ranges
17979 .iter()
17980 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17981 .collect();
17982 drop(snapshot);
17983
17984 let mut buffers = HashSet::default();
17985 for buffer_id in buffer_ids {
17986 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17987 let buffer = buffer_entity.read(cx);
17988 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17989 {
17990 buffers.insert(buffer_entity);
17991 }
17992 }
17993 }
17994
17995 if let Some(project) = &self.project {
17996 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17997 } else {
17998 Task::ready(Ok(()))
17999 }
18000 }
18001
18002 fn do_stage_or_unstage_and_next(
18003 &mut self,
18004 stage: bool,
18005 window: &mut Window,
18006 cx: &mut Context<Self>,
18007 ) {
18008 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18009
18010 if ranges.iter().any(|range| range.start != range.end) {
18011 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18012 return;
18013 }
18014
18015 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18016 let snapshot = self.snapshot(window, cx);
18017 let position = self.selections.newest::<Point>(cx).head();
18018 let mut row = snapshot
18019 .buffer_snapshot
18020 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18021 .find(|hunk| hunk.row_range.start.0 > position.row)
18022 .map(|hunk| hunk.row_range.start);
18023
18024 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18025 // Outside of the project diff editor, wrap around to the beginning.
18026 if !all_diff_hunks_expanded {
18027 row = row.or_else(|| {
18028 snapshot
18029 .buffer_snapshot
18030 .diff_hunks_in_range(Point::zero()..position)
18031 .find(|hunk| hunk.row_range.end.0 < position.row)
18032 .map(|hunk| hunk.row_range.start)
18033 });
18034 }
18035
18036 if let Some(row) = row {
18037 let destination = Point::new(row.0, 0);
18038 let autoscroll = Autoscroll::center();
18039
18040 self.unfold_ranges(&[destination..destination], false, false, cx);
18041 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18042 s.select_ranges([destination..destination]);
18043 });
18044 }
18045 }
18046
18047 fn do_stage_or_unstage(
18048 &self,
18049 stage: bool,
18050 buffer_id: BufferId,
18051 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18052 cx: &mut App,
18053 ) -> Option<()> {
18054 let project = self.project()?;
18055 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18056 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18057 let buffer_snapshot = buffer.read(cx).snapshot();
18058 let file_exists = buffer_snapshot
18059 .file()
18060 .is_some_and(|file| file.disk_state().exists());
18061 diff.update(cx, |diff, cx| {
18062 diff.stage_or_unstage_hunks(
18063 stage,
18064 &hunks
18065 .map(|hunk| buffer_diff::DiffHunk {
18066 buffer_range: hunk.buffer_range,
18067 diff_base_byte_range: hunk.diff_base_byte_range,
18068 secondary_status: hunk.secondary_status,
18069 range: Point::zero()..Point::zero(), // unused
18070 })
18071 .collect::<Vec<_>>(),
18072 &buffer_snapshot,
18073 file_exists,
18074 cx,
18075 )
18076 });
18077 None
18078 }
18079
18080 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18081 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18082 self.buffer
18083 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18084 }
18085
18086 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18087 self.buffer.update(cx, |buffer, cx| {
18088 let ranges = vec![Anchor::min()..Anchor::max()];
18089 if !buffer.all_diff_hunks_expanded()
18090 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18091 {
18092 buffer.collapse_diff_hunks(ranges, cx);
18093 true
18094 } else {
18095 false
18096 }
18097 })
18098 }
18099
18100 fn toggle_diff_hunks_in_ranges(
18101 &mut self,
18102 ranges: Vec<Range<Anchor>>,
18103 cx: &mut Context<Editor>,
18104 ) {
18105 self.buffer.update(cx, |buffer, cx| {
18106 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18107 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18108 })
18109 }
18110
18111 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18112 self.buffer.update(cx, |buffer, cx| {
18113 let snapshot = buffer.snapshot(cx);
18114 let excerpt_id = range.end.excerpt_id;
18115 let point_range = range.to_point(&snapshot);
18116 let expand = !buffer.single_hunk_is_expanded(range, cx);
18117 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18118 })
18119 }
18120
18121 pub(crate) fn apply_all_diff_hunks(
18122 &mut self,
18123 _: &ApplyAllDiffHunks,
18124 window: &mut Window,
18125 cx: &mut Context<Self>,
18126 ) {
18127 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18128
18129 let buffers = self.buffer.read(cx).all_buffers();
18130 for branch_buffer in buffers {
18131 branch_buffer.update(cx, |branch_buffer, cx| {
18132 branch_buffer.merge_into_base(Vec::new(), cx);
18133 });
18134 }
18135
18136 if let Some(project) = self.project.clone() {
18137 self.save(
18138 SaveOptions {
18139 format: true,
18140 autosave: false,
18141 },
18142 project,
18143 window,
18144 cx,
18145 )
18146 .detach_and_log_err(cx);
18147 }
18148 }
18149
18150 pub(crate) fn apply_selected_diff_hunks(
18151 &mut self,
18152 _: &ApplyDiffHunk,
18153 window: &mut Window,
18154 cx: &mut Context<Self>,
18155 ) {
18156 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18157 let snapshot = self.snapshot(window, cx);
18158 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18159 let mut ranges_by_buffer = HashMap::default();
18160 self.transact(window, cx, |editor, _window, cx| {
18161 for hunk in hunks {
18162 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18163 ranges_by_buffer
18164 .entry(buffer.clone())
18165 .or_insert_with(Vec::new)
18166 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18167 }
18168 }
18169
18170 for (buffer, ranges) in ranges_by_buffer {
18171 buffer.update(cx, |buffer, cx| {
18172 buffer.merge_into_base(ranges, cx);
18173 });
18174 }
18175 });
18176
18177 if let Some(project) = self.project.clone() {
18178 self.save(
18179 SaveOptions {
18180 format: true,
18181 autosave: false,
18182 },
18183 project,
18184 window,
18185 cx,
18186 )
18187 .detach_and_log_err(cx);
18188 }
18189 }
18190
18191 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18192 if hovered != self.gutter_hovered {
18193 self.gutter_hovered = hovered;
18194 cx.notify();
18195 }
18196 }
18197
18198 pub fn insert_blocks(
18199 &mut self,
18200 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18201 autoscroll: Option<Autoscroll>,
18202 cx: &mut Context<Self>,
18203 ) -> Vec<CustomBlockId> {
18204 let blocks = self
18205 .display_map
18206 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18207 if let Some(autoscroll) = autoscroll {
18208 self.request_autoscroll(autoscroll, cx);
18209 }
18210 cx.notify();
18211 blocks
18212 }
18213
18214 pub fn resize_blocks(
18215 &mut self,
18216 heights: HashMap<CustomBlockId, u32>,
18217 autoscroll: Option<Autoscroll>,
18218 cx: &mut Context<Self>,
18219 ) {
18220 self.display_map
18221 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18222 if let Some(autoscroll) = autoscroll {
18223 self.request_autoscroll(autoscroll, cx);
18224 }
18225 cx.notify();
18226 }
18227
18228 pub fn replace_blocks(
18229 &mut self,
18230 renderers: HashMap<CustomBlockId, RenderBlock>,
18231 autoscroll: Option<Autoscroll>,
18232 cx: &mut Context<Self>,
18233 ) {
18234 self.display_map
18235 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18236 if let Some(autoscroll) = autoscroll {
18237 self.request_autoscroll(autoscroll, cx);
18238 }
18239 cx.notify();
18240 }
18241
18242 pub fn remove_blocks(
18243 &mut self,
18244 block_ids: HashSet<CustomBlockId>,
18245 autoscroll: Option<Autoscroll>,
18246 cx: &mut Context<Self>,
18247 ) {
18248 self.display_map.update(cx, |display_map, cx| {
18249 display_map.remove_blocks(block_ids, cx)
18250 });
18251 if let Some(autoscroll) = autoscroll {
18252 self.request_autoscroll(autoscroll, cx);
18253 }
18254 cx.notify();
18255 }
18256
18257 pub fn row_for_block(
18258 &self,
18259 block_id: CustomBlockId,
18260 cx: &mut Context<Self>,
18261 ) -> Option<DisplayRow> {
18262 self.display_map
18263 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18264 }
18265
18266 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18267 self.focused_block = Some(focused_block);
18268 }
18269
18270 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18271 self.focused_block.take()
18272 }
18273
18274 pub fn insert_creases(
18275 &mut self,
18276 creases: impl IntoIterator<Item = Crease<Anchor>>,
18277 cx: &mut Context<Self>,
18278 ) -> Vec<CreaseId> {
18279 self.display_map
18280 .update(cx, |map, cx| map.insert_creases(creases, cx))
18281 }
18282
18283 pub fn remove_creases(
18284 &mut self,
18285 ids: impl IntoIterator<Item = CreaseId>,
18286 cx: &mut Context<Self>,
18287 ) -> Vec<(CreaseId, Range<Anchor>)> {
18288 self.display_map
18289 .update(cx, |map, cx| map.remove_creases(ids, cx))
18290 }
18291
18292 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18293 self.display_map
18294 .update(cx, |map, cx| map.snapshot(cx))
18295 .longest_row()
18296 }
18297
18298 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18299 self.display_map
18300 .update(cx, |map, cx| map.snapshot(cx))
18301 .max_point()
18302 }
18303
18304 pub fn text(&self, cx: &App) -> String {
18305 self.buffer.read(cx).read(cx).text()
18306 }
18307
18308 pub fn is_empty(&self, cx: &App) -> bool {
18309 self.buffer.read(cx).read(cx).is_empty()
18310 }
18311
18312 pub fn text_option(&self, cx: &App) -> Option<String> {
18313 let text = self.text(cx);
18314 let text = text.trim();
18315
18316 if text.is_empty() {
18317 return None;
18318 }
18319
18320 Some(text.to_string())
18321 }
18322
18323 pub fn set_text(
18324 &mut self,
18325 text: impl Into<Arc<str>>,
18326 window: &mut Window,
18327 cx: &mut Context<Self>,
18328 ) {
18329 self.transact(window, cx, |this, _, cx| {
18330 this.buffer
18331 .read(cx)
18332 .as_singleton()
18333 .expect("you can only call set_text on editors for singleton buffers")
18334 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18335 });
18336 }
18337
18338 pub fn display_text(&self, cx: &mut App) -> String {
18339 self.display_map
18340 .update(cx, |map, cx| map.snapshot(cx))
18341 .text()
18342 }
18343
18344 fn create_minimap(
18345 &self,
18346 minimap_settings: MinimapSettings,
18347 window: &mut Window,
18348 cx: &mut Context<Self>,
18349 ) -> Option<Entity<Self>> {
18350 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18351 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18352 }
18353
18354 fn initialize_new_minimap(
18355 &self,
18356 minimap_settings: MinimapSettings,
18357 window: &mut Window,
18358 cx: &mut Context<Self>,
18359 ) -> Entity<Self> {
18360 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18361
18362 let mut minimap = Editor::new_internal(
18363 EditorMode::Minimap {
18364 parent: cx.weak_entity(),
18365 },
18366 self.buffer.clone(),
18367 None,
18368 Some(self.display_map.clone()),
18369 window,
18370 cx,
18371 );
18372 minimap.scroll_manager.clone_state(&self.scroll_manager);
18373 minimap.set_text_style_refinement(TextStyleRefinement {
18374 font_size: Some(MINIMAP_FONT_SIZE),
18375 font_weight: Some(MINIMAP_FONT_WEIGHT),
18376 ..Default::default()
18377 });
18378 minimap.update_minimap_configuration(minimap_settings, cx);
18379 cx.new(|_| minimap)
18380 }
18381
18382 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18383 let current_line_highlight = minimap_settings
18384 .current_line_highlight
18385 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18386 self.set_current_line_highlight(Some(current_line_highlight));
18387 }
18388
18389 pub fn minimap(&self) -> Option<&Entity<Self>> {
18390 self.minimap
18391 .as_ref()
18392 .filter(|_| self.minimap_visibility.visible())
18393 }
18394
18395 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18396 let mut wrap_guides = smallvec![];
18397
18398 if self.show_wrap_guides == Some(false) {
18399 return wrap_guides;
18400 }
18401
18402 let settings = self.buffer.read(cx).language_settings(cx);
18403 if settings.show_wrap_guides {
18404 match self.soft_wrap_mode(cx) {
18405 SoftWrap::Column(soft_wrap) => {
18406 wrap_guides.push((soft_wrap as usize, true));
18407 }
18408 SoftWrap::Bounded(soft_wrap) => {
18409 wrap_guides.push((soft_wrap as usize, true));
18410 }
18411 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18412 }
18413 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18414 }
18415
18416 wrap_guides
18417 }
18418
18419 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18420 let settings = self.buffer.read(cx).language_settings(cx);
18421 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18422 match mode {
18423 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18424 SoftWrap::None
18425 }
18426 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18427 language_settings::SoftWrap::PreferredLineLength => {
18428 SoftWrap::Column(settings.preferred_line_length)
18429 }
18430 language_settings::SoftWrap::Bounded => {
18431 SoftWrap::Bounded(settings.preferred_line_length)
18432 }
18433 }
18434 }
18435
18436 pub fn set_soft_wrap_mode(
18437 &mut self,
18438 mode: language_settings::SoftWrap,
18439
18440 cx: &mut Context<Self>,
18441 ) {
18442 self.soft_wrap_mode_override = Some(mode);
18443 cx.notify();
18444 }
18445
18446 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18447 self.hard_wrap = hard_wrap;
18448 cx.notify();
18449 }
18450
18451 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18452 self.text_style_refinement = Some(style);
18453 }
18454
18455 /// called by the Element so we know what style we were most recently rendered with.
18456 pub(crate) fn set_style(
18457 &mut self,
18458 style: EditorStyle,
18459 window: &mut Window,
18460 cx: &mut Context<Self>,
18461 ) {
18462 // We intentionally do not inform the display map about the minimap style
18463 // so that wrapping is not recalculated and stays consistent for the editor
18464 // and its linked minimap.
18465 if !self.mode.is_minimap() {
18466 let rem_size = window.rem_size();
18467 self.display_map.update(cx, |map, cx| {
18468 map.set_font(
18469 style.text.font(),
18470 style.text.font_size.to_pixels(rem_size),
18471 cx,
18472 )
18473 });
18474 }
18475 self.style = Some(style);
18476 }
18477
18478 pub fn style(&self) -> Option<&EditorStyle> {
18479 self.style.as_ref()
18480 }
18481
18482 // Called by the element. This method is not designed to be called outside of the editor
18483 // element's layout code because it does not notify when rewrapping is computed synchronously.
18484 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18485 self.display_map
18486 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18487 }
18488
18489 pub fn set_soft_wrap(&mut self) {
18490 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18491 }
18492
18493 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18494 if self.soft_wrap_mode_override.is_some() {
18495 self.soft_wrap_mode_override.take();
18496 } else {
18497 let soft_wrap = match self.soft_wrap_mode(cx) {
18498 SoftWrap::GitDiff => return,
18499 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18500 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18501 language_settings::SoftWrap::None
18502 }
18503 };
18504 self.soft_wrap_mode_override = Some(soft_wrap);
18505 }
18506 cx.notify();
18507 }
18508
18509 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18510 let Some(workspace) = self.workspace() else {
18511 return;
18512 };
18513 let fs = workspace.read(cx).app_state().fs.clone();
18514 let current_show = TabBarSettings::get_global(cx).show;
18515 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18516 setting.show = Some(!current_show);
18517 });
18518 }
18519
18520 pub fn toggle_indent_guides(
18521 &mut self,
18522 _: &ToggleIndentGuides,
18523 _: &mut Window,
18524 cx: &mut Context<Self>,
18525 ) {
18526 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18527 self.buffer
18528 .read(cx)
18529 .language_settings(cx)
18530 .indent_guides
18531 .enabled
18532 });
18533 self.show_indent_guides = Some(!currently_enabled);
18534 cx.notify();
18535 }
18536
18537 fn should_show_indent_guides(&self) -> Option<bool> {
18538 self.show_indent_guides
18539 }
18540
18541 pub fn toggle_line_numbers(
18542 &mut self,
18543 _: &ToggleLineNumbers,
18544 _: &mut Window,
18545 cx: &mut Context<Self>,
18546 ) {
18547 let mut editor_settings = EditorSettings::get_global(cx).clone();
18548 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18549 EditorSettings::override_global(editor_settings, cx);
18550 }
18551
18552 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18553 if let Some(show_line_numbers) = self.show_line_numbers {
18554 return show_line_numbers;
18555 }
18556 EditorSettings::get_global(cx).gutter.line_numbers
18557 }
18558
18559 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18560 self.use_relative_line_numbers
18561 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18562 }
18563
18564 pub fn toggle_relative_line_numbers(
18565 &mut self,
18566 _: &ToggleRelativeLineNumbers,
18567 _: &mut Window,
18568 cx: &mut Context<Self>,
18569 ) {
18570 let is_relative = self.should_use_relative_line_numbers(cx);
18571 self.set_relative_line_number(Some(!is_relative), cx)
18572 }
18573
18574 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18575 self.use_relative_line_numbers = is_relative;
18576 cx.notify();
18577 }
18578
18579 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18580 self.show_gutter = show_gutter;
18581 cx.notify();
18582 }
18583
18584 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18585 self.show_scrollbars = ScrollbarAxes {
18586 horizontal: show,
18587 vertical: show,
18588 };
18589 cx.notify();
18590 }
18591
18592 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18593 self.show_scrollbars.vertical = show;
18594 cx.notify();
18595 }
18596
18597 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18598 self.show_scrollbars.horizontal = show;
18599 cx.notify();
18600 }
18601
18602 pub fn set_minimap_visibility(
18603 &mut self,
18604 minimap_visibility: MinimapVisibility,
18605 window: &mut Window,
18606 cx: &mut Context<Self>,
18607 ) {
18608 if self.minimap_visibility != minimap_visibility {
18609 if minimap_visibility.visible() && self.minimap.is_none() {
18610 let minimap_settings = EditorSettings::get_global(cx).minimap;
18611 self.minimap =
18612 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18613 }
18614 self.minimap_visibility = minimap_visibility;
18615 cx.notify();
18616 }
18617 }
18618
18619 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18620 self.set_show_scrollbars(false, cx);
18621 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18622 }
18623
18624 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18625 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18626 }
18627
18628 /// Normally the text in full mode and auto height editors is padded on the
18629 /// left side by roughly half a character width for improved hit testing.
18630 ///
18631 /// Use this method to disable this for cases where this is not wanted (e.g.
18632 /// if you want to align the editor text with some other text above or below)
18633 /// or if you want to add this padding to single-line editors.
18634 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18635 self.offset_content = offset_content;
18636 cx.notify();
18637 }
18638
18639 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18640 self.show_line_numbers = Some(show_line_numbers);
18641 cx.notify();
18642 }
18643
18644 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18645 self.disable_expand_excerpt_buttons = true;
18646 cx.notify();
18647 }
18648
18649 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18650 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18651 cx.notify();
18652 }
18653
18654 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18655 self.show_code_actions = Some(show_code_actions);
18656 cx.notify();
18657 }
18658
18659 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18660 self.show_runnables = Some(show_runnables);
18661 cx.notify();
18662 }
18663
18664 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18665 self.show_breakpoints = Some(show_breakpoints);
18666 cx.notify();
18667 }
18668
18669 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18670 if self.display_map.read(cx).masked != masked {
18671 self.display_map.update(cx, |map, _| map.masked = masked);
18672 }
18673 cx.notify()
18674 }
18675
18676 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18677 self.show_wrap_guides = Some(show_wrap_guides);
18678 cx.notify();
18679 }
18680
18681 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18682 self.show_indent_guides = Some(show_indent_guides);
18683 cx.notify();
18684 }
18685
18686 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18687 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18688 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18689 if let Some(dir) = file.abs_path(cx).parent() {
18690 return Some(dir.to_owned());
18691 }
18692 }
18693
18694 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18695 return Some(project_path.path.to_path_buf());
18696 }
18697 }
18698
18699 None
18700 }
18701
18702 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18703 self.active_excerpt(cx)?
18704 .1
18705 .read(cx)
18706 .file()
18707 .and_then(|f| f.as_local())
18708 }
18709
18710 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18711 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18712 let buffer = buffer.read(cx);
18713 if let Some(project_path) = buffer.project_path(cx) {
18714 let project = self.project()?.read(cx);
18715 project.absolute_path(&project_path, cx)
18716 } else {
18717 buffer
18718 .file()
18719 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18720 }
18721 })
18722 }
18723
18724 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18725 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18726 let project_path = buffer.read(cx).project_path(cx)?;
18727 let project = self.project()?.read(cx);
18728 let entry = project.entry_for_path(&project_path, cx)?;
18729 let path = entry.path.to_path_buf();
18730 Some(path)
18731 })
18732 }
18733
18734 pub fn reveal_in_finder(
18735 &mut self,
18736 _: &RevealInFileManager,
18737 _window: &mut Window,
18738 cx: &mut Context<Self>,
18739 ) {
18740 if let Some(target) = self.target_file(cx) {
18741 cx.reveal_path(&target.abs_path(cx));
18742 }
18743 }
18744
18745 pub fn copy_path(
18746 &mut self,
18747 _: &zed_actions::workspace::CopyPath,
18748 _window: &mut Window,
18749 cx: &mut Context<Self>,
18750 ) {
18751 if let Some(path) = self.target_file_abs_path(cx) {
18752 if let Some(path) = path.to_str() {
18753 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18754 }
18755 }
18756 }
18757
18758 pub fn copy_relative_path(
18759 &mut self,
18760 _: &zed_actions::workspace::CopyRelativePath,
18761 _window: &mut Window,
18762 cx: &mut Context<Self>,
18763 ) {
18764 if let Some(path) = self.target_file_path(cx) {
18765 if let Some(path) = path.to_str() {
18766 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18767 }
18768 }
18769 }
18770
18771 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18772 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18773 buffer.read(cx).project_path(cx)
18774 } else {
18775 None
18776 }
18777 }
18778
18779 // Returns true if the editor handled a go-to-line request
18780 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18781 maybe!({
18782 let breakpoint_store = self.breakpoint_store.as_ref()?;
18783
18784 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18785 else {
18786 self.clear_row_highlights::<ActiveDebugLine>();
18787 return None;
18788 };
18789
18790 let position = active_stack_frame.position;
18791 let buffer_id = position.buffer_id?;
18792 let snapshot = self
18793 .project
18794 .as_ref()?
18795 .read(cx)
18796 .buffer_for_id(buffer_id, cx)?
18797 .read(cx)
18798 .snapshot();
18799
18800 let mut handled = false;
18801 for (id, ExcerptRange { context, .. }) in
18802 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18803 {
18804 if context.start.cmp(&position, &snapshot).is_ge()
18805 || context.end.cmp(&position, &snapshot).is_lt()
18806 {
18807 continue;
18808 }
18809 let snapshot = self.buffer.read(cx).snapshot(cx);
18810 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18811
18812 handled = true;
18813 self.clear_row_highlights::<ActiveDebugLine>();
18814
18815 self.go_to_line::<ActiveDebugLine>(
18816 multibuffer_anchor,
18817 Some(cx.theme().colors().editor_debugger_active_line_background),
18818 window,
18819 cx,
18820 );
18821
18822 cx.notify();
18823 }
18824
18825 handled.then_some(())
18826 })
18827 .is_some()
18828 }
18829
18830 pub fn copy_file_name_without_extension(
18831 &mut self,
18832 _: &CopyFileNameWithoutExtension,
18833 _: &mut Window,
18834 cx: &mut Context<Self>,
18835 ) {
18836 if let Some(file) = self.target_file(cx) {
18837 if let Some(file_stem) = file.path().file_stem() {
18838 if let Some(name) = file_stem.to_str() {
18839 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18840 }
18841 }
18842 }
18843 }
18844
18845 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18846 if let Some(file) = self.target_file(cx) {
18847 if let Some(file_name) = file.path().file_name() {
18848 if let Some(name) = file_name.to_str() {
18849 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18850 }
18851 }
18852 }
18853 }
18854
18855 pub fn toggle_git_blame(
18856 &mut self,
18857 _: &::git::Blame,
18858 window: &mut Window,
18859 cx: &mut Context<Self>,
18860 ) {
18861 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18862
18863 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18864 self.start_git_blame(true, window, cx);
18865 }
18866
18867 cx.notify();
18868 }
18869
18870 pub fn toggle_git_blame_inline(
18871 &mut self,
18872 _: &ToggleGitBlameInline,
18873 window: &mut Window,
18874 cx: &mut Context<Self>,
18875 ) {
18876 self.toggle_git_blame_inline_internal(true, window, cx);
18877 cx.notify();
18878 }
18879
18880 pub fn open_git_blame_commit(
18881 &mut self,
18882 _: &OpenGitBlameCommit,
18883 window: &mut Window,
18884 cx: &mut Context<Self>,
18885 ) {
18886 self.open_git_blame_commit_internal(window, cx);
18887 }
18888
18889 fn open_git_blame_commit_internal(
18890 &mut self,
18891 window: &mut Window,
18892 cx: &mut Context<Self>,
18893 ) -> Option<()> {
18894 let blame = self.blame.as_ref()?;
18895 let snapshot = self.snapshot(window, cx);
18896 let cursor = self.selections.newest::<Point>(cx).head();
18897 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18898 let blame_entry = blame
18899 .update(cx, |blame, cx| {
18900 blame
18901 .blame_for_rows(
18902 &[RowInfo {
18903 buffer_id: Some(buffer.remote_id()),
18904 buffer_row: Some(point.row),
18905 ..Default::default()
18906 }],
18907 cx,
18908 )
18909 .next()
18910 })
18911 .flatten()?;
18912 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18913 let repo = blame.read(cx).repository(cx)?;
18914 let workspace = self.workspace()?.downgrade();
18915 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18916 None
18917 }
18918
18919 pub fn git_blame_inline_enabled(&self) -> bool {
18920 self.git_blame_inline_enabled
18921 }
18922
18923 pub fn toggle_selection_menu(
18924 &mut self,
18925 _: &ToggleSelectionMenu,
18926 _: &mut Window,
18927 cx: &mut Context<Self>,
18928 ) {
18929 self.show_selection_menu = self
18930 .show_selection_menu
18931 .map(|show_selections_menu| !show_selections_menu)
18932 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18933
18934 cx.notify();
18935 }
18936
18937 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18938 self.show_selection_menu
18939 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18940 }
18941
18942 fn start_git_blame(
18943 &mut self,
18944 user_triggered: bool,
18945 window: &mut Window,
18946 cx: &mut Context<Self>,
18947 ) {
18948 if let Some(project) = self.project() {
18949 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18950 return;
18951 };
18952
18953 if buffer.read(cx).file().is_none() {
18954 return;
18955 }
18956
18957 let focused = self.focus_handle(cx).contains_focused(window, cx);
18958
18959 let project = project.clone();
18960 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18961 self.blame_subscription =
18962 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18963 self.blame = Some(blame);
18964 }
18965 }
18966
18967 fn toggle_git_blame_inline_internal(
18968 &mut self,
18969 user_triggered: bool,
18970 window: &mut Window,
18971 cx: &mut Context<Self>,
18972 ) {
18973 if self.git_blame_inline_enabled {
18974 self.git_blame_inline_enabled = false;
18975 self.show_git_blame_inline = false;
18976 self.show_git_blame_inline_delay_task.take();
18977 } else {
18978 self.git_blame_inline_enabled = true;
18979 self.start_git_blame_inline(user_triggered, window, cx);
18980 }
18981
18982 cx.notify();
18983 }
18984
18985 fn start_git_blame_inline(
18986 &mut self,
18987 user_triggered: bool,
18988 window: &mut Window,
18989 cx: &mut Context<Self>,
18990 ) {
18991 self.start_git_blame(user_triggered, window, cx);
18992
18993 if ProjectSettings::get_global(cx)
18994 .git
18995 .inline_blame_delay()
18996 .is_some()
18997 {
18998 self.start_inline_blame_timer(window, cx);
18999 } else {
19000 self.show_git_blame_inline = true
19001 }
19002 }
19003
19004 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19005 self.blame.as_ref()
19006 }
19007
19008 pub fn show_git_blame_gutter(&self) -> bool {
19009 self.show_git_blame_gutter
19010 }
19011
19012 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19013 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19014 }
19015
19016 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19017 self.show_git_blame_inline
19018 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19019 && !self.newest_selection_head_on_empty_line(cx)
19020 && self.has_blame_entries(cx)
19021 }
19022
19023 fn has_blame_entries(&self, cx: &App) -> bool {
19024 self.blame()
19025 .map_or(false, |blame| blame.read(cx).has_generated_entries())
19026 }
19027
19028 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19029 let cursor_anchor = self.selections.newest_anchor().head();
19030
19031 let snapshot = self.buffer.read(cx).snapshot(cx);
19032 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19033
19034 snapshot.line_len(buffer_row) == 0
19035 }
19036
19037 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19038 let buffer_and_selection = maybe!({
19039 let selection = self.selections.newest::<Point>(cx);
19040 let selection_range = selection.range();
19041
19042 let multi_buffer = self.buffer().read(cx);
19043 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19044 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19045
19046 let (buffer, range, _) = if selection.reversed {
19047 buffer_ranges.first()
19048 } else {
19049 buffer_ranges.last()
19050 }?;
19051
19052 let selection = text::ToPoint::to_point(&range.start, buffer).row
19053 ..text::ToPoint::to_point(&range.end, buffer).row;
19054 Some((
19055 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
19056 selection,
19057 ))
19058 });
19059
19060 let Some((buffer, selection)) = buffer_and_selection else {
19061 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19062 };
19063
19064 let Some(project) = self.project() else {
19065 return Task::ready(Err(anyhow!("editor does not have project")));
19066 };
19067
19068 project.update(cx, |project, cx| {
19069 project.get_permalink_to_line(&buffer, selection, cx)
19070 })
19071 }
19072
19073 pub fn copy_permalink_to_line(
19074 &mut self,
19075 _: &CopyPermalinkToLine,
19076 window: &mut Window,
19077 cx: &mut Context<Self>,
19078 ) {
19079 let permalink_task = self.get_permalink_to_line(cx);
19080 let workspace = self.workspace();
19081
19082 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19083 Ok(permalink) => {
19084 cx.update(|_, cx| {
19085 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19086 })
19087 .ok();
19088 }
19089 Err(err) => {
19090 let message = format!("Failed to copy permalink: {err}");
19091
19092 anyhow::Result::<()>::Err(err).log_err();
19093
19094 if let Some(workspace) = workspace {
19095 workspace
19096 .update_in(cx, |workspace, _, cx| {
19097 struct CopyPermalinkToLine;
19098
19099 workspace.show_toast(
19100 Toast::new(
19101 NotificationId::unique::<CopyPermalinkToLine>(),
19102 message,
19103 ),
19104 cx,
19105 )
19106 })
19107 .ok();
19108 }
19109 }
19110 })
19111 .detach();
19112 }
19113
19114 pub fn copy_file_location(
19115 &mut self,
19116 _: &CopyFileLocation,
19117 _: &mut Window,
19118 cx: &mut Context<Self>,
19119 ) {
19120 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19121 if let Some(file) = self.target_file(cx) {
19122 if let Some(path) = file.path().to_str() {
19123 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19124 }
19125 }
19126 }
19127
19128 pub fn open_permalink_to_line(
19129 &mut self,
19130 _: &OpenPermalinkToLine,
19131 window: &mut Window,
19132 cx: &mut Context<Self>,
19133 ) {
19134 let permalink_task = self.get_permalink_to_line(cx);
19135 let workspace = self.workspace();
19136
19137 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19138 Ok(permalink) => {
19139 cx.update(|_, cx| {
19140 cx.open_url(permalink.as_ref());
19141 })
19142 .ok();
19143 }
19144 Err(err) => {
19145 let message = format!("Failed to open permalink: {err}");
19146
19147 anyhow::Result::<()>::Err(err).log_err();
19148
19149 if let Some(workspace) = workspace {
19150 workspace
19151 .update(cx, |workspace, cx| {
19152 struct OpenPermalinkToLine;
19153
19154 workspace.show_toast(
19155 Toast::new(
19156 NotificationId::unique::<OpenPermalinkToLine>(),
19157 message,
19158 ),
19159 cx,
19160 )
19161 })
19162 .ok();
19163 }
19164 }
19165 })
19166 .detach();
19167 }
19168
19169 pub fn insert_uuid_v4(
19170 &mut self,
19171 _: &InsertUuidV4,
19172 window: &mut Window,
19173 cx: &mut Context<Self>,
19174 ) {
19175 self.insert_uuid(UuidVersion::V4, window, cx);
19176 }
19177
19178 pub fn insert_uuid_v7(
19179 &mut self,
19180 _: &InsertUuidV7,
19181 window: &mut Window,
19182 cx: &mut Context<Self>,
19183 ) {
19184 self.insert_uuid(UuidVersion::V7, window, cx);
19185 }
19186
19187 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19188 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19189 self.transact(window, cx, |this, window, cx| {
19190 let edits = this
19191 .selections
19192 .all::<Point>(cx)
19193 .into_iter()
19194 .map(|selection| {
19195 let uuid = match version {
19196 UuidVersion::V4 => uuid::Uuid::new_v4(),
19197 UuidVersion::V7 => uuid::Uuid::now_v7(),
19198 };
19199
19200 (selection.range(), uuid.to_string())
19201 });
19202 this.edit(edits, cx);
19203 this.refresh_edit_prediction(true, false, window, cx);
19204 });
19205 }
19206
19207 pub fn open_selections_in_multibuffer(
19208 &mut self,
19209 _: &OpenSelectionsInMultibuffer,
19210 window: &mut Window,
19211 cx: &mut Context<Self>,
19212 ) {
19213 let multibuffer = self.buffer.read(cx);
19214
19215 let Some(buffer) = multibuffer.as_singleton() else {
19216 return;
19217 };
19218
19219 let Some(workspace) = self.workspace() else {
19220 return;
19221 };
19222
19223 let title = multibuffer.title(cx).to_string();
19224
19225 let locations = self
19226 .selections
19227 .all_anchors(cx)
19228 .into_iter()
19229 .map(|selection| Location {
19230 buffer: buffer.clone(),
19231 range: selection.start.text_anchor..selection.end.text_anchor,
19232 })
19233 .collect::<Vec<_>>();
19234
19235 cx.spawn_in(window, async move |_, cx| {
19236 workspace.update_in(cx, |workspace, window, cx| {
19237 Self::open_locations_in_multibuffer(
19238 workspace,
19239 locations,
19240 format!("Selections for '{title}'"),
19241 false,
19242 MultibufferSelectionMode::All,
19243 window,
19244 cx,
19245 );
19246 })
19247 })
19248 .detach();
19249 }
19250
19251 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19252 /// last highlight added will be used.
19253 ///
19254 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19255 pub fn highlight_rows<T: 'static>(
19256 &mut self,
19257 range: Range<Anchor>,
19258 color: Hsla,
19259 options: RowHighlightOptions,
19260 cx: &mut Context<Self>,
19261 ) {
19262 let snapshot = self.buffer().read(cx).snapshot(cx);
19263 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19264 let ix = row_highlights.binary_search_by(|highlight| {
19265 Ordering::Equal
19266 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19267 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19268 });
19269
19270 if let Err(mut ix) = ix {
19271 let index = post_inc(&mut self.highlight_order);
19272
19273 // If this range intersects with the preceding highlight, then merge it with
19274 // the preceding highlight. Otherwise insert a new highlight.
19275 let mut merged = false;
19276 if ix > 0 {
19277 let prev_highlight = &mut row_highlights[ix - 1];
19278 if prev_highlight
19279 .range
19280 .end
19281 .cmp(&range.start, &snapshot)
19282 .is_ge()
19283 {
19284 ix -= 1;
19285 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19286 prev_highlight.range.end = range.end;
19287 }
19288 merged = true;
19289 prev_highlight.index = index;
19290 prev_highlight.color = color;
19291 prev_highlight.options = options;
19292 }
19293 }
19294
19295 if !merged {
19296 row_highlights.insert(
19297 ix,
19298 RowHighlight {
19299 range: range.clone(),
19300 index,
19301 color,
19302 options,
19303 type_id: TypeId::of::<T>(),
19304 },
19305 );
19306 }
19307
19308 // If any of the following highlights intersect with this one, merge them.
19309 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19310 let highlight = &row_highlights[ix];
19311 if next_highlight
19312 .range
19313 .start
19314 .cmp(&highlight.range.end, &snapshot)
19315 .is_le()
19316 {
19317 if next_highlight
19318 .range
19319 .end
19320 .cmp(&highlight.range.end, &snapshot)
19321 .is_gt()
19322 {
19323 row_highlights[ix].range.end = next_highlight.range.end;
19324 }
19325 row_highlights.remove(ix + 1);
19326 } else {
19327 break;
19328 }
19329 }
19330 }
19331 }
19332
19333 /// Remove any highlighted row ranges of the given type that intersect the
19334 /// given ranges.
19335 pub fn remove_highlighted_rows<T: 'static>(
19336 &mut self,
19337 ranges_to_remove: Vec<Range<Anchor>>,
19338 cx: &mut Context<Self>,
19339 ) {
19340 let snapshot = self.buffer().read(cx).snapshot(cx);
19341 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19342 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19343 row_highlights.retain(|highlight| {
19344 while let Some(range_to_remove) = ranges_to_remove.peek() {
19345 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19346 Ordering::Less | Ordering::Equal => {
19347 ranges_to_remove.next();
19348 }
19349 Ordering::Greater => {
19350 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19351 Ordering::Less | Ordering::Equal => {
19352 return false;
19353 }
19354 Ordering::Greater => break,
19355 }
19356 }
19357 }
19358 }
19359
19360 true
19361 })
19362 }
19363
19364 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19365 pub fn clear_row_highlights<T: 'static>(&mut self) {
19366 self.highlighted_rows.remove(&TypeId::of::<T>());
19367 }
19368
19369 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19370 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19371 self.highlighted_rows
19372 .get(&TypeId::of::<T>())
19373 .map_or(&[] as &[_], |vec| vec.as_slice())
19374 .iter()
19375 .map(|highlight| (highlight.range.clone(), highlight.color))
19376 }
19377
19378 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19379 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19380 /// Allows to ignore certain kinds of highlights.
19381 pub fn highlighted_display_rows(
19382 &self,
19383 window: &mut Window,
19384 cx: &mut App,
19385 ) -> BTreeMap<DisplayRow, LineHighlight> {
19386 let snapshot = self.snapshot(window, cx);
19387 let mut used_highlight_orders = HashMap::default();
19388 self.highlighted_rows
19389 .iter()
19390 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19391 .fold(
19392 BTreeMap::<DisplayRow, LineHighlight>::new(),
19393 |mut unique_rows, highlight| {
19394 let start = highlight.range.start.to_display_point(&snapshot);
19395 let end = highlight.range.end.to_display_point(&snapshot);
19396 let start_row = start.row().0;
19397 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19398 && end.column() == 0
19399 {
19400 end.row().0.saturating_sub(1)
19401 } else {
19402 end.row().0
19403 };
19404 for row in start_row..=end_row {
19405 let used_index =
19406 used_highlight_orders.entry(row).or_insert(highlight.index);
19407 if highlight.index >= *used_index {
19408 *used_index = highlight.index;
19409 unique_rows.insert(
19410 DisplayRow(row),
19411 LineHighlight {
19412 include_gutter: highlight.options.include_gutter,
19413 border: None,
19414 background: highlight.color.into(),
19415 type_id: Some(highlight.type_id),
19416 },
19417 );
19418 }
19419 }
19420 unique_rows
19421 },
19422 )
19423 }
19424
19425 pub fn highlighted_display_row_for_autoscroll(
19426 &self,
19427 snapshot: &DisplaySnapshot,
19428 ) -> Option<DisplayRow> {
19429 self.highlighted_rows
19430 .values()
19431 .flat_map(|highlighted_rows| highlighted_rows.iter())
19432 .filter_map(|highlight| {
19433 if highlight.options.autoscroll {
19434 Some(highlight.range.start.to_display_point(snapshot).row())
19435 } else {
19436 None
19437 }
19438 })
19439 .min()
19440 }
19441
19442 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19443 self.highlight_background::<SearchWithinRange>(
19444 ranges,
19445 |colors| colors.colors().editor_document_highlight_read_background,
19446 cx,
19447 )
19448 }
19449
19450 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19451 self.breadcrumb_header = Some(new_header);
19452 }
19453
19454 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19455 self.clear_background_highlights::<SearchWithinRange>(cx);
19456 }
19457
19458 pub fn highlight_background<T: 'static>(
19459 &mut self,
19460 ranges: &[Range<Anchor>],
19461 color_fetcher: fn(&Theme) -> Hsla,
19462 cx: &mut Context<Self>,
19463 ) {
19464 self.background_highlights.insert(
19465 HighlightKey::Type(TypeId::of::<T>()),
19466 (color_fetcher, Arc::from(ranges)),
19467 );
19468 self.scrollbar_marker_state.dirty = true;
19469 cx.notify();
19470 }
19471
19472 pub fn highlight_background_key<T: 'static>(
19473 &mut self,
19474 key: usize,
19475 ranges: &[Range<Anchor>],
19476 color_fetcher: fn(&Theme) -> Hsla,
19477 cx: &mut Context<Self>,
19478 ) {
19479 self.background_highlights.insert(
19480 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19481 (color_fetcher, Arc::from(ranges)),
19482 );
19483 self.scrollbar_marker_state.dirty = true;
19484 cx.notify();
19485 }
19486
19487 pub fn clear_background_highlights<T: 'static>(
19488 &mut self,
19489 cx: &mut Context<Self>,
19490 ) -> Option<BackgroundHighlight> {
19491 let text_highlights = self
19492 .background_highlights
19493 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19494 if !text_highlights.1.is_empty() {
19495 self.scrollbar_marker_state.dirty = true;
19496 cx.notify();
19497 }
19498 Some(text_highlights)
19499 }
19500
19501 pub fn highlight_gutter<T: 'static>(
19502 &mut self,
19503 ranges: impl Into<Vec<Range<Anchor>>>,
19504 color_fetcher: fn(&App) -> Hsla,
19505 cx: &mut Context<Self>,
19506 ) {
19507 self.gutter_highlights
19508 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19509 cx.notify();
19510 }
19511
19512 pub fn clear_gutter_highlights<T: 'static>(
19513 &mut self,
19514 cx: &mut Context<Self>,
19515 ) -> Option<GutterHighlight> {
19516 cx.notify();
19517 self.gutter_highlights.remove(&TypeId::of::<T>())
19518 }
19519
19520 pub fn insert_gutter_highlight<T: 'static>(
19521 &mut self,
19522 range: Range<Anchor>,
19523 color_fetcher: fn(&App) -> Hsla,
19524 cx: &mut Context<Self>,
19525 ) {
19526 let snapshot = self.buffer().read(cx).snapshot(cx);
19527 let mut highlights = self
19528 .gutter_highlights
19529 .remove(&TypeId::of::<T>())
19530 .map(|(_, highlights)| highlights)
19531 .unwrap_or_default();
19532 let ix = highlights.binary_search_by(|highlight| {
19533 Ordering::Equal
19534 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19535 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19536 });
19537 if let Err(ix) = ix {
19538 highlights.insert(ix, range);
19539 }
19540 self.gutter_highlights
19541 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19542 }
19543
19544 pub fn remove_gutter_highlights<T: 'static>(
19545 &mut self,
19546 ranges_to_remove: Vec<Range<Anchor>>,
19547 cx: &mut Context<Self>,
19548 ) {
19549 let snapshot = self.buffer().read(cx).snapshot(cx);
19550 let Some((color_fetcher, mut gutter_highlights)) =
19551 self.gutter_highlights.remove(&TypeId::of::<T>())
19552 else {
19553 return;
19554 };
19555 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19556 gutter_highlights.retain(|highlight| {
19557 while let Some(range_to_remove) = ranges_to_remove.peek() {
19558 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19559 Ordering::Less | Ordering::Equal => {
19560 ranges_to_remove.next();
19561 }
19562 Ordering::Greater => {
19563 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19564 Ordering::Less | Ordering::Equal => {
19565 return false;
19566 }
19567 Ordering::Greater => break,
19568 }
19569 }
19570 }
19571 }
19572
19573 true
19574 });
19575 self.gutter_highlights
19576 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19577 }
19578
19579 #[cfg(feature = "test-support")]
19580 pub fn all_text_highlights(
19581 &self,
19582 window: &mut Window,
19583 cx: &mut Context<Self>,
19584 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19585 let snapshot = self.snapshot(window, cx);
19586 self.display_map.update(cx, |display_map, _| {
19587 display_map
19588 .all_text_highlights()
19589 .map(|highlight| {
19590 let (style, ranges) = highlight.as_ref();
19591 (
19592 *style,
19593 ranges
19594 .iter()
19595 .map(|range| range.clone().to_display_points(&snapshot))
19596 .collect(),
19597 )
19598 })
19599 .collect()
19600 })
19601 }
19602
19603 #[cfg(feature = "test-support")]
19604 pub fn all_text_background_highlights(
19605 &self,
19606 window: &mut Window,
19607 cx: &mut Context<Self>,
19608 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19609 let snapshot = self.snapshot(window, cx);
19610 let buffer = &snapshot.buffer_snapshot;
19611 let start = buffer.anchor_before(0);
19612 let end = buffer.anchor_after(buffer.len());
19613 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19614 }
19615
19616 #[cfg(feature = "test-support")]
19617 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19618 let snapshot = self.buffer().read(cx).snapshot(cx);
19619
19620 let highlights = self
19621 .background_highlights
19622 .get(&HighlightKey::Type(TypeId::of::<
19623 items::BufferSearchHighlights,
19624 >()));
19625
19626 if let Some((_color, ranges)) = highlights {
19627 ranges
19628 .iter()
19629 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19630 .collect_vec()
19631 } else {
19632 vec![]
19633 }
19634 }
19635
19636 fn document_highlights_for_position<'a>(
19637 &'a self,
19638 position: Anchor,
19639 buffer: &'a MultiBufferSnapshot,
19640 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19641 let read_highlights = self
19642 .background_highlights
19643 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19644 .map(|h| &h.1);
19645 let write_highlights = self
19646 .background_highlights
19647 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19648 .map(|h| &h.1);
19649 let left_position = position.bias_left(buffer);
19650 let right_position = position.bias_right(buffer);
19651 read_highlights
19652 .into_iter()
19653 .chain(write_highlights)
19654 .flat_map(move |ranges| {
19655 let start_ix = match ranges.binary_search_by(|probe| {
19656 let cmp = probe.end.cmp(&left_position, buffer);
19657 if cmp.is_ge() {
19658 Ordering::Greater
19659 } else {
19660 Ordering::Less
19661 }
19662 }) {
19663 Ok(i) | Err(i) => i,
19664 };
19665
19666 ranges[start_ix..]
19667 .iter()
19668 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19669 })
19670 }
19671
19672 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19673 self.background_highlights
19674 .get(&HighlightKey::Type(TypeId::of::<T>()))
19675 .map_or(false, |(_, highlights)| !highlights.is_empty())
19676 }
19677
19678 pub fn background_highlights_in_range(
19679 &self,
19680 search_range: Range<Anchor>,
19681 display_snapshot: &DisplaySnapshot,
19682 theme: &Theme,
19683 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19684 let mut results = Vec::new();
19685 for (color_fetcher, ranges) in self.background_highlights.values() {
19686 let color = color_fetcher(theme);
19687 let start_ix = match ranges.binary_search_by(|probe| {
19688 let cmp = probe
19689 .end
19690 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19691 if cmp.is_gt() {
19692 Ordering::Greater
19693 } else {
19694 Ordering::Less
19695 }
19696 }) {
19697 Ok(i) | Err(i) => i,
19698 };
19699 for range in &ranges[start_ix..] {
19700 if range
19701 .start
19702 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19703 .is_ge()
19704 {
19705 break;
19706 }
19707
19708 let start = range.start.to_display_point(display_snapshot);
19709 let end = range.end.to_display_point(display_snapshot);
19710 results.push((start..end, color))
19711 }
19712 }
19713 results
19714 }
19715
19716 pub fn background_highlight_row_ranges<T: 'static>(
19717 &self,
19718 search_range: Range<Anchor>,
19719 display_snapshot: &DisplaySnapshot,
19720 count: usize,
19721 ) -> Vec<RangeInclusive<DisplayPoint>> {
19722 let mut results = Vec::new();
19723 let Some((_, ranges)) = self
19724 .background_highlights
19725 .get(&HighlightKey::Type(TypeId::of::<T>()))
19726 else {
19727 return vec![];
19728 };
19729
19730 let start_ix = match ranges.binary_search_by(|probe| {
19731 let cmp = probe
19732 .end
19733 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19734 if cmp.is_gt() {
19735 Ordering::Greater
19736 } else {
19737 Ordering::Less
19738 }
19739 }) {
19740 Ok(i) | Err(i) => i,
19741 };
19742 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19743 if let (Some(start_display), Some(end_display)) = (start, end) {
19744 results.push(
19745 start_display.to_display_point(display_snapshot)
19746 ..=end_display.to_display_point(display_snapshot),
19747 );
19748 }
19749 };
19750 let mut start_row: Option<Point> = None;
19751 let mut end_row: Option<Point> = None;
19752 if ranges.len() > count {
19753 return Vec::new();
19754 }
19755 for range in &ranges[start_ix..] {
19756 if range
19757 .start
19758 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19759 .is_ge()
19760 {
19761 break;
19762 }
19763 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19764 if let Some(current_row) = &end_row {
19765 if end.row == current_row.row {
19766 continue;
19767 }
19768 }
19769 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19770 if start_row.is_none() {
19771 assert_eq!(end_row, None);
19772 start_row = Some(start);
19773 end_row = Some(end);
19774 continue;
19775 }
19776 if let Some(current_end) = end_row.as_mut() {
19777 if start.row > current_end.row + 1 {
19778 push_region(start_row, end_row);
19779 start_row = Some(start);
19780 end_row = Some(end);
19781 } else {
19782 // Merge two hunks.
19783 *current_end = end;
19784 }
19785 } else {
19786 unreachable!();
19787 }
19788 }
19789 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19790 push_region(start_row, end_row);
19791 results
19792 }
19793
19794 pub fn gutter_highlights_in_range(
19795 &self,
19796 search_range: Range<Anchor>,
19797 display_snapshot: &DisplaySnapshot,
19798 cx: &App,
19799 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19800 let mut results = Vec::new();
19801 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19802 let color = color_fetcher(cx);
19803 let start_ix = match ranges.binary_search_by(|probe| {
19804 let cmp = probe
19805 .end
19806 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19807 if cmp.is_gt() {
19808 Ordering::Greater
19809 } else {
19810 Ordering::Less
19811 }
19812 }) {
19813 Ok(i) | Err(i) => i,
19814 };
19815 for range in &ranges[start_ix..] {
19816 if range
19817 .start
19818 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19819 .is_ge()
19820 {
19821 break;
19822 }
19823
19824 let start = range.start.to_display_point(display_snapshot);
19825 let end = range.end.to_display_point(display_snapshot);
19826 results.push((start..end, color))
19827 }
19828 }
19829 results
19830 }
19831
19832 /// Get the text ranges corresponding to the redaction query
19833 pub fn redacted_ranges(
19834 &self,
19835 search_range: Range<Anchor>,
19836 display_snapshot: &DisplaySnapshot,
19837 cx: &App,
19838 ) -> Vec<Range<DisplayPoint>> {
19839 display_snapshot
19840 .buffer_snapshot
19841 .redacted_ranges(search_range, |file| {
19842 if let Some(file) = file {
19843 file.is_private()
19844 && EditorSettings::get(
19845 Some(SettingsLocation {
19846 worktree_id: file.worktree_id(cx),
19847 path: file.path().as_ref(),
19848 }),
19849 cx,
19850 )
19851 .redact_private_values
19852 } else {
19853 false
19854 }
19855 })
19856 .map(|range| {
19857 range.start.to_display_point(display_snapshot)
19858 ..range.end.to_display_point(display_snapshot)
19859 })
19860 .collect()
19861 }
19862
19863 pub fn highlight_text_key<T: 'static>(
19864 &mut self,
19865 key: usize,
19866 ranges: Vec<Range<Anchor>>,
19867 style: HighlightStyle,
19868 cx: &mut Context<Self>,
19869 ) {
19870 self.display_map.update(cx, |map, _| {
19871 map.highlight_text(
19872 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19873 ranges,
19874 style,
19875 );
19876 });
19877 cx.notify();
19878 }
19879
19880 pub fn highlight_text<T: 'static>(
19881 &mut self,
19882 ranges: Vec<Range<Anchor>>,
19883 style: HighlightStyle,
19884 cx: &mut Context<Self>,
19885 ) {
19886 self.display_map.update(cx, |map, _| {
19887 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19888 });
19889 cx.notify();
19890 }
19891
19892 pub(crate) fn highlight_inlays<T: 'static>(
19893 &mut self,
19894 highlights: Vec<InlayHighlight>,
19895 style: HighlightStyle,
19896 cx: &mut Context<Self>,
19897 ) {
19898 self.display_map.update(cx, |map, _| {
19899 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19900 });
19901 cx.notify();
19902 }
19903
19904 pub fn text_highlights<'a, T: 'static>(
19905 &'a self,
19906 cx: &'a App,
19907 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19908 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19909 }
19910
19911 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19912 let cleared = self
19913 .display_map
19914 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19915 if cleared {
19916 cx.notify();
19917 }
19918 }
19919
19920 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19921 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19922 && self.focus_handle.is_focused(window)
19923 }
19924
19925 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19926 self.show_cursor_when_unfocused = is_enabled;
19927 cx.notify();
19928 }
19929
19930 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19931 cx.notify();
19932 }
19933
19934 fn on_debug_session_event(
19935 &mut self,
19936 _session: Entity<Session>,
19937 event: &SessionEvent,
19938 cx: &mut Context<Self>,
19939 ) {
19940 match event {
19941 SessionEvent::InvalidateInlineValue => {
19942 self.refresh_inline_values(cx);
19943 }
19944 _ => {}
19945 }
19946 }
19947
19948 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19949 let Some(project) = self.project.clone() else {
19950 return;
19951 };
19952
19953 if !self.inline_value_cache.enabled {
19954 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19955 self.splice_inlays(&inlays, Vec::new(), cx);
19956 return;
19957 }
19958
19959 let current_execution_position = self
19960 .highlighted_rows
19961 .get(&TypeId::of::<ActiveDebugLine>())
19962 .and_then(|lines| lines.last().map(|line| line.range.end));
19963
19964 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19965 let inline_values = editor
19966 .update(cx, |editor, cx| {
19967 let Some(current_execution_position) = current_execution_position else {
19968 return Some(Task::ready(Ok(Vec::new())));
19969 };
19970
19971 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19972 let snapshot = buffer.snapshot(cx);
19973
19974 let excerpt = snapshot.excerpt_containing(
19975 current_execution_position..current_execution_position,
19976 )?;
19977
19978 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19979 })?;
19980
19981 let range =
19982 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19983
19984 project.inline_values(buffer, range, cx)
19985 })
19986 .ok()
19987 .flatten()?
19988 .await
19989 .context("refreshing debugger inlays")
19990 .log_err()?;
19991
19992 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19993
19994 for (buffer_id, inline_value) in inline_values
19995 .into_iter()
19996 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19997 {
19998 buffer_inline_values
19999 .entry(buffer_id)
20000 .or_default()
20001 .push(inline_value);
20002 }
20003
20004 editor
20005 .update(cx, |editor, cx| {
20006 let snapshot = editor.buffer.read(cx).snapshot(cx);
20007 let mut new_inlays = Vec::default();
20008
20009 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20010 let buffer_id = buffer_snapshot.remote_id();
20011 buffer_inline_values
20012 .get(&buffer_id)
20013 .into_iter()
20014 .flatten()
20015 .for_each(|hint| {
20016 let inlay = Inlay::debugger(
20017 post_inc(&mut editor.next_inlay_id),
20018 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20019 hint.text(),
20020 );
20021 if !inlay.text.chars().contains(&'\n') {
20022 new_inlays.push(inlay);
20023 }
20024 });
20025 }
20026
20027 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20028 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20029
20030 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20031 })
20032 .ok()?;
20033 Some(())
20034 });
20035 }
20036
20037 fn on_buffer_event(
20038 &mut self,
20039 multibuffer: &Entity<MultiBuffer>,
20040 event: &multi_buffer::Event,
20041 window: &mut Window,
20042 cx: &mut Context<Self>,
20043 ) {
20044 match event {
20045 multi_buffer::Event::Edited {
20046 singleton_buffer_edited,
20047 edited_buffer,
20048 } => {
20049 self.scrollbar_marker_state.dirty = true;
20050 self.active_indent_guides_state.dirty = true;
20051 self.refresh_active_diagnostics(cx);
20052 self.refresh_code_actions(window, cx);
20053 self.refresh_selected_text_highlights(true, window, cx);
20054 self.refresh_single_line_folds(window, cx);
20055 refresh_matching_bracket_highlights(self, window, cx);
20056 if self.has_active_edit_prediction() {
20057 self.update_visible_edit_prediction(window, cx);
20058 }
20059 if let Some(project) = self.project.as_ref() {
20060 if let Some(edited_buffer) = edited_buffer {
20061 project.update(cx, |project, cx| {
20062 self.registered_buffers
20063 .entry(edited_buffer.read(cx).remote_id())
20064 .or_insert_with(|| {
20065 project.register_buffer_with_language_servers(edited_buffer, cx)
20066 });
20067 });
20068 }
20069 }
20070 cx.emit(EditorEvent::BufferEdited);
20071 cx.emit(SearchEvent::MatchesInvalidated);
20072
20073 if let Some(buffer) = edited_buffer {
20074 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20075 }
20076
20077 if *singleton_buffer_edited {
20078 if let Some(buffer) = edited_buffer {
20079 if buffer.read(cx).file().is_none() {
20080 cx.emit(EditorEvent::TitleChanged);
20081 }
20082 }
20083 if let Some(project) = &self.project {
20084 #[allow(clippy::mutable_key_type)]
20085 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20086 multibuffer
20087 .all_buffers()
20088 .into_iter()
20089 .filter_map(|buffer| {
20090 buffer.update(cx, |buffer, cx| {
20091 let language = buffer.language()?;
20092 let should_discard = project.update(cx, |project, cx| {
20093 project.is_local()
20094 && !project.has_language_servers_for(buffer, cx)
20095 });
20096 should_discard.not().then_some(language.clone())
20097 })
20098 })
20099 .collect::<HashSet<_>>()
20100 });
20101 if !languages_affected.is_empty() {
20102 self.refresh_inlay_hints(
20103 InlayHintRefreshReason::BufferEdited(languages_affected),
20104 cx,
20105 );
20106 }
20107 }
20108 }
20109
20110 let Some(project) = &self.project else { return };
20111 let (telemetry, is_via_ssh) = {
20112 let project = project.read(cx);
20113 let telemetry = project.client().telemetry().clone();
20114 let is_via_ssh = project.is_via_ssh();
20115 (telemetry, is_via_ssh)
20116 };
20117 refresh_linked_ranges(self, window, cx);
20118 telemetry.log_edit_event("editor", is_via_ssh);
20119 }
20120 multi_buffer::Event::ExcerptsAdded {
20121 buffer,
20122 predecessor,
20123 excerpts,
20124 } => {
20125 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20126 let buffer_id = buffer.read(cx).remote_id();
20127 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
20128 if let Some(project) = &self.project {
20129 update_uncommitted_diff_for_buffer(
20130 cx.entity(),
20131 project,
20132 [buffer.clone()],
20133 self.buffer.clone(),
20134 cx,
20135 )
20136 .detach();
20137 }
20138 }
20139 self.update_lsp_data(false, Some(buffer_id), window, cx);
20140 cx.emit(EditorEvent::ExcerptsAdded {
20141 buffer: buffer.clone(),
20142 predecessor: *predecessor,
20143 excerpts: excerpts.clone(),
20144 });
20145 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20146 }
20147 multi_buffer::Event::ExcerptsRemoved {
20148 ids,
20149 removed_buffer_ids,
20150 } => {
20151 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20152 let buffer = self.buffer.read(cx);
20153 self.registered_buffers
20154 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20155 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20156 cx.emit(EditorEvent::ExcerptsRemoved {
20157 ids: ids.clone(),
20158 removed_buffer_ids: removed_buffer_ids.clone(),
20159 });
20160 }
20161 multi_buffer::Event::ExcerptsEdited {
20162 excerpt_ids,
20163 buffer_ids,
20164 } => {
20165 self.display_map.update(cx, |map, cx| {
20166 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20167 });
20168 cx.emit(EditorEvent::ExcerptsEdited {
20169 ids: excerpt_ids.clone(),
20170 });
20171 }
20172 multi_buffer::Event::ExcerptsExpanded { ids } => {
20173 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20174 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20175 }
20176 multi_buffer::Event::Reparsed(buffer_id) => {
20177 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20178 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20179
20180 cx.emit(EditorEvent::Reparsed(*buffer_id));
20181 }
20182 multi_buffer::Event::DiffHunksToggled => {
20183 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20184 }
20185 multi_buffer::Event::LanguageChanged(buffer_id) => {
20186 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20187 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20188 cx.emit(EditorEvent::Reparsed(*buffer_id));
20189 cx.notify();
20190 }
20191 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20192 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20193 multi_buffer::Event::FileHandleChanged
20194 | multi_buffer::Event::Reloaded
20195 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20196 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20197 multi_buffer::Event::DiagnosticsUpdated => {
20198 self.update_diagnostics_state(window, cx);
20199 }
20200 _ => {}
20201 };
20202 }
20203
20204 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20205 if !self.diagnostics_enabled() {
20206 return;
20207 }
20208 self.refresh_active_diagnostics(cx);
20209 self.refresh_inline_diagnostics(true, window, cx);
20210 self.scrollbar_marker_state.dirty = true;
20211 cx.notify();
20212 }
20213
20214 pub fn start_temporary_diff_override(&mut self) {
20215 self.load_diff_task.take();
20216 self.temporary_diff_override = true;
20217 }
20218
20219 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20220 self.temporary_diff_override = false;
20221 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20222 self.buffer.update(cx, |buffer, cx| {
20223 buffer.set_all_diff_hunks_collapsed(cx);
20224 });
20225
20226 if let Some(project) = self.project.clone() {
20227 self.load_diff_task = Some(
20228 update_uncommitted_diff_for_buffer(
20229 cx.entity(),
20230 &project,
20231 self.buffer.read(cx).all_buffers(),
20232 self.buffer.clone(),
20233 cx,
20234 )
20235 .shared(),
20236 );
20237 }
20238 }
20239
20240 fn on_display_map_changed(
20241 &mut self,
20242 _: Entity<DisplayMap>,
20243 _: &mut Window,
20244 cx: &mut Context<Self>,
20245 ) {
20246 cx.notify();
20247 }
20248
20249 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20250 if self.diagnostics_enabled() {
20251 let new_severity = EditorSettings::get_global(cx)
20252 .diagnostics_max_severity
20253 .unwrap_or(DiagnosticSeverity::Hint);
20254 self.set_max_diagnostics_severity(new_severity, cx);
20255 }
20256 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20257 self.update_edit_prediction_settings(cx);
20258 self.refresh_edit_prediction(true, false, window, cx);
20259 self.refresh_inline_values(cx);
20260 self.refresh_inlay_hints(
20261 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20262 self.selections.newest_anchor().head(),
20263 &self.buffer.read(cx).snapshot(cx),
20264 cx,
20265 )),
20266 cx,
20267 );
20268
20269 let old_cursor_shape = self.cursor_shape;
20270 let old_show_breadcrumbs = self.show_breadcrumbs;
20271
20272 {
20273 let editor_settings = EditorSettings::get_global(cx);
20274 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20275 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20276 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20277 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20278 }
20279
20280 if old_cursor_shape != self.cursor_shape {
20281 cx.emit(EditorEvent::CursorShapeChanged);
20282 }
20283
20284 if old_show_breadcrumbs != self.show_breadcrumbs {
20285 cx.emit(EditorEvent::BreadcrumbsChanged);
20286 }
20287
20288 let project_settings = ProjectSettings::get_global(cx);
20289 self.serialize_dirty_buffers =
20290 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20291
20292 if self.mode.is_full() {
20293 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20294 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20295 if self.show_inline_diagnostics != show_inline_diagnostics {
20296 self.show_inline_diagnostics = show_inline_diagnostics;
20297 self.refresh_inline_diagnostics(false, window, cx);
20298 }
20299
20300 if self.git_blame_inline_enabled != inline_blame_enabled {
20301 self.toggle_git_blame_inline_internal(false, window, cx);
20302 }
20303
20304 let minimap_settings = EditorSettings::get_global(cx).minimap;
20305 if self.minimap_visibility != MinimapVisibility::Disabled {
20306 if self.minimap_visibility.settings_visibility()
20307 != minimap_settings.minimap_enabled()
20308 {
20309 self.set_minimap_visibility(
20310 MinimapVisibility::for_mode(self.mode(), cx),
20311 window,
20312 cx,
20313 );
20314 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20315 minimap_entity.update(cx, |minimap_editor, cx| {
20316 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20317 })
20318 }
20319 }
20320 }
20321
20322 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20323 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20324 }) {
20325 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20326 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20327 }
20328 self.refresh_colors(false, None, window, cx);
20329 }
20330
20331 cx.notify();
20332 }
20333
20334 pub fn set_searchable(&mut self, searchable: bool) {
20335 self.searchable = searchable;
20336 }
20337
20338 pub fn searchable(&self) -> bool {
20339 self.searchable
20340 }
20341
20342 fn open_proposed_changes_editor(
20343 &mut self,
20344 _: &OpenProposedChangesEditor,
20345 window: &mut Window,
20346 cx: &mut Context<Self>,
20347 ) {
20348 let Some(workspace) = self.workspace() else {
20349 cx.propagate();
20350 return;
20351 };
20352
20353 let selections = self.selections.all::<usize>(cx);
20354 let multi_buffer = self.buffer.read(cx);
20355 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20356 let mut new_selections_by_buffer = HashMap::default();
20357 for selection in selections {
20358 for (buffer, range, _) in
20359 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20360 {
20361 let mut range = range.to_point(buffer);
20362 range.start.column = 0;
20363 range.end.column = buffer.line_len(range.end.row);
20364 new_selections_by_buffer
20365 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20366 .or_insert(Vec::new())
20367 .push(range)
20368 }
20369 }
20370
20371 let proposed_changes_buffers = new_selections_by_buffer
20372 .into_iter()
20373 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20374 .collect::<Vec<_>>();
20375 let proposed_changes_editor = cx.new(|cx| {
20376 ProposedChangesEditor::new(
20377 "Proposed changes",
20378 proposed_changes_buffers,
20379 self.project.clone(),
20380 window,
20381 cx,
20382 )
20383 });
20384
20385 window.defer(cx, move |window, cx| {
20386 workspace.update(cx, |workspace, cx| {
20387 workspace.active_pane().update(cx, |pane, cx| {
20388 pane.add_item(
20389 Box::new(proposed_changes_editor),
20390 true,
20391 true,
20392 None,
20393 window,
20394 cx,
20395 );
20396 });
20397 });
20398 });
20399 }
20400
20401 pub fn open_excerpts_in_split(
20402 &mut self,
20403 _: &OpenExcerptsSplit,
20404 window: &mut Window,
20405 cx: &mut Context<Self>,
20406 ) {
20407 self.open_excerpts_common(None, true, window, cx)
20408 }
20409
20410 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20411 self.open_excerpts_common(None, false, window, cx)
20412 }
20413
20414 fn open_excerpts_common(
20415 &mut self,
20416 jump_data: Option<JumpData>,
20417 split: bool,
20418 window: &mut Window,
20419 cx: &mut Context<Self>,
20420 ) {
20421 let Some(workspace) = self.workspace() else {
20422 cx.propagate();
20423 return;
20424 };
20425
20426 if self.buffer.read(cx).is_singleton() {
20427 cx.propagate();
20428 return;
20429 }
20430
20431 let mut new_selections_by_buffer = HashMap::default();
20432 match &jump_data {
20433 Some(JumpData::MultiBufferPoint {
20434 excerpt_id,
20435 position,
20436 anchor,
20437 line_offset_from_top,
20438 }) => {
20439 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20440 if let Some(buffer) = multi_buffer_snapshot
20441 .buffer_id_for_excerpt(*excerpt_id)
20442 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20443 {
20444 let buffer_snapshot = buffer.read(cx).snapshot();
20445 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20446 language::ToPoint::to_point(anchor, &buffer_snapshot)
20447 } else {
20448 buffer_snapshot.clip_point(*position, Bias::Left)
20449 };
20450 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20451 new_selections_by_buffer.insert(
20452 buffer,
20453 (
20454 vec![jump_to_offset..jump_to_offset],
20455 Some(*line_offset_from_top),
20456 ),
20457 );
20458 }
20459 }
20460 Some(JumpData::MultiBufferRow {
20461 row,
20462 line_offset_from_top,
20463 }) => {
20464 let point = MultiBufferPoint::new(row.0, 0);
20465 if let Some((buffer, buffer_point, _)) =
20466 self.buffer.read(cx).point_to_buffer_point(point, cx)
20467 {
20468 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20469 new_selections_by_buffer
20470 .entry(buffer)
20471 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20472 .0
20473 .push(buffer_offset..buffer_offset)
20474 }
20475 }
20476 None => {
20477 let selections = self.selections.all::<usize>(cx);
20478 let multi_buffer = self.buffer.read(cx);
20479 for selection in selections {
20480 for (snapshot, range, _, anchor) in multi_buffer
20481 .snapshot(cx)
20482 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20483 {
20484 if let Some(anchor) = anchor {
20485 // selection is in a deleted hunk
20486 let Some(buffer_id) = anchor.buffer_id else {
20487 continue;
20488 };
20489 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20490 continue;
20491 };
20492 let offset = text::ToOffset::to_offset(
20493 &anchor.text_anchor,
20494 &buffer_handle.read(cx).snapshot(),
20495 );
20496 let range = offset..offset;
20497 new_selections_by_buffer
20498 .entry(buffer_handle)
20499 .or_insert((Vec::new(), None))
20500 .0
20501 .push(range)
20502 } else {
20503 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20504 else {
20505 continue;
20506 };
20507 new_selections_by_buffer
20508 .entry(buffer_handle)
20509 .or_insert((Vec::new(), None))
20510 .0
20511 .push(range)
20512 }
20513 }
20514 }
20515 }
20516 }
20517
20518 new_selections_by_buffer
20519 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20520
20521 if new_selections_by_buffer.is_empty() {
20522 return;
20523 }
20524
20525 // We defer the pane interaction because we ourselves are a workspace item
20526 // and activating a new item causes the pane to call a method on us reentrantly,
20527 // which panics if we're on the stack.
20528 window.defer(cx, move |window, cx| {
20529 workspace.update(cx, |workspace, cx| {
20530 let pane = if split {
20531 workspace.adjacent_pane(window, cx)
20532 } else {
20533 workspace.active_pane().clone()
20534 };
20535
20536 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20537 let editor = buffer
20538 .read(cx)
20539 .file()
20540 .is_none()
20541 .then(|| {
20542 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20543 // so `workspace.open_project_item` will never find them, always opening a new editor.
20544 // Instead, we try to activate the existing editor in the pane first.
20545 let (editor, pane_item_index) =
20546 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20547 let editor = item.downcast::<Editor>()?;
20548 let singleton_buffer =
20549 editor.read(cx).buffer().read(cx).as_singleton()?;
20550 if singleton_buffer == buffer {
20551 Some((editor, i))
20552 } else {
20553 None
20554 }
20555 })?;
20556 pane.update(cx, |pane, cx| {
20557 pane.activate_item(pane_item_index, true, true, window, cx)
20558 });
20559 Some(editor)
20560 })
20561 .flatten()
20562 .unwrap_or_else(|| {
20563 workspace.open_project_item::<Self>(
20564 pane.clone(),
20565 buffer,
20566 true,
20567 true,
20568 window,
20569 cx,
20570 )
20571 });
20572
20573 editor.update(cx, |editor, cx| {
20574 let autoscroll = match scroll_offset {
20575 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20576 None => Autoscroll::newest(),
20577 };
20578 let nav_history = editor.nav_history.take();
20579 editor.change_selections(
20580 SelectionEffects::scroll(autoscroll),
20581 window,
20582 cx,
20583 |s| {
20584 s.select_ranges(ranges);
20585 },
20586 );
20587 editor.nav_history = nav_history;
20588 });
20589 }
20590 })
20591 });
20592 }
20593
20594 // For now, don't allow opening excerpts in buffers that aren't backed by
20595 // regular project files.
20596 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20597 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20598 }
20599
20600 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20601 let snapshot = self.buffer.read(cx).read(cx);
20602 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20603 Some(
20604 ranges
20605 .iter()
20606 .map(move |range| {
20607 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20608 })
20609 .collect(),
20610 )
20611 }
20612
20613 fn selection_replacement_ranges(
20614 &self,
20615 range: Range<OffsetUtf16>,
20616 cx: &mut App,
20617 ) -> Vec<Range<OffsetUtf16>> {
20618 let selections = self.selections.all::<OffsetUtf16>(cx);
20619 let newest_selection = selections
20620 .iter()
20621 .max_by_key(|selection| selection.id)
20622 .unwrap();
20623 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20624 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20625 let snapshot = self.buffer.read(cx).read(cx);
20626 selections
20627 .into_iter()
20628 .map(|mut selection| {
20629 selection.start.0 =
20630 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20631 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20632 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20633 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20634 })
20635 .collect()
20636 }
20637
20638 fn report_editor_event(
20639 &self,
20640 reported_event: ReportEditorEvent,
20641 file_extension: Option<String>,
20642 cx: &App,
20643 ) {
20644 if cfg!(any(test, feature = "test-support")) {
20645 return;
20646 }
20647
20648 let Some(project) = &self.project else { return };
20649
20650 // If None, we are in a file without an extension
20651 let file = self
20652 .buffer
20653 .read(cx)
20654 .as_singleton()
20655 .and_then(|b| b.read(cx).file());
20656 let file_extension = file_extension.or(file
20657 .as_ref()
20658 .and_then(|file| Path::new(file.file_name(cx)).extension())
20659 .and_then(|e| e.to_str())
20660 .map(|a| a.to_string()));
20661
20662 let vim_mode = vim_enabled(cx);
20663
20664 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20665 let copilot_enabled = edit_predictions_provider
20666 == language::language_settings::EditPredictionProvider::Copilot;
20667 let copilot_enabled_for_language = self
20668 .buffer
20669 .read(cx)
20670 .language_settings(cx)
20671 .show_edit_predictions;
20672
20673 let project = project.read(cx);
20674 let event_type = reported_event.event_type();
20675
20676 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20677 telemetry::event!(
20678 event_type,
20679 type = if auto_saved {"autosave"} else {"manual"},
20680 file_extension,
20681 vim_mode,
20682 copilot_enabled,
20683 copilot_enabled_for_language,
20684 edit_predictions_provider,
20685 is_via_ssh = project.is_via_ssh(),
20686 );
20687 } else {
20688 telemetry::event!(
20689 event_type,
20690 file_extension,
20691 vim_mode,
20692 copilot_enabled,
20693 copilot_enabled_for_language,
20694 edit_predictions_provider,
20695 is_via_ssh = project.is_via_ssh(),
20696 );
20697 };
20698 }
20699
20700 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20701 /// with each line being an array of {text, highlight} objects.
20702 fn copy_highlight_json(
20703 &mut self,
20704 _: &CopyHighlightJson,
20705 window: &mut Window,
20706 cx: &mut Context<Self>,
20707 ) {
20708 #[derive(Serialize)]
20709 struct Chunk<'a> {
20710 text: String,
20711 highlight: Option<&'a str>,
20712 }
20713
20714 let snapshot = self.buffer.read(cx).snapshot(cx);
20715 let range = self
20716 .selected_text_range(false, window, cx)
20717 .and_then(|selection| {
20718 if selection.range.is_empty() {
20719 None
20720 } else {
20721 Some(selection.range)
20722 }
20723 })
20724 .unwrap_or_else(|| 0..snapshot.len());
20725
20726 let chunks = snapshot.chunks(range, true);
20727 let mut lines = Vec::new();
20728 let mut line: VecDeque<Chunk> = VecDeque::new();
20729
20730 let Some(style) = self.style.as_ref() else {
20731 return;
20732 };
20733
20734 for chunk in chunks {
20735 let highlight = chunk
20736 .syntax_highlight_id
20737 .and_then(|id| id.name(&style.syntax));
20738 let mut chunk_lines = chunk.text.split('\n').peekable();
20739 while let Some(text) = chunk_lines.next() {
20740 let mut merged_with_last_token = false;
20741 if let Some(last_token) = line.back_mut() {
20742 if last_token.highlight == highlight {
20743 last_token.text.push_str(text);
20744 merged_with_last_token = true;
20745 }
20746 }
20747
20748 if !merged_with_last_token {
20749 line.push_back(Chunk {
20750 text: text.into(),
20751 highlight,
20752 });
20753 }
20754
20755 if chunk_lines.peek().is_some() {
20756 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20757 line.pop_front();
20758 }
20759 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20760 line.pop_back();
20761 }
20762
20763 lines.push(mem::take(&mut line));
20764 }
20765 }
20766 }
20767
20768 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20769 return;
20770 };
20771 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20772 }
20773
20774 pub fn open_context_menu(
20775 &mut self,
20776 _: &OpenContextMenu,
20777 window: &mut Window,
20778 cx: &mut Context<Self>,
20779 ) {
20780 self.request_autoscroll(Autoscroll::newest(), cx);
20781 let position = self.selections.newest_display(cx).start;
20782 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20783 }
20784
20785 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20786 &self.inlay_hint_cache
20787 }
20788
20789 pub fn replay_insert_event(
20790 &mut self,
20791 text: &str,
20792 relative_utf16_range: Option<Range<isize>>,
20793 window: &mut Window,
20794 cx: &mut Context<Self>,
20795 ) {
20796 if !self.input_enabled {
20797 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20798 return;
20799 }
20800 if let Some(relative_utf16_range) = relative_utf16_range {
20801 let selections = self.selections.all::<OffsetUtf16>(cx);
20802 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20803 let new_ranges = selections.into_iter().map(|range| {
20804 let start = OffsetUtf16(
20805 range
20806 .head()
20807 .0
20808 .saturating_add_signed(relative_utf16_range.start),
20809 );
20810 let end = OffsetUtf16(
20811 range
20812 .head()
20813 .0
20814 .saturating_add_signed(relative_utf16_range.end),
20815 );
20816 start..end
20817 });
20818 s.select_ranges(new_ranges);
20819 });
20820 }
20821
20822 self.handle_input(text, window, cx);
20823 }
20824
20825 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20826 let Some(provider) = self.semantics_provider.as_ref() else {
20827 return false;
20828 };
20829
20830 let mut supports = false;
20831 self.buffer().update(cx, |this, cx| {
20832 this.for_each_buffer(|buffer| {
20833 supports |= provider.supports_inlay_hints(buffer, cx);
20834 });
20835 });
20836
20837 supports
20838 }
20839
20840 pub fn is_focused(&self, window: &Window) -> bool {
20841 self.focus_handle.is_focused(window)
20842 }
20843
20844 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20845 cx.emit(EditorEvent::Focused);
20846
20847 if let Some(descendant) = self
20848 .last_focused_descendant
20849 .take()
20850 .and_then(|descendant| descendant.upgrade())
20851 {
20852 window.focus(&descendant);
20853 } else {
20854 if let Some(blame) = self.blame.as_ref() {
20855 blame.update(cx, GitBlame::focus)
20856 }
20857
20858 self.blink_manager.update(cx, BlinkManager::enable);
20859 self.show_cursor_names(window, cx);
20860 self.buffer.update(cx, |buffer, cx| {
20861 buffer.finalize_last_transaction(cx);
20862 if self.leader_id.is_none() {
20863 buffer.set_active_selections(
20864 &self.selections.disjoint_anchors(),
20865 self.selections.line_mode,
20866 self.cursor_shape,
20867 cx,
20868 );
20869 }
20870 });
20871 }
20872 }
20873
20874 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20875 cx.emit(EditorEvent::FocusedIn)
20876 }
20877
20878 fn handle_focus_out(
20879 &mut self,
20880 event: FocusOutEvent,
20881 _window: &mut Window,
20882 cx: &mut Context<Self>,
20883 ) {
20884 if event.blurred != self.focus_handle {
20885 self.last_focused_descendant = Some(event.blurred);
20886 }
20887 self.selection_drag_state = SelectionDragState::None;
20888 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20889 }
20890
20891 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20892 self.blink_manager.update(cx, BlinkManager::disable);
20893 self.buffer
20894 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20895
20896 if let Some(blame) = self.blame.as_ref() {
20897 blame.update(cx, GitBlame::blur)
20898 }
20899 if !self.hover_state.focused(window, cx) {
20900 hide_hover(self, cx);
20901 }
20902 if !self
20903 .context_menu
20904 .borrow()
20905 .as_ref()
20906 .is_some_and(|context_menu| context_menu.focused(window, cx))
20907 {
20908 self.hide_context_menu(window, cx);
20909 }
20910 self.discard_edit_prediction(false, cx);
20911 cx.emit(EditorEvent::Blurred);
20912 cx.notify();
20913 }
20914
20915 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20916 let mut pending: String = window
20917 .pending_input_keystrokes()
20918 .into_iter()
20919 .flatten()
20920 .filter_map(|keystroke| {
20921 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20922 keystroke.key_char.clone()
20923 } else {
20924 None
20925 }
20926 })
20927 .collect();
20928
20929 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20930 pending = "".to_string();
20931 }
20932
20933 let existing_pending = self
20934 .text_highlights::<PendingInput>(cx)
20935 .map(|(_, ranges)| ranges.to_vec());
20936 if existing_pending.is_none() && pending.is_empty() {
20937 return;
20938 }
20939 let transaction =
20940 self.transact(window, cx, |this, window, cx| {
20941 let selections = this.selections.all::<usize>(cx);
20942 let edits = selections
20943 .iter()
20944 .map(|selection| (selection.end..selection.end, pending.clone()));
20945 this.edit(edits, cx);
20946 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20947 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20948 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20949 }));
20950 });
20951 if let Some(existing_ranges) = existing_pending {
20952 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20953 this.edit(edits, cx);
20954 }
20955 });
20956
20957 let snapshot = self.snapshot(window, cx);
20958 let ranges = self
20959 .selections
20960 .all::<usize>(cx)
20961 .into_iter()
20962 .map(|selection| {
20963 snapshot.buffer_snapshot.anchor_after(selection.end)
20964 ..snapshot
20965 .buffer_snapshot
20966 .anchor_before(selection.end + pending.len())
20967 })
20968 .collect();
20969
20970 if pending.is_empty() {
20971 self.clear_highlights::<PendingInput>(cx);
20972 } else {
20973 self.highlight_text::<PendingInput>(
20974 ranges,
20975 HighlightStyle {
20976 underline: Some(UnderlineStyle {
20977 thickness: px(1.),
20978 color: None,
20979 wavy: false,
20980 }),
20981 ..Default::default()
20982 },
20983 cx,
20984 );
20985 }
20986
20987 self.ime_transaction = self.ime_transaction.or(transaction);
20988 if let Some(transaction) = self.ime_transaction {
20989 self.buffer.update(cx, |buffer, cx| {
20990 buffer.group_until_transaction(transaction, cx);
20991 });
20992 }
20993
20994 if self.text_highlights::<PendingInput>(cx).is_none() {
20995 self.ime_transaction.take();
20996 }
20997 }
20998
20999 pub fn register_action_renderer(
21000 &mut self,
21001 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21002 ) -> Subscription {
21003 let id = self.next_editor_action_id.post_inc();
21004 self.editor_actions
21005 .borrow_mut()
21006 .insert(id, Box::new(listener));
21007
21008 let editor_actions = self.editor_actions.clone();
21009 Subscription::new(move || {
21010 editor_actions.borrow_mut().remove(&id);
21011 })
21012 }
21013
21014 pub fn register_action<A: Action>(
21015 &mut self,
21016 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21017 ) -> Subscription {
21018 let id = self.next_editor_action_id.post_inc();
21019 let listener = Arc::new(listener);
21020 self.editor_actions.borrow_mut().insert(
21021 id,
21022 Box::new(move |_, window, _| {
21023 let listener = listener.clone();
21024 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21025 let action = action.downcast_ref().unwrap();
21026 if phase == DispatchPhase::Bubble {
21027 listener(action, window, cx)
21028 }
21029 })
21030 }),
21031 );
21032
21033 let editor_actions = self.editor_actions.clone();
21034 Subscription::new(move || {
21035 editor_actions.borrow_mut().remove(&id);
21036 })
21037 }
21038
21039 pub fn file_header_size(&self) -> u32 {
21040 FILE_HEADER_HEIGHT
21041 }
21042
21043 pub fn restore(
21044 &mut self,
21045 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21046 window: &mut Window,
21047 cx: &mut Context<Self>,
21048 ) {
21049 let workspace = self.workspace();
21050 let project = self.project();
21051 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21052 let mut tasks = Vec::new();
21053 for (buffer_id, changes) in revert_changes {
21054 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21055 buffer.update(cx, |buffer, cx| {
21056 buffer.edit(
21057 changes
21058 .into_iter()
21059 .map(|(range, text)| (range, text.to_string())),
21060 None,
21061 cx,
21062 );
21063 });
21064
21065 if let Some(project) =
21066 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21067 {
21068 project.update(cx, |project, cx| {
21069 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21070 })
21071 }
21072 }
21073 }
21074 tasks
21075 });
21076 cx.spawn_in(window, async move |_, cx| {
21077 for (buffer, task) in save_tasks {
21078 let result = task.await;
21079 if result.is_err() {
21080 let Some(path) = buffer
21081 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21082 .ok()
21083 else {
21084 continue;
21085 };
21086 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21087 let Some(task) = cx
21088 .update_window_entity(workspace, |workspace, window, cx| {
21089 workspace
21090 .open_path_preview(path, None, false, false, false, window, cx)
21091 })
21092 .ok()
21093 else {
21094 continue;
21095 };
21096 task.await.log_err();
21097 }
21098 }
21099 }
21100 })
21101 .detach();
21102 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21103 selections.refresh()
21104 });
21105 }
21106
21107 pub fn to_pixel_point(
21108 &self,
21109 source: multi_buffer::Anchor,
21110 editor_snapshot: &EditorSnapshot,
21111 window: &mut Window,
21112 ) -> Option<gpui::Point<Pixels>> {
21113 let source_point = source.to_display_point(editor_snapshot);
21114 self.display_to_pixel_point(source_point, editor_snapshot, window)
21115 }
21116
21117 pub fn display_to_pixel_point(
21118 &self,
21119 source: DisplayPoint,
21120 editor_snapshot: &EditorSnapshot,
21121 window: &mut Window,
21122 ) -> Option<gpui::Point<Pixels>> {
21123 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21124 let text_layout_details = self.text_layout_details(window);
21125 let scroll_top = text_layout_details
21126 .scroll_anchor
21127 .scroll_position(editor_snapshot)
21128 .y;
21129
21130 if source.row().as_f32() < scroll_top.floor() {
21131 return None;
21132 }
21133 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21134 let source_y = line_height * (source.row().as_f32() - scroll_top);
21135 Some(gpui::Point::new(source_x, source_y))
21136 }
21137
21138 pub fn has_visible_completions_menu(&self) -> bool {
21139 !self.edit_prediction_preview_is_active()
21140 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
21141 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21142 })
21143 }
21144
21145 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21146 if self.mode.is_minimap() {
21147 return;
21148 }
21149 self.addons
21150 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21151 }
21152
21153 pub fn unregister_addon<T: Addon>(&mut self) {
21154 self.addons.remove(&std::any::TypeId::of::<T>());
21155 }
21156
21157 pub fn addon<T: Addon>(&self) -> Option<&T> {
21158 let type_id = std::any::TypeId::of::<T>();
21159 self.addons
21160 .get(&type_id)
21161 .and_then(|item| item.to_any().downcast_ref::<T>())
21162 }
21163
21164 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21165 let type_id = std::any::TypeId::of::<T>();
21166 self.addons
21167 .get_mut(&type_id)
21168 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21169 }
21170
21171 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21172 let text_layout_details = self.text_layout_details(window);
21173 let style = &text_layout_details.editor_style;
21174 let font_id = window.text_system().resolve_font(&style.text.font());
21175 let font_size = style.text.font_size.to_pixels(window.rem_size());
21176 let line_height = style.text.line_height_in_pixels(window.rem_size());
21177 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21178 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21179
21180 CharacterDimensions {
21181 em_width,
21182 em_advance,
21183 line_height,
21184 }
21185 }
21186
21187 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21188 self.load_diff_task.clone()
21189 }
21190
21191 fn read_metadata_from_db(
21192 &mut self,
21193 item_id: u64,
21194 workspace_id: WorkspaceId,
21195 window: &mut Window,
21196 cx: &mut Context<Editor>,
21197 ) {
21198 if self.is_singleton(cx)
21199 && !self.mode.is_minimap()
21200 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21201 {
21202 let buffer_snapshot = OnceCell::new();
21203
21204 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21205 if !folds.is_empty() {
21206 let snapshot =
21207 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21208 self.fold_ranges(
21209 folds
21210 .into_iter()
21211 .map(|(start, end)| {
21212 snapshot.clip_offset(start, Bias::Left)
21213 ..snapshot.clip_offset(end, Bias::Right)
21214 })
21215 .collect(),
21216 false,
21217 window,
21218 cx,
21219 );
21220 }
21221 }
21222
21223 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21224 if !selections.is_empty() {
21225 let snapshot =
21226 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21227 // skip adding the initial selection to selection history
21228 self.selection_history.mode = SelectionHistoryMode::Skipping;
21229 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21230 s.select_ranges(selections.into_iter().map(|(start, end)| {
21231 snapshot.clip_offset(start, Bias::Left)
21232 ..snapshot.clip_offset(end, Bias::Right)
21233 }));
21234 });
21235 self.selection_history.mode = SelectionHistoryMode::Normal;
21236 }
21237 };
21238 }
21239
21240 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21241 }
21242
21243 fn update_lsp_data(
21244 &mut self,
21245 ignore_cache: bool,
21246 for_buffer: Option<BufferId>,
21247 window: &mut Window,
21248 cx: &mut Context<'_, Self>,
21249 ) {
21250 self.pull_diagnostics(for_buffer, window, cx);
21251 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21252 }
21253}
21254
21255fn vim_enabled(cx: &App) -> bool {
21256 cx.global::<SettingsStore>()
21257 .raw_user_settings()
21258 .get("vim_mode")
21259 == Some(&serde_json::Value::Bool(true))
21260}
21261
21262fn process_completion_for_edit(
21263 completion: &Completion,
21264 intent: CompletionIntent,
21265 buffer: &Entity<Buffer>,
21266 cursor_position: &text::Anchor,
21267 cx: &mut Context<Editor>,
21268) -> CompletionEdit {
21269 let buffer = buffer.read(cx);
21270 let buffer_snapshot = buffer.snapshot();
21271 let (snippet, new_text) = if completion.is_snippet() {
21272 // Workaround for typescript language server issues so that methods don't expand within
21273 // strings and functions with type expressions. The previous point is used because the query
21274 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21275 let mut snippet_source = completion.new_text.clone();
21276 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21277 previous_point.column = previous_point.column.saturating_sub(1);
21278 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21279 if scope.prefers_label_for_snippet_in_completion() {
21280 if let Some(label) = completion.label() {
21281 if matches!(
21282 completion.kind(),
21283 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21284 ) {
21285 snippet_source = label;
21286 }
21287 }
21288 }
21289 }
21290 match Snippet::parse(&snippet_source).log_err() {
21291 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21292 None => (None, completion.new_text.clone()),
21293 }
21294 } else {
21295 (None, completion.new_text.clone())
21296 };
21297
21298 let mut range_to_replace = {
21299 let replace_range = &completion.replace_range;
21300 if let CompletionSource::Lsp {
21301 insert_range: Some(insert_range),
21302 ..
21303 } = &completion.source
21304 {
21305 debug_assert_eq!(
21306 insert_range.start, replace_range.start,
21307 "insert_range and replace_range should start at the same position"
21308 );
21309 debug_assert!(
21310 insert_range
21311 .start
21312 .cmp(cursor_position, &buffer_snapshot)
21313 .is_le(),
21314 "insert_range should start before or at cursor position"
21315 );
21316 debug_assert!(
21317 replace_range
21318 .start
21319 .cmp(cursor_position, &buffer_snapshot)
21320 .is_le(),
21321 "replace_range should start before or at cursor position"
21322 );
21323
21324 let should_replace = match intent {
21325 CompletionIntent::CompleteWithInsert => false,
21326 CompletionIntent::CompleteWithReplace => true,
21327 CompletionIntent::Complete | CompletionIntent::Compose => {
21328 let insert_mode =
21329 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21330 .completions
21331 .lsp_insert_mode;
21332 match insert_mode {
21333 LspInsertMode::Insert => false,
21334 LspInsertMode::Replace => true,
21335 LspInsertMode::ReplaceSubsequence => {
21336 let mut text_to_replace = buffer.chars_for_range(
21337 buffer.anchor_before(replace_range.start)
21338 ..buffer.anchor_after(replace_range.end),
21339 );
21340 let mut current_needle = text_to_replace.next();
21341 for haystack_ch in completion.label.text.chars() {
21342 if let Some(needle_ch) = current_needle {
21343 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21344 current_needle = text_to_replace.next();
21345 }
21346 }
21347 }
21348 current_needle.is_none()
21349 }
21350 LspInsertMode::ReplaceSuffix => {
21351 if replace_range
21352 .end
21353 .cmp(cursor_position, &buffer_snapshot)
21354 .is_gt()
21355 {
21356 let range_after_cursor = *cursor_position..replace_range.end;
21357 let text_after_cursor = buffer
21358 .text_for_range(
21359 buffer.anchor_before(range_after_cursor.start)
21360 ..buffer.anchor_after(range_after_cursor.end),
21361 )
21362 .collect::<String>()
21363 .to_ascii_lowercase();
21364 completion
21365 .label
21366 .text
21367 .to_ascii_lowercase()
21368 .ends_with(&text_after_cursor)
21369 } else {
21370 true
21371 }
21372 }
21373 }
21374 }
21375 };
21376
21377 if should_replace {
21378 replace_range.clone()
21379 } else {
21380 insert_range.clone()
21381 }
21382 } else {
21383 replace_range.clone()
21384 }
21385 };
21386
21387 if range_to_replace
21388 .end
21389 .cmp(cursor_position, &buffer_snapshot)
21390 .is_lt()
21391 {
21392 range_to_replace.end = *cursor_position;
21393 }
21394
21395 CompletionEdit {
21396 new_text,
21397 replace_range: range_to_replace.to_offset(buffer),
21398 snippet,
21399 }
21400}
21401
21402struct CompletionEdit {
21403 new_text: String,
21404 replace_range: Range<usize>,
21405 snippet: Option<Snippet>,
21406}
21407
21408fn insert_extra_newline_brackets(
21409 buffer: &MultiBufferSnapshot,
21410 range: Range<usize>,
21411 language: &language::LanguageScope,
21412) -> bool {
21413 let leading_whitespace_len = buffer
21414 .reversed_chars_at(range.start)
21415 .take_while(|c| c.is_whitespace() && *c != '\n')
21416 .map(|c| c.len_utf8())
21417 .sum::<usize>();
21418 let trailing_whitespace_len = buffer
21419 .chars_at(range.end)
21420 .take_while(|c| c.is_whitespace() && *c != '\n')
21421 .map(|c| c.len_utf8())
21422 .sum::<usize>();
21423 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21424
21425 language.brackets().any(|(pair, enabled)| {
21426 let pair_start = pair.start.trim_end();
21427 let pair_end = pair.end.trim_start();
21428
21429 enabled
21430 && pair.newline
21431 && buffer.contains_str_at(range.end, pair_end)
21432 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21433 })
21434}
21435
21436fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21437 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21438 [(buffer, range, _)] => (*buffer, range.clone()),
21439 _ => return false,
21440 };
21441 let pair = {
21442 let mut result: Option<BracketMatch> = None;
21443
21444 for pair in buffer
21445 .all_bracket_ranges(range.clone())
21446 .filter(move |pair| {
21447 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21448 })
21449 {
21450 let len = pair.close_range.end - pair.open_range.start;
21451
21452 if let Some(existing) = &result {
21453 let existing_len = existing.close_range.end - existing.open_range.start;
21454 if len > existing_len {
21455 continue;
21456 }
21457 }
21458
21459 result = Some(pair);
21460 }
21461
21462 result
21463 };
21464 let Some(pair) = pair else {
21465 return false;
21466 };
21467 pair.newline_only
21468 && buffer
21469 .chars_for_range(pair.open_range.end..range.start)
21470 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21471 .all(|c| c.is_whitespace() && c != '\n')
21472}
21473
21474fn update_uncommitted_diff_for_buffer(
21475 editor: Entity<Editor>,
21476 project: &Entity<Project>,
21477 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21478 buffer: Entity<MultiBuffer>,
21479 cx: &mut App,
21480) -> Task<()> {
21481 let mut tasks = Vec::new();
21482 project.update(cx, |project, cx| {
21483 for buffer in buffers {
21484 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21485 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21486 }
21487 }
21488 });
21489 cx.spawn(async move |cx| {
21490 let diffs = future::join_all(tasks).await;
21491 if editor
21492 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21493 .unwrap_or(false)
21494 {
21495 return;
21496 }
21497
21498 buffer
21499 .update(cx, |buffer, cx| {
21500 for diff in diffs.into_iter().flatten() {
21501 buffer.add_diff(diff, cx);
21502 }
21503 })
21504 .ok();
21505 })
21506}
21507
21508fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21509 let tab_size = tab_size.get() as usize;
21510 let mut width = offset;
21511
21512 for ch in text.chars() {
21513 width += if ch == '\t' {
21514 tab_size - (width % tab_size)
21515 } else {
21516 1
21517 };
21518 }
21519
21520 width - offset
21521}
21522
21523#[cfg(test)]
21524mod tests {
21525 use super::*;
21526
21527 #[test]
21528 fn test_string_size_with_expanded_tabs() {
21529 let nz = |val| NonZeroU32::new(val).unwrap();
21530 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21531 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21532 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21533 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21534 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21535 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21536 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21537 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21538 }
21539}
21540
21541/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21542struct WordBreakingTokenizer<'a> {
21543 input: &'a str,
21544}
21545
21546impl<'a> WordBreakingTokenizer<'a> {
21547 fn new(input: &'a str) -> Self {
21548 Self { input }
21549 }
21550}
21551
21552fn is_char_ideographic(ch: char) -> bool {
21553 use unicode_script::Script::*;
21554 use unicode_script::UnicodeScript;
21555 matches!(ch.script(), Han | Tangut | Yi)
21556}
21557
21558fn is_grapheme_ideographic(text: &str) -> bool {
21559 text.chars().any(is_char_ideographic)
21560}
21561
21562fn is_grapheme_whitespace(text: &str) -> bool {
21563 text.chars().any(|x| x.is_whitespace())
21564}
21565
21566fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21567 text.chars().next().map_or(false, |ch| {
21568 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21569 })
21570}
21571
21572#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21573enum WordBreakToken<'a> {
21574 Word { token: &'a str, grapheme_len: usize },
21575 InlineWhitespace { token: &'a str, grapheme_len: usize },
21576 Newline,
21577}
21578
21579impl<'a> Iterator for WordBreakingTokenizer<'a> {
21580 /// Yields a span, the count of graphemes in the token, and whether it was
21581 /// whitespace. Note that it also breaks at word boundaries.
21582 type Item = WordBreakToken<'a>;
21583
21584 fn next(&mut self) -> Option<Self::Item> {
21585 use unicode_segmentation::UnicodeSegmentation;
21586 if self.input.is_empty() {
21587 return None;
21588 }
21589
21590 let mut iter = self.input.graphemes(true).peekable();
21591 let mut offset = 0;
21592 let mut grapheme_len = 0;
21593 if let Some(first_grapheme) = iter.next() {
21594 let is_newline = first_grapheme == "\n";
21595 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21596 offset += first_grapheme.len();
21597 grapheme_len += 1;
21598 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21599 if let Some(grapheme) = iter.peek().copied() {
21600 if should_stay_with_preceding_ideograph(grapheme) {
21601 offset += grapheme.len();
21602 grapheme_len += 1;
21603 }
21604 }
21605 } else {
21606 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21607 let mut next_word_bound = words.peek().copied();
21608 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21609 next_word_bound = words.next();
21610 }
21611 while let Some(grapheme) = iter.peek().copied() {
21612 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21613 break;
21614 };
21615 if is_grapheme_whitespace(grapheme) != is_whitespace
21616 || (grapheme == "\n") != is_newline
21617 {
21618 break;
21619 };
21620 offset += grapheme.len();
21621 grapheme_len += 1;
21622 iter.next();
21623 }
21624 }
21625 let token = &self.input[..offset];
21626 self.input = &self.input[offset..];
21627 if token == "\n" {
21628 Some(WordBreakToken::Newline)
21629 } else if is_whitespace {
21630 Some(WordBreakToken::InlineWhitespace {
21631 token,
21632 grapheme_len,
21633 })
21634 } else {
21635 Some(WordBreakToken::Word {
21636 token,
21637 grapheme_len,
21638 })
21639 }
21640 } else {
21641 None
21642 }
21643 }
21644}
21645
21646#[test]
21647fn test_word_breaking_tokenizer() {
21648 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21649 ("", &[]),
21650 (" ", &[whitespace(" ", 2)]),
21651 ("Ʒ", &[word("Ʒ", 1)]),
21652 ("Ǽ", &[word("Ǽ", 1)]),
21653 ("⋑", &[word("⋑", 1)]),
21654 ("⋑⋑", &[word("⋑⋑", 2)]),
21655 (
21656 "原理,进而",
21657 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21658 ),
21659 (
21660 "hello world",
21661 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21662 ),
21663 (
21664 "hello, world",
21665 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21666 ),
21667 (
21668 " hello world",
21669 &[
21670 whitespace(" ", 2),
21671 word("hello", 5),
21672 whitespace(" ", 1),
21673 word("world", 5),
21674 ],
21675 ),
21676 (
21677 "这是什么 \n 钢笔",
21678 &[
21679 word("这", 1),
21680 word("是", 1),
21681 word("什", 1),
21682 word("么", 1),
21683 whitespace(" ", 1),
21684 newline(),
21685 whitespace(" ", 1),
21686 word("钢", 1),
21687 word("笔", 1),
21688 ],
21689 ),
21690 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21691 ];
21692
21693 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21694 WordBreakToken::Word {
21695 token,
21696 grapheme_len,
21697 }
21698 }
21699
21700 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21701 WordBreakToken::InlineWhitespace {
21702 token,
21703 grapheme_len,
21704 }
21705 }
21706
21707 fn newline() -> WordBreakToken<'static> {
21708 WordBreakToken::Newline
21709 }
21710
21711 for (input, result) in tests {
21712 assert_eq!(
21713 WordBreakingTokenizer::new(input)
21714 .collect::<Vec<_>>()
21715 .as_slice(),
21716 *result,
21717 );
21718 }
21719}
21720
21721fn wrap_with_prefix(
21722 first_line_prefix: String,
21723 subsequent_lines_prefix: String,
21724 unwrapped_text: String,
21725 wrap_column: usize,
21726 tab_size: NonZeroU32,
21727 preserve_existing_whitespace: bool,
21728) -> String {
21729 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21730 let subsequent_lines_prefix_len =
21731 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21732 let mut wrapped_text = String::new();
21733 let mut current_line = first_line_prefix.clone();
21734 let mut is_first_line = true;
21735
21736 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21737 let mut current_line_len = first_line_prefix_len;
21738 let mut in_whitespace = false;
21739 for token in tokenizer {
21740 let have_preceding_whitespace = in_whitespace;
21741 match token {
21742 WordBreakToken::Word {
21743 token,
21744 grapheme_len,
21745 } => {
21746 in_whitespace = false;
21747 let current_prefix_len = if is_first_line {
21748 first_line_prefix_len
21749 } else {
21750 subsequent_lines_prefix_len
21751 };
21752 if current_line_len + grapheme_len > wrap_column
21753 && current_line_len != current_prefix_len
21754 {
21755 wrapped_text.push_str(current_line.trim_end());
21756 wrapped_text.push('\n');
21757 is_first_line = false;
21758 current_line = subsequent_lines_prefix.clone();
21759 current_line_len = subsequent_lines_prefix_len;
21760 }
21761 current_line.push_str(token);
21762 current_line_len += grapheme_len;
21763 }
21764 WordBreakToken::InlineWhitespace {
21765 mut token,
21766 mut grapheme_len,
21767 } => {
21768 in_whitespace = true;
21769 if have_preceding_whitespace && !preserve_existing_whitespace {
21770 continue;
21771 }
21772 if !preserve_existing_whitespace {
21773 token = " ";
21774 grapheme_len = 1;
21775 }
21776 let current_prefix_len = if is_first_line {
21777 first_line_prefix_len
21778 } else {
21779 subsequent_lines_prefix_len
21780 };
21781 if current_line_len + grapheme_len > wrap_column {
21782 wrapped_text.push_str(current_line.trim_end());
21783 wrapped_text.push('\n');
21784 is_first_line = false;
21785 current_line = subsequent_lines_prefix.clone();
21786 current_line_len = subsequent_lines_prefix_len;
21787 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21788 current_line.push_str(token);
21789 current_line_len += grapheme_len;
21790 }
21791 }
21792 WordBreakToken::Newline => {
21793 in_whitespace = true;
21794 let current_prefix_len = if is_first_line {
21795 first_line_prefix_len
21796 } else {
21797 subsequent_lines_prefix_len
21798 };
21799 if preserve_existing_whitespace {
21800 wrapped_text.push_str(current_line.trim_end());
21801 wrapped_text.push('\n');
21802 is_first_line = false;
21803 current_line = subsequent_lines_prefix.clone();
21804 current_line_len = subsequent_lines_prefix_len;
21805 } else if have_preceding_whitespace {
21806 continue;
21807 } else if current_line_len + 1 > wrap_column
21808 && current_line_len != current_prefix_len
21809 {
21810 wrapped_text.push_str(current_line.trim_end());
21811 wrapped_text.push('\n');
21812 is_first_line = false;
21813 current_line = subsequent_lines_prefix.clone();
21814 current_line_len = subsequent_lines_prefix_len;
21815 } else if current_line_len != current_prefix_len {
21816 current_line.push(' ');
21817 current_line_len += 1;
21818 }
21819 }
21820 }
21821 }
21822
21823 if !current_line.is_empty() {
21824 wrapped_text.push_str(¤t_line);
21825 }
21826 wrapped_text
21827}
21828
21829#[test]
21830fn test_wrap_with_prefix() {
21831 assert_eq!(
21832 wrap_with_prefix(
21833 "# ".to_string(),
21834 "# ".to_string(),
21835 "abcdefg".to_string(),
21836 4,
21837 NonZeroU32::new(4).unwrap(),
21838 false,
21839 ),
21840 "# abcdefg"
21841 );
21842 assert_eq!(
21843 wrap_with_prefix(
21844 "".to_string(),
21845 "".to_string(),
21846 "\thello world".to_string(),
21847 8,
21848 NonZeroU32::new(4).unwrap(),
21849 false,
21850 ),
21851 "hello\nworld"
21852 );
21853 assert_eq!(
21854 wrap_with_prefix(
21855 "// ".to_string(),
21856 "// ".to_string(),
21857 "xx \nyy zz aa bb cc".to_string(),
21858 12,
21859 NonZeroU32::new(4).unwrap(),
21860 false,
21861 ),
21862 "// xx yy zz\n// aa bb cc"
21863 );
21864 assert_eq!(
21865 wrap_with_prefix(
21866 String::new(),
21867 String::new(),
21868 "这是什么 \n 钢笔".to_string(),
21869 3,
21870 NonZeroU32::new(4).unwrap(),
21871 false,
21872 ),
21873 "这是什\n么 钢\n笔"
21874 );
21875}
21876
21877pub trait CollaborationHub {
21878 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21879 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21880 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21881}
21882
21883impl CollaborationHub for Entity<Project> {
21884 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21885 self.read(cx).collaborators()
21886 }
21887
21888 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21889 self.read(cx).user_store().read(cx).participant_indices()
21890 }
21891
21892 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21893 let this = self.read(cx);
21894 let user_ids = this.collaborators().values().map(|c| c.user_id);
21895 this.user_store().read(cx).participant_names(user_ids, cx)
21896 }
21897}
21898
21899pub trait SemanticsProvider {
21900 fn hover(
21901 &self,
21902 buffer: &Entity<Buffer>,
21903 position: text::Anchor,
21904 cx: &mut App,
21905 ) -> Option<Task<Vec<project::Hover>>>;
21906
21907 fn inline_values(
21908 &self,
21909 buffer_handle: Entity<Buffer>,
21910 range: Range<text::Anchor>,
21911 cx: &mut App,
21912 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21913
21914 fn inlay_hints(
21915 &self,
21916 buffer_handle: Entity<Buffer>,
21917 range: Range<text::Anchor>,
21918 cx: &mut App,
21919 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21920
21921 fn resolve_inlay_hint(
21922 &self,
21923 hint: InlayHint,
21924 buffer_handle: Entity<Buffer>,
21925 server_id: LanguageServerId,
21926 cx: &mut App,
21927 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21928
21929 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21930
21931 fn document_highlights(
21932 &self,
21933 buffer: &Entity<Buffer>,
21934 position: text::Anchor,
21935 cx: &mut App,
21936 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21937
21938 fn definitions(
21939 &self,
21940 buffer: &Entity<Buffer>,
21941 position: text::Anchor,
21942 kind: GotoDefinitionKind,
21943 cx: &mut App,
21944 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21945
21946 fn range_for_rename(
21947 &self,
21948 buffer: &Entity<Buffer>,
21949 position: text::Anchor,
21950 cx: &mut App,
21951 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21952
21953 fn perform_rename(
21954 &self,
21955 buffer: &Entity<Buffer>,
21956 position: text::Anchor,
21957 new_name: String,
21958 cx: &mut App,
21959 ) -> Option<Task<Result<ProjectTransaction>>>;
21960}
21961
21962pub trait CompletionProvider {
21963 fn completions(
21964 &self,
21965 excerpt_id: ExcerptId,
21966 buffer: &Entity<Buffer>,
21967 buffer_position: text::Anchor,
21968 trigger: CompletionContext,
21969 window: &mut Window,
21970 cx: &mut Context<Editor>,
21971 ) -> Task<Result<Vec<CompletionResponse>>>;
21972
21973 fn resolve_completions(
21974 &self,
21975 _buffer: Entity<Buffer>,
21976 _completion_indices: Vec<usize>,
21977 _completions: Rc<RefCell<Box<[Completion]>>>,
21978 _cx: &mut Context<Editor>,
21979 ) -> Task<Result<bool>> {
21980 Task::ready(Ok(false))
21981 }
21982
21983 fn apply_additional_edits_for_completion(
21984 &self,
21985 _buffer: Entity<Buffer>,
21986 _completions: Rc<RefCell<Box<[Completion]>>>,
21987 _completion_index: usize,
21988 _push_to_history: bool,
21989 _cx: &mut Context<Editor>,
21990 ) -> Task<Result<Option<language::Transaction>>> {
21991 Task::ready(Ok(None))
21992 }
21993
21994 fn is_completion_trigger(
21995 &self,
21996 buffer: &Entity<Buffer>,
21997 position: language::Anchor,
21998 text: &str,
21999 trigger_in_words: bool,
22000 menu_is_open: bool,
22001 cx: &mut Context<Editor>,
22002 ) -> bool;
22003
22004 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22005
22006 fn sort_completions(&self) -> bool {
22007 true
22008 }
22009
22010 fn filter_completions(&self) -> bool {
22011 true
22012 }
22013}
22014
22015pub trait CodeActionProvider {
22016 fn id(&self) -> Arc<str>;
22017
22018 fn code_actions(
22019 &self,
22020 buffer: &Entity<Buffer>,
22021 range: Range<text::Anchor>,
22022 window: &mut Window,
22023 cx: &mut App,
22024 ) -> Task<Result<Vec<CodeAction>>>;
22025
22026 fn apply_code_action(
22027 &self,
22028 buffer_handle: Entity<Buffer>,
22029 action: CodeAction,
22030 excerpt_id: ExcerptId,
22031 push_to_history: bool,
22032 window: &mut Window,
22033 cx: &mut App,
22034 ) -> Task<Result<ProjectTransaction>>;
22035}
22036
22037impl CodeActionProvider for Entity<Project> {
22038 fn id(&self) -> Arc<str> {
22039 "project".into()
22040 }
22041
22042 fn code_actions(
22043 &self,
22044 buffer: &Entity<Buffer>,
22045 range: Range<text::Anchor>,
22046 _window: &mut Window,
22047 cx: &mut App,
22048 ) -> Task<Result<Vec<CodeAction>>> {
22049 self.update(cx, |project, cx| {
22050 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22051 let code_actions = project.code_actions(buffer, range, None, cx);
22052 cx.background_spawn(async move {
22053 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22054 Ok(code_lens_actions
22055 .context("code lens fetch")?
22056 .into_iter()
22057 .chain(code_actions.context("code action fetch")?)
22058 .collect())
22059 })
22060 })
22061 }
22062
22063 fn apply_code_action(
22064 &self,
22065 buffer_handle: Entity<Buffer>,
22066 action: CodeAction,
22067 _excerpt_id: ExcerptId,
22068 push_to_history: bool,
22069 _window: &mut Window,
22070 cx: &mut App,
22071 ) -> Task<Result<ProjectTransaction>> {
22072 self.update(cx, |project, cx| {
22073 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22074 })
22075 }
22076}
22077
22078fn snippet_completions(
22079 project: &Project,
22080 buffer: &Entity<Buffer>,
22081 buffer_position: text::Anchor,
22082 cx: &mut App,
22083) -> Task<Result<CompletionResponse>> {
22084 let languages = buffer.read(cx).languages_at(buffer_position);
22085 let snippet_store = project.snippets().read(cx);
22086
22087 let scopes: Vec<_> = languages
22088 .iter()
22089 .filter_map(|language| {
22090 let language_name = language.lsp_id();
22091 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22092
22093 if snippets.is_empty() {
22094 None
22095 } else {
22096 Some((language.default_scope(), snippets))
22097 }
22098 })
22099 .collect();
22100
22101 if scopes.is_empty() {
22102 return Task::ready(Ok(CompletionResponse {
22103 completions: vec![],
22104 is_incomplete: false,
22105 }));
22106 }
22107
22108 let snapshot = buffer.read(cx).text_snapshot();
22109 let chars: String = snapshot
22110 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22111 .collect();
22112 let executor = cx.background_executor().clone();
22113
22114 cx.background_spawn(async move {
22115 let mut is_incomplete = false;
22116 let mut completions: Vec<Completion> = Vec::new();
22117 for (scope, snippets) in scopes.into_iter() {
22118 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22119 let mut last_word = chars
22120 .chars()
22121 .take_while(|c| classifier.is_word(*c))
22122 .collect::<String>();
22123 last_word = last_word.chars().rev().collect();
22124
22125 if last_word.is_empty() {
22126 return Ok(CompletionResponse {
22127 completions: vec![],
22128 is_incomplete: true,
22129 });
22130 }
22131
22132 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22133 let to_lsp = |point: &text::Anchor| {
22134 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22135 point_to_lsp(end)
22136 };
22137 let lsp_end = to_lsp(&buffer_position);
22138
22139 let candidates = snippets
22140 .iter()
22141 .enumerate()
22142 .flat_map(|(ix, snippet)| {
22143 snippet
22144 .prefix
22145 .iter()
22146 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22147 })
22148 .collect::<Vec<StringMatchCandidate>>();
22149
22150 const MAX_RESULTS: usize = 100;
22151 let mut matches = fuzzy::match_strings(
22152 &candidates,
22153 &last_word,
22154 last_word.chars().any(|c| c.is_uppercase()),
22155 true,
22156 MAX_RESULTS,
22157 &Default::default(),
22158 executor.clone(),
22159 )
22160 .await;
22161
22162 if matches.len() >= MAX_RESULTS {
22163 is_incomplete = true;
22164 }
22165
22166 // Remove all candidates where the query's start does not match the start of any word in the candidate
22167 if let Some(query_start) = last_word.chars().next() {
22168 matches.retain(|string_match| {
22169 split_words(&string_match.string).any(|word| {
22170 // Check that the first codepoint of the word as lowercase matches the first
22171 // codepoint of the query as lowercase
22172 word.chars()
22173 .flat_map(|codepoint| codepoint.to_lowercase())
22174 .zip(query_start.to_lowercase())
22175 .all(|(word_cp, query_cp)| word_cp == query_cp)
22176 })
22177 });
22178 }
22179
22180 let matched_strings = matches
22181 .into_iter()
22182 .map(|m| m.string)
22183 .collect::<HashSet<_>>();
22184
22185 completions.extend(snippets.iter().filter_map(|snippet| {
22186 let matching_prefix = snippet
22187 .prefix
22188 .iter()
22189 .find(|prefix| matched_strings.contains(*prefix))?;
22190 let start = as_offset - last_word.len();
22191 let start = snapshot.anchor_before(start);
22192 let range = start..buffer_position;
22193 let lsp_start = to_lsp(&start);
22194 let lsp_range = lsp::Range {
22195 start: lsp_start,
22196 end: lsp_end,
22197 };
22198 Some(Completion {
22199 replace_range: range,
22200 new_text: snippet.body.clone(),
22201 source: CompletionSource::Lsp {
22202 insert_range: None,
22203 server_id: LanguageServerId(usize::MAX),
22204 resolved: true,
22205 lsp_completion: Box::new(lsp::CompletionItem {
22206 label: snippet.prefix.first().unwrap().clone(),
22207 kind: Some(CompletionItemKind::SNIPPET),
22208 label_details: snippet.description.as_ref().map(|description| {
22209 lsp::CompletionItemLabelDetails {
22210 detail: Some(description.clone()),
22211 description: None,
22212 }
22213 }),
22214 insert_text_format: Some(InsertTextFormat::SNIPPET),
22215 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22216 lsp::InsertReplaceEdit {
22217 new_text: snippet.body.clone(),
22218 insert: lsp_range,
22219 replace: lsp_range,
22220 },
22221 )),
22222 filter_text: Some(snippet.body.clone()),
22223 sort_text: Some(char::MAX.to_string()),
22224 ..lsp::CompletionItem::default()
22225 }),
22226 lsp_defaults: None,
22227 },
22228 label: CodeLabel {
22229 text: matching_prefix.clone(),
22230 runs: Vec::new(),
22231 filter_range: 0..matching_prefix.len(),
22232 },
22233 icon_path: None,
22234 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22235 single_line: snippet.name.clone().into(),
22236 plain_text: snippet
22237 .description
22238 .clone()
22239 .map(|description| description.into()),
22240 }),
22241 insert_text_mode: None,
22242 confirm: None,
22243 })
22244 }))
22245 }
22246
22247 Ok(CompletionResponse {
22248 completions,
22249 is_incomplete,
22250 })
22251 })
22252}
22253
22254impl CompletionProvider for Entity<Project> {
22255 fn completions(
22256 &self,
22257 _excerpt_id: ExcerptId,
22258 buffer: &Entity<Buffer>,
22259 buffer_position: text::Anchor,
22260 options: CompletionContext,
22261 _window: &mut Window,
22262 cx: &mut Context<Editor>,
22263 ) -> Task<Result<Vec<CompletionResponse>>> {
22264 self.update(cx, |project, cx| {
22265 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22266 let project_completions = project.completions(buffer, buffer_position, options, cx);
22267 cx.background_spawn(async move {
22268 let mut responses = project_completions.await?;
22269 let snippets = snippets.await?;
22270 if !snippets.completions.is_empty() {
22271 responses.push(snippets);
22272 }
22273 Ok(responses)
22274 })
22275 })
22276 }
22277
22278 fn resolve_completions(
22279 &self,
22280 buffer: Entity<Buffer>,
22281 completion_indices: Vec<usize>,
22282 completions: Rc<RefCell<Box<[Completion]>>>,
22283 cx: &mut Context<Editor>,
22284 ) -> Task<Result<bool>> {
22285 self.update(cx, |project, cx| {
22286 project.lsp_store().update(cx, |lsp_store, cx| {
22287 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22288 })
22289 })
22290 }
22291
22292 fn apply_additional_edits_for_completion(
22293 &self,
22294 buffer: Entity<Buffer>,
22295 completions: Rc<RefCell<Box<[Completion]>>>,
22296 completion_index: usize,
22297 push_to_history: bool,
22298 cx: &mut Context<Editor>,
22299 ) -> Task<Result<Option<language::Transaction>>> {
22300 self.update(cx, |project, cx| {
22301 project.lsp_store().update(cx, |lsp_store, cx| {
22302 lsp_store.apply_additional_edits_for_completion(
22303 buffer,
22304 completions,
22305 completion_index,
22306 push_to_history,
22307 cx,
22308 )
22309 })
22310 })
22311 }
22312
22313 fn is_completion_trigger(
22314 &self,
22315 buffer: &Entity<Buffer>,
22316 position: language::Anchor,
22317 text: &str,
22318 trigger_in_words: bool,
22319 menu_is_open: bool,
22320 cx: &mut Context<Editor>,
22321 ) -> bool {
22322 let mut chars = text.chars();
22323 let char = if let Some(char) = chars.next() {
22324 char
22325 } else {
22326 return false;
22327 };
22328 if chars.next().is_some() {
22329 return false;
22330 }
22331
22332 let buffer = buffer.read(cx);
22333 let snapshot = buffer.snapshot();
22334 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22335 return false;
22336 }
22337 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22338 if trigger_in_words && classifier.is_word(char) {
22339 return true;
22340 }
22341
22342 buffer.completion_triggers().contains(text)
22343 }
22344}
22345
22346impl SemanticsProvider for Entity<Project> {
22347 fn hover(
22348 &self,
22349 buffer: &Entity<Buffer>,
22350 position: text::Anchor,
22351 cx: &mut App,
22352 ) -> Option<Task<Vec<project::Hover>>> {
22353 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22354 }
22355
22356 fn document_highlights(
22357 &self,
22358 buffer: &Entity<Buffer>,
22359 position: text::Anchor,
22360 cx: &mut App,
22361 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22362 Some(self.update(cx, |project, cx| {
22363 project.document_highlights(buffer, position, cx)
22364 }))
22365 }
22366
22367 fn definitions(
22368 &self,
22369 buffer: &Entity<Buffer>,
22370 position: text::Anchor,
22371 kind: GotoDefinitionKind,
22372 cx: &mut App,
22373 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22374 Some(self.update(cx, |project, cx| match kind {
22375 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22376 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22377 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22378 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22379 }))
22380 }
22381
22382 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22383 self.update(cx, |project, cx| {
22384 if project
22385 .active_debug_session(cx)
22386 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22387 {
22388 return true;
22389 }
22390
22391 buffer.update(cx, |buffer, cx| {
22392 project.any_language_server_supports_inlay_hints(buffer, cx)
22393 })
22394 })
22395 }
22396
22397 fn inline_values(
22398 &self,
22399 buffer_handle: Entity<Buffer>,
22400 range: Range<text::Anchor>,
22401 cx: &mut App,
22402 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22403 self.update(cx, |project, cx| {
22404 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22405
22406 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22407 })
22408 }
22409
22410 fn inlay_hints(
22411 &self,
22412 buffer_handle: Entity<Buffer>,
22413 range: Range<text::Anchor>,
22414 cx: &mut App,
22415 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22416 Some(self.update(cx, |project, cx| {
22417 project.inlay_hints(buffer_handle, range, cx)
22418 }))
22419 }
22420
22421 fn resolve_inlay_hint(
22422 &self,
22423 hint: InlayHint,
22424 buffer_handle: Entity<Buffer>,
22425 server_id: LanguageServerId,
22426 cx: &mut App,
22427 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22428 Some(self.update(cx, |project, cx| {
22429 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22430 }))
22431 }
22432
22433 fn range_for_rename(
22434 &self,
22435 buffer: &Entity<Buffer>,
22436 position: text::Anchor,
22437 cx: &mut App,
22438 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22439 Some(self.update(cx, |project, cx| {
22440 let buffer = buffer.clone();
22441 let task = project.prepare_rename(buffer.clone(), position, cx);
22442 cx.spawn(async move |_, cx| {
22443 Ok(match task.await? {
22444 PrepareRenameResponse::Success(range) => Some(range),
22445 PrepareRenameResponse::InvalidPosition => None,
22446 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22447 // Fallback on using TreeSitter info to determine identifier range
22448 buffer.read_with(cx, |buffer, _| {
22449 let snapshot = buffer.snapshot();
22450 let (range, kind) = snapshot.surrounding_word(position, false);
22451 if kind != Some(CharKind::Word) {
22452 return None;
22453 }
22454 Some(
22455 snapshot.anchor_before(range.start)
22456 ..snapshot.anchor_after(range.end),
22457 )
22458 })?
22459 }
22460 })
22461 })
22462 }))
22463 }
22464
22465 fn perform_rename(
22466 &self,
22467 buffer: &Entity<Buffer>,
22468 position: text::Anchor,
22469 new_name: String,
22470 cx: &mut App,
22471 ) -> Option<Task<Result<ProjectTransaction>>> {
22472 Some(self.update(cx, |project, cx| {
22473 project.perform_rename(buffer.clone(), position, new_name, cx)
22474 }))
22475 }
22476}
22477
22478fn inlay_hint_settings(
22479 location: Anchor,
22480 snapshot: &MultiBufferSnapshot,
22481 cx: &mut Context<Editor>,
22482) -> InlayHintSettings {
22483 let file = snapshot.file_at(location);
22484 let language = snapshot.language_at(location).map(|l| l.name());
22485 language_settings(language, file, cx).inlay_hints
22486}
22487
22488fn consume_contiguous_rows(
22489 contiguous_row_selections: &mut Vec<Selection<Point>>,
22490 selection: &Selection<Point>,
22491 display_map: &DisplaySnapshot,
22492 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22493) -> (MultiBufferRow, MultiBufferRow) {
22494 contiguous_row_selections.push(selection.clone());
22495 let start_row = starting_row(selection, display_map);
22496 let mut end_row = ending_row(selection, display_map);
22497
22498 while let Some(next_selection) = selections.peek() {
22499 if next_selection.start.row <= end_row.0 {
22500 end_row = ending_row(next_selection, display_map);
22501 contiguous_row_selections.push(selections.next().unwrap().clone());
22502 } else {
22503 break;
22504 }
22505 }
22506 (start_row, end_row)
22507}
22508
22509fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22510 if selection.start.column > 0 {
22511 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22512 } else {
22513 MultiBufferRow(selection.start.row)
22514 }
22515}
22516
22517fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22518 if next_selection.end.column > 0 || next_selection.is_empty() {
22519 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22520 } else {
22521 MultiBufferRow(next_selection.end.row)
22522 }
22523}
22524
22525impl EditorSnapshot {
22526 pub fn remote_selections_in_range<'a>(
22527 &'a self,
22528 range: &'a Range<Anchor>,
22529 collaboration_hub: &dyn CollaborationHub,
22530 cx: &'a App,
22531 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22532 let participant_names = collaboration_hub.user_names(cx);
22533 let participant_indices = collaboration_hub.user_participant_indices(cx);
22534 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22535 let collaborators_by_replica_id = collaborators_by_peer_id
22536 .values()
22537 .map(|collaborator| (collaborator.replica_id, collaborator))
22538 .collect::<HashMap<_, _>>();
22539 self.buffer_snapshot
22540 .selections_in_range(range, false)
22541 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22542 if replica_id == AGENT_REPLICA_ID {
22543 Some(RemoteSelection {
22544 replica_id,
22545 selection,
22546 cursor_shape,
22547 line_mode,
22548 collaborator_id: CollaboratorId::Agent,
22549 user_name: Some("Agent".into()),
22550 color: cx.theme().players().agent(),
22551 })
22552 } else {
22553 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22554 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22555 let user_name = participant_names.get(&collaborator.user_id).cloned();
22556 Some(RemoteSelection {
22557 replica_id,
22558 selection,
22559 cursor_shape,
22560 line_mode,
22561 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22562 user_name,
22563 color: if let Some(index) = participant_index {
22564 cx.theme().players().color_for_participant(index.0)
22565 } else {
22566 cx.theme().players().absent()
22567 },
22568 })
22569 }
22570 })
22571 }
22572
22573 pub fn hunks_for_ranges(
22574 &self,
22575 ranges: impl IntoIterator<Item = Range<Point>>,
22576 ) -> Vec<MultiBufferDiffHunk> {
22577 let mut hunks = Vec::new();
22578 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22579 HashMap::default();
22580 for query_range in ranges {
22581 let query_rows =
22582 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22583 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22584 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22585 ) {
22586 // Include deleted hunks that are adjacent to the query range, because
22587 // otherwise they would be missed.
22588 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22589 if hunk.status().is_deleted() {
22590 intersects_range |= hunk.row_range.start == query_rows.end;
22591 intersects_range |= hunk.row_range.end == query_rows.start;
22592 }
22593 if intersects_range {
22594 if !processed_buffer_rows
22595 .entry(hunk.buffer_id)
22596 .or_default()
22597 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22598 {
22599 continue;
22600 }
22601 hunks.push(hunk);
22602 }
22603 }
22604 }
22605
22606 hunks
22607 }
22608
22609 fn display_diff_hunks_for_rows<'a>(
22610 &'a self,
22611 display_rows: Range<DisplayRow>,
22612 folded_buffers: &'a HashSet<BufferId>,
22613 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22614 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22615 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22616
22617 self.buffer_snapshot
22618 .diff_hunks_in_range(buffer_start..buffer_end)
22619 .filter_map(|hunk| {
22620 if folded_buffers.contains(&hunk.buffer_id) {
22621 return None;
22622 }
22623
22624 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22625 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22626
22627 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22628 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22629
22630 let display_hunk = if hunk_display_start.column() != 0 {
22631 DisplayDiffHunk::Folded {
22632 display_row: hunk_display_start.row(),
22633 }
22634 } else {
22635 let mut end_row = hunk_display_end.row();
22636 if hunk_display_end.column() > 0 {
22637 end_row.0 += 1;
22638 }
22639 let is_created_file = hunk.is_created_file();
22640 DisplayDiffHunk::Unfolded {
22641 status: hunk.status(),
22642 diff_base_byte_range: hunk.diff_base_byte_range,
22643 display_row_range: hunk_display_start.row()..end_row,
22644 multi_buffer_range: Anchor::range_in_buffer(
22645 hunk.excerpt_id,
22646 hunk.buffer_id,
22647 hunk.buffer_range,
22648 ),
22649 is_created_file,
22650 }
22651 };
22652
22653 Some(display_hunk)
22654 })
22655 }
22656
22657 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22658 self.display_snapshot.buffer_snapshot.language_at(position)
22659 }
22660
22661 pub fn is_focused(&self) -> bool {
22662 self.is_focused
22663 }
22664
22665 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22666 self.placeholder_text.as_ref()
22667 }
22668
22669 pub fn scroll_position(&self) -> gpui::Point<f32> {
22670 self.scroll_anchor.scroll_position(&self.display_snapshot)
22671 }
22672
22673 fn gutter_dimensions(
22674 &self,
22675 font_id: FontId,
22676 font_size: Pixels,
22677 max_line_number_width: Pixels,
22678 cx: &App,
22679 ) -> Option<GutterDimensions> {
22680 if !self.show_gutter {
22681 return None;
22682 }
22683
22684 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22685 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22686
22687 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22688 matches!(
22689 ProjectSettings::get_global(cx).git.git_gutter,
22690 Some(GitGutterSetting::TrackedFiles)
22691 )
22692 });
22693 let gutter_settings = EditorSettings::get_global(cx).gutter;
22694 let show_line_numbers = self
22695 .show_line_numbers
22696 .unwrap_or(gutter_settings.line_numbers);
22697 let line_gutter_width = if show_line_numbers {
22698 // Avoid flicker-like gutter resizes when the line number gains another digit by
22699 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22700 let min_width_for_number_on_gutter =
22701 ch_advance * gutter_settings.min_line_number_digits as f32;
22702 max_line_number_width.max(min_width_for_number_on_gutter)
22703 } else {
22704 0.0.into()
22705 };
22706
22707 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22708 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22709
22710 let git_blame_entries_width =
22711 self.git_blame_gutter_max_author_length
22712 .map(|max_author_length| {
22713 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22714 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22715
22716 /// The number of characters to dedicate to gaps and margins.
22717 const SPACING_WIDTH: usize = 4;
22718
22719 let max_char_count = max_author_length.min(renderer.max_author_length())
22720 + ::git::SHORT_SHA_LENGTH
22721 + MAX_RELATIVE_TIMESTAMP.len()
22722 + SPACING_WIDTH;
22723
22724 ch_advance * max_char_count
22725 });
22726
22727 let is_singleton = self.buffer_snapshot.is_singleton();
22728
22729 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22730 left_padding += if !is_singleton {
22731 ch_width * 4.0
22732 } else if show_runnables || show_breakpoints {
22733 ch_width * 3.0
22734 } else if show_git_gutter && show_line_numbers {
22735 ch_width * 2.0
22736 } else if show_git_gutter || show_line_numbers {
22737 ch_width
22738 } else {
22739 px(0.)
22740 };
22741
22742 let shows_folds = is_singleton && gutter_settings.folds;
22743
22744 let right_padding = if shows_folds && show_line_numbers {
22745 ch_width * 4.0
22746 } else if shows_folds || (!is_singleton && show_line_numbers) {
22747 ch_width * 3.0
22748 } else if show_line_numbers {
22749 ch_width
22750 } else {
22751 px(0.)
22752 };
22753
22754 Some(GutterDimensions {
22755 left_padding,
22756 right_padding,
22757 width: line_gutter_width + left_padding + right_padding,
22758 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22759 git_blame_entries_width,
22760 })
22761 }
22762
22763 pub fn render_crease_toggle(
22764 &self,
22765 buffer_row: MultiBufferRow,
22766 row_contains_cursor: bool,
22767 editor: Entity<Editor>,
22768 window: &mut Window,
22769 cx: &mut App,
22770 ) -> Option<AnyElement> {
22771 let folded = self.is_line_folded(buffer_row);
22772 let mut is_foldable = false;
22773
22774 if let Some(crease) = self
22775 .crease_snapshot
22776 .query_row(buffer_row, &self.buffer_snapshot)
22777 {
22778 is_foldable = true;
22779 match crease {
22780 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22781 if let Some(render_toggle) = render_toggle {
22782 let toggle_callback =
22783 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22784 if folded {
22785 editor.update(cx, |editor, cx| {
22786 editor.fold_at(buffer_row, window, cx)
22787 });
22788 } else {
22789 editor.update(cx, |editor, cx| {
22790 editor.unfold_at(buffer_row, window, cx)
22791 });
22792 }
22793 });
22794 return Some((render_toggle)(
22795 buffer_row,
22796 folded,
22797 toggle_callback,
22798 window,
22799 cx,
22800 ));
22801 }
22802 }
22803 }
22804 }
22805
22806 is_foldable |= self.starts_indent(buffer_row);
22807
22808 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22809 Some(
22810 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22811 .toggle_state(folded)
22812 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22813 if folded {
22814 this.unfold_at(buffer_row, window, cx);
22815 } else {
22816 this.fold_at(buffer_row, window, cx);
22817 }
22818 }))
22819 .into_any_element(),
22820 )
22821 } else {
22822 None
22823 }
22824 }
22825
22826 pub fn render_crease_trailer(
22827 &self,
22828 buffer_row: MultiBufferRow,
22829 window: &mut Window,
22830 cx: &mut App,
22831 ) -> Option<AnyElement> {
22832 let folded = self.is_line_folded(buffer_row);
22833 if let Crease::Inline { render_trailer, .. } = self
22834 .crease_snapshot
22835 .query_row(buffer_row, &self.buffer_snapshot)?
22836 {
22837 let render_trailer = render_trailer.as_ref()?;
22838 Some(render_trailer(buffer_row, folded, window, cx))
22839 } else {
22840 None
22841 }
22842 }
22843}
22844
22845impl Deref for EditorSnapshot {
22846 type Target = DisplaySnapshot;
22847
22848 fn deref(&self) -> &Self::Target {
22849 &self.display_snapshot
22850 }
22851}
22852
22853#[derive(Clone, Debug, PartialEq, Eq)]
22854pub enum EditorEvent {
22855 InputIgnored {
22856 text: Arc<str>,
22857 },
22858 InputHandled {
22859 utf16_range_to_replace: Option<Range<isize>>,
22860 text: Arc<str>,
22861 },
22862 ExcerptsAdded {
22863 buffer: Entity<Buffer>,
22864 predecessor: ExcerptId,
22865 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22866 },
22867 ExcerptsRemoved {
22868 ids: Vec<ExcerptId>,
22869 removed_buffer_ids: Vec<BufferId>,
22870 },
22871 BufferFoldToggled {
22872 ids: Vec<ExcerptId>,
22873 folded: bool,
22874 },
22875 ExcerptsEdited {
22876 ids: Vec<ExcerptId>,
22877 },
22878 ExcerptsExpanded {
22879 ids: Vec<ExcerptId>,
22880 },
22881 BufferEdited,
22882 Edited {
22883 transaction_id: clock::Lamport,
22884 },
22885 Reparsed(BufferId),
22886 Focused,
22887 FocusedIn,
22888 Blurred,
22889 DirtyChanged,
22890 Saved,
22891 TitleChanged,
22892 DiffBaseChanged,
22893 SelectionsChanged {
22894 local: bool,
22895 },
22896 ScrollPositionChanged {
22897 local: bool,
22898 autoscroll: bool,
22899 },
22900 Closed,
22901 TransactionUndone {
22902 transaction_id: clock::Lamport,
22903 },
22904 TransactionBegun {
22905 transaction_id: clock::Lamport,
22906 },
22907 Reloaded,
22908 CursorShapeChanged,
22909 BreadcrumbsChanged,
22910 PushedToNavHistory {
22911 anchor: Anchor,
22912 is_deactivate: bool,
22913 },
22914}
22915
22916impl EventEmitter<EditorEvent> for Editor {}
22917
22918impl Focusable for Editor {
22919 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22920 self.focus_handle.clone()
22921 }
22922}
22923
22924impl Render for Editor {
22925 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22926 let settings = ThemeSettings::get_global(cx);
22927
22928 let mut text_style = match self.mode {
22929 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22930 color: cx.theme().colors().editor_foreground,
22931 font_family: settings.ui_font.family.clone(),
22932 font_features: settings.ui_font.features.clone(),
22933 font_fallbacks: settings.ui_font.fallbacks.clone(),
22934 font_size: rems(0.875).into(),
22935 font_weight: settings.ui_font.weight,
22936 line_height: relative(settings.buffer_line_height.value()),
22937 ..Default::default()
22938 },
22939 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22940 color: cx.theme().colors().editor_foreground,
22941 font_family: settings.buffer_font.family.clone(),
22942 font_features: settings.buffer_font.features.clone(),
22943 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22944 font_size: settings.buffer_font_size(cx).into(),
22945 font_weight: settings.buffer_font.weight,
22946 line_height: relative(settings.buffer_line_height.value()),
22947 ..Default::default()
22948 },
22949 };
22950 if let Some(text_style_refinement) = &self.text_style_refinement {
22951 text_style.refine(text_style_refinement)
22952 }
22953
22954 let background = match self.mode {
22955 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22956 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22957 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22958 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22959 };
22960
22961 EditorElement::new(
22962 &cx.entity(),
22963 EditorStyle {
22964 background,
22965 border: cx.theme().colors().border,
22966 local_player: cx.theme().players().local(),
22967 text: text_style,
22968 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22969 syntax: cx.theme().syntax().clone(),
22970 status: cx.theme().status().clone(),
22971 inlay_hints_style: make_inlay_hints_style(cx),
22972 edit_prediction_styles: make_suggestion_styles(cx),
22973 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22974 show_underlines: self.diagnostics_enabled(),
22975 },
22976 )
22977 }
22978}
22979
22980impl EntityInputHandler for Editor {
22981 fn text_for_range(
22982 &mut self,
22983 range_utf16: Range<usize>,
22984 adjusted_range: &mut Option<Range<usize>>,
22985 _: &mut Window,
22986 cx: &mut Context<Self>,
22987 ) -> Option<String> {
22988 let snapshot = self.buffer.read(cx).read(cx);
22989 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22990 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22991 if (start.0..end.0) != range_utf16 {
22992 adjusted_range.replace(start.0..end.0);
22993 }
22994 Some(snapshot.text_for_range(start..end).collect())
22995 }
22996
22997 fn selected_text_range(
22998 &mut self,
22999 ignore_disabled_input: bool,
23000 _: &mut Window,
23001 cx: &mut Context<Self>,
23002 ) -> Option<UTF16Selection> {
23003 // Prevent the IME menu from appearing when holding down an alphabetic key
23004 // while input is disabled.
23005 if !ignore_disabled_input && !self.input_enabled {
23006 return None;
23007 }
23008
23009 let selection = self.selections.newest::<OffsetUtf16>(cx);
23010 let range = selection.range();
23011
23012 Some(UTF16Selection {
23013 range: range.start.0..range.end.0,
23014 reversed: selection.reversed,
23015 })
23016 }
23017
23018 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23019 let snapshot = self.buffer.read(cx).read(cx);
23020 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23021 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23022 }
23023
23024 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23025 self.clear_highlights::<InputComposition>(cx);
23026 self.ime_transaction.take();
23027 }
23028
23029 fn replace_text_in_range(
23030 &mut self,
23031 range_utf16: Option<Range<usize>>,
23032 text: &str,
23033 window: &mut Window,
23034 cx: &mut Context<Self>,
23035 ) {
23036 if !self.input_enabled {
23037 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23038 return;
23039 }
23040
23041 self.transact(window, cx, |this, window, cx| {
23042 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23043 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23044 Some(this.selection_replacement_ranges(range_utf16, cx))
23045 } else {
23046 this.marked_text_ranges(cx)
23047 };
23048
23049 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23050 let newest_selection_id = this.selections.newest_anchor().id;
23051 this.selections
23052 .all::<OffsetUtf16>(cx)
23053 .iter()
23054 .zip(ranges_to_replace.iter())
23055 .find_map(|(selection, range)| {
23056 if selection.id == newest_selection_id {
23057 Some(
23058 (range.start.0 as isize - selection.head().0 as isize)
23059 ..(range.end.0 as isize - selection.head().0 as isize),
23060 )
23061 } else {
23062 None
23063 }
23064 })
23065 });
23066
23067 cx.emit(EditorEvent::InputHandled {
23068 utf16_range_to_replace: range_to_replace,
23069 text: text.into(),
23070 });
23071
23072 if let Some(new_selected_ranges) = new_selected_ranges {
23073 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23074 selections.select_ranges(new_selected_ranges)
23075 });
23076 this.backspace(&Default::default(), window, cx);
23077 }
23078
23079 this.handle_input(text, window, cx);
23080 });
23081
23082 if let Some(transaction) = self.ime_transaction {
23083 self.buffer.update(cx, |buffer, cx| {
23084 buffer.group_until_transaction(transaction, cx);
23085 });
23086 }
23087
23088 self.unmark_text(window, cx);
23089 }
23090
23091 fn replace_and_mark_text_in_range(
23092 &mut self,
23093 range_utf16: Option<Range<usize>>,
23094 text: &str,
23095 new_selected_range_utf16: Option<Range<usize>>,
23096 window: &mut Window,
23097 cx: &mut Context<Self>,
23098 ) {
23099 if !self.input_enabled {
23100 return;
23101 }
23102
23103 let transaction = self.transact(window, cx, |this, window, cx| {
23104 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23105 let snapshot = this.buffer.read(cx).read(cx);
23106 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23107 for marked_range in &mut marked_ranges {
23108 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23109 marked_range.start.0 += relative_range_utf16.start;
23110 marked_range.start =
23111 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23112 marked_range.end =
23113 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23114 }
23115 }
23116 Some(marked_ranges)
23117 } else if let Some(range_utf16) = range_utf16 {
23118 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23119 Some(this.selection_replacement_ranges(range_utf16, cx))
23120 } else {
23121 None
23122 };
23123
23124 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23125 let newest_selection_id = this.selections.newest_anchor().id;
23126 this.selections
23127 .all::<OffsetUtf16>(cx)
23128 .iter()
23129 .zip(ranges_to_replace.iter())
23130 .find_map(|(selection, range)| {
23131 if selection.id == newest_selection_id {
23132 Some(
23133 (range.start.0 as isize - selection.head().0 as isize)
23134 ..(range.end.0 as isize - selection.head().0 as isize),
23135 )
23136 } else {
23137 None
23138 }
23139 })
23140 });
23141
23142 cx.emit(EditorEvent::InputHandled {
23143 utf16_range_to_replace: range_to_replace,
23144 text: text.into(),
23145 });
23146
23147 if let Some(ranges) = ranges_to_replace {
23148 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23149 s.select_ranges(ranges)
23150 });
23151 }
23152
23153 let marked_ranges = {
23154 let snapshot = this.buffer.read(cx).read(cx);
23155 this.selections
23156 .disjoint_anchors()
23157 .iter()
23158 .map(|selection| {
23159 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23160 })
23161 .collect::<Vec<_>>()
23162 };
23163
23164 if text.is_empty() {
23165 this.unmark_text(window, cx);
23166 } else {
23167 this.highlight_text::<InputComposition>(
23168 marked_ranges.clone(),
23169 HighlightStyle {
23170 underline: Some(UnderlineStyle {
23171 thickness: px(1.),
23172 color: None,
23173 wavy: false,
23174 }),
23175 ..Default::default()
23176 },
23177 cx,
23178 );
23179 }
23180
23181 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23182 let use_autoclose = this.use_autoclose;
23183 let use_auto_surround = this.use_auto_surround;
23184 this.set_use_autoclose(false);
23185 this.set_use_auto_surround(false);
23186 this.handle_input(text, window, cx);
23187 this.set_use_autoclose(use_autoclose);
23188 this.set_use_auto_surround(use_auto_surround);
23189
23190 if let Some(new_selected_range) = new_selected_range_utf16 {
23191 let snapshot = this.buffer.read(cx).read(cx);
23192 let new_selected_ranges = marked_ranges
23193 .into_iter()
23194 .map(|marked_range| {
23195 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23196 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23197 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23198 snapshot.clip_offset_utf16(new_start, Bias::Left)
23199 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23200 })
23201 .collect::<Vec<_>>();
23202
23203 drop(snapshot);
23204 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23205 selections.select_ranges(new_selected_ranges)
23206 });
23207 }
23208 });
23209
23210 self.ime_transaction = self.ime_transaction.or(transaction);
23211 if let Some(transaction) = self.ime_transaction {
23212 self.buffer.update(cx, |buffer, cx| {
23213 buffer.group_until_transaction(transaction, cx);
23214 });
23215 }
23216
23217 if self.text_highlights::<InputComposition>(cx).is_none() {
23218 self.ime_transaction.take();
23219 }
23220 }
23221
23222 fn bounds_for_range(
23223 &mut self,
23224 range_utf16: Range<usize>,
23225 element_bounds: gpui::Bounds<Pixels>,
23226 window: &mut Window,
23227 cx: &mut Context<Self>,
23228 ) -> Option<gpui::Bounds<Pixels>> {
23229 let text_layout_details = self.text_layout_details(window);
23230 let CharacterDimensions {
23231 em_width,
23232 em_advance,
23233 line_height,
23234 } = self.character_dimensions(window);
23235
23236 let snapshot = self.snapshot(window, cx);
23237 let scroll_position = snapshot.scroll_position();
23238 let scroll_left = scroll_position.x * em_advance;
23239
23240 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23241 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23242 + self.gutter_dimensions.full_width();
23243 let y = line_height * (start.row().as_f32() - scroll_position.y);
23244
23245 Some(Bounds {
23246 origin: element_bounds.origin + point(x, y),
23247 size: size(em_width, line_height),
23248 })
23249 }
23250
23251 fn character_index_for_point(
23252 &mut self,
23253 point: gpui::Point<Pixels>,
23254 _window: &mut Window,
23255 _cx: &mut Context<Self>,
23256 ) -> Option<usize> {
23257 let position_map = self.last_position_map.as_ref()?;
23258 if !position_map.text_hitbox.contains(&point) {
23259 return None;
23260 }
23261 let display_point = position_map.point_for_position(point).previous_valid;
23262 let anchor = position_map
23263 .snapshot
23264 .display_point_to_anchor(display_point, Bias::Left);
23265 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23266 Some(utf16_offset.0)
23267 }
23268}
23269
23270trait SelectionExt {
23271 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23272 fn spanned_rows(
23273 &self,
23274 include_end_if_at_line_start: bool,
23275 map: &DisplaySnapshot,
23276 ) -> Range<MultiBufferRow>;
23277}
23278
23279impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23280 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23281 let start = self
23282 .start
23283 .to_point(&map.buffer_snapshot)
23284 .to_display_point(map);
23285 let end = self
23286 .end
23287 .to_point(&map.buffer_snapshot)
23288 .to_display_point(map);
23289 if self.reversed {
23290 end..start
23291 } else {
23292 start..end
23293 }
23294 }
23295
23296 fn spanned_rows(
23297 &self,
23298 include_end_if_at_line_start: bool,
23299 map: &DisplaySnapshot,
23300 ) -> Range<MultiBufferRow> {
23301 let start = self.start.to_point(&map.buffer_snapshot);
23302 let mut end = self.end.to_point(&map.buffer_snapshot);
23303 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23304 end.row -= 1;
23305 }
23306
23307 let buffer_start = map.prev_line_boundary(start).0;
23308 let buffer_end = map.next_line_boundary(end).0;
23309 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23310 }
23311}
23312
23313impl<T: InvalidationRegion> InvalidationStack<T> {
23314 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23315 where
23316 S: Clone + ToOffset,
23317 {
23318 while let Some(region) = self.last() {
23319 let all_selections_inside_invalidation_ranges =
23320 if selections.len() == region.ranges().len() {
23321 selections
23322 .iter()
23323 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23324 .all(|(selection, invalidation_range)| {
23325 let head = selection.head().to_offset(buffer);
23326 invalidation_range.start <= head && invalidation_range.end >= head
23327 })
23328 } else {
23329 false
23330 };
23331
23332 if all_selections_inside_invalidation_ranges {
23333 break;
23334 } else {
23335 self.pop();
23336 }
23337 }
23338 }
23339}
23340
23341impl<T> Default for InvalidationStack<T> {
23342 fn default() -> Self {
23343 Self(Default::default())
23344 }
23345}
23346
23347impl<T> Deref for InvalidationStack<T> {
23348 type Target = Vec<T>;
23349
23350 fn deref(&self) -> &Self::Target {
23351 &self.0
23352 }
23353}
23354
23355impl<T> DerefMut for InvalidationStack<T> {
23356 fn deref_mut(&mut self) -> &mut Self::Target {
23357 &mut self.0
23358 }
23359}
23360
23361impl InvalidationRegion for SnippetState {
23362 fn ranges(&self) -> &[Range<Anchor>] {
23363 &self.ranges[self.active_index]
23364 }
23365}
23366
23367fn edit_prediction_edit_text(
23368 current_snapshot: &BufferSnapshot,
23369 edits: &[(Range<Anchor>, String)],
23370 edit_preview: &EditPreview,
23371 include_deletions: bool,
23372 cx: &App,
23373) -> HighlightedText {
23374 let edits = edits
23375 .iter()
23376 .map(|(anchor, text)| {
23377 (
23378 anchor.start.text_anchor..anchor.end.text_anchor,
23379 text.clone(),
23380 )
23381 })
23382 .collect::<Vec<_>>();
23383
23384 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23385}
23386
23387fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23388 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23389 // Just show the raw edit text with basic styling
23390 let mut text = String::new();
23391 let mut highlights = Vec::new();
23392
23393 let insertion_highlight_style = HighlightStyle {
23394 color: Some(cx.theme().colors().text),
23395 ..Default::default()
23396 };
23397
23398 for (_, edit_text) in edits {
23399 let start_offset = text.len();
23400 text.push_str(edit_text);
23401 let end_offset = text.len();
23402
23403 if start_offset < end_offset {
23404 highlights.push((start_offset..end_offset, insertion_highlight_style));
23405 }
23406 }
23407
23408 HighlightedText {
23409 text: text.into(),
23410 highlights,
23411 }
23412}
23413
23414pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23415 match severity {
23416 lsp::DiagnosticSeverity::ERROR => colors.error,
23417 lsp::DiagnosticSeverity::WARNING => colors.warning,
23418 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23419 lsp::DiagnosticSeverity::HINT => colors.info,
23420 _ => colors.ignored,
23421 }
23422}
23423
23424pub fn styled_runs_for_code_label<'a>(
23425 label: &'a CodeLabel,
23426 syntax_theme: &'a theme::SyntaxTheme,
23427) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23428 let fade_out = HighlightStyle {
23429 fade_out: Some(0.35),
23430 ..Default::default()
23431 };
23432
23433 let mut prev_end = label.filter_range.end;
23434 label
23435 .runs
23436 .iter()
23437 .enumerate()
23438 .flat_map(move |(ix, (range, highlight_id))| {
23439 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23440 style
23441 } else {
23442 return Default::default();
23443 };
23444 let mut muted_style = style;
23445 muted_style.highlight(fade_out);
23446
23447 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23448 if range.start >= label.filter_range.end {
23449 if range.start > prev_end {
23450 runs.push((prev_end..range.start, fade_out));
23451 }
23452 runs.push((range.clone(), muted_style));
23453 } else if range.end <= label.filter_range.end {
23454 runs.push((range.clone(), style));
23455 } else {
23456 runs.push((range.start..label.filter_range.end, style));
23457 runs.push((label.filter_range.end..range.end, muted_style));
23458 }
23459 prev_end = cmp::max(prev_end, range.end);
23460
23461 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23462 runs.push((prev_end..label.text.len(), fade_out));
23463 }
23464
23465 runs
23466 })
23467}
23468
23469pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23470 let mut prev_index = 0;
23471 let mut prev_codepoint: Option<char> = None;
23472 text.char_indices()
23473 .chain([(text.len(), '\0')])
23474 .filter_map(move |(index, codepoint)| {
23475 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23476 let is_boundary = index == text.len()
23477 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23478 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23479 if is_boundary {
23480 let chunk = &text[prev_index..index];
23481 prev_index = index;
23482 Some(chunk)
23483 } else {
23484 None
23485 }
23486 })
23487}
23488
23489pub trait RangeToAnchorExt: Sized {
23490 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23491
23492 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23493 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23494 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23495 }
23496}
23497
23498impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23499 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23500 let start_offset = self.start.to_offset(snapshot);
23501 let end_offset = self.end.to_offset(snapshot);
23502 if start_offset == end_offset {
23503 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23504 } else {
23505 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23506 }
23507 }
23508}
23509
23510pub trait RowExt {
23511 fn as_f32(&self) -> f32;
23512
23513 fn next_row(&self) -> Self;
23514
23515 fn previous_row(&self) -> Self;
23516
23517 fn minus(&self, other: Self) -> u32;
23518}
23519
23520impl RowExt for DisplayRow {
23521 fn as_f32(&self) -> f32 {
23522 self.0 as f32
23523 }
23524
23525 fn next_row(&self) -> Self {
23526 Self(self.0 + 1)
23527 }
23528
23529 fn previous_row(&self) -> Self {
23530 Self(self.0.saturating_sub(1))
23531 }
23532
23533 fn minus(&self, other: Self) -> u32 {
23534 self.0 - other.0
23535 }
23536}
23537
23538impl RowExt for MultiBufferRow {
23539 fn as_f32(&self) -> f32 {
23540 self.0 as f32
23541 }
23542
23543 fn next_row(&self) -> Self {
23544 Self(self.0 + 1)
23545 }
23546
23547 fn previous_row(&self) -> Self {
23548 Self(self.0.saturating_sub(1))
23549 }
23550
23551 fn minus(&self, other: Self) -> u32 {
23552 self.0 - other.0
23553 }
23554}
23555
23556trait RowRangeExt {
23557 type Row;
23558
23559 fn len(&self) -> usize;
23560
23561 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23562}
23563
23564impl RowRangeExt for Range<MultiBufferRow> {
23565 type Row = MultiBufferRow;
23566
23567 fn len(&self) -> usize {
23568 (self.end.0 - self.start.0) as usize
23569 }
23570
23571 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23572 (self.start.0..self.end.0).map(MultiBufferRow)
23573 }
23574}
23575
23576impl RowRangeExt for Range<DisplayRow> {
23577 type Row = DisplayRow;
23578
23579 fn len(&self) -> usize {
23580 (self.end.0 - self.start.0) as usize
23581 }
23582
23583 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23584 (self.start.0..self.end.0).map(DisplayRow)
23585 }
23586}
23587
23588/// If select range has more than one line, we
23589/// just point the cursor to range.start.
23590fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23591 if range.start.row == range.end.row {
23592 range
23593 } else {
23594 range.start..range.start
23595 }
23596}
23597pub struct KillRing(ClipboardItem);
23598impl Global for KillRing {}
23599
23600const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23601
23602enum BreakpointPromptEditAction {
23603 Log,
23604 Condition,
23605 HitCondition,
23606}
23607
23608struct BreakpointPromptEditor {
23609 pub(crate) prompt: Entity<Editor>,
23610 editor: WeakEntity<Editor>,
23611 breakpoint_anchor: Anchor,
23612 breakpoint: Breakpoint,
23613 edit_action: BreakpointPromptEditAction,
23614 block_ids: HashSet<CustomBlockId>,
23615 editor_margins: Arc<Mutex<EditorMargins>>,
23616 _subscriptions: Vec<Subscription>,
23617}
23618
23619impl BreakpointPromptEditor {
23620 const MAX_LINES: u8 = 4;
23621
23622 fn new(
23623 editor: WeakEntity<Editor>,
23624 breakpoint_anchor: Anchor,
23625 breakpoint: Breakpoint,
23626 edit_action: BreakpointPromptEditAction,
23627 window: &mut Window,
23628 cx: &mut Context<Self>,
23629 ) -> Self {
23630 let base_text = match edit_action {
23631 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23632 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23633 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23634 }
23635 .map(|msg| msg.to_string())
23636 .unwrap_or_default();
23637
23638 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23639 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23640
23641 let prompt = cx.new(|cx| {
23642 let mut prompt = Editor::new(
23643 EditorMode::AutoHeight {
23644 min_lines: 1,
23645 max_lines: Some(Self::MAX_LINES as usize),
23646 },
23647 buffer,
23648 None,
23649 window,
23650 cx,
23651 );
23652 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23653 prompt.set_show_cursor_when_unfocused(false, cx);
23654 prompt.set_placeholder_text(
23655 match edit_action {
23656 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23657 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23658 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23659 },
23660 cx,
23661 );
23662
23663 prompt
23664 });
23665
23666 Self {
23667 prompt,
23668 editor,
23669 breakpoint_anchor,
23670 breakpoint,
23671 edit_action,
23672 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23673 block_ids: Default::default(),
23674 _subscriptions: vec![],
23675 }
23676 }
23677
23678 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23679 self.block_ids.extend(block_ids)
23680 }
23681
23682 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23683 if let Some(editor) = self.editor.upgrade() {
23684 let message = self
23685 .prompt
23686 .read(cx)
23687 .buffer
23688 .read(cx)
23689 .as_singleton()
23690 .expect("A multi buffer in breakpoint prompt isn't possible")
23691 .read(cx)
23692 .as_rope()
23693 .to_string();
23694
23695 editor.update(cx, |editor, cx| {
23696 editor.edit_breakpoint_at_anchor(
23697 self.breakpoint_anchor,
23698 self.breakpoint.clone(),
23699 match self.edit_action {
23700 BreakpointPromptEditAction::Log => {
23701 BreakpointEditAction::EditLogMessage(message.into())
23702 }
23703 BreakpointPromptEditAction::Condition => {
23704 BreakpointEditAction::EditCondition(message.into())
23705 }
23706 BreakpointPromptEditAction::HitCondition => {
23707 BreakpointEditAction::EditHitCondition(message.into())
23708 }
23709 },
23710 cx,
23711 );
23712
23713 editor.remove_blocks(self.block_ids.clone(), None, cx);
23714 cx.focus_self(window);
23715 });
23716 }
23717 }
23718
23719 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23720 self.editor
23721 .update(cx, |editor, cx| {
23722 editor.remove_blocks(self.block_ids.clone(), None, cx);
23723 window.focus(&editor.focus_handle);
23724 })
23725 .log_err();
23726 }
23727
23728 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23729 let settings = ThemeSettings::get_global(cx);
23730 let text_style = TextStyle {
23731 color: if self.prompt.read(cx).read_only(cx) {
23732 cx.theme().colors().text_disabled
23733 } else {
23734 cx.theme().colors().text
23735 },
23736 font_family: settings.buffer_font.family.clone(),
23737 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23738 font_size: settings.buffer_font_size(cx).into(),
23739 font_weight: settings.buffer_font.weight,
23740 line_height: relative(settings.buffer_line_height.value()),
23741 ..Default::default()
23742 };
23743 EditorElement::new(
23744 &self.prompt,
23745 EditorStyle {
23746 background: cx.theme().colors().editor_background,
23747 local_player: cx.theme().players().local(),
23748 text: text_style,
23749 ..Default::default()
23750 },
23751 )
23752 }
23753}
23754
23755impl Render for BreakpointPromptEditor {
23756 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23757 let editor_margins = *self.editor_margins.lock();
23758 let gutter_dimensions = editor_margins.gutter;
23759 h_flex()
23760 .key_context("Editor")
23761 .bg(cx.theme().colors().editor_background)
23762 .border_y_1()
23763 .border_color(cx.theme().status().info_border)
23764 .size_full()
23765 .py(window.line_height() / 2.5)
23766 .on_action(cx.listener(Self::confirm))
23767 .on_action(cx.listener(Self::cancel))
23768 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23769 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23770 }
23771}
23772
23773impl Focusable for BreakpointPromptEditor {
23774 fn focus_handle(&self, cx: &App) -> FocusHandle {
23775 self.prompt.focus_handle(cx)
23776 }
23777}
23778
23779fn all_edits_insertions_or_deletions(
23780 edits: &Vec<(Range<Anchor>, String)>,
23781 snapshot: &MultiBufferSnapshot,
23782) -> bool {
23783 let mut all_insertions = true;
23784 let mut all_deletions = true;
23785
23786 for (range, new_text) in edits.iter() {
23787 let range_is_empty = range.to_offset(snapshot).is_empty();
23788 let text_is_empty = new_text.is_empty();
23789
23790 if range_is_empty != text_is_empty {
23791 if range_is_empty {
23792 all_deletions = false;
23793 } else {
23794 all_insertions = false;
23795 }
23796 } else {
23797 return false;
23798 }
23799
23800 if !all_insertions && !all_deletions {
23801 return false;
23802 }
23803 }
23804 all_insertions || all_deletions
23805}
23806
23807struct MissingEditPredictionKeybindingTooltip;
23808
23809impl Render for MissingEditPredictionKeybindingTooltip {
23810 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23811 ui::tooltip_container(window, cx, |container, _, cx| {
23812 container
23813 .flex_shrink_0()
23814 .max_w_80()
23815 .min_h(rems_from_px(124.))
23816 .justify_between()
23817 .child(
23818 v_flex()
23819 .flex_1()
23820 .text_ui_sm(cx)
23821 .child(Label::new("Conflict with Accept Keybinding"))
23822 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23823 )
23824 .child(
23825 h_flex()
23826 .pb_1()
23827 .gap_1()
23828 .items_end()
23829 .w_full()
23830 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23831 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23832 }))
23833 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23834 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23835 })),
23836 )
23837 })
23838 }
23839}
23840
23841#[derive(Debug, Clone, Copy, PartialEq)]
23842pub struct LineHighlight {
23843 pub background: Background,
23844 pub border: Option<gpui::Hsla>,
23845 pub include_gutter: bool,
23846 pub type_id: Option<TypeId>,
23847}
23848
23849struct LineManipulationResult {
23850 pub new_text: String,
23851 pub line_count_before: usize,
23852 pub line_count_after: usize,
23853}
23854
23855fn render_diff_hunk_controls(
23856 row: u32,
23857 status: &DiffHunkStatus,
23858 hunk_range: Range<Anchor>,
23859 is_created_file: bool,
23860 line_height: Pixels,
23861 editor: &Entity<Editor>,
23862 _window: &mut Window,
23863 cx: &mut App,
23864) -> AnyElement {
23865 h_flex()
23866 .h(line_height)
23867 .mr_1()
23868 .gap_1()
23869 .px_0p5()
23870 .pb_1()
23871 .border_x_1()
23872 .border_b_1()
23873 .border_color(cx.theme().colors().border_variant)
23874 .rounded_b_lg()
23875 .bg(cx.theme().colors().editor_background)
23876 .gap_1()
23877 .block_mouse_except_scroll()
23878 .shadow_md()
23879 .child(if status.has_secondary_hunk() {
23880 Button::new(("stage", row as u64), "Stage")
23881 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23882 .tooltip({
23883 let focus_handle = editor.focus_handle(cx);
23884 move |window, cx| {
23885 Tooltip::for_action_in(
23886 "Stage Hunk",
23887 &::git::ToggleStaged,
23888 &focus_handle,
23889 window,
23890 cx,
23891 )
23892 }
23893 })
23894 .on_click({
23895 let editor = editor.clone();
23896 move |_event, _window, cx| {
23897 editor.update(cx, |editor, cx| {
23898 editor.stage_or_unstage_diff_hunks(
23899 true,
23900 vec![hunk_range.start..hunk_range.start],
23901 cx,
23902 );
23903 });
23904 }
23905 })
23906 } else {
23907 Button::new(("unstage", row as u64), "Unstage")
23908 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23909 .tooltip({
23910 let focus_handle = editor.focus_handle(cx);
23911 move |window, cx| {
23912 Tooltip::for_action_in(
23913 "Unstage Hunk",
23914 &::git::ToggleStaged,
23915 &focus_handle,
23916 window,
23917 cx,
23918 )
23919 }
23920 })
23921 .on_click({
23922 let editor = editor.clone();
23923 move |_event, _window, cx| {
23924 editor.update(cx, |editor, cx| {
23925 editor.stage_or_unstage_diff_hunks(
23926 false,
23927 vec![hunk_range.start..hunk_range.start],
23928 cx,
23929 );
23930 });
23931 }
23932 })
23933 })
23934 .child(
23935 Button::new(("restore", row as u64), "Restore")
23936 .tooltip({
23937 let focus_handle = editor.focus_handle(cx);
23938 move |window, cx| {
23939 Tooltip::for_action_in(
23940 "Restore Hunk",
23941 &::git::Restore,
23942 &focus_handle,
23943 window,
23944 cx,
23945 )
23946 }
23947 })
23948 .on_click({
23949 let editor = editor.clone();
23950 move |_event, window, cx| {
23951 editor.update(cx, |editor, cx| {
23952 let snapshot = editor.snapshot(window, cx);
23953 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23954 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23955 });
23956 }
23957 })
23958 .disabled(is_created_file),
23959 )
23960 .when(
23961 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23962 |el| {
23963 el.child(
23964 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23965 .shape(IconButtonShape::Square)
23966 .icon_size(IconSize::Small)
23967 // .disabled(!has_multiple_hunks)
23968 .tooltip({
23969 let focus_handle = editor.focus_handle(cx);
23970 move |window, cx| {
23971 Tooltip::for_action_in(
23972 "Next Hunk",
23973 &GoToHunk,
23974 &focus_handle,
23975 window,
23976 cx,
23977 )
23978 }
23979 })
23980 .on_click({
23981 let editor = editor.clone();
23982 move |_event, window, cx| {
23983 editor.update(cx, |editor, cx| {
23984 let snapshot = editor.snapshot(window, cx);
23985 let position =
23986 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23987 editor.go_to_hunk_before_or_after_position(
23988 &snapshot,
23989 position,
23990 Direction::Next,
23991 window,
23992 cx,
23993 );
23994 editor.expand_selected_diff_hunks(cx);
23995 });
23996 }
23997 }),
23998 )
23999 .child(
24000 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24001 .shape(IconButtonShape::Square)
24002 .icon_size(IconSize::Small)
24003 // .disabled(!has_multiple_hunks)
24004 .tooltip({
24005 let focus_handle = editor.focus_handle(cx);
24006 move |window, cx| {
24007 Tooltip::for_action_in(
24008 "Previous Hunk",
24009 &GoToPreviousHunk,
24010 &focus_handle,
24011 window,
24012 cx,
24013 )
24014 }
24015 })
24016 .on_click({
24017 let editor = editor.clone();
24018 move |_event, window, cx| {
24019 editor.update(cx, |editor, cx| {
24020 let snapshot = editor.snapshot(window, cx);
24021 let point =
24022 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24023 editor.go_to_hunk_before_or_after_position(
24024 &snapshot,
24025 point,
24026 Direction::Prev,
24027 window,
24028 cx,
24029 );
24030 editor.expand_selected_diff_hunks(cx);
24031 });
24032 }
24033 }),
24034 )
24035 },
24036 )
24037 .into_any_element()
24038}