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 old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14838
14839 let edits = old_selections
14840 .iter()
14841 // only consider the first selection for now
14842 .take(1)
14843 .map(|selection| {
14844 // Only requires two branches once if-let-chains stabilize (#53667)
14845 let selection_range = if !selection.is_empty() {
14846 selection.range()
14847 } else if let Some((_, ancestor_range)) =
14848 buffer.syntax_ancestor(selection.start..selection.end)
14849 {
14850 match ancestor_range {
14851 MultiOrSingleBufferOffsetRange::Single(range) => range,
14852 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14853 }
14854 } else {
14855 selection.range()
14856 };
14857
14858 let mut new_range = selection_range.clone();
14859 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(new_range.clone()) {
14860 new_range = match ancestor_range {
14861 MultiOrSingleBufferOffsetRange::Single(range) => range,
14862 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14863 };
14864 if new_range.start < selection_range.start
14865 || new_range.end > selection_range.end
14866 {
14867 break;
14868 }
14869 }
14870
14871 (selection, selection_range, new_range)
14872 })
14873 .collect::<Vec<_>>();
14874
14875 self.transact(window, cx, |editor, window, cx| {
14876 for (_, child, parent) in &edits {
14877 let text = buffer.text_for_range(child.clone()).collect::<String>();
14878 editor.replace_text_in_range(Some(parent.clone()), &text, window, cx);
14879 }
14880
14881 editor.change_selections(
14882 SelectionEffects::scroll(Autoscroll::fit()),
14883 window,
14884 cx,
14885 |s| {
14886 s.select(
14887 edits
14888 .iter()
14889 .map(|(s, old, new)| Selection {
14890 id: s.id,
14891 start: new.start,
14892 end: new.start + old.len(),
14893 goal: SelectionGoal::None,
14894 reversed: s.reversed,
14895 })
14896 .collect(),
14897 );
14898 },
14899 );
14900 });
14901 }
14902
14903 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14904 if !EditorSettings::get_global(cx).gutter.runnables {
14905 self.clear_tasks();
14906 return Task::ready(());
14907 }
14908 let project = self.project().map(Entity::downgrade);
14909 let task_sources = self.lsp_task_sources(cx);
14910 let multi_buffer = self.buffer.downgrade();
14911 cx.spawn_in(window, async move |editor, cx| {
14912 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14913 let Some(project) = project.and_then(|p| p.upgrade()) else {
14914 return;
14915 };
14916 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14917 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14918 }) else {
14919 return;
14920 };
14921
14922 let hide_runnables = project
14923 .update(cx, |project, cx| {
14924 // Do not display any test indicators in non-dev server remote projects.
14925 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14926 })
14927 .unwrap_or(true);
14928 if hide_runnables {
14929 return;
14930 }
14931 let new_rows =
14932 cx.background_spawn({
14933 let snapshot = display_snapshot.clone();
14934 async move {
14935 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14936 }
14937 })
14938 .await;
14939 let Ok(lsp_tasks) =
14940 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14941 else {
14942 return;
14943 };
14944 let lsp_tasks = lsp_tasks.await;
14945
14946 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14947 lsp_tasks
14948 .into_iter()
14949 .flat_map(|(kind, tasks)| {
14950 tasks.into_iter().filter_map(move |(location, task)| {
14951 Some((kind.clone(), location?, task))
14952 })
14953 })
14954 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14955 let buffer = location.target.buffer;
14956 let buffer_snapshot = buffer.read(cx).snapshot();
14957 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14958 |(excerpt_id, snapshot, _)| {
14959 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14960 display_snapshot
14961 .buffer_snapshot
14962 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14963 } else {
14964 None
14965 }
14966 },
14967 );
14968 if let Some(offset) = offset {
14969 let task_buffer_range =
14970 location.target.range.to_point(&buffer_snapshot);
14971 let context_buffer_range =
14972 task_buffer_range.to_offset(&buffer_snapshot);
14973 let context_range = BufferOffset(context_buffer_range.start)
14974 ..BufferOffset(context_buffer_range.end);
14975
14976 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14977 .or_insert_with(|| RunnableTasks {
14978 templates: Vec::new(),
14979 offset,
14980 column: task_buffer_range.start.column,
14981 extra_variables: HashMap::default(),
14982 context_range,
14983 })
14984 .templates
14985 .push((kind, task.original_task().clone()));
14986 }
14987
14988 acc
14989 })
14990 }) else {
14991 return;
14992 };
14993
14994 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14995 buffer.language_settings(cx).tasks.prefer_lsp
14996 }) else {
14997 return;
14998 };
14999
15000 let rows = Self::runnable_rows(
15001 project,
15002 display_snapshot,
15003 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15004 new_rows,
15005 cx.clone(),
15006 )
15007 .await;
15008 editor
15009 .update(cx, |editor, _| {
15010 editor.clear_tasks();
15011 for (key, mut value) in rows {
15012 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15013 value.templates.extend(lsp_tasks.templates);
15014 }
15015
15016 editor.insert_tasks(key, value);
15017 }
15018 for (key, value) in lsp_tasks_by_rows {
15019 editor.insert_tasks(key, value);
15020 }
15021 })
15022 .ok();
15023 })
15024 }
15025 fn fetch_runnable_ranges(
15026 snapshot: &DisplaySnapshot,
15027 range: Range<Anchor>,
15028 ) -> Vec<language::RunnableRange> {
15029 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15030 }
15031
15032 fn runnable_rows(
15033 project: Entity<Project>,
15034 snapshot: DisplaySnapshot,
15035 prefer_lsp: bool,
15036 runnable_ranges: Vec<RunnableRange>,
15037 cx: AsyncWindowContext,
15038 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15039 cx.spawn(async move |cx| {
15040 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15041 for mut runnable in runnable_ranges {
15042 let Some(tasks) = cx
15043 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15044 .ok()
15045 else {
15046 continue;
15047 };
15048 let mut tasks = tasks.await;
15049
15050 if prefer_lsp {
15051 tasks.retain(|(task_kind, _)| {
15052 !matches!(task_kind, TaskSourceKind::Language { .. })
15053 });
15054 }
15055 if tasks.is_empty() {
15056 continue;
15057 }
15058
15059 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15060 let Some(row) = snapshot
15061 .buffer_snapshot
15062 .buffer_line_for_row(MultiBufferRow(point.row))
15063 .map(|(_, range)| range.start.row)
15064 else {
15065 continue;
15066 };
15067
15068 let context_range =
15069 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15070 runnable_rows.push((
15071 (runnable.buffer_id, row),
15072 RunnableTasks {
15073 templates: tasks,
15074 offset: snapshot
15075 .buffer_snapshot
15076 .anchor_before(runnable.run_range.start),
15077 context_range,
15078 column: point.column,
15079 extra_variables: runnable.extra_captures,
15080 },
15081 ));
15082 }
15083 runnable_rows
15084 })
15085 }
15086
15087 fn templates_with_tags(
15088 project: &Entity<Project>,
15089 runnable: &mut Runnable,
15090 cx: &mut App,
15091 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15092 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15093 let (worktree_id, file) = project
15094 .buffer_for_id(runnable.buffer, cx)
15095 .and_then(|buffer| buffer.read(cx).file())
15096 .map(|file| (file.worktree_id(cx), file.clone()))
15097 .unzip();
15098
15099 (
15100 project.task_store().read(cx).task_inventory().cloned(),
15101 worktree_id,
15102 file,
15103 )
15104 });
15105
15106 let tags = mem::take(&mut runnable.tags);
15107 let language = runnable.language.clone();
15108 cx.spawn(async move |cx| {
15109 let mut templates_with_tags = Vec::new();
15110 if let Some(inventory) = inventory {
15111 for RunnableTag(tag) in tags {
15112 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15113 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15114 }) else {
15115 return templates_with_tags;
15116 };
15117 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15118 move |(_, template)| {
15119 template.tags.iter().any(|source_tag| source_tag == &tag)
15120 },
15121 ));
15122 }
15123 }
15124 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15125
15126 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15127 // Strongest source wins; if we have worktree tag binding, prefer that to
15128 // global and language bindings;
15129 // if we have a global binding, prefer that to language binding.
15130 let first_mismatch = templates_with_tags
15131 .iter()
15132 .position(|(tag_source, _)| tag_source != leading_tag_source);
15133 if let Some(index) = first_mismatch {
15134 templates_with_tags.truncate(index);
15135 }
15136 }
15137
15138 templates_with_tags
15139 })
15140 }
15141
15142 pub fn move_to_enclosing_bracket(
15143 &mut self,
15144 _: &MoveToEnclosingBracket,
15145 window: &mut Window,
15146 cx: &mut Context<Self>,
15147 ) {
15148 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15149 self.change_selections(Default::default(), window, cx, |s| {
15150 s.move_offsets_with(|snapshot, selection| {
15151 let Some(enclosing_bracket_ranges) =
15152 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15153 else {
15154 return;
15155 };
15156
15157 let mut best_length = usize::MAX;
15158 let mut best_inside = false;
15159 let mut best_in_bracket_range = false;
15160 let mut best_destination = None;
15161 for (open, close) in enclosing_bracket_ranges {
15162 let close = close.to_inclusive();
15163 let length = close.end() - open.start;
15164 let inside = selection.start >= open.end && selection.end <= *close.start();
15165 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15166 || close.contains(&selection.head());
15167
15168 // If best is next to a bracket and current isn't, skip
15169 if !in_bracket_range && best_in_bracket_range {
15170 continue;
15171 }
15172
15173 // Prefer smaller lengths unless best is inside and current isn't
15174 if length > best_length && (best_inside || !inside) {
15175 continue;
15176 }
15177
15178 best_length = length;
15179 best_inside = inside;
15180 best_in_bracket_range = in_bracket_range;
15181 best_destination = Some(
15182 if close.contains(&selection.start) && close.contains(&selection.end) {
15183 if inside { open.end } else { open.start }
15184 } else if inside {
15185 *close.start()
15186 } else {
15187 *close.end()
15188 },
15189 );
15190 }
15191
15192 if let Some(destination) = best_destination {
15193 selection.collapse_to(destination, SelectionGoal::None);
15194 }
15195 })
15196 });
15197 }
15198
15199 pub fn undo_selection(
15200 &mut self,
15201 _: &UndoSelection,
15202 window: &mut Window,
15203 cx: &mut Context<Self>,
15204 ) {
15205 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15206 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15207 self.selection_history.mode = SelectionHistoryMode::Undoing;
15208 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15209 this.end_selection(window, cx);
15210 this.change_selections(
15211 SelectionEffects::scroll(Autoscroll::newest()),
15212 window,
15213 cx,
15214 |s| s.select_anchors(entry.selections.to_vec()),
15215 );
15216 });
15217 self.selection_history.mode = SelectionHistoryMode::Normal;
15218
15219 self.select_next_state = entry.select_next_state;
15220 self.select_prev_state = entry.select_prev_state;
15221 self.add_selections_state = entry.add_selections_state;
15222 }
15223 }
15224
15225 pub fn redo_selection(
15226 &mut self,
15227 _: &RedoSelection,
15228 window: &mut Window,
15229 cx: &mut Context<Self>,
15230 ) {
15231 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15232 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15233 self.selection_history.mode = SelectionHistoryMode::Redoing;
15234 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15235 this.end_selection(window, cx);
15236 this.change_selections(
15237 SelectionEffects::scroll(Autoscroll::newest()),
15238 window,
15239 cx,
15240 |s| s.select_anchors(entry.selections.to_vec()),
15241 );
15242 });
15243 self.selection_history.mode = SelectionHistoryMode::Normal;
15244
15245 self.select_next_state = entry.select_next_state;
15246 self.select_prev_state = entry.select_prev_state;
15247 self.add_selections_state = entry.add_selections_state;
15248 }
15249 }
15250
15251 pub fn expand_excerpts(
15252 &mut self,
15253 action: &ExpandExcerpts,
15254 _: &mut Window,
15255 cx: &mut Context<Self>,
15256 ) {
15257 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15258 }
15259
15260 pub fn expand_excerpts_down(
15261 &mut self,
15262 action: &ExpandExcerptsDown,
15263 _: &mut Window,
15264 cx: &mut Context<Self>,
15265 ) {
15266 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15267 }
15268
15269 pub fn expand_excerpts_up(
15270 &mut self,
15271 action: &ExpandExcerptsUp,
15272 _: &mut Window,
15273 cx: &mut Context<Self>,
15274 ) {
15275 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15276 }
15277
15278 pub fn expand_excerpts_for_direction(
15279 &mut self,
15280 lines: u32,
15281 direction: ExpandExcerptDirection,
15282
15283 cx: &mut Context<Self>,
15284 ) {
15285 let selections = self.selections.disjoint_anchors();
15286
15287 let lines = if lines == 0 {
15288 EditorSettings::get_global(cx).expand_excerpt_lines
15289 } else {
15290 lines
15291 };
15292
15293 self.buffer.update(cx, |buffer, cx| {
15294 let snapshot = buffer.snapshot(cx);
15295 let mut excerpt_ids = selections
15296 .iter()
15297 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15298 .collect::<Vec<_>>();
15299 excerpt_ids.sort();
15300 excerpt_ids.dedup();
15301 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15302 })
15303 }
15304
15305 pub fn expand_excerpt(
15306 &mut self,
15307 excerpt: ExcerptId,
15308 direction: ExpandExcerptDirection,
15309 window: &mut Window,
15310 cx: &mut Context<Self>,
15311 ) {
15312 let current_scroll_position = self.scroll_position(cx);
15313 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15314 let mut should_scroll_up = false;
15315
15316 if direction == ExpandExcerptDirection::Down {
15317 let multi_buffer = self.buffer.read(cx);
15318 let snapshot = multi_buffer.snapshot(cx);
15319 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15320 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15321 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15322 let buffer_snapshot = buffer.read(cx).snapshot();
15323 let excerpt_end_row =
15324 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15325 let last_row = buffer_snapshot.max_point().row;
15326 let lines_below = last_row.saturating_sub(excerpt_end_row);
15327 should_scroll_up = lines_below >= lines_to_expand;
15328 }
15329 }
15330 }
15331 }
15332
15333 self.buffer.update(cx, |buffer, cx| {
15334 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15335 });
15336
15337 if should_scroll_up {
15338 let new_scroll_position =
15339 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15340 self.set_scroll_position(new_scroll_position, window, cx);
15341 }
15342 }
15343
15344 pub fn go_to_singleton_buffer_point(
15345 &mut self,
15346 point: Point,
15347 window: &mut Window,
15348 cx: &mut Context<Self>,
15349 ) {
15350 self.go_to_singleton_buffer_range(point..point, window, cx);
15351 }
15352
15353 pub fn go_to_singleton_buffer_range(
15354 &mut self,
15355 range: Range<Point>,
15356 window: &mut Window,
15357 cx: &mut Context<Self>,
15358 ) {
15359 let multibuffer = self.buffer().read(cx);
15360 let Some(buffer) = multibuffer.as_singleton() else {
15361 return;
15362 };
15363 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15364 return;
15365 };
15366 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15367 return;
15368 };
15369 self.change_selections(
15370 SelectionEffects::default().nav_history(true),
15371 window,
15372 cx,
15373 |s| s.select_anchor_ranges([start..end]),
15374 );
15375 }
15376
15377 pub fn go_to_diagnostic(
15378 &mut self,
15379 action: &GoToDiagnostic,
15380 window: &mut Window,
15381 cx: &mut Context<Self>,
15382 ) {
15383 if !self.diagnostics_enabled() {
15384 return;
15385 }
15386 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15387 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15388 }
15389
15390 pub fn go_to_prev_diagnostic(
15391 &mut self,
15392 action: &GoToPreviousDiagnostic,
15393 window: &mut Window,
15394 cx: &mut Context<Self>,
15395 ) {
15396 if !self.diagnostics_enabled() {
15397 return;
15398 }
15399 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15400 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15401 }
15402
15403 pub fn go_to_diagnostic_impl(
15404 &mut self,
15405 direction: Direction,
15406 severity: GoToDiagnosticSeverityFilter,
15407 window: &mut Window,
15408 cx: &mut Context<Self>,
15409 ) {
15410 let buffer = self.buffer.read(cx).snapshot(cx);
15411 let selection = self.selections.newest::<usize>(cx);
15412
15413 let mut active_group_id = None;
15414 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15415 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15416 active_group_id = Some(active_group.group_id);
15417 }
15418 }
15419
15420 fn filtered(
15421 snapshot: EditorSnapshot,
15422 severity: GoToDiagnosticSeverityFilter,
15423 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15424 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15425 diagnostics
15426 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15427 .filter(|entry| entry.range.start != entry.range.end)
15428 .filter(|entry| !entry.diagnostic.is_unnecessary)
15429 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15430 }
15431
15432 let snapshot = self.snapshot(window, cx);
15433 let before = filtered(
15434 snapshot.clone(),
15435 severity,
15436 buffer
15437 .diagnostics_in_range(0..selection.start)
15438 .filter(|entry| entry.range.start <= selection.start),
15439 );
15440 let after = filtered(
15441 snapshot,
15442 severity,
15443 buffer
15444 .diagnostics_in_range(selection.start..buffer.len())
15445 .filter(|entry| entry.range.start >= selection.start),
15446 );
15447
15448 let mut found: Option<DiagnosticEntry<usize>> = None;
15449 if direction == Direction::Prev {
15450 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15451 {
15452 for diagnostic in prev_diagnostics.into_iter().rev() {
15453 if diagnostic.range.start != selection.start
15454 || active_group_id
15455 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15456 {
15457 found = Some(diagnostic);
15458 break 'outer;
15459 }
15460 }
15461 }
15462 } else {
15463 for diagnostic in after.chain(before) {
15464 if diagnostic.range.start != selection.start
15465 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15466 {
15467 found = Some(diagnostic);
15468 break;
15469 }
15470 }
15471 }
15472 let Some(next_diagnostic) = found else {
15473 return;
15474 };
15475
15476 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15477 return;
15478 };
15479 self.change_selections(Default::default(), window, cx, |s| {
15480 s.select_ranges(vec![
15481 next_diagnostic.range.start..next_diagnostic.range.start,
15482 ])
15483 });
15484 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15485 self.refresh_edit_prediction(false, true, window, cx);
15486 }
15487
15488 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15489 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15490 let snapshot = self.snapshot(window, cx);
15491 let selection = self.selections.newest::<Point>(cx);
15492 self.go_to_hunk_before_or_after_position(
15493 &snapshot,
15494 selection.head(),
15495 Direction::Next,
15496 window,
15497 cx,
15498 );
15499 }
15500
15501 pub fn go_to_hunk_before_or_after_position(
15502 &mut self,
15503 snapshot: &EditorSnapshot,
15504 position: Point,
15505 direction: Direction,
15506 window: &mut Window,
15507 cx: &mut Context<Editor>,
15508 ) {
15509 let row = if direction == Direction::Next {
15510 self.hunk_after_position(snapshot, position)
15511 .map(|hunk| hunk.row_range.start)
15512 } else {
15513 self.hunk_before_position(snapshot, position)
15514 };
15515
15516 if let Some(row) = row {
15517 let destination = Point::new(row.0, 0);
15518 let autoscroll = Autoscroll::center();
15519
15520 self.unfold_ranges(&[destination..destination], false, false, cx);
15521 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15522 s.select_ranges([destination..destination]);
15523 });
15524 }
15525 }
15526
15527 fn hunk_after_position(
15528 &mut self,
15529 snapshot: &EditorSnapshot,
15530 position: Point,
15531 ) -> Option<MultiBufferDiffHunk> {
15532 snapshot
15533 .buffer_snapshot
15534 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15535 .find(|hunk| hunk.row_range.start.0 > position.row)
15536 .or_else(|| {
15537 snapshot
15538 .buffer_snapshot
15539 .diff_hunks_in_range(Point::zero()..position)
15540 .find(|hunk| hunk.row_range.end.0 < position.row)
15541 })
15542 }
15543
15544 fn go_to_prev_hunk(
15545 &mut self,
15546 _: &GoToPreviousHunk,
15547 window: &mut Window,
15548 cx: &mut Context<Self>,
15549 ) {
15550 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15551 let snapshot = self.snapshot(window, cx);
15552 let selection = self.selections.newest::<Point>(cx);
15553 self.go_to_hunk_before_or_after_position(
15554 &snapshot,
15555 selection.head(),
15556 Direction::Prev,
15557 window,
15558 cx,
15559 );
15560 }
15561
15562 fn hunk_before_position(
15563 &mut self,
15564 snapshot: &EditorSnapshot,
15565 position: Point,
15566 ) -> Option<MultiBufferRow> {
15567 snapshot
15568 .buffer_snapshot
15569 .diff_hunk_before(position)
15570 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15571 }
15572
15573 fn go_to_next_change(
15574 &mut self,
15575 _: &GoToNextChange,
15576 window: &mut Window,
15577 cx: &mut Context<Self>,
15578 ) {
15579 if let Some(selections) = self
15580 .change_list
15581 .next_change(1, Direction::Next)
15582 .map(|s| s.to_vec())
15583 {
15584 self.change_selections(Default::default(), window, cx, |s| {
15585 let map = s.display_map();
15586 s.select_display_ranges(selections.iter().map(|a| {
15587 let point = a.to_display_point(&map);
15588 point..point
15589 }))
15590 })
15591 }
15592 }
15593
15594 fn go_to_previous_change(
15595 &mut self,
15596 _: &GoToPreviousChange,
15597 window: &mut Window,
15598 cx: &mut Context<Self>,
15599 ) {
15600 if let Some(selections) = self
15601 .change_list
15602 .next_change(1, Direction::Prev)
15603 .map(|s| s.to_vec())
15604 {
15605 self.change_selections(Default::default(), window, cx, |s| {
15606 let map = s.display_map();
15607 s.select_display_ranges(selections.iter().map(|a| {
15608 let point = a.to_display_point(&map);
15609 point..point
15610 }))
15611 })
15612 }
15613 }
15614
15615 fn go_to_line<T: 'static>(
15616 &mut self,
15617 position: Anchor,
15618 highlight_color: Option<Hsla>,
15619 window: &mut Window,
15620 cx: &mut Context<Self>,
15621 ) {
15622 let snapshot = self.snapshot(window, cx).display_snapshot;
15623 let position = position.to_point(&snapshot.buffer_snapshot);
15624 let start = snapshot
15625 .buffer_snapshot
15626 .clip_point(Point::new(position.row, 0), Bias::Left);
15627 let end = start + Point::new(1, 0);
15628 let start = snapshot.buffer_snapshot.anchor_before(start);
15629 let end = snapshot.buffer_snapshot.anchor_before(end);
15630
15631 self.highlight_rows::<T>(
15632 start..end,
15633 highlight_color
15634 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15635 Default::default(),
15636 cx,
15637 );
15638
15639 if self.buffer.read(cx).is_singleton() {
15640 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15641 }
15642 }
15643
15644 pub fn go_to_definition(
15645 &mut self,
15646 _: &GoToDefinition,
15647 window: &mut Window,
15648 cx: &mut Context<Self>,
15649 ) -> Task<Result<Navigated>> {
15650 let definition =
15651 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15652 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15653 cx.spawn_in(window, async move |editor, cx| {
15654 if definition.await? == Navigated::Yes {
15655 return Ok(Navigated::Yes);
15656 }
15657 match fallback_strategy {
15658 GoToDefinitionFallback::None => Ok(Navigated::No),
15659 GoToDefinitionFallback::FindAllReferences => {
15660 match editor.update_in(cx, |editor, window, cx| {
15661 editor.find_all_references(&FindAllReferences, window, cx)
15662 })? {
15663 Some(references) => references.await,
15664 None => Ok(Navigated::No),
15665 }
15666 }
15667 }
15668 })
15669 }
15670
15671 pub fn go_to_declaration(
15672 &mut self,
15673 _: &GoToDeclaration,
15674 window: &mut Window,
15675 cx: &mut Context<Self>,
15676 ) -> Task<Result<Navigated>> {
15677 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15678 }
15679
15680 pub fn go_to_declaration_split(
15681 &mut self,
15682 _: &GoToDeclaration,
15683 window: &mut Window,
15684 cx: &mut Context<Self>,
15685 ) -> Task<Result<Navigated>> {
15686 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15687 }
15688
15689 pub fn go_to_implementation(
15690 &mut self,
15691 _: &GoToImplementation,
15692 window: &mut Window,
15693 cx: &mut Context<Self>,
15694 ) -> Task<Result<Navigated>> {
15695 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15696 }
15697
15698 pub fn go_to_implementation_split(
15699 &mut self,
15700 _: &GoToImplementationSplit,
15701 window: &mut Window,
15702 cx: &mut Context<Self>,
15703 ) -> Task<Result<Navigated>> {
15704 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15705 }
15706
15707 pub fn go_to_type_definition(
15708 &mut self,
15709 _: &GoToTypeDefinition,
15710 window: &mut Window,
15711 cx: &mut Context<Self>,
15712 ) -> Task<Result<Navigated>> {
15713 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15714 }
15715
15716 pub fn go_to_definition_split(
15717 &mut self,
15718 _: &GoToDefinitionSplit,
15719 window: &mut Window,
15720 cx: &mut Context<Self>,
15721 ) -> Task<Result<Navigated>> {
15722 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15723 }
15724
15725 pub fn go_to_type_definition_split(
15726 &mut self,
15727 _: &GoToTypeDefinitionSplit,
15728 window: &mut Window,
15729 cx: &mut Context<Self>,
15730 ) -> Task<Result<Navigated>> {
15731 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15732 }
15733
15734 fn go_to_definition_of_kind(
15735 &mut self,
15736 kind: GotoDefinitionKind,
15737 split: bool,
15738 window: &mut Window,
15739 cx: &mut Context<Self>,
15740 ) -> Task<Result<Navigated>> {
15741 let Some(provider) = self.semantics_provider.clone() else {
15742 return Task::ready(Ok(Navigated::No));
15743 };
15744 let head = self.selections.newest::<usize>(cx).head();
15745 let buffer = self.buffer.read(cx);
15746 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15747 return Task::ready(Ok(Navigated::No));
15748 };
15749 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15750 return Task::ready(Ok(Navigated::No));
15751 };
15752
15753 cx.spawn_in(window, async move |editor, cx| {
15754 let definitions = definitions.await?;
15755 let navigated = editor
15756 .update_in(cx, |editor, window, cx| {
15757 editor.navigate_to_hover_links(
15758 Some(kind),
15759 definitions
15760 .into_iter()
15761 .filter(|location| {
15762 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15763 })
15764 .map(HoverLink::Text)
15765 .collect::<Vec<_>>(),
15766 split,
15767 window,
15768 cx,
15769 )
15770 })?
15771 .await?;
15772 anyhow::Ok(navigated)
15773 })
15774 }
15775
15776 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15777 let selection = self.selections.newest_anchor();
15778 let head = selection.head();
15779 let tail = selection.tail();
15780
15781 let Some((buffer, start_position)) =
15782 self.buffer.read(cx).text_anchor_for_position(head, cx)
15783 else {
15784 return;
15785 };
15786
15787 let end_position = if head != tail {
15788 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15789 return;
15790 };
15791 Some(pos)
15792 } else {
15793 None
15794 };
15795
15796 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15797 let url = if let Some(end_pos) = end_position {
15798 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15799 } else {
15800 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15801 };
15802
15803 if let Some(url) = url {
15804 editor.update(cx, |_, cx| {
15805 cx.open_url(&url);
15806 })
15807 } else {
15808 Ok(())
15809 }
15810 });
15811
15812 url_finder.detach();
15813 }
15814
15815 pub fn open_selected_filename(
15816 &mut self,
15817 _: &OpenSelectedFilename,
15818 window: &mut Window,
15819 cx: &mut Context<Self>,
15820 ) {
15821 let Some(workspace) = self.workspace() else {
15822 return;
15823 };
15824
15825 let position = self.selections.newest_anchor().head();
15826
15827 let Some((buffer, buffer_position)) =
15828 self.buffer.read(cx).text_anchor_for_position(position, cx)
15829 else {
15830 return;
15831 };
15832
15833 let project = self.project.clone();
15834
15835 cx.spawn_in(window, async move |_, cx| {
15836 let result = find_file(&buffer, project, buffer_position, cx).await;
15837
15838 if let Some((_, path)) = result {
15839 workspace
15840 .update_in(cx, |workspace, window, cx| {
15841 workspace.open_resolved_path(path, window, cx)
15842 })?
15843 .await?;
15844 }
15845 anyhow::Ok(())
15846 })
15847 .detach();
15848 }
15849
15850 pub(crate) fn navigate_to_hover_links(
15851 &mut self,
15852 kind: Option<GotoDefinitionKind>,
15853 definitions: Vec<HoverLink>,
15854 split: bool,
15855 window: &mut Window,
15856 cx: &mut Context<Editor>,
15857 ) -> Task<Result<Navigated>> {
15858 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15859 let mut first_url_or_file = None;
15860 let definitions: Vec<_> = definitions
15861 .into_iter()
15862 .filter_map(|def| match def {
15863 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15864 HoverLink::InlayHint(lsp_location, server_id) => {
15865 let computation =
15866 self.compute_target_location(lsp_location, server_id, window, cx);
15867 Some(cx.background_spawn(computation))
15868 }
15869 HoverLink::Url(url) => {
15870 first_url_or_file = Some(Either::Left(url));
15871 None
15872 }
15873 HoverLink::File(path) => {
15874 first_url_or_file = Some(Either::Right(path));
15875 None
15876 }
15877 })
15878 .collect();
15879
15880 let workspace = self.workspace();
15881
15882 cx.spawn_in(window, async move |editor, acx| {
15883 let mut locations: Vec<Location> = future::join_all(definitions)
15884 .await
15885 .into_iter()
15886 .filter_map(|location| location.transpose())
15887 .collect::<Result<_>>()
15888 .context("location tasks")?;
15889
15890 if locations.len() > 1 {
15891 let Some(workspace) = workspace else {
15892 return Ok(Navigated::No);
15893 };
15894
15895 let tab_kind = match kind {
15896 Some(GotoDefinitionKind::Implementation) => "Implementations",
15897 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15898 Some(GotoDefinitionKind::Declaration) => "Declarations",
15899 Some(GotoDefinitionKind::Type) => "Types",
15900 };
15901 let title = editor
15902 .update_in(acx, |_, _, cx| {
15903 let target = locations
15904 .iter()
15905 .map(|location| {
15906 location
15907 .buffer
15908 .read(cx)
15909 .text_for_range(location.range.clone())
15910 .collect::<String>()
15911 })
15912 .filter(|text| !text.contains('\n'))
15913 .unique()
15914 .take(3)
15915 .join(", ");
15916 if target.is_empty() {
15917 tab_kind.to_owned()
15918 } else {
15919 format!("{tab_kind} for {target}")
15920 }
15921 })
15922 .context("buffer title")?;
15923
15924 let opened = workspace
15925 .update_in(acx, |workspace, window, cx| {
15926 Self::open_locations_in_multibuffer(
15927 workspace,
15928 locations,
15929 title,
15930 split,
15931 MultibufferSelectionMode::First,
15932 window,
15933 cx,
15934 )
15935 })
15936 .is_ok();
15937
15938 anyhow::Ok(Navigated::from_bool(opened))
15939 } else if locations.is_empty() {
15940 // If there is one definition, just open it directly
15941 match first_url_or_file {
15942 Some(Either::Left(url)) => {
15943 acx.update(|_, cx| cx.open_url(&url))?;
15944 Ok(Navigated::Yes)
15945 }
15946 Some(Either::Right(path)) => {
15947 let Some(workspace) = workspace else {
15948 return Ok(Navigated::No);
15949 };
15950
15951 workspace
15952 .update_in(acx, |workspace, window, cx| {
15953 workspace.open_resolved_path(path, window, cx)
15954 })?
15955 .await?;
15956 Ok(Navigated::Yes)
15957 }
15958 None => Ok(Navigated::No),
15959 }
15960 } else {
15961 let Some(workspace) = workspace else {
15962 return Ok(Navigated::No);
15963 };
15964
15965 let target = locations.pop().unwrap();
15966 editor.update_in(acx, |editor, window, cx| {
15967 let pane = workspace.read(cx).active_pane().clone();
15968
15969 let range = target.range.to_point(target.buffer.read(cx));
15970 let range = editor.range_for_match(&range);
15971 let range = collapse_multiline_range(range);
15972
15973 if !split
15974 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15975 {
15976 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15977 } else {
15978 window.defer(cx, move |window, cx| {
15979 let target_editor: Entity<Self> =
15980 workspace.update(cx, |workspace, cx| {
15981 let pane = if split {
15982 workspace.adjacent_pane(window, cx)
15983 } else {
15984 workspace.active_pane().clone()
15985 };
15986
15987 workspace.open_project_item(
15988 pane,
15989 target.buffer.clone(),
15990 true,
15991 true,
15992 window,
15993 cx,
15994 )
15995 });
15996 target_editor.update(cx, |target_editor, cx| {
15997 // When selecting a definition in a different buffer, disable the nav history
15998 // to avoid creating a history entry at the previous cursor location.
15999 pane.update(cx, |pane, _| pane.disable_history());
16000 target_editor.go_to_singleton_buffer_range(range, window, cx);
16001 pane.update(cx, |pane, _| pane.enable_history());
16002 });
16003 });
16004 }
16005 Navigated::Yes
16006 })
16007 }
16008 })
16009 }
16010
16011 fn compute_target_location(
16012 &self,
16013 lsp_location: lsp::Location,
16014 server_id: LanguageServerId,
16015 window: &mut Window,
16016 cx: &mut Context<Self>,
16017 ) -> Task<anyhow::Result<Option<Location>>> {
16018 let Some(project) = self.project.clone() else {
16019 return Task::ready(Ok(None));
16020 };
16021
16022 cx.spawn_in(window, async move |editor, cx| {
16023 let location_task = editor.update(cx, |_, cx| {
16024 project.update(cx, |project, cx| {
16025 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16026 })
16027 })?;
16028 let location = Some({
16029 let target_buffer_handle = location_task.await.context("open local buffer")?;
16030 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16031 let target_start = target_buffer
16032 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16033 let target_end = target_buffer
16034 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16035 target_buffer.anchor_after(target_start)
16036 ..target_buffer.anchor_before(target_end)
16037 })?;
16038 Location {
16039 buffer: target_buffer_handle,
16040 range,
16041 }
16042 });
16043 Ok(location)
16044 })
16045 }
16046
16047 pub fn find_all_references(
16048 &mut self,
16049 _: &FindAllReferences,
16050 window: &mut Window,
16051 cx: &mut Context<Self>,
16052 ) -> Option<Task<Result<Navigated>>> {
16053 let selection = self.selections.newest::<usize>(cx);
16054 let multi_buffer = self.buffer.read(cx);
16055 let head = selection.head();
16056
16057 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16058 let head_anchor = multi_buffer_snapshot.anchor_at(
16059 head,
16060 if head < selection.tail() {
16061 Bias::Right
16062 } else {
16063 Bias::Left
16064 },
16065 );
16066
16067 match self
16068 .find_all_references_task_sources
16069 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16070 {
16071 Ok(_) => {
16072 log::info!(
16073 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16074 );
16075 return None;
16076 }
16077 Err(i) => {
16078 self.find_all_references_task_sources.insert(i, head_anchor);
16079 }
16080 }
16081
16082 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16083 let workspace = self.workspace()?;
16084 let project = workspace.read(cx).project().clone();
16085 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16086 Some(cx.spawn_in(window, async move |editor, cx| {
16087 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16088 if let Ok(i) = editor
16089 .find_all_references_task_sources
16090 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16091 {
16092 editor.find_all_references_task_sources.remove(i);
16093 }
16094 });
16095
16096 let locations = references.await?;
16097 if locations.is_empty() {
16098 return anyhow::Ok(Navigated::No);
16099 }
16100
16101 workspace.update_in(cx, |workspace, window, cx| {
16102 let target = locations
16103 .iter()
16104 .map(|location| {
16105 location
16106 .buffer
16107 .read(cx)
16108 .text_for_range(location.range.clone())
16109 .collect::<String>()
16110 })
16111 .filter(|text| !text.contains('\n'))
16112 .unique()
16113 .take(3)
16114 .join(", ");
16115 let title = if target.is_empty() {
16116 "References".to_owned()
16117 } else {
16118 format!("References to {target}")
16119 };
16120 Self::open_locations_in_multibuffer(
16121 workspace,
16122 locations,
16123 title,
16124 false,
16125 MultibufferSelectionMode::First,
16126 window,
16127 cx,
16128 );
16129 Navigated::Yes
16130 })
16131 }))
16132 }
16133
16134 /// Opens a multibuffer with the given project locations in it
16135 pub fn open_locations_in_multibuffer(
16136 workspace: &mut Workspace,
16137 mut locations: Vec<Location>,
16138 title: String,
16139 split: bool,
16140 multibuffer_selection_mode: MultibufferSelectionMode,
16141 window: &mut Window,
16142 cx: &mut Context<Workspace>,
16143 ) {
16144 if locations.is_empty() {
16145 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16146 return;
16147 }
16148
16149 // If there are multiple definitions, open them in a multibuffer
16150 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16151 let mut locations = locations.into_iter().peekable();
16152 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16153 let capability = workspace.project().read(cx).capability();
16154
16155 let excerpt_buffer = cx.new(|cx| {
16156 let mut multibuffer = MultiBuffer::new(capability);
16157 while let Some(location) = locations.next() {
16158 let buffer = location.buffer.read(cx);
16159 let mut ranges_for_buffer = Vec::new();
16160 let range = location.range.to_point(buffer);
16161 ranges_for_buffer.push(range.clone());
16162
16163 while let Some(next_location) = locations.peek() {
16164 if next_location.buffer == location.buffer {
16165 ranges_for_buffer.push(next_location.range.to_point(buffer));
16166 locations.next();
16167 } else {
16168 break;
16169 }
16170 }
16171
16172 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16173 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16174 PathKey::for_buffer(&location.buffer, cx),
16175 location.buffer.clone(),
16176 ranges_for_buffer,
16177 DEFAULT_MULTIBUFFER_CONTEXT,
16178 cx,
16179 );
16180 ranges.extend(new_ranges)
16181 }
16182
16183 multibuffer.with_title(title)
16184 });
16185
16186 let editor = cx.new(|cx| {
16187 Editor::for_multibuffer(
16188 excerpt_buffer,
16189 Some(workspace.project().clone()),
16190 window,
16191 cx,
16192 )
16193 });
16194 editor.update(cx, |editor, cx| {
16195 match multibuffer_selection_mode {
16196 MultibufferSelectionMode::First => {
16197 if let Some(first_range) = ranges.first() {
16198 editor.change_selections(
16199 SelectionEffects::no_scroll(),
16200 window,
16201 cx,
16202 |selections| {
16203 selections.clear_disjoint();
16204 selections
16205 .select_anchor_ranges(std::iter::once(first_range.clone()));
16206 },
16207 );
16208 }
16209 editor.highlight_background::<Self>(
16210 &ranges,
16211 |theme| theme.colors().editor_highlighted_line_background,
16212 cx,
16213 );
16214 }
16215 MultibufferSelectionMode::All => {
16216 editor.change_selections(
16217 SelectionEffects::no_scroll(),
16218 window,
16219 cx,
16220 |selections| {
16221 selections.clear_disjoint();
16222 selections.select_anchor_ranges(ranges);
16223 },
16224 );
16225 }
16226 }
16227 editor.register_buffers_with_language_servers(cx);
16228 });
16229
16230 let item = Box::new(editor);
16231 let item_id = item.item_id();
16232
16233 if split {
16234 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16235 } else {
16236 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16237 let (preview_item_id, preview_item_idx) =
16238 workspace.active_pane().read_with(cx, |pane, _| {
16239 (pane.preview_item_id(), pane.preview_item_idx())
16240 });
16241
16242 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16243
16244 if let Some(preview_item_id) = preview_item_id {
16245 workspace.active_pane().update(cx, |pane, cx| {
16246 pane.remove_item(preview_item_id, false, false, window, cx);
16247 });
16248 }
16249 } else {
16250 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16251 }
16252 }
16253 workspace.active_pane().update(cx, |pane, cx| {
16254 pane.set_preview_item_id(Some(item_id), cx);
16255 });
16256 }
16257
16258 pub fn rename(
16259 &mut self,
16260 _: &Rename,
16261 window: &mut Window,
16262 cx: &mut Context<Self>,
16263 ) -> Option<Task<Result<()>>> {
16264 use language::ToOffset as _;
16265
16266 let provider = self.semantics_provider.clone()?;
16267 let selection = self.selections.newest_anchor().clone();
16268 let (cursor_buffer, cursor_buffer_position) = self
16269 .buffer
16270 .read(cx)
16271 .text_anchor_for_position(selection.head(), cx)?;
16272 let (tail_buffer, cursor_buffer_position_end) = self
16273 .buffer
16274 .read(cx)
16275 .text_anchor_for_position(selection.tail(), cx)?;
16276 if tail_buffer != cursor_buffer {
16277 return None;
16278 }
16279
16280 let snapshot = cursor_buffer.read(cx).snapshot();
16281 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16282 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16283 let prepare_rename = provider
16284 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16285 .unwrap_or_else(|| Task::ready(Ok(None)));
16286 drop(snapshot);
16287
16288 Some(cx.spawn_in(window, async move |this, cx| {
16289 let rename_range = if let Some(range) = prepare_rename.await? {
16290 Some(range)
16291 } else {
16292 this.update(cx, |this, cx| {
16293 let buffer = this.buffer.read(cx).snapshot(cx);
16294 let mut buffer_highlights = this
16295 .document_highlights_for_position(selection.head(), &buffer)
16296 .filter(|highlight| {
16297 highlight.start.excerpt_id == selection.head().excerpt_id
16298 && highlight.end.excerpt_id == selection.head().excerpt_id
16299 });
16300 buffer_highlights
16301 .next()
16302 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16303 })?
16304 };
16305 if let Some(rename_range) = rename_range {
16306 this.update_in(cx, |this, window, cx| {
16307 let snapshot = cursor_buffer.read(cx).snapshot();
16308 let rename_buffer_range = rename_range.to_offset(&snapshot);
16309 let cursor_offset_in_rename_range =
16310 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16311 let cursor_offset_in_rename_range_end =
16312 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16313
16314 this.take_rename(false, window, cx);
16315 let buffer = this.buffer.read(cx).read(cx);
16316 let cursor_offset = selection.head().to_offset(&buffer);
16317 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16318 let rename_end = rename_start + rename_buffer_range.len();
16319 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16320 let mut old_highlight_id = None;
16321 let old_name: Arc<str> = buffer
16322 .chunks(rename_start..rename_end, true)
16323 .map(|chunk| {
16324 if old_highlight_id.is_none() {
16325 old_highlight_id = chunk.syntax_highlight_id;
16326 }
16327 chunk.text
16328 })
16329 .collect::<String>()
16330 .into();
16331
16332 drop(buffer);
16333
16334 // Position the selection in the rename editor so that it matches the current selection.
16335 this.show_local_selections = false;
16336 let rename_editor = cx.new(|cx| {
16337 let mut editor = Editor::single_line(window, cx);
16338 editor.buffer.update(cx, |buffer, cx| {
16339 buffer.edit([(0..0, old_name.clone())], None, cx)
16340 });
16341 let rename_selection_range = match cursor_offset_in_rename_range
16342 .cmp(&cursor_offset_in_rename_range_end)
16343 {
16344 Ordering::Equal => {
16345 editor.select_all(&SelectAll, window, cx);
16346 return editor;
16347 }
16348 Ordering::Less => {
16349 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16350 }
16351 Ordering::Greater => {
16352 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16353 }
16354 };
16355 if rename_selection_range.end > old_name.len() {
16356 editor.select_all(&SelectAll, window, cx);
16357 } else {
16358 editor.change_selections(Default::default(), window, cx, |s| {
16359 s.select_ranges([rename_selection_range]);
16360 });
16361 }
16362 editor
16363 });
16364 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16365 if e == &EditorEvent::Focused {
16366 cx.emit(EditorEvent::FocusedIn)
16367 }
16368 })
16369 .detach();
16370
16371 let write_highlights =
16372 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16373 let read_highlights =
16374 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16375 let ranges = write_highlights
16376 .iter()
16377 .flat_map(|(_, ranges)| ranges.iter())
16378 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16379 .cloned()
16380 .collect();
16381
16382 this.highlight_text::<Rename>(
16383 ranges,
16384 HighlightStyle {
16385 fade_out: Some(0.6),
16386 ..Default::default()
16387 },
16388 cx,
16389 );
16390 let rename_focus_handle = rename_editor.focus_handle(cx);
16391 window.focus(&rename_focus_handle);
16392 let block_id = this.insert_blocks(
16393 [BlockProperties {
16394 style: BlockStyle::Flex,
16395 placement: BlockPlacement::Below(range.start),
16396 height: Some(1),
16397 render: Arc::new({
16398 let rename_editor = rename_editor.clone();
16399 move |cx: &mut BlockContext| {
16400 let mut text_style = cx.editor_style.text.clone();
16401 if let Some(highlight_style) = old_highlight_id
16402 .and_then(|h| h.style(&cx.editor_style.syntax))
16403 {
16404 text_style = text_style.highlight(highlight_style);
16405 }
16406 div()
16407 .block_mouse_except_scroll()
16408 .pl(cx.anchor_x)
16409 .child(EditorElement::new(
16410 &rename_editor,
16411 EditorStyle {
16412 background: cx.theme().system().transparent,
16413 local_player: cx.editor_style.local_player,
16414 text: text_style,
16415 scrollbar_width: cx.editor_style.scrollbar_width,
16416 syntax: cx.editor_style.syntax.clone(),
16417 status: cx.editor_style.status.clone(),
16418 inlay_hints_style: HighlightStyle {
16419 font_weight: Some(FontWeight::BOLD),
16420 ..make_inlay_hints_style(cx.app)
16421 },
16422 edit_prediction_styles: make_suggestion_styles(
16423 cx.app,
16424 ),
16425 ..EditorStyle::default()
16426 },
16427 ))
16428 .into_any_element()
16429 }
16430 }),
16431 priority: 0,
16432 }],
16433 Some(Autoscroll::fit()),
16434 cx,
16435 )[0];
16436 this.pending_rename = Some(RenameState {
16437 range,
16438 old_name,
16439 editor: rename_editor,
16440 block_id,
16441 });
16442 })?;
16443 }
16444
16445 Ok(())
16446 }))
16447 }
16448
16449 pub fn confirm_rename(
16450 &mut self,
16451 _: &ConfirmRename,
16452 window: &mut Window,
16453 cx: &mut Context<Self>,
16454 ) -> Option<Task<Result<()>>> {
16455 let rename = self.take_rename(false, window, cx)?;
16456 let workspace = self.workspace()?.downgrade();
16457 let (buffer, start) = self
16458 .buffer
16459 .read(cx)
16460 .text_anchor_for_position(rename.range.start, cx)?;
16461 let (end_buffer, _) = self
16462 .buffer
16463 .read(cx)
16464 .text_anchor_for_position(rename.range.end, cx)?;
16465 if buffer != end_buffer {
16466 return None;
16467 }
16468
16469 let old_name = rename.old_name;
16470 let new_name = rename.editor.read(cx).text(cx);
16471
16472 let rename = self.semantics_provider.as_ref()?.perform_rename(
16473 &buffer,
16474 start,
16475 new_name.clone(),
16476 cx,
16477 )?;
16478
16479 Some(cx.spawn_in(window, async move |editor, cx| {
16480 let project_transaction = rename.await?;
16481 Self::open_project_transaction(
16482 &editor,
16483 workspace,
16484 project_transaction,
16485 format!("Rename: {} → {}", old_name, new_name),
16486 cx,
16487 )
16488 .await?;
16489
16490 editor.update(cx, |editor, cx| {
16491 editor.refresh_document_highlights(cx);
16492 })?;
16493 Ok(())
16494 }))
16495 }
16496
16497 fn take_rename(
16498 &mut self,
16499 moving_cursor: bool,
16500 window: &mut Window,
16501 cx: &mut Context<Self>,
16502 ) -> Option<RenameState> {
16503 let rename = self.pending_rename.take()?;
16504 if rename.editor.focus_handle(cx).is_focused(window) {
16505 window.focus(&self.focus_handle);
16506 }
16507
16508 self.remove_blocks(
16509 [rename.block_id].into_iter().collect(),
16510 Some(Autoscroll::fit()),
16511 cx,
16512 );
16513 self.clear_highlights::<Rename>(cx);
16514 self.show_local_selections = true;
16515
16516 if moving_cursor {
16517 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16518 editor.selections.newest::<usize>(cx).head()
16519 });
16520
16521 // Update the selection to match the position of the selection inside
16522 // the rename editor.
16523 let snapshot = self.buffer.read(cx).read(cx);
16524 let rename_range = rename.range.to_offset(&snapshot);
16525 let cursor_in_editor = snapshot
16526 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16527 .min(rename_range.end);
16528 drop(snapshot);
16529
16530 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16531 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16532 });
16533 } else {
16534 self.refresh_document_highlights(cx);
16535 }
16536
16537 Some(rename)
16538 }
16539
16540 pub fn pending_rename(&self) -> Option<&RenameState> {
16541 self.pending_rename.as_ref()
16542 }
16543
16544 fn format(
16545 &mut self,
16546 _: &Format,
16547 window: &mut Window,
16548 cx: &mut Context<Self>,
16549 ) -> Option<Task<Result<()>>> {
16550 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16551
16552 let project = match &self.project {
16553 Some(project) => project.clone(),
16554 None => return None,
16555 };
16556
16557 Some(self.perform_format(
16558 project,
16559 FormatTrigger::Manual,
16560 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16561 window,
16562 cx,
16563 ))
16564 }
16565
16566 fn format_selections(
16567 &mut self,
16568 _: &FormatSelections,
16569 window: &mut Window,
16570 cx: &mut Context<Self>,
16571 ) -> Option<Task<Result<()>>> {
16572 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16573
16574 let project = match &self.project {
16575 Some(project) => project.clone(),
16576 None => return None,
16577 };
16578
16579 let ranges = self
16580 .selections
16581 .all_adjusted(cx)
16582 .into_iter()
16583 .map(|selection| selection.range())
16584 .collect_vec();
16585
16586 Some(self.perform_format(
16587 project,
16588 FormatTrigger::Manual,
16589 FormatTarget::Ranges(ranges),
16590 window,
16591 cx,
16592 ))
16593 }
16594
16595 fn perform_format(
16596 &mut self,
16597 project: Entity<Project>,
16598 trigger: FormatTrigger,
16599 target: FormatTarget,
16600 window: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) -> Task<Result<()>> {
16603 let buffer = self.buffer.clone();
16604 let (buffers, target) = match target {
16605 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16606 FormatTarget::Ranges(selection_ranges) => {
16607 let multi_buffer = buffer.read(cx);
16608 let snapshot = multi_buffer.read(cx);
16609 let mut buffers = HashSet::default();
16610 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16611 BTreeMap::new();
16612 for selection_range in selection_ranges {
16613 for (buffer, buffer_range, _) in
16614 snapshot.range_to_buffer_ranges(selection_range)
16615 {
16616 let buffer_id = buffer.remote_id();
16617 let start = buffer.anchor_before(buffer_range.start);
16618 let end = buffer.anchor_after(buffer_range.end);
16619 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16620 buffer_id_to_ranges
16621 .entry(buffer_id)
16622 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16623 .or_insert_with(|| vec![start..end]);
16624 }
16625 }
16626 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16627 }
16628 };
16629
16630 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16631 let selections_prev = transaction_id_prev
16632 .and_then(|transaction_id_prev| {
16633 // default to selections as they were after the last edit, if we have them,
16634 // instead of how they are now.
16635 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16636 // will take you back to where you made the last edit, instead of staying where you scrolled
16637 self.selection_history
16638 .transaction(transaction_id_prev)
16639 .map(|t| t.0.clone())
16640 })
16641 .unwrap_or_else(|| {
16642 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16643 self.selections.disjoint_anchors()
16644 });
16645
16646 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16647 let format = project.update(cx, |project, cx| {
16648 project.format(buffers, target, true, trigger, cx)
16649 });
16650
16651 cx.spawn_in(window, async move |editor, cx| {
16652 let transaction = futures::select_biased! {
16653 transaction = format.log_err().fuse() => transaction,
16654 () = timeout => {
16655 log::warn!("timed out waiting for formatting");
16656 None
16657 }
16658 };
16659
16660 buffer
16661 .update(cx, |buffer, cx| {
16662 if let Some(transaction) = transaction {
16663 if !buffer.is_singleton() {
16664 buffer.push_transaction(&transaction.0, cx);
16665 }
16666 }
16667 cx.notify();
16668 })
16669 .ok();
16670
16671 if let Some(transaction_id_now) =
16672 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16673 {
16674 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16675 if has_new_transaction {
16676 _ = editor.update(cx, |editor, _| {
16677 editor
16678 .selection_history
16679 .insert_transaction(transaction_id_now, selections_prev);
16680 });
16681 }
16682 }
16683
16684 Ok(())
16685 })
16686 }
16687
16688 fn organize_imports(
16689 &mut self,
16690 _: &OrganizeImports,
16691 window: &mut Window,
16692 cx: &mut Context<Self>,
16693 ) -> Option<Task<Result<()>>> {
16694 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16695 let project = match &self.project {
16696 Some(project) => project.clone(),
16697 None => return None,
16698 };
16699 Some(self.perform_code_action_kind(
16700 project,
16701 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16702 window,
16703 cx,
16704 ))
16705 }
16706
16707 fn perform_code_action_kind(
16708 &mut self,
16709 project: Entity<Project>,
16710 kind: CodeActionKind,
16711 window: &mut Window,
16712 cx: &mut Context<Self>,
16713 ) -> Task<Result<()>> {
16714 let buffer = self.buffer.clone();
16715 let buffers = buffer.read(cx).all_buffers();
16716 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16717 let apply_action = project.update(cx, |project, cx| {
16718 project.apply_code_action_kind(buffers, kind, true, cx)
16719 });
16720 cx.spawn_in(window, async move |_, cx| {
16721 let transaction = futures::select_biased! {
16722 () = timeout => {
16723 log::warn!("timed out waiting for executing code action");
16724 None
16725 }
16726 transaction = apply_action.log_err().fuse() => transaction,
16727 };
16728 buffer
16729 .update(cx, |buffer, cx| {
16730 // check if we need this
16731 if let Some(transaction) = transaction {
16732 if !buffer.is_singleton() {
16733 buffer.push_transaction(&transaction.0, cx);
16734 }
16735 }
16736 cx.notify();
16737 })
16738 .ok();
16739 Ok(())
16740 })
16741 }
16742
16743 pub fn restart_language_server(
16744 &mut self,
16745 _: &RestartLanguageServer,
16746 _: &mut Window,
16747 cx: &mut Context<Self>,
16748 ) {
16749 if let Some(project) = self.project.clone() {
16750 self.buffer.update(cx, |multi_buffer, cx| {
16751 project.update(cx, |project, cx| {
16752 project.restart_language_servers_for_buffers(
16753 multi_buffer.all_buffers().into_iter().collect(),
16754 HashSet::default(),
16755 cx,
16756 );
16757 });
16758 })
16759 }
16760 }
16761
16762 pub fn stop_language_server(
16763 &mut self,
16764 _: &StopLanguageServer,
16765 _: &mut Window,
16766 cx: &mut Context<Self>,
16767 ) {
16768 if let Some(project) = self.project.clone() {
16769 self.buffer.update(cx, |multi_buffer, cx| {
16770 project.update(cx, |project, cx| {
16771 project.stop_language_servers_for_buffers(
16772 multi_buffer.all_buffers().into_iter().collect(),
16773 HashSet::default(),
16774 cx,
16775 );
16776 cx.emit(project::Event::RefreshInlayHints);
16777 });
16778 });
16779 }
16780 }
16781
16782 fn cancel_language_server_work(
16783 workspace: &mut Workspace,
16784 _: &actions::CancelLanguageServerWork,
16785 _: &mut Window,
16786 cx: &mut Context<Workspace>,
16787 ) {
16788 let project = workspace.project();
16789 let buffers = workspace
16790 .active_item(cx)
16791 .and_then(|item| item.act_as::<Editor>(cx))
16792 .map_or(HashSet::default(), |editor| {
16793 editor.read(cx).buffer.read(cx).all_buffers()
16794 });
16795 project.update(cx, |project, cx| {
16796 project.cancel_language_server_work_for_buffers(buffers, cx);
16797 });
16798 }
16799
16800 fn show_character_palette(
16801 &mut self,
16802 _: &ShowCharacterPalette,
16803 window: &mut Window,
16804 _: &mut Context<Self>,
16805 ) {
16806 window.show_character_palette();
16807 }
16808
16809 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16810 if !self.diagnostics_enabled() {
16811 return;
16812 }
16813
16814 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16815 let buffer = self.buffer.read(cx).snapshot(cx);
16816 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16817 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16818 let is_valid = buffer
16819 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16820 .any(|entry| {
16821 entry.diagnostic.is_primary
16822 && !entry.range.is_empty()
16823 && entry.range.start == primary_range_start
16824 && entry.diagnostic.message == active_diagnostics.active_message
16825 });
16826
16827 if !is_valid {
16828 self.dismiss_diagnostics(cx);
16829 }
16830 }
16831 }
16832
16833 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16834 match &self.active_diagnostics {
16835 ActiveDiagnostic::Group(group) => Some(group),
16836 _ => None,
16837 }
16838 }
16839
16840 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16841 if !self.diagnostics_enabled() {
16842 return;
16843 }
16844 self.dismiss_diagnostics(cx);
16845 self.active_diagnostics = ActiveDiagnostic::All;
16846 }
16847
16848 fn activate_diagnostics(
16849 &mut self,
16850 buffer_id: BufferId,
16851 diagnostic: DiagnosticEntry<usize>,
16852 window: &mut Window,
16853 cx: &mut Context<Self>,
16854 ) {
16855 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16856 return;
16857 }
16858 self.dismiss_diagnostics(cx);
16859 let snapshot = self.snapshot(window, cx);
16860 let buffer = self.buffer.read(cx).snapshot(cx);
16861 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16862 return;
16863 };
16864
16865 let diagnostic_group = buffer
16866 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16867 .collect::<Vec<_>>();
16868
16869 let blocks =
16870 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16871
16872 let blocks = self.display_map.update(cx, |display_map, cx| {
16873 display_map.insert_blocks(blocks, cx).into_iter().collect()
16874 });
16875 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16876 active_range: buffer.anchor_before(diagnostic.range.start)
16877 ..buffer.anchor_after(diagnostic.range.end),
16878 active_message: diagnostic.diagnostic.message.clone(),
16879 group_id: diagnostic.diagnostic.group_id,
16880 blocks,
16881 });
16882 cx.notify();
16883 }
16884
16885 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16886 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16887 return;
16888 };
16889
16890 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16891 if let ActiveDiagnostic::Group(group) = prev {
16892 self.display_map.update(cx, |display_map, cx| {
16893 display_map.remove_blocks(group.blocks, cx);
16894 });
16895 cx.notify();
16896 }
16897 }
16898
16899 /// Disable inline diagnostics rendering for this editor.
16900 pub fn disable_inline_diagnostics(&mut self) {
16901 self.inline_diagnostics_enabled = false;
16902 self.inline_diagnostics_update = Task::ready(());
16903 self.inline_diagnostics.clear();
16904 }
16905
16906 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16907 self.diagnostics_enabled = false;
16908 self.dismiss_diagnostics(cx);
16909 self.inline_diagnostics_update = Task::ready(());
16910 self.inline_diagnostics.clear();
16911 }
16912
16913 pub fn diagnostics_enabled(&self) -> bool {
16914 self.diagnostics_enabled && self.mode.is_full()
16915 }
16916
16917 pub fn inline_diagnostics_enabled(&self) -> bool {
16918 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16919 }
16920
16921 pub fn show_inline_diagnostics(&self) -> bool {
16922 self.show_inline_diagnostics
16923 }
16924
16925 pub fn toggle_inline_diagnostics(
16926 &mut self,
16927 _: &ToggleInlineDiagnostics,
16928 window: &mut Window,
16929 cx: &mut Context<Editor>,
16930 ) {
16931 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16932 self.refresh_inline_diagnostics(false, window, cx);
16933 }
16934
16935 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16936 self.diagnostics_max_severity = severity;
16937 self.display_map.update(cx, |display_map, _| {
16938 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16939 });
16940 }
16941
16942 pub fn toggle_diagnostics(
16943 &mut self,
16944 _: &ToggleDiagnostics,
16945 window: &mut Window,
16946 cx: &mut Context<Editor>,
16947 ) {
16948 if !self.diagnostics_enabled() {
16949 return;
16950 }
16951
16952 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16953 EditorSettings::get_global(cx)
16954 .diagnostics_max_severity
16955 .filter(|severity| severity != &DiagnosticSeverity::Off)
16956 .unwrap_or(DiagnosticSeverity::Hint)
16957 } else {
16958 DiagnosticSeverity::Off
16959 };
16960 self.set_max_diagnostics_severity(new_severity, cx);
16961 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16962 self.active_diagnostics = ActiveDiagnostic::None;
16963 self.inline_diagnostics_update = Task::ready(());
16964 self.inline_diagnostics.clear();
16965 } else {
16966 self.refresh_inline_diagnostics(false, window, cx);
16967 }
16968
16969 cx.notify();
16970 }
16971
16972 pub fn toggle_minimap(
16973 &mut self,
16974 _: &ToggleMinimap,
16975 window: &mut Window,
16976 cx: &mut Context<Editor>,
16977 ) {
16978 if self.supports_minimap(cx) {
16979 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16980 }
16981 }
16982
16983 fn refresh_inline_diagnostics(
16984 &mut self,
16985 debounce: bool,
16986 window: &mut Window,
16987 cx: &mut Context<Self>,
16988 ) {
16989 let max_severity = ProjectSettings::get_global(cx)
16990 .diagnostics
16991 .inline
16992 .max_severity
16993 .unwrap_or(self.diagnostics_max_severity);
16994
16995 if !self.inline_diagnostics_enabled()
16996 || !self.show_inline_diagnostics
16997 || max_severity == DiagnosticSeverity::Off
16998 {
16999 self.inline_diagnostics_update = Task::ready(());
17000 self.inline_diagnostics.clear();
17001 return;
17002 }
17003
17004 let debounce_ms = ProjectSettings::get_global(cx)
17005 .diagnostics
17006 .inline
17007 .update_debounce_ms;
17008 let debounce = if debounce && debounce_ms > 0 {
17009 Some(Duration::from_millis(debounce_ms))
17010 } else {
17011 None
17012 };
17013 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17014 if let Some(debounce) = debounce {
17015 cx.background_executor().timer(debounce).await;
17016 }
17017 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17018 editor
17019 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17020 .ok()
17021 }) else {
17022 return;
17023 };
17024
17025 let new_inline_diagnostics = cx
17026 .background_spawn(async move {
17027 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17028 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17029 let message = diagnostic_entry
17030 .diagnostic
17031 .message
17032 .split_once('\n')
17033 .map(|(line, _)| line)
17034 .map(SharedString::new)
17035 .unwrap_or_else(|| {
17036 SharedString::from(diagnostic_entry.diagnostic.message)
17037 });
17038 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17039 let (Ok(i) | Err(i)) = inline_diagnostics
17040 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17041 inline_diagnostics.insert(
17042 i,
17043 (
17044 start_anchor,
17045 InlineDiagnostic {
17046 message,
17047 group_id: diagnostic_entry.diagnostic.group_id,
17048 start: diagnostic_entry.range.start.to_point(&snapshot),
17049 is_primary: diagnostic_entry.diagnostic.is_primary,
17050 severity: diagnostic_entry.diagnostic.severity,
17051 },
17052 ),
17053 );
17054 }
17055 inline_diagnostics
17056 })
17057 .await;
17058
17059 editor
17060 .update(cx, |editor, cx| {
17061 editor.inline_diagnostics = new_inline_diagnostics;
17062 cx.notify();
17063 })
17064 .ok();
17065 });
17066 }
17067
17068 fn pull_diagnostics(
17069 &mut self,
17070 buffer_id: Option<BufferId>,
17071 window: &Window,
17072 cx: &mut Context<Self>,
17073 ) -> Option<()> {
17074 if !self.mode().is_full() {
17075 return None;
17076 }
17077 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17078 .diagnostics
17079 .lsp_pull_diagnostics;
17080 if !pull_diagnostics_settings.enabled {
17081 return None;
17082 }
17083 let project = self.project()?.downgrade();
17084 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17085 let mut buffers = self.buffer.read(cx).all_buffers();
17086 if let Some(buffer_id) = buffer_id {
17087 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17088 }
17089
17090 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17091 cx.background_executor().timer(debounce).await;
17092
17093 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17094 buffers
17095 .into_iter()
17096 .filter_map(|buffer| {
17097 project
17098 .update(cx, |project, cx| {
17099 project.lsp_store().update(cx, |lsp_store, cx| {
17100 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17101 })
17102 })
17103 .ok()
17104 })
17105 .collect::<FuturesUnordered<_>>()
17106 }) else {
17107 return;
17108 };
17109
17110 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17111 match pull_task {
17112 Ok(()) => {
17113 if editor
17114 .update_in(cx, |editor, window, cx| {
17115 editor.update_diagnostics_state(window, cx);
17116 })
17117 .is_err()
17118 {
17119 return;
17120 }
17121 }
17122 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17123 }
17124 }
17125 });
17126
17127 Some(())
17128 }
17129
17130 pub fn set_selections_from_remote(
17131 &mut self,
17132 selections: Vec<Selection<Anchor>>,
17133 pending_selection: Option<Selection<Anchor>>,
17134 window: &mut Window,
17135 cx: &mut Context<Self>,
17136 ) {
17137 let old_cursor_position = self.selections.newest_anchor().head();
17138 self.selections.change_with(cx, |s| {
17139 s.select_anchors(selections);
17140 if let Some(pending_selection) = pending_selection {
17141 s.set_pending(pending_selection, SelectMode::Character);
17142 } else {
17143 s.clear_pending();
17144 }
17145 });
17146 self.selections_did_change(
17147 false,
17148 &old_cursor_position,
17149 SelectionEffects::default(),
17150 window,
17151 cx,
17152 );
17153 }
17154
17155 pub fn transact(
17156 &mut self,
17157 window: &mut Window,
17158 cx: &mut Context<Self>,
17159 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17160 ) -> Option<TransactionId> {
17161 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17162 this.start_transaction_at(Instant::now(), window, cx);
17163 update(this, window, cx);
17164 this.end_transaction_at(Instant::now(), cx)
17165 })
17166 }
17167
17168 pub fn start_transaction_at(
17169 &mut self,
17170 now: Instant,
17171 window: &mut Window,
17172 cx: &mut Context<Self>,
17173 ) -> Option<TransactionId> {
17174 self.end_selection(window, cx);
17175 if let Some(tx_id) = self
17176 .buffer
17177 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17178 {
17179 self.selection_history
17180 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17181 cx.emit(EditorEvent::TransactionBegun {
17182 transaction_id: tx_id,
17183 });
17184 Some(tx_id)
17185 } else {
17186 None
17187 }
17188 }
17189
17190 pub fn end_transaction_at(
17191 &mut self,
17192 now: Instant,
17193 cx: &mut Context<Self>,
17194 ) -> Option<TransactionId> {
17195 if let Some(transaction_id) = self
17196 .buffer
17197 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17198 {
17199 if let Some((_, end_selections)) =
17200 self.selection_history.transaction_mut(transaction_id)
17201 {
17202 *end_selections = Some(self.selections.disjoint_anchors());
17203 } else {
17204 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17205 }
17206
17207 cx.emit(EditorEvent::Edited { transaction_id });
17208 Some(transaction_id)
17209 } else {
17210 None
17211 }
17212 }
17213
17214 pub fn modify_transaction_selection_history(
17215 &mut self,
17216 transaction_id: TransactionId,
17217 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17218 ) -> bool {
17219 self.selection_history
17220 .transaction_mut(transaction_id)
17221 .map(modify)
17222 .is_some()
17223 }
17224
17225 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17226 if self.selection_mark_mode {
17227 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17228 s.move_with(|_, sel| {
17229 sel.collapse_to(sel.head(), SelectionGoal::None);
17230 });
17231 })
17232 }
17233 self.selection_mark_mode = true;
17234 cx.notify();
17235 }
17236
17237 pub fn swap_selection_ends(
17238 &mut self,
17239 _: &actions::SwapSelectionEnds,
17240 window: &mut Window,
17241 cx: &mut Context<Self>,
17242 ) {
17243 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17244 s.move_with(|_, sel| {
17245 if sel.start != sel.end {
17246 sel.reversed = !sel.reversed
17247 }
17248 });
17249 });
17250 self.request_autoscroll(Autoscroll::newest(), cx);
17251 cx.notify();
17252 }
17253
17254 pub fn toggle_focus(
17255 workspace: &mut Workspace,
17256 _: &actions::ToggleFocus,
17257 window: &mut Window,
17258 cx: &mut Context<Workspace>,
17259 ) {
17260 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17261 return;
17262 };
17263 workspace.activate_item(&item, true, true, window, cx);
17264 }
17265
17266 pub fn toggle_fold(
17267 &mut self,
17268 _: &actions::ToggleFold,
17269 window: &mut Window,
17270 cx: &mut Context<Self>,
17271 ) {
17272 if self.is_singleton(cx) {
17273 let selection = self.selections.newest::<Point>(cx);
17274
17275 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17276 let range = if selection.is_empty() {
17277 let point = selection.head().to_display_point(&display_map);
17278 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17279 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17280 .to_point(&display_map);
17281 start..end
17282 } else {
17283 selection.range()
17284 };
17285 if display_map.folds_in_range(range).next().is_some() {
17286 self.unfold_lines(&Default::default(), window, cx)
17287 } else {
17288 self.fold(&Default::default(), window, cx)
17289 }
17290 } else {
17291 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17292 let buffer_ids: HashSet<_> = self
17293 .selections
17294 .disjoint_anchor_ranges()
17295 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17296 .collect();
17297
17298 let should_unfold = buffer_ids
17299 .iter()
17300 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17301
17302 for buffer_id in buffer_ids {
17303 if should_unfold {
17304 self.unfold_buffer(buffer_id, cx);
17305 } else {
17306 self.fold_buffer(buffer_id, cx);
17307 }
17308 }
17309 }
17310 }
17311
17312 pub fn toggle_fold_recursive(
17313 &mut self,
17314 _: &actions::ToggleFoldRecursive,
17315 window: &mut Window,
17316 cx: &mut Context<Self>,
17317 ) {
17318 let selection = self.selections.newest::<Point>(cx);
17319
17320 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17321 let range = if selection.is_empty() {
17322 let point = selection.head().to_display_point(&display_map);
17323 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17324 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17325 .to_point(&display_map);
17326 start..end
17327 } else {
17328 selection.range()
17329 };
17330 if display_map.folds_in_range(range).next().is_some() {
17331 self.unfold_recursive(&Default::default(), window, cx)
17332 } else {
17333 self.fold_recursive(&Default::default(), window, cx)
17334 }
17335 }
17336
17337 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17338 if self.is_singleton(cx) {
17339 let mut to_fold = Vec::new();
17340 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17341 let selections = self.selections.all_adjusted(cx);
17342
17343 for selection in selections {
17344 let range = selection.range().sorted();
17345 let buffer_start_row = range.start.row;
17346
17347 if range.start.row != range.end.row {
17348 let mut found = false;
17349 let mut row = range.start.row;
17350 while row <= range.end.row {
17351 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17352 {
17353 found = true;
17354 row = crease.range().end.row + 1;
17355 to_fold.push(crease);
17356 } else {
17357 row += 1
17358 }
17359 }
17360 if found {
17361 continue;
17362 }
17363 }
17364
17365 for row in (0..=range.start.row).rev() {
17366 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17367 if crease.range().end.row >= buffer_start_row {
17368 to_fold.push(crease);
17369 if row <= range.start.row {
17370 break;
17371 }
17372 }
17373 }
17374 }
17375 }
17376
17377 self.fold_creases(to_fold, true, window, cx);
17378 } else {
17379 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17380 let buffer_ids = self
17381 .selections
17382 .disjoint_anchor_ranges()
17383 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17384 .collect::<HashSet<_>>();
17385 for buffer_id in buffer_ids {
17386 self.fold_buffer(buffer_id, cx);
17387 }
17388 }
17389 }
17390
17391 pub fn toggle_fold_all(
17392 &mut self,
17393 _: &actions::ToggleFoldAll,
17394 window: &mut Window,
17395 cx: &mut Context<Self>,
17396 ) {
17397 if self.buffer.read(cx).is_singleton() {
17398 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17399 let has_folds = display_map
17400 .folds_in_range(0..display_map.buffer_snapshot.len())
17401 .next()
17402 .is_some();
17403
17404 if has_folds {
17405 self.unfold_all(&actions::UnfoldAll, window, cx);
17406 } else {
17407 self.fold_all(&actions::FoldAll, window, cx);
17408 }
17409 } else {
17410 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17411 let should_unfold = buffer_ids
17412 .iter()
17413 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17414
17415 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17416 editor
17417 .update_in(cx, |editor, _, cx| {
17418 for buffer_id in buffer_ids {
17419 if should_unfold {
17420 editor.unfold_buffer(buffer_id, cx);
17421 } else {
17422 editor.fold_buffer(buffer_id, cx);
17423 }
17424 }
17425 })
17426 .ok();
17427 });
17428 }
17429 }
17430
17431 fn fold_at_level(
17432 &mut self,
17433 fold_at: &FoldAtLevel,
17434 window: &mut Window,
17435 cx: &mut Context<Self>,
17436 ) {
17437 if !self.buffer.read(cx).is_singleton() {
17438 return;
17439 }
17440
17441 let fold_at_level = fold_at.0;
17442 let snapshot = self.buffer.read(cx).snapshot(cx);
17443 let mut to_fold = Vec::new();
17444 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17445
17446 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17447 while start_row < end_row {
17448 match self
17449 .snapshot(window, cx)
17450 .crease_for_buffer_row(MultiBufferRow(start_row))
17451 {
17452 Some(crease) => {
17453 let nested_start_row = crease.range().start.row + 1;
17454 let nested_end_row = crease.range().end.row;
17455
17456 if current_level < fold_at_level {
17457 stack.push((nested_start_row, nested_end_row, current_level + 1));
17458 } else if current_level == fold_at_level {
17459 to_fold.push(crease);
17460 }
17461
17462 start_row = nested_end_row + 1;
17463 }
17464 None => start_row += 1,
17465 }
17466 }
17467 }
17468
17469 self.fold_creases(to_fold, true, window, cx);
17470 }
17471
17472 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17473 if self.buffer.read(cx).is_singleton() {
17474 let mut fold_ranges = Vec::new();
17475 let snapshot = self.buffer.read(cx).snapshot(cx);
17476
17477 for row in 0..snapshot.max_row().0 {
17478 if let Some(foldable_range) = self
17479 .snapshot(window, cx)
17480 .crease_for_buffer_row(MultiBufferRow(row))
17481 {
17482 fold_ranges.push(foldable_range);
17483 }
17484 }
17485
17486 self.fold_creases(fold_ranges, true, window, cx);
17487 } else {
17488 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17489 editor
17490 .update_in(cx, |editor, _, cx| {
17491 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17492 editor.fold_buffer(buffer_id, cx);
17493 }
17494 })
17495 .ok();
17496 });
17497 }
17498 }
17499
17500 pub fn fold_function_bodies(
17501 &mut self,
17502 _: &actions::FoldFunctionBodies,
17503 window: &mut Window,
17504 cx: &mut Context<Self>,
17505 ) {
17506 let snapshot = self.buffer.read(cx).snapshot(cx);
17507
17508 let ranges = snapshot
17509 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17510 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17511 .collect::<Vec<_>>();
17512
17513 let creases = ranges
17514 .into_iter()
17515 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17516 .collect();
17517
17518 self.fold_creases(creases, true, window, cx);
17519 }
17520
17521 pub fn fold_recursive(
17522 &mut self,
17523 _: &actions::FoldRecursive,
17524 window: &mut Window,
17525 cx: &mut Context<Self>,
17526 ) {
17527 let mut to_fold = Vec::new();
17528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17529 let selections = self.selections.all_adjusted(cx);
17530
17531 for selection in selections {
17532 let range = selection.range().sorted();
17533 let buffer_start_row = range.start.row;
17534
17535 if range.start.row != range.end.row {
17536 let mut found = false;
17537 for row in range.start.row..=range.end.row {
17538 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17539 found = true;
17540 to_fold.push(crease);
17541 }
17542 }
17543 if found {
17544 continue;
17545 }
17546 }
17547
17548 for row in (0..=range.start.row).rev() {
17549 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17550 if crease.range().end.row >= buffer_start_row {
17551 to_fold.push(crease);
17552 } else {
17553 break;
17554 }
17555 }
17556 }
17557 }
17558
17559 self.fold_creases(to_fold, true, window, cx);
17560 }
17561
17562 pub fn fold_at(
17563 &mut self,
17564 buffer_row: MultiBufferRow,
17565 window: &mut Window,
17566 cx: &mut Context<Self>,
17567 ) {
17568 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17569
17570 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17571 let autoscroll = self
17572 .selections
17573 .all::<Point>(cx)
17574 .iter()
17575 .any(|selection| crease.range().overlaps(&selection.range()));
17576
17577 self.fold_creases(vec![crease], autoscroll, window, cx);
17578 }
17579 }
17580
17581 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17582 if self.is_singleton(cx) {
17583 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17584 let buffer = &display_map.buffer_snapshot;
17585 let selections = self.selections.all::<Point>(cx);
17586 let ranges = selections
17587 .iter()
17588 .map(|s| {
17589 let range = s.display_range(&display_map).sorted();
17590 let mut start = range.start.to_point(&display_map);
17591 let mut end = range.end.to_point(&display_map);
17592 start.column = 0;
17593 end.column = buffer.line_len(MultiBufferRow(end.row));
17594 start..end
17595 })
17596 .collect::<Vec<_>>();
17597
17598 self.unfold_ranges(&ranges, true, true, cx);
17599 } else {
17600 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17601 let buffer_ids = self
17602 .selections
17603 .disjoint_anchor_ranges()
17604 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17605 .collect::<HashSet<_>>();
17606 for buffer_id in buffer_ids {
17607 self.unfold_buffer(buffer_id, cx);
17608 }
17609 }
17610 }
17611
17612 pub fn unfold_recursive(
17613 &mut self,
17614 _: &UnfoldRecursive,
17615 _window: &mut Window,
17616 cx: &mut Context<Self>,
17617 ) {
17618 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17619 let selections = self.selections.all::<Point>(cx);
17620 let ranges = selections
17621 .iter()
17622 .map(|s| {
17623 let mut range = s.display_range(&display_map).sorted();
17624 *range.start.column_mut() = 0;
17625 *range.end.column_mut() = display_map.line_len(range.end.row());
17626 let start = range.start.to_point(&display_map);
17627 let end = range.end.to_point(&display_map);
17628 start..end
17629 })
17630 .collect::<Vec<_>>();
17631
17632 self.unfold_ranges(&ranges, true, true, cx);
17633 }
17634
17635 pub fn unfold_at(
17636 &mut self,
17637 buffer_row: MultiBufferRow,
17638 _window: &mut Window,
17639 cx: &mut Context<Self>,
17640 ) {
17641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17642
17643 let intersection_range = Point::new(buffer_row.0, 0)
17644 ..Point::new(
17645 buffer_row.0,
17646 display_map.buffer_snapshot.line_len(buffer_row),
17647 );
17648
17649 let autoscroll = self
17650 .selections
17651 .all::<Point>(cx)
17652 .iter()
17653 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17654
17655 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17656 }
17657
17658 pub fn unfold_all(
17659 &mut self,
17660 _: &actions::UnfoldAll,
17661 _window: &mut Window,
17662 cx: &mut Context<Self>,
17663 ) {
17664 if self.buffer.read(cx).is_singleton() {
17665 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17666 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17667 } else {
17668 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17669 editor
17670 .update(cx, |editor, cx| {
17671 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17672 editor.unfold_buffer(buffer_id, cx);
17673 }
17674 })
17675 .ok();
17676 });
17677 }
17678 }
17679
17680 pub fn fold_selected_ranges(
17681 &mut self,
17682 _: &FoldSelectedRanges,
17683 window: &mut Window,
17684 cx: &mut Context<Self>,
17685 ) {
17686 let selections = self.selections.all_adjusted(cx);
17687 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17688 let ranges = selections
17689 .into_iter()
17690 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17691 .collect::<Vec<_>>();
17692 self.fold_creases(ranges, true, window, cx);
17693 }
17694
17695 pub fn fold_ranges<T: ToOffset + Clone>(
17696 &mut self,
17697 ranges: Vec<Range<T>>,
17698 auto_scroll: bool,
17699 window: &mut Window,
17700 cx: &mut Context<Self>,
17701 ) {
17702 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17703 let ranges = ranges
17704 .into_iter()
17705 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17706 .collect::<Vec<_>>();
17707 self.fold_creases(ranges, auto_scroll, window, cx);
17708 }
17709
17710 pub fn fold_creases<T: ToOffset + Clone>(
17711 &mut self,
17712 creases: Vec<Crease<T>>,
17713 auto_scroll: bool,
17714 _window: &mut Window,
17715 cx: &mut Context<Self>,
17716 ) {
17717 if creases.is_empty() {
17718 return;
17719 }
17720
17721 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17722
17723 if auto_scroll {
17724 self.request_autoscroll(Autoscroll::fit(), cx);
17725 }
17726
17727 cx.notify();
17728
17729 self.scrollbar_marker_state.dirty = true;
17730 self.folds_did_change(cx);
17731 }
17732
17733 /// Removes any folds whose ranges intersect any of the given ranges.
17734 pub fn unfold_ranges<T: ToOffset + Clone>(
17735 &mut self,
17736 ranges: &[Range<T>],
17737 inclusive: bool,
17738 auto_scroll: bool,
17739 cx: &mut Context<Self>,
17740 ) {
17741 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17742 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17743 });
17744 self.folds_did_change(cx);
17745 }
17746
17747 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17748 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17749 return;
17750 }
17751 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17752 self.display_map.update(cx, |display_map, cx| {
17753 display_map.fold_buffers([buffer_id], cx)
17754 });
17755 cx.emit(EditorEvent::BufferFoldToggled {
17756 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17757 folded: true,
17758 });
17759 cx.notify();
17760 }
17761
17762 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17763 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17764 return;
17765 }
17766 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17767 self.display_map.update(cx, |display_map, cx| {
17768 display_map.unfold_buffers([buffer_id], cx);
17769 });
17770 cx.emit(EditorEvent::BufferFoldToggled {
17771 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17772 folded: false,
17773 });
17774 cx.notify();
17775 }
17776
17777 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17778 self.display_map.read(cx).is_buffer_folded(buffer)
17779 }
17780
17781 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17782 self.display_map.read(cx).folded_buffers()
17783 }
17784
17785 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17786 self.display_map.update(cx, |display_map, cx| {
17787 display_map.disable_header_for_buffer(buffer_id, cx);
17788 });
17789 cx.notify();
17790 }
17791
17792 /// Removes any folds with the given ranges.
17793 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17794 &mut self,
17795 ranges: &[Range<T>],
17796 type_id: TypeId,
17797 auto_scroll: bool,
17798 cx: &mut Context<Self>,
17799 ) {
17800 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17801 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17802 });
17803 self.folds_did_change(cx);
17804 }
17805
17806 fn remove_folds_with<T: ToOffset + Clone>(
17807 &mut self,
17808 ranges: &[Range<T>],
17809 auto_scroll: bool,
17810 cx: &mut Context<Self>,
17811 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17812 ) {
17813 if ranges.is_empty() {
17814 return;
17815 }
17816
17817 let mut buffers_affected = HashSet::default();
17818 let multi_buffer = self.buffer().read(cx);
17819 for range in ranges {
17820 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17821 buffers_affected.insert(buffer.read(cx).remote_id());
17822 };
17823 }
17824
17825 self.display_map.update(cx, update);
17826
17827 if auto_scroll {
17828 self.request_autoscroll(Autoscroll::fit(), cx);
17829 }
17830
17831 cx.notify();
17832 self.scrollbar_marker_state.dirty = true;
17833 self.active_indent_guides_state.dirty = true;
17834 }
17835
17836 pub fn update_renderer_widths(
17837 &mut self,
17838 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17839 cx: &mut Context<Self>,
17840 ) -> bool {
17841 self.display_map
17842 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17843 }
17844
17845 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17846 self.display_map.read(cx).fold_placeholder.clone()
17847 }
17848
17849 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17850 self.buffer.update(cx, |buffer, cx| {
17851 buffer.set_all_diff_hunks_expanded(cx);
17852 });
17853 }
17854
17855 pub fn expand_all_diff_hunks(
17856 &mut self,
17857 _: &ExpandAllDiffHunks,
17858 _window: &mut Window,
17859 cx: &mut Context<Self>,
17860 ) {
17861 self.buffer.update(cx, |buffer, cx| {
17862 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17863 });
17864 }
17865
17866 pub fn toggle_selected_diff_hunks(
17867 &mut self,
17868 _: &ToggleSelectedDiffHunks,
17869 _window: &mut Window,
17870 cx: &mut Context<Self>,
17871 ) {
17872 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17873 self.toggle_diff_hunks_in_ranges(ranges, cx);
17874 }
17875
17876 pub fn diff_hunks_in_ranges<'a>(
17877 &'a self,
17878 ranges: &'a [Range<Anchor>],
17879 buffer: &'a MultiBufferSnapshot,
17880 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17881 ranges.iter().flat_map(move |range| {
17882 let end_excerpt_id = range.end.excerpt_id;
17883 let range = range.to_point(buffer);
17884 let mut peek_end = range.end;
17885 if range.end.row < buffer.max_row().0 {
17886 peek_end = Point::new(range.end.row + 1, 0);
17887 }
17888 buffer
17889 .diff_hunks_in_range(range.start..peek_end)
17890 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17891 })
17892 }
17893
17894 pub fn has_stageable_diff_hunks_in_ranges(
17895 &self,
17896 ranges: &[Range<Anchor>],
17897 snapshot: &MultiBufferSnapshot,
17898 ) -> bool {
17899 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17900 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17901 }
17902
17903 pub fn toggle_staged_selected_diff_hunks(
17904 &mut self,
17905 _: &::git::ToggleStaged,
17906 _: &mut Window,
17907 cx: &mut Context<Self>,
17908 ) {
17909 let snapshot = self.buffer.read(cx).snapshot(cx);
17910 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17911 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17912 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17913 }
17914
17915 pub fn set_render_diff_hunk_controls(
17916 &mut self,
17917 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17918 cx: &mut Context<Self>,
17919 ) {
17920 self.render_diff_hunk_controls = render_diff_hunk_controls;
17921 cx.notify();
17922 }
17923
17924 pub fn stage_and_next(
17925 &mut self,
17926 _: &::git::StageAndNext,
17927 window: &mut Window,
17928 cx: &mut Context<Self>,
17929 ) {
17930 self.do_stage_or_unstage_and_next(true, window, cx);
17931 }
17932
17933 pub fn unstage_and_next(
17934 &mut self,
17935 _: &::git::UnstageAndNext,
17936 window: &mut Window,
17937 cx: &mut Context<Self>,
17938 ) {
17939 self.do_stage_or_unstage_and_next(false, window, cx);
17940 }
17941
17942 pub fn stage_or_unstage_diff_hunks(
17943 &mut self,
17944 stage: bool,
17945 ranges: Vec<Range<Anchor>>,
17946 cx: &mut Context<Self>,
17947 ) {
17948 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17949 cx.spawn(async move |this, cx| {
17950 task.await?;
17951 this.update(cx, |this, cx| {
17952 let snapshot = this.buffer.read(cx).snapshot(cx);
17953 let chunk_by = this
17954 .diff_hunks_in_ranges(&ranges, &snapshot)
17955 .chunk_by(|hunk| hunk.buffer_id);
17956 for (buffer_id, hunks) in &chunk_by {
17957 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17958 }
17959 })
17960 })
17961 .detach_and_log_err(cx);
17962 }
17963
17964 fn save_buffers_for_ranges_if_needed(
17965 &mut self,
17966 ranges: &[Range<Anchor>],
17967 cx: &mut Context<Editor>,
17968 ) -> Task<Result<()>> {
17969 let multibuffer = self.buffer.read(cx);
17970 let snapshot = multibuffer.read(cx);
17971 let buffer_ids: HashSet<_> = ranges
17972 .iter()
17973 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17974 .collect();
17975 drop(snapshot);
17976
17977 let mut buffers = HashSet::default();
17978 for buffer_id in buffer_ids {
17979 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17980 let buffer = buffer_entity.read(cx);
17981 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17982 {
17983 buffers.insert(buffer_entity);
17984 }
17985 }
17986 }
17987
17988 if let Some(project) = &self.project {
17989 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17990 } else {
17991 Task::ready(Ok(()))
17992 }
17993 }
17994
17995 fn do_stage_or_unstage_and_next(
17996 &mut self,
17997 stage: bool,
17998 window: &mut Window,
17999 cx: &mut Context<Self>,
18000 ) {
18001 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18002
18003 if ranges.iter().any(|range| range.start != range.end) {
18004 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18005 return;
18006 }
18007
18008 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18009 let snapshot = self.snapshot(window, cx);
18010 let position = self.selections.newest::<Point>(cx).head();
18011 let mut row = snapshot
18012 .buffer_snapshot
18013 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18014 .find(|hunk| hunk.row_range.start.0 > position.row)
18015 .map(|hunk| hunk.row_range.start);
18016
18017 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18018 // Outside of the project diff editor, wrap around to the beginning.
18019 if !all_diff_hunks_expanded {
18020 row = row.or_else(|| {
18021 snapshot
18022 .buffer_snapshot
18023 .diff_hunks_in_range(Point::zero()..position)
18024 .find(|hunk| hunk.row_range.end.0 < position.row)
18025 .map(|hunk| hunk.row_range.start)
18026 });
18027 }
18028
18029 if let Some(row) = row {
18030 let destination = Point::new(row.0, 0);
18031 let autoscroll = Autoscroll::center();
18032
18033 self.unfold_ranges(&[destination..destination], false, false, cx);
18034 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18035 s.select_ranges([destination..destination]);
18036 });
18037 }
18038 }
18039
18040 fn do_stage_or_unstage(
18041 &self,
18042 stage: bool,
18043 buffer_id: BufferId,
18044 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18045 cx: &mut App,
18046 ) -> Option<()> {
18047 let project = self.project()?;
18048 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18049 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18050 let buffer_snapshot = buffer.read(cx).snapshot();
18051 let file_exists = buffer_snapshot
18052 .file()
18053 .is_some_and(|file| file.disk_state().exists());
18054 diff.update(cx, |diff, cx| {
18055 diff.stage_or_unstage_hunks(
18056 stage,
18057 &hunks
18058 .map(|hunk| buffer_diff::DiffHunk {
18059 buffer_range: hunk.buffer_range,
18060 diff_base_byte_range: hunk.diff_base_byte_range,
18061 secondary_status: hunk.secondary_status,
18062 range: Point::zero()..Point::zero(), // unused
18063 })
18064 .collect::<Vec<_>>(),
18065 &buffer_snapshot,
18066 file_exists,
18067 cx,
18068 )
18069 });
18070 None
18071 }
18072
18073 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18074 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18075 self.buffer
18076 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18077 }
18078
18079 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18080 self.buffer.update(cx, |buffer, cx| {
18081 let ranges = vec![Anchor::min()..Anchor::max()];
18082 if !buffer.all_diff_hunks_expanded()
18083 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18084 {
18085 buffer.collapse_diff_hunks(ranges, cx);
18086 true
18087 } else {
18088 false
18089 }
18090 })
18091 }
18092
18093 fn toggle_diff_hunks_in_ranges(
18094 &mut self,
18095 ranges: Vec<Range<Anchor>>,
18096 cx: &mut Context<Editor>,
18097 ) {
18098 self.buffer.update(cx, |buffer, cx| {
18099 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18100 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18101 })
18102 }
18103
18104 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18105 self.buffer.update(cx, |buffer, cx| {
18106 let snapshot = buffer.snapshot(cx);
18107 let excerpt_id = range.end.excerpt_id;
18108 let point_range = range.to_point(&snapshot);
18109 let expand = !buffer.single_hunk_is_expanded(range, cx);
18110 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18111 })
18112 }
18113
18114 pub(crate) fn apply_all_diff_hunks(
18115 &mut self,
18116 _: &ApplyAllDiffHunks,
18117 window: &mut Window,
18118 cx: &mut Context<Self>,
18119 ) {
18120 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18121
18122 let buffers = self.buffer.read(cx).all_buffers();
18123 for branch_buffer in buffers {
18124 branch_buffer.update(cx, |branch_buffer, cx| {
18125 branch_buffer.merge_into_base(Vec::new(), cx);
18126 });
18127 }
18128
18129 if let Some(project) = self.project.clone() {
18130 self.save(
18131 SaveOptions {
18132 format: true,
18133 autosave: false,
18134 },
18135 project,
18136 window,
18137 cx,
18138 )
18139 .detach_and_log_err(cx);
18140 }
18141 }
18142
18143 pub(crate) fn apply_selected_diff_hunks(
18144 &mut self,
18145 _: &ApplyDiffHunk,
18146 window: &mut Window,
18147 cx: &mut Context<Self>,
18148 ) {
18149 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18150 let snapshot = self.snapshot(window, cx);
18151 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18152 let mut ranges_by_buffer = HashMap::default();
18153 self.transact(window, cx, |editor, _window, cx| {
18154 for hunk in hunks {
18155 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18156 ranges_by_buffer
18157 .entry(buffer.clone())
18158 .or_insert_with(Vec::new)
18159 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18160 }
18161 }
18162
18163 for (buffer, ranges) in ranges_by_buffer {
18164 buffer.update(cx, |buffer, cx| {
18165 buffer.merge_into_base(ranges, cx);
18166 });
18167 }
18168 });
18169
18170 if let Some(project) = self.project.clone() {
18171 self.save(
18172 SaveOptions {
18173 format: true,
18174 autosave: false,
18175 },
18176 project,
18177 window,
18178 cx,
18179 )
18180 .detach_and_log_err(cx);
18181 }
18182 }
18183
18184 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18185 if hovered != self.gutter_hovered {
18186 self.gutter_hovered = hovered;
18187 cx.notify();
18188 }
18189 }
18190
18191 pub fn insert_blocks(
18192 &mut self,
18193 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18194 autoscroll: Option<Autoscroll>,
18195 cx: &mut Context<Self>,
18196 ) -> Vec<CustomBlockId> {
18197 let blocks = self
18198 .display_map
18199 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18200 if let Some(autoscroll) = autoscroll {
18201 self.request_autoscroll(autoscroll, cx);
18202 }
18203 cx.notify();
18204 blocks
18205 }
18206
18207 pub fn resize_blocks(
18208 &mut self,
18209 heights: HashMap<CustomBlockId, u32>,
18210 autoscroll: Option<Autoscroll>,
18211 cx: &mut Context<Self>,
18212 ) {
18213 self.display_map
18214 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18215 if let Some(autoscroll) = autoscroll {
18216 self.request_autoscroll(autoscroll, cx);
18217 }
18218 cx.notify();
18219 }
18220
18221 pub fn replace_blocks(
18222 &mut self,
18223 renderers: HashMap<CustomBlockId, RenderBlock>,
18224 autoscroll: Option<Autoscroll>,
18225 cx: &mut Context<Self>,
18226 ) {
18227 self.display_map
18228 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18229 if let Some(autoscroll) = autoscroll {
18230 self.request_autoscroll(autoscroll, cx);
18231 }
18232 cx.notify();
18233 }
18234
18235 pub fn remove_blocks(
18236 &mut self,
18237 block_ids: HashSet<CustomBlockId>,
18238 autoscroll: Option<Autoscroll>,
18239 cx: &mut Context<Self>,
18240 ) {
18241 self.display_map.update(cx, |display_map, cx| {
18242 display_map.remove_blocks(block_ids, cx)
18243 });
18244 if let Some(autoscroll) = autoscroll {
18245 self.request_autoscroll(autoscroll, cx);
18246 }
18247 cx.notify();
18248 }
18249
18250 pub fn row_for_block(
18251 &self,
18252 block_id: CustomBlockId,
18253 cx: &mut Context<Self>,
18254 ) -> Option<DisplayRow> {
18255 self.display_map
18256 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18257 }
18258
18259 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18260 self.focused_block = Some(focused_block);
18261 }
18262
18263 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18264 self.focused_block.take()
18265 }
18266
18267 pub fn insert_creases(
18268 &mut self,
18269 creases: impl IntoIterator<Item = Crease<Anchor>>,
18270 cx: &mut Context<Self>,
18271 ) -> Vec<CreaseId> {
18272 self.display_map
18273 .update(cx, |map, cx| map.insert_creases(creases, cx))
18274 }
18275
18276 pub fn remove_creases(
18277 &mut self,
18278 ids: impl IntoIterator<Item = CreaseId>,
18279 cx: &mut Context<Self>,
18280 ) -> Vec<(CreaseId, Range<Anchor>)> {
18281 self.display_map
18282 .update(cx, |map, cx| map.remove_creases(ids, cx))
18283 }
18284
18285 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18286 self.display_map
18287 .update(cx, |map, cx| map.snapshot(cx))
18288 .longest_row()
18289 }
18290
18291 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18292 self.display_map
18293 .update(cx, |map, cx| map.snapshot(cx))
18294 .max_point()
18295 }
18296
18297 pub fn text(&self, cx: &App) -> String {
18298 self.buffer.read(cx).read(cx).text()
18299 }
18300
18301 pub fn is_empty(&self, cx: &App) -> bool {
18302 self.buffer.read(cx).read(cx).is_empty()
18303 }
18304
18305 pub fn text_option(&self, cx: &App) -> Option<String> {
18306 let text = self.text(cx);
18307 let text = text.trim();
18308
18309 if text.is_empty() {
18310 return None;
18311 }
18312
18313 Some(text.to_string())
18314 }
18315
18316 pub fn set_text(
18317 &mut self,
18318 text: impl Into<Arc<str>>,
18319 window: &mut Window,
18320 cx: &mut Context<Self>,
18321 ) {
18322 self.transact(window, cx, |this, _, cx| {
18323 this.buffer
18324 .read(cx)
18325 .as_singleton()
18326 .expect("you can only call set_text on editors for singleton buffers")
18327 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18328 });
18329 }
18330
18331 pub fn display_text(&self, cx: &mut App) -> String {
18332 self.display_map
18333 .update(cx, |map, cx| map.snapshot(cx))
18334 .text()
18335 }
18336
18337 fn create_minimap(
18338 &self,
18339 minimap_settings: MinimapSettings,
18340 window: &mut Window,
18341 cx: &mut Context<Self>,
18342 ) -> Option<Entity<Self>> {
18343 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18344 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18345 }
18346
18347 fn initialize_new_minimap(
18348 &self,
18349 minimap_settings: MinimapSettings,
18350 window: &mut Window,
18351 cx: &mut Context<Self>,
18352 ) -> Entity<Self> {
18353 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18354
18355 let mut minimap = Editor::new_internal(
18356 EditorMode::Minimap {
18357 parent: cx.weak_entity(),
18358 },
18359 self.buffer.clone(),
18360 None,
18361 Some(self.display_map.clone()),
18362 window,
18363 cx,
18364 );
18365 minimap.scroll_manager.clone_state(&self.scroll_manager);
18366 minimap.set_text_style_refinement(TextStyleRefinement {
18367 font_size: Some(MINIMAP_FONT_SIZE),
18368 font_weight: Some(MINIMAP_FONT_WEIGHT),
18369 ..Default::default()
18370 });
18371 minimap.update_minimap_configuration(minimap_settings, cx);
18372 cx.new(|_| minimap)
18373 }
18374
18375 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18376 let current_line_highlight = minimap_settings
18377 .current_line_highlight
18378 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18379 self.set_current_line_highlight(Some(current_line_highlight));
18380 }
18381
18382 pub fn minimap(&self) -> Option<&Entity<Self>> {
18383 self.minimap
18384 .as_ref()
18385 .filter(|_| self.minimap_visibility.visible())
18386 }
18387
18388 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18389 let mut wrap_guides = smallvec![];
18390
18391 if self.show_wrap_guides == Some(false) {
18392 return wrap_guides;
18393 }
18394
18395 let settings = self.buffer.read(cx).language_settings(cx);
18396 if settings.show_wrap_guides {
18397 match self.soft_wrap_mode(cx) {
18398 SoftWrap::Column(soft_wrap) => {
18399 wrap_guides.push((soft_wrap as usize, true));
18400 }
18401 SoftWrap::Bounded(soft_wrap) => {
18402 wrap_guides.push((soft_wrap as usize, true));
18403 }
18404 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18405 }
18406 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18407 }
18408
18409 wrap_guides
18410 }
18411
18412 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18413 let settings = self.buffer.read(cx).language_settings(cx);
18414 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18415 match mode {
18416 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18417 SoftWrap::None
18418 }
18419 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18420 language_settings::SoftWrap::PreferredLineLength => {
18421 SoftWrap::Column(settings.preferred_line_length)
18422 }
18423 language_settings::SoftWrap::Bounded => {
18424 SoftWrap::Bounded(settings.preferred_line_length)
18425 }
18426 }
18427 }
18428
18429 pub fn set_soft_wrap_mode(
18430 &mut self,
18431 mode: language_settings::SoftWrap,
18432
18433 cx: &mut Context<Self>,
18434 ) {
18435 self.soft_wrap_mode_override = Some(mode);
18436 cx.notify();
18437 }
18438
18439 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18440 self.hard_wrap = hard_wrap;
18441 cx.notify();
18442 }
18443
18444 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18445 self.text_style_refinement = Some(style);
18446 }
18447
18448 /// called by the Element so we know what style we were most recently rendered with.
18449 pub(crate) fn set_style(
18450 &mut self,
18451 style: EditorStyle,
18452 window: &mut Window,
18453 cx: &mut Context<Self>,
18454 ) {
18455 // We intentionally do not inform the display map about the minimap style
18456 // so that wrapping is not recalculated and stays consistent for the editor
18457 // and its linked minimap.
18458 if !self.mode.is_minimap() {
18459 let rem_size = window.rem_size();
18460 self.display_map.update(cx, |map, cx| {
18461 map.set_font(
18462 style.text.font(),
18463 style.text.font_size.to_pixels(rem_size),
18464 cx,
18465 )
18466 });
18467 }
18468 self.style = Some(style);
18469 }
18470
18471 pub fn style(&self) -> Option<&EditorStyle> {
18472 self.style.as_ref()
18473 }
18474
18475 // Called by the element. This method is not designed to be called outside of the editor
18476 // element's layout code because it does not notify when rewrapping is computed synchronously.
18477 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18478 self.display_map
18479 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18480 }
18481
18482 pub fn set_soft_wrap(&mut self) {
18483 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18484 }
18485
18486 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18487 if self.soft_wrap_mode_override.is_some() {
18488 self.soft_wrap_mode_override.take();
18489 } else {
18490 let soft_wrap = match self.soft_wrap_mode(cx) {
18491 SoftWrap::GitDiff => return,
18492 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18493 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18494 language_settings::SoftWrap::None
18495 }
18496 };
18497 self.soft_wrap_mode_override = Some(soft_wrap);
18498 }
18499 cx.notify();
18500 }
18501
18502 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18503 let Some(workspace) = self.workspace() else {
18504 return;
18505 };
18506 let fs = workspace.read(cx).app_state().fs.clone();
18507 let current_show = TabBarSettings::get_global(cx).show;
18508 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18509 setting.show = Some(!current_show);
18510 });
18511 }
18512
18513 pub fn toggle_indent_guides(
18514 &mut self,
18515 _: &ToggleIndentGuides,
18516 _: &mut Window,
18517 cx: &mut Context<Self>,
18518 ) {
18519 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18520 self.buffer
18521 .read(cx)
18522 .language_settings(cx)
18523 .indent_guides
18524 .enabled
18525 });
18526 self.show_indent_guides = Some(!currently_enabled);
18527 cx.notify();
18528 }
18529
18530 fn should_show_indent_guides(&self) -> Option<bool> {
18531 self.show_indent_guides
18532 }
18533
18534 pub fn toggle_line_numbers(
18535 &mut self,
18536 _: &ToggleLineNumbers,
18537 _: &mut Window,
18538 cx: &mut Context<Self>,
18539 ) {
18540 let mut editor_settings = EditorSettings::get_global(cx).clone();
18541 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18542 EditorSettings::override_global(editor_settings, cx);
18543 }
18544
18545 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18546 if let Some(show_line_numbers) = self.show_line_numbers {
18547 return show_line_numbers;
18548 }
18549 EditorSettings::get_global(cx).gutter.line_numbers
18550 }
18551
18552 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18553 self.use_relative_line_numbers
18554 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18555 }
18556
18557 pub fn toggle_relative_line_numbers(
18558 &mut self,
18559 _: &ToggleRelativeLineNumbers,
18560 _: &mut Window,
18561 cx: &mut Context<Self>,
18562 ) {
18563 let is_relative = self.should_use_relative_line_numbers(cx);
18564 self.set_relative_line_number(Some(!is_relative), cx)
18565 }
18566
18567 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18568 self.use_relative_line_numbers = is_relative;
18569 cx.notify();
18570 }
18571
18572 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18573 self.show_gutter = show_gutter;
18574 cx.notify();
18575 }
18576
18577 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18578 self.show_scrollbars = ScrollbarAxes {
18579 horizontal: show,
18580 vertical: show,
18581 };
18582 cx.notify();
18583 }
18584
18585 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18586 self.show_scrollbars.vertical = show;
18587 cx.notify();
18588 }
18589
18590 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18591 self.show_scrollbars.horizontal = show;
18592 cx.notify();
18593 }
18594
18595 pub fn set_minimap_visibility(
18596 &mut self,
18597 minimap_visibility: MinimapVisibility,
18598 window: &mut Window,
18599 cx: &mut Context<Self>,
18600 ) {
18601 if self.minimap_visibility != minimap_visibility {
18602 if minimap_visibility.visible() && self.minimap.is_none() {
18603 let minimap_settings = EditorSettings::get_global(cx).minimap;
18604 self.minimap =
18605 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18606 }
18607 self.minimap_visibility = minimap_visibility;
18608 cx.notify();
18609 }
18610 }
18611
18612 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18613 self.set_show_scrollbars(false, cx);
18614 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18615 }
18616
18617 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18618 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18619 }
18620
18621 /// Normally the text in full mode and auto height editors is padded on the
18622 /// left side by roughly half a character width for improved hit testing.
18623 ///
18624 /// Use this method to disable this for cases where this is not wanted (e.g.
18625 /// if you want to align the editor text with some other text above or below)
18626 /// or if you want to add this padding to single-line editors.
18627 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18628 self.offset_content = offset_content;
18629 cx.notify();
18630 }
18631
18632 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18633 self.show_line_numbers = Some(show_line_numbers);
18634 cx.notify();
18635 }
18636
18637 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18638 self.disable_expand_excerpt_buttons = true;
18639 cx.notify();
18640 }
18641
18642 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18643 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18644 cx.notify();
18645 }
18646
18647 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18648 self.show_code_actions = Some(show_code_actions);
18649 cx.notify();
18650 }
18651
18652 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18653 self.show_runnables = Some(show_runnables);
18654 cx.notify();
18655 }
18656
18657 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18658 self.show_breakpoints = Some(show_breakpoints);
18659 cx.notify();
18660 }
18661
18662 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18663 if self.display_map.read(cx).masked != masked {
18664 self.display_map.update(cx, |map, _| map.masked = masked);
18665 }
18666 cx.notify()
18667 }
18668
18669 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18670 self.show_wrap_guides = Some(show_wrap_guides);
18671 cx.notify();
18672 }
18673
18674 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18675 self.show_indent_guides = Some(show_indent_guides);
18676 cx.notify();
18677 }
18678
18679 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18680 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18681 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18682 if let Some(dir) = file.abs_path(cx).parent() {
18683 return Some(dir.to_owned());
18684 }
18685 }
18686
18687 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18688 return Some(project_path.path.to_path_buf());
18689 }
18690 }
18691
18692 None
18693 }
18694
18695 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18696 self.active_excerpt(cx)?
18697 .1
18698 .read(cx)
18699 .file()
18700 .and_then(|f| f.as_local())
18701 }
18702
18703 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18704 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18705 let buffer = buffer.read(cx);
18706 if let Some(project_path) = buffer.project_path(cx) {
18707 let project = self.project()?.read(cx);
18708 project.absolute_path(&project_path, cx)
18709 } else {
18710 buffer
18711 .file()
18712 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18713 }
18714 })
18715 }
18716
18717 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18718 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18719 let project_path = buffer.read(cx).project_path(cx)?;
18720 let project = self.project()?.read(cx);
18721 let entry = project.entry_for_path(&project_path, cx)?;
18722 let path = entry.path.to_path_buf();
18723 Some(path)
18724 })
18725 }
18726
18727 pub fn reveal_in_finder(
18728 &mut self,
18729 _: &RevealInFileManager,
18730 _window: &mut Window,
18731 cx: &mut Context<Self>,
18732 ) {
18733 if let Some(target) = self.target_file(cx) {
18734 cx.reveal_path(&target.abs_path(cx));
18735 }
18736 }
18737
18738 pub fn copy_path(
18739 &mut self,
18740 _: &zed_actions::workspace::CopyPath,
18741 _window: &mut Window,
18742 cx: &mut Context<Self>,
18743 ) {
18744 if let Some(path) = self.target_file_abs_path(cx) {
18745 if let Some(path) = path.to_str() {
18746 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18747 }
18748 }
18749 }
18750
18751 pub fn copy_relative_path(
18752 &mut self,
18753 _: &zed_actions::workspace::CopyRelativePath,
18754 _window: &mut Window,
18755 cx: &mut Context<Self>,
18756 ) {
18757 if let Some(path) = self.target_file_path(cx) {
18758 if let Some(path) = path.to_str() {
18759 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18760 }
18761 }
18762 }
18763
18764 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18765 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18766 buffer.read(cx).project_path(cx)
18767 } else {
18768 None
18769 }
18770 }
18771
18772 // Returns true if the editor handled a go-to-line request
18773 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18774 maybe!({
18775 let breakpoint_store = self.breakpoint_store.as_ref()?;
18776
18777 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18778 else {
18779 self.clear_row_highlights::<ActiveDebugLine>();
18780 return None;
18781 };
18782
18783 let position = active_stack_frame.position;
18784 let buffer_id = position.buffer_id?;
18785 let snapshot = self
18786 .project
18787 .as_ref()?
18788 .read(cx)
18789 .buffer_for_id(buffer_id, cx)?
18790 .read(cx)
18791 .snapshot();
18792
18793 let mut handled = false;
18794 for (id, ExcerptRange { context, .. }) in
18795 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18796 {
18797 if context.start.cmp(&position, &snapshot).is_ge()
18798 || context.end.cmp(&position, &snapshot).is_lt()
18799 {
18800 continue;
18801 }
18802 let snapshot = self.buffer.read(cx).snapshot(cx);
18803 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18804
18805 handled = true;
18806 self.clear_row_highlights::<ActiveDebugLine>();
18807
18808 self.go_to_line::<ActiveDebugLine>(
18809 multibuffer_anchor,
18810 Some(cx.theme().colors().editor_debugger_active_line_background),
18811 window,
18812 cx,
18813 );
18814
18815 cx.notify();
18816 }
18817
18818 handled.then_some(())
18819 })
18820 .is_some()
18821 }
18822
18823 pub fn copy_file_name_without_extension(
18824 &mut self,
18825 _: &CopyFileNameWithoutExtension,
18826 _: &mut Window,
18827 cx: &mut Context<Self>,
18828 ) {
18829 if let Some(file) = self.target_file(cx) {
18830 if let Some(file_stem) = file.path().file_stem() {
18831 if let Some(name) = file_stem.to_str() {
18832 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18833 }
18834 }
18835 }
18836 }
18837
18838 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18839 if let Some(file) = self.target_file(cx) {
18840 if let Some(file_name) = file.path().file_name() {
18841 if let Some(name) = file_name.to_str() {
18842 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18843 }
18844 }
18845 }
18846 }
18847
18848 pub fn toggle_git_blame(
18849 &mut self,
18850 _: &::git::Blame,
18851 window: &mut Window,
18852 cx: &mut Context<Self>,
18853 ) {
18854 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18855
18856 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18857 self.start_git_blame(true, window, cx);
18858 }
18859
18860 cx.notify();
18861 }
18862
18863 pub fn toggle_git_blame_inline(
18864 &mut self,
18865 _: &ToggleGitBlameInline,
18866 window: &mut Window,
18867 cx: &mut Context<Self>,
18868 ) {
18869 self.toggle_git_blame_inline_internal(true, window, cx);
18870 cx.notify();
18871 }
18872
18873 pub fn open_git_blame_commit(
18874 &mut self,
18875 _: &OpenGitBlameCommit,
18876 window: &mut Window,
18877 cx: &mut Context<Self>,
18878 ) {
18879 self.open_git_blame_commit_internal(window, cx);
18880 }
18881
18882 fn open_git_blame_commit_internal(
18883 &mut self,
18884 window: &mut Window,
18885 cx: &mut Context<Self>,
18886 ) -> Option<()> {
18887 let blame = self.blame.as_ref()?;
18888 let snapshot = self.snapshot(window, cx);
18889 let cursor = self.selections.newest::<Point>(cx).head();
18890 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18891 let blame_entry = blame
18892 .update(cx, |blame, cx| {
18893 blame
18894 .blame_for_rows(
18895 &[RowInfo {
18896 buffer_id: Some(buffer.remote_id()),
18897 buffer_row: Some(point.row),
18898 ..Default::default()
18899 }],
18900 cx,
18901 )
18902 .next()
18903 })
18904 .flatten()?;
18905 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18906 let repo = blame.read(cx).repository(cx)?;
18907 let workspace = self.workspace()?.downgrade();
18908 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18909 None
18910 }
18911
18912 pub fn git_blame_inline_enabled(&self) -> bool {
18913 self.git_blame_inline_enabled
18914 }
18915
18916 pub fn toggle_selection_menu(
18917 &mut self,
18918 _: &ToggleSelectionMenu,
18919 _: &mut Window,
18920 cx: &mut Context<Self>,
18921 ) {
18922 self.show_selection_menu = self
18923 .show_selection_menu
18924 .map(|show_selections_menu| !show_selections_menu)
18925 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18926
18927 cx.notify();
18928 }
18929
18930 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18931 self.show_selection_menu
18932 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18933 }
18934
18935 fn start_git_blame(
18936 &mut self,
18937 user_triggered: bool,
18938 window: &mut Window,
18939 cx: &mut Context<Self>,
18940 ) {
18941 if let Some(project) = self.project() {
18942 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18943 return;
18944 };
18945
18946 if buffer.read(cx).file().is_none() {
18947 return;
18948 }
18949
18950 let focused = self.focus_handle(cx).contains_focused(window, cx);
18951
18952 let project = project.clone();
18953 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18954 self.blame_subscription =
18955 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18956 self.blame = Some(blame);
18957 }
18958 }
18959
18960 fn toggle_git_blame_inline_internal(
18961 &mut self,
18962 user_triggered: bool,
18963 window: &mut Window,
18964 cx: &mut Context<Self>,
18965 ) {
18966 if self.git_blame_inline_enabled {
18967 self.git_blame_inline_enabled = false;
18968 self.show_git_blame_inline = false;
18969 self.show_git_blame_inline_delay_task.take();
18970 } else {
18971 self.git_blame_inline_enabled = true;
18972 self.start_git_blame_inline(user_triggered, window, cx);
18973 }
18974
18975 cx.notify();
18976 }
18977
18978 fn start_git_blame_inline(
18979 &mut self,
18980 user_triggered: bool,
18981 window: &mut Window,
18982 cx: &mut Context<Self>,
18983 ) {
18984 self.start_git_blame(user_triggered, window, cx);
18985
18986 if ProjectSettings::get_global(cx)
18987 .git
18988 .inline_blame_delay()
18989 .is_some()
18990 {
18991 self.start_inline_blame_timer(window, cx);
18992 } else {
18993 self.show_git_blame_inline = true
18994 }
18995 }
18996
18997 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18998 self.blame.as_ref()
18999 }
19000
19001 pub fn show_git_blame_gutter(&self) -> bool {
19002 self.show_git_blame_gutter
19003 }
19004
19005 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19006 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19007 }
19008
19009 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19010 self.show_git_blame_inline
19011 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19012 && !self.newest_selection_head_on_empty_line(cx)
19013 && self.has_blame_entries(cx)
19014 }
19015
19016 fn has_blame_entries(&self, cx: &App) -> bool {
19017 self.blame()
19018 .map_or(false, |blame| blame.read(cx).has_generated_entries())
19019 }
19020
19021 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19022 let cursor_anchor = self.selections.newest_anchor().head();
19023
19024 let snapshot = self.buffer.read(cx).snapshot(cx);
19025 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19026
19027 snapshot.line_len(buffer_row) == 0
19028 }
19029
19030 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19031 let buffer_and_selection = maybe!({
19032 let selection = self.selections.newest::<Point>(cx);
19033 let selection_range = selection.range();
19034
19035 let multi_buffer = self.buffer().read(cx);
19036 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19037 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19038
19039 let (buffer, range, _) = if selection.reversed {
19040 buffer_ranges.first()
19041 } else {
19042 buffer_ranges.last()
19043 }?;
19044
19045 let selection = text::ToPoint::to_point(&range.start, &buffer).row
19046 ..text::ToPoint::to_point(&range.end, &buffer).row;
19047 Some((
19048 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
19049 selection,
19050 ))
19051 });
19052
19053 let Some((buffer, selection)) = buffer_and_selection else {
19054 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19055 };
19056
19057 let Some(project) = self.project() else {
19058 return Task::ready(Err(anyhow!("editor does not have project")));
19059 };
19060
19061 project.update(cx, |project, cx| {
19062 project.get_permalink_to_line(&buffer, selection, cx)
19063 })
19064 }
19065
19066 pub fn copy_permalink_to_line(
19067 &mut self,
19068 _: &CopyPermalinkToLine,
19069 window: &mut Window,
19070 cx: &mut Context<Self>,
19071 ) {
19072 let permalink_task = self.get_permalink_to_line(cx);
19073 let workspace = self.workspace();
19074
19075 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19076 Ok(permalink) => {
19077 cx.update(|_, cx| {
19078 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19079 })
19080 .ok();
19081 }
19082 Err(err) => {
19083 let message = format!("Failed to copy permalink: {err}");
19084
19085 anyhow::Result::<()>::Err(err).log_err();
19086
19087 if let Some(workspace) = workspace {
19088 workspace
19089 .update_in(cx, |workspace, _, cx| {
19090 struct CopyPermalinkToLine;
19091
19092 workspace.show_toast(
19093 Toast::new(
19094 NotificationId::unique::<CopyPermalinkToLine>(),
19095 message,
19096 ),
19097 cx,
19098 )
19099 })
19100 .ok();
19101 }
19102 }
19103 })
19104 .detach();
19105 }
19106
19107 pub fn copy_file_location(
19108 &mut self,
19109 _: &CopyFileLocation,
19110 _: &mut Window,
19111 cx: &mut Context<Self>,
19112 ) {
19113 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19114 if let Some(file) = self.target_file(cx) {
19115 if let Some(path) = file.path().to_str() {
19116 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19117 }
19118 }
19119 }
19120
19121 pub fn open_permalink_to_line(
19122 &mut self,
19123 _: &OpenPermalinkToLine,
19124 window: &mut Window,
19125 cx: &mut Context<Self>,
19126 ) {
19127 let permalink_task = self.get_permalink_to_line(cx);
19128 let workspace = self.workspace();
19129
19130 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19131 Ok(permalink) => {
19132 cx.update(|_, cx| {
19133 cx.open_url(permalink.as_ref());
19134 })
19135 .ok();
19136 }
19137 Err(err) => {
19138 let message = format!("Failed to open permalink: {err}");
19139
19140 anyhow::Result::<()>::Err(err).log_err();
19141
19142 if let Some(workspace) = workspace {
19143 workspace
19144 .update(cx, |workspace, cx| {
19145 struct OpenPermalinkToLine;
19146
19147 workspace.show_toast(
19148 Toast::new(
19149 NotificationId::unique::<OpenPermalinkToLine>(),
19150 message,
19151 ),
19152 cx,
19153 )
19154 })
19155 .ok();
19156 }
19157 }
19158 })
19159 .detach();
19160 }
19161
19162 pub fn insert_uuid_v4(
19163 &mut self,
19164 _: &InsertUuidV4,
19165 window: &mut Window,
19166 cx: &mut Context<Self>,
19167 ) {
19168 self.insert_uuid(UuidVersion::V4, window, cx);
19169 }
19170
19171 pub fn insert_uuid_v7(
19172 &mut self,
19173 _: &InsertUuidV7,
19174 window: &mut Window,
19175 cx: &mut Context<Self>,
19176 ) {
19177 self.insert_uuid(UuidVersion::V7, window, cx);
19178 }
19179
19180 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19181 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19182 self.transact(window, cx, |this, window, cx| {
19183 let edits = this
19184 .selections
19185 .all::<Point>(cx)
19186 .into_iter()
19187 .map(|selection| {
19188 let uuid = match version {
19189 UuidVersion::V4 => uuid::Uuid::new_v4(),
19190 UuidVersion::V7 => uuid::Uuid::now_v7(),
19191 };
19192
19193 (selection.range(), uuid.to_string())
19194 });
19195 this.edit(edits, cx);
19196 this.refresh_edit_prediction(true, false, window, cx);
19197 });
19198 }
19199
19200 pub fn open_selections_in_multibuffer(
19201 &mut self,
19202 _: &OpenSelectionsInMultibuffer,
19203 window: &mut Window,
19204 cx: &mut Context<Self>,
19205 ) {
19206 let multibuffer = self.buffer.read(cx);
19207
19208 let Some(buffer) = multibuffer.as_singleton() else {
19209 return;
19210 };
19211
19212 let Some(workspace) = self.workspace() else {
19213 return;
19214 };
19215
19216 let title = multibuffer.title(cx).to_string();
19217
19218 let locations = self
19219 .selections
19220 .all_anchors(cx)
19221 .into_iter()
19222 .map(|selection| Location {
19223 buffer: buffer.clone(),
19224 range: selection.start.text_anchor..selection.end.text_anchor,
19225 })
19226 .collect::<Vec<_>>();
19227
19228 cx.spawn_in(window, async move |_, cx| {
19229 workspace.update_in(cx, |workspace, window, cx| {
19230 Self::open_locations_in_multibuffer(
19231 workspace,
19232 locations,
19233 format!("Selections for '{title}'"),
19234 false,
19235 MultibufferSelectionMode::All,
19236 window,
19237 cx,
19238 );
19239 })
19240 })
19241 .detach();
19242 }
19243
19244 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19245 /// last highlight added will be used.
19246 ///
19247 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19248 pub fn highlight_rows<T: 'static>(
19249 &mut self,
19250 range: Range<Anchor>,
19251 color: Hsla,
19252 options: RowHighlightOptions,
19253 cx: &mut Context<Self>,
19254 ) {
19255 let snapshot = self.buffer().read(cx).snapshot(cx);
19256 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19257 let ix = row_highlights.binary_search_by(|highlight| {
19258 Ordering::Equal
19259 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19260 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19261 });
19262
19263 if let Err(mut ix) = ix {
19264 let index = post_inc(&mut self.highlight_order);
19265
19266 // If this range intersects with the preceding highlight, then merge it with
19267 // the preceding highlight. Otherwise insert a new highlight.
19268 let mut merged = false;
19269 if ix > 0 {
19270 let prev_highlight = &mut row_highlights[ix - 1];
19271 if prev_highlight
19272 .range
19273 .end
19274 .cmp(&range.start, &snapshot)
19275 .is_ge()
19276 {
19277 ix -= 1;
19278 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19279 prev_highlight.range.end = range.end;
19280 }
19281 merged = true;
19282 prev_highlight.index = index;
19283 prev_highlight.color = color;
19284 prev_highlight.options = options;
19285 }
19286 }
19287
19288 if !merged {
19289 row_highlights.insert(
19290 ix,
19291 RowHighlight {
19292 range: range.clone(),
19293 index,
19294 color,
19295 options,
19296 type_id: TypeId::of::<T>(),
19297 },
19298 );
19299 }
19300
19301 // If any of the following highlights intersect with this one, merge them.
19302 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19303 let highlight = &row_highlights[ix];
19304 if next_highlight
19305 .range
19306 .start
19307 .cmp(&highlight.range.end, &snapshot)
19308 .is_le()
19309 {
19310 if next_highlight
19311 .range
19312 .end
19313 .cmp(&highlight.range.end, &snapshot)
19314 .is_gt()
19315 {
19316 row_highlights[ix].range.end = next_highlight.range.end;
19317 }
19318 row_highlights.remove(ix + 1);
19319 } else {
19320 break;
19321 }
19322 }
19323 }
19324 }
19325
19326 /// Remove any highlighted row ranges of the given type that intersect the
19327 /// given ranges.
19328 pub fn remove_highlighted_rows<T: 'static>(
19329 &mut self,
19330 ranges_to_remove: Vec<Range<Anchor>>,
19331 cx: &mut Context<Self>,
19332 ) {
19333 let snapshot = self.buffer().read(cx).snapshot(cx);
19334 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19335 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19336 row_highlights.retain(|highlight| {
19337 while let Some(range_to_remove) = ranges_to_remove.peek() {
19338 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19339 Ordering::Less | Ordering::Equal => {
19340 ranges_to_remove.next();
19341 }
19342 Ordering::Greater => {
19343 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19344 Ordering::Less | Ordering::Equal => {
19345 return false;
19346 }
19347 Ordering::Greater => break,
19348 }
19349 }
19350 }
19351 }
19352
19353 true
19354 })
19355 }
19356
19357 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19358 pub fn clear_row_highlights<T: 'static>(&mut self) {
19359 self.highlighted_rows.remove(&TypeId::of::<T>());
19360 }
19361
19362 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19363 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19364 self.highlighted_rows
19365 .get(&TypeId::of::<T>())
19366 .map_or(&[] as &[_], |vec| vec.as_slice())
19367 .iter()
19368 .map(|highlight| (highlight.range.clone(), highlight.color))
19369 }
19370
19371 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19372 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19373 /// Allows to ignore certain kinds of highlights.
19374 pub fn highlighted_display_rows(
19375 &self,
19376 window: &mut Window,
19377 cx: &mut App,
19378 ) -> BTreeMap<DisplayRow, LineHighlight> {
19379 let snapshot = self.snapshot(window, cx);
19380 let mut used_highlight_orders = HashMap::default();
19381 self.highlighted_rows
19382 .iter()
19383 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19384 .fold(
19385 BTreeMap::<DisplayRow, LineHighlight>::new(),
19386 |mut unique_rows, highlight| {
19387 let start = highlight.range.start.to_display_point(&snapshot);
19388 let end = highlight.range.end.to_display_point(&snapshot);
19389 let start_row = start.row().0;
19390 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19391 && end.column() == 0
19392 {
19393 end.row().0.saturating_sub(1)
19394 } else {
19395 end.row().0
19396 };
19397 for row in start_row..=end_row {
19398 let used_index =
19399 used_highlight_orders.entry(row).or_insert(highlight.index);
19400 if highlight.index >= *used_index {
19401 *used_index = highlight.index;
19402 unique_rows.insert(
19403 DisplayRow(row),
19404 LineHighlight {
19405 include_gutter: highlight.options.include_gutter,
19406 border: None,
19407 background: highlight.color.into(),
19408 type_id: Some(highlight.type_id),
19409 },
19410 );
19411 }
19412 }
19413 unique_rows
19414 },
19415 )
19416 }
19417
19418 pub fn highlighted_display_row_for_autoscroll(
19419 &self,
19420 snapshot: &DisplaySnapshot,
19421 ) -> Option<DisplayRow> {
19422 self.highlighted_rows
19423 .values()
19424 .flat_map(|highlighted_rows| highlighted_rows.iter())
19425 .filter_map(|highlight| {
19426 if highlight.options.autoscroll {
19427 Some(highlight.range.start.to_display_point(snapshot).row())
19428 } else {
19429 None
19430 }
19431 })
19432 .min()
19433 }
19434
19435 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19436 self.highlight_background::<SearchWithinRange>(
19437 ranges,
19438 |colors| colors.colors().editor_document_highlight_read_background,
19439 cx,
19440 )
19441 }
19442
19443 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19444 self.breadcrumb_header = Some(new_header);
19445 }
19446
19447 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19448 self.clear_background_highlights::<SearchWithinRange>(cx);
19449 }
19450
19451 pub fn highlight_background<T: 'static>(
19452 &mut self,
19453 ranges: &[Range<Anchor>],
19454 color_fetcher: fn(&Theme) -> Hsla,
19455 cx: &mut Context<Self>,
19456 ) {
19457 self.background_highlights.insert(
19458 HighlightKey::Type(TypeId::of::<T>()),
19459 (color_fetcher, Arc::from(ranges)),
19460 );
19461 self.scrollbar_marker_state.dirty = true;
19462 cx.notify();
19463 }
19464
19465 pub fn highlight_background_key<T: 'static>(
19466 &mut self,
19467 key: usize,
19468 ranges: &[Range<Anchor>],
19469 color_fetcher: fn(&Theme) -> Hsla,
19470 cx: &mut Context<Self>,
19471 ) {
19472 self.background_highlights.insert(
19473 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19474 (color_fetcher, Arc::from(ranges)),
19475 );
19476 self.scrollbar_marker_state.dirty = true;
19477 cx.notify();
19478 }
19479
19480 pub fn clear_background_highlights<T: 'static>(
19481 &mut self,
19482 cx: &mut Context<Self>,
19483 ) -> Option<BackgroundHighlight> {
19484 let text_highlights = self
19485 .background_highlights
19486 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19487 if !text_highlights.1.is_empty() {
19488 self.scrollbar_marker_state.dirty = true;
19489 cx.notify();
19490 }
19491 Some(text_highlights)
19492 }
19493
19494 pub fn highlight_gutter<T: 'static>(
19495 &mut self,
19496 ranges: impl Into<Vec<Range<Anchor>>>,
19497 color_fetcher: fn(&App) -> Hsla,
19498 cx: &mut Context<Self>,
19499 ) {
19500 self.gutter_highlights
19501 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19502 cx.notify();
19503 }
19504
19505 pub fn clear_gutter_highlights<T: 'static>(
19506 &mut self,
19507 cx: &mut Context<Self>,
19508 ) -> Option<GutterHighlight> {
19509 cx.notify();
19510 self.gutter_highlights.remove(&TypeId::of::<T>())
19511 }
19512
19513 pub fn insert_gutter_highlight<T: 'static>(
19514 &mut self,
19515 range: Range<Anchor>,
19516 color_fetcher: fn(&App) -> Hsla,
19517 cx: &mut Context<Self>,
19518 ) {
19519 let snapshot = self.buffer().read(cx).snapshot(cx);
19520 let mut highlights = self
19521 .gutter_highlights
19522 .remove(&TypeId::of::<T>())
19523 .map(|(_, highlights)| highlights)
19524 .unwrap_or_default();
19525 let ix = highlights.binary_search_by(|highlight| {
19526 Ordering::Equal
19527 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19528 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19529 });
19530 if let Err(ix) = ix {
19531 highlights.insert(ix, range);
19532 }
19533 self.gutter_highlights
19534 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19535 }
19536
19537 pub fn remove_gutter_highlights<T: 'static>(
19538 &mut self,
19539 ranges_to_remove: Vec<Range<Anchor>>,
19540 cx: &mut Context<Self>,
19541 ) {
19542 let snapshot = self.buffer().read(cx).snapshot(cx);
19543 let Some((color_fetcher, mut gutter_highlights)) =
19544 self.gutter_highlights.remove(&TypeId::of::<T>())
19545 else {
19546 return;
19547 };
19548 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19549 gutter_highlights.retain(|highlight| {
19550 while let Some(range_to_remove) = ranges_to_remove.peek() {
19551 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19552 Ordering::Less | Ordering::Equal => {
19553 ranges_to_remove.next();
19554 }
19555 Ordering::Greater => {
19556 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19557 Ordering::Less | Ordering::Equal => {
19558 return false;
19559 }
19560 Ordering::Greater => break,
19561 }
19562 }
19563 }
19564 }
19565
19566 true
19567 });
19568 self.gutter_highlights
19569 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19570 }
19571
19572 #[cfg(feature = "test-support")]
19573 pub fn all_text_highlights(
19574 &self,
19575 window: &mut Window,
19576 cx: &mut Context<Self>,
19577 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19578 let snapshot = self.snapshot(window, cx);
19579 self.display_map.update(cx, |display_map, _| {
19580 display_map
19581 .all_text_highlights()
19582 .map(|highlight| {
19583 let (style, ranges) = highlight.as_ref();
19584 (
19585 *style,
19586 ranges
19587 .iter()
19588 .map(|range| range.clone().to_display_points(&snapshot))
19589 .collect(),
19590 )
19591 })
19592 .collect()
19593 })
19594 }
19595
19596 #[cfg(feature = "test-support")]
19597 pub fn all_text_background_highlights(
19598 &self,
19599 window: &mut Window,
19600 cx: &mut Context<Self>,
19601 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19602 let snapshot = self.snapshot(window, cx);
19603 let buffer = &snapshot.buffer_snapshot;
19604 let start = buffer.anchor_before(0);
19605 let end = buffer.anchor_after(buffer.len());
19606 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19607 }
19608
19609 #[cfg(feature = "test-support")]
19610 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19611 let snapshot = self.buffer().read(cx).snapshot(cx);
19612
19613 let highlights = self
19614 .background_highlights
19615 .get(&HighlightKey::Type(TypeId::of::<
19616 items::BufferSearchHighlights,
19617 >()));
19618
19619 if let Some((_color, ranges)) = highlights {
19620 ranges
19621 .iter()
19622 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19623 .collect_vec()
19624 } else {
19625 vec![]
19626 }
19627 }
19628
19629 fn document_highlights_for_position<'a>(
19630 &'a self,
19631 position: Anchor,
19632 buffer: &'a MultiBufferSnapshot,
19633 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19634 let read_highlights = self
19635 .background_highlights
19636 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19637 .map(|h| &h.1);
19638 let write_highlights = self
19639 .background_highlights
19640 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19641 .map(|h| &h.1);
19642 let left_position = position.bias_left(buffer);
19643 let right_position = position.bias_right(buffer);
19644 read_highlights
19645 .into_iter()
19646 .chain(write_highlights)
19647 .flat_map(move |ranges| {
19648 let start_ix = match ranges.binary_search_by(|probe| {
19649 let cmp = probe.end.cmp(&left_position, buffer);
19650 if cmp.is_ge() {
19651 Ordering::Greater
19652 } else {
19653 Ordering::Less
19654 }
19655 }) {
19656 Ok(i) | Err(i) => i,
19657 };
19658
19659 ranges[start_ix..]
19660 .iter()
19661 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19662 })
19663 }
19664
19665 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19666 self.background_highlights
19667 .get(&HighlightKey::Type(TypeId::of::<T>()))
19668 .map_or(false, |(_, highlights)| !highlights.is_empty())
19669 }
19670
19671 pub fn background_highlights_in_range(
19672 &self,
19673 search_range: Range<Anchor>,
19674 display_snapshot: &DisplaySnapshot,
19675 theme: &Theme,
19676 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19677 let mut results = Vec::new();
19678 for (color_fetcher, ranges) in self.background_highlights.values() {
19679 let color = color_fetcher(theme);
19680 let start_ix = match ranges.binary_search_by(|probe| {
19681 let cmp = probe
19682 .end
19683 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19684 if cmp.is_gt() {
19685 Ordering::Greater
19686 } else {
19687 Ordering::Less
19688 }
19689 }) {
19690 Ok(i) | Err(i) => i,
19691 };
19692 for range in &ranges[start_ix..] {
19693 if range
19694 .start
19695 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19696 .is_ge()
19697 {
19698 break;
19699 }
19700
19701 let start = range.start.to_display_point(display_snapshot);
19702 let end = range.end.to_display_point(display_snapshot);
19703 results.push((start..end, color))
19704 }
19705 }
19706 results
19707 }
19708
19709 pub fn background_highlight_row_ranges<T: 'static>(
19710 &self,
19711 search_range: Range<Anchor>,
19712 display_snapshot: &DisplaySnapshot,
19713 count: usize,
19714 ) -> Vec<RangeInclusive<DisplayPoint>> {
19715 let mut results = Vec::new();
19716 let Some((_, ranges)) = self
19717 .background_highlights
19718 .get(&HighlightKey::Type(TypeId::of::<T>()))
19719 else {
19720 return vec![];
19721 };
19722
19723 let start_ix = match ranges.binary_search_by(|probe| {
19724 let cmp = probe
19725 .end
19726 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19727 if cmp.is_gt() {
19728 Ordering::Greater
19729 } else {
19730 Ordering::Less
19731 }
19732 }) {
19733 Ok(i) | Err(i) => i,
19734 };
19735 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19736 if let (Some(start_display), Some(end_display)) = (start, end) {
19737 results.push(
19738 start_display.to_display_point(display_snapshot)
19739 ..=end_display.to_display_point(display_snapshot),
19740 );
19741 }
19742 };
19743 let mut start_row: Option<Point> = None;
19744 let mut end_row: Option<Point> = None;
19745 if ranges.len() > count {
19746 return Vec::new();
19747 }
19748 for range in &ranges[start_ix..] {
19749 if range
19750 .start
19751 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19752 .is_ge()
19753 {
19754 break;
19755 }
19756 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19757 if let Some(current_row) = &end_row {
19758 if end.row == current_row.row {
19759 continue;
19760 }
19761 }
19762 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19763 if start_row.is_none() {
19764 assert_eq!(end_row, None);
19765 start_row = Some(start);
19766 end_row = Some(end);
19767 continue;
19768 }
19769 if let Some(current_end) = end_row.as_mut() {
19770 if start.row > current_end.row + 1 {
19771 push_region(start_row, end_row);
19772 start_row = Some(start);
19773 end_row = Some(end);
19774 } else {
19775 // Merge two hunks.
19776 *current_end = end;
19777 }
19778 } else {
19779 unreachable!();
19780 }
19781 }
19782 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19783 push_region(start_row, end_row);
19784 results
19785 }
19786
19787 pub fn gutter_highlights_in_range(
19788 &self,
19789 search_range: Range<Anchor>,
19790 display_snapshot: &DisplaySnapshot,
19791 cx: &App,
19792 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19793 let mut results = Vec::new();
19794 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19795 let color = color_fetcher(cx);
19796 let start_ix = match ranges.binary_search_by(|probe| {
19797 let cmp = probe
19798 .end
19799 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19800 if cmp.is_gt() {
19801 Ordering::Greater
19802 } else {
19803 Ordering::Less
19804 }
19805 }) {
19806 Ok(i) | Err(i) => i,
19807 };
19808 for range in &ranges[start_ix..] {
19809 if range
19810 .start
19811 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19812 .is_ge()
19813 {
19814 break;
19815 }
19816
19817 let start = range.start.to_display_point(display_snapshot);
19818 let end = range.end.to_display_point(display_snapshot);
19819 results.push((start..end, color))
19820 }
19821 }
19822 results
19823 }
19824
19825 /// Get the text ranges corresponding to the redaction query
19826 pub fn redacted_ranges(
19827 &self,
19828 search_range: Range<Anchor>,
19829 display_snapshot: &DisplaySnapshot,
19830 cx: &App,
19831 ) -> Vec<Range<DisplayPoint>> {
19832 display_snapshot
19833 .buffer_snapshot
19834 .redacted_ranges(search_range, |file| {
19835 if let Some(file) = file {
19836 file.is_private()
19837 && EditorSettings::get(
19838 Some(SettingsLocation {
19839 worktree_id: file.worktree_id(cx),
19840 path: file.path().as_ref(),
19841 }),
19842 cx,
19843 )
19844 .redact_private_values
19845 } else {
19846 false
19847 }
19848 })
19849 .map(|range| {
19850 range.start.to_display_point(display_snapshot)
19851 ..range.end.to_display_point(display_snapshot)
19852 })
19853 .collect()
19854 }
19855
19856 pub fn highlight_text_key<T: 'static>(
19857 &mut self,
19858 key: usize,
19859 ranges: Vec<Range<Anchor>>,
19860 style: HighlightStyle,
19861 cx: &mut Context<Self>,
19862 ) {
19863 self.display_map.update(cx, |map, _| {
19864 map.highlight_text(
19865 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19866 ranges,
19867 style,
19868 );
19869 });
19870 cx.notify();
19871 }
19872
19873 pub fn highlight_text<T: 'static>(
19874 &mut self,
19875 ranges: Vec<Range<Anchor>>,
19876 style: HighlightStyle,
19877 cx: &mut Context<Self>,
19878 ) {
19879 self.display_map.update(cx, |map, _| {
19880 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19881 });
19882 cx.notify();
19883 }
19884
19885 pub(crate) fn highlight_inlays<T: 'static>(
19886 &mut self,
19887 highlights: Vec<InlayHighlight>,
19888 style: HighlightStyle,
19889 cx: &mut Context<Self>,
19890 ) {
19891 self.display_map.update(cx, |map, _| {
19892 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19893 });
19894 cx.notify();
19895 }
19896
19897 pub fn text_highlights<'a, T: 'static>(
19898 &'a self,
19899 cx: &'a App,
19900 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19901 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19902 }
19903
19904 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19905 let cleared = self
19906 .display_map
19907 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19908 if cleared {
19909 cx.notify();
19910 }
19911 }
19912
19913 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19914 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19915 && self.focus_handle.is_focused(window)
19916 }
19917
19918 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19919 self.show_cursor_when_unfocused = is_enabled;
19920 cx.notify();
19921 }
19922
19923 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19924 cx.notify();
19925 }
19926
19927 fn on_debug_session_event(
19928 &mut self,
19929 _session: Entity<Session>,
19930 event: &SessionEvent,
19931 cx: &mut Context<Self>,
19932 ) {
19933 match event {
19934 SessionEvent::InvalidateInlineValue => {
19935 self.refresh_inline_values(cx);
19936 }
19937 _ => {}
19938 }
19939 }
19940
19941 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19942 let Some(project) = self.project.clone() else {
19943 return;
19944 };
19945
19946 if !self.inline_value_cache.enabled {
19947 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19948 self.splice_inlays(&inlays, Vec::new(), cx);
19949 return;
19950 }
19951
19952 let current_execution_position = self
19953 .highlighted_rows
19954 .get(&TypeId::of::<ActiveDebugLine>())
19955 .and_then(|lines| lines.last().map(|line| line.range.end));
19956
19957 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19958 let inline_values = editor
19959 .update(cx, |editor, cx| {
19960 let Some(current_execution_position) = current_execution_position else {
19961 return Some(Task::ready(Ok(Vec::new())));
19962 };
19963
19964 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19965 let snapshot = buffer.snapshot(cx);
19966
19967 let excerpt = snapshot.excerpt_containing(
19968 current_execution_position..current_execution_position,
19969 )?;
19970
19971 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19972 })?;
19973
19974 let range =
19975 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19976
19977 project.inline_values(buffer, range, cx)
19978 })
19979 .ok()
19980 .flatten()?
19981 .await
19982 .context("refreshing debugger inlays")
19983 .log_err()?;
19984
19985 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19986
19987 for (buffer_id, inline_value) in inline_values
19988 .into_iter()
19989 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19990 {
19991 buffer_inline_values
19992 .entry(buffer_id)
19993 .or_default()
19994 .push(inline_value);
19995 }
19996
19997 editor
19998 .update(cx, |editor, cx| {
19999 let snapshot = editor.buffer.read(cx).snapshot(cx);
20000 let mut new_inlays = Vec::default();
20001
20002 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20003 let buffer_id = buffer_snapshot.remote_id();
20004 buffer_inline_values
20005 .get(&buffer_id)
20006 .into_iter()
20007 .flatten()
20008 .for_each(|hint| {
20009 let inlay = Inlay::debugger(
20010 post_inc(&mut editor.next_inlay_id),
20011 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20012 hint.text(),
20013 );
20014 if !inlay.text.chars().contains(&'\n') {
20015 new_inlays.push(inlay);
20016 }
20017 });
20018 }
20019
20020 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20021 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20022
20023 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20024 })
20025 .ok()?;
20026 Some(())
20027 });
20028 }
20029
20030 fn on_buffer_event(
20031 &mut self,
20032 multibuffer: &Entity<MultiBuffer>,
20033 event: &multi_buffer::Event,
20034 window: &mut Window,
20035 cx: &mut Context<Self>,
20036 ) {
20037 match event {
20038 multi_buffer::Event::Edited {
20039 singleton_buffer_edited,
20040 edited_buffer,
20041 } => {
20042 self.scrollbar_marker_state.dirty = true;
20043 self.active_indent_guides_state.dirty = true;
20044 self.refresh_active_diagnostics(cx);
20045 self.refresh_code_actions(window, cx);
20046 self.refresh_selected_text_highlights(true, window, cx);
20047 self.refresh_single_line_folds(window, cx);
20048 refresh_matching_bracket_highlights(self, window, cx);
20049 if self.has_active_edit_prediction() {
20050 self.update_visible_edit_prediction(window, cx);
20051 }
20052 if let Some(project) = self.project.as_ref() {
20053 if let Some(edited_buffer) = edited_buffer {
20054 project.update(cx, |project, cx| {
20055 self.registered_buffers
20056 .entry(edited_buffer.read(cx).remote_id())
20057 .or_insert_with(|| {
20058 project
20059 .register_buffer_with_language_servers(&edited_buffer, cx)
20060 });
20061 });
20062 }
20063 }
20064 cx.emit(EditorEvent::BufferEdited);
20065 cx.emit(SearchEvent::MatchesInvalidated);
20066
20067 if let Some(buffer) = edited_buffer {
20068 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20069 }
20070
20071 if *singleton_buffer_edited {
20072 if let Some(buffer) = edited_buffer {
20073 if buffer.read(cx).file().is_none() {
20074 cx.emit(EditorEvent::TitleChanged);
20075 }
20076 }
20077 if let Some(project) = &self.project {
20078 #[allow(clippy::mutable_key_type)]
20079 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20080 multibuffer
20081 .all_buffers()
20082 .into_iter()
20083 .filter_map(|buffer| {
20084 buffer.update(cx, |buffer, cx| {
20085 let language = buffer.language()?;
20086 let should_discard = project.update(cx, |project, cx| {
20087 project.is_local()
20088 && !project.has_language_servers_for(buffer, cx)
20089 });
20090 should_discard.not().then_some(language.clone())
20091 })
20092 })
20093 .collect::<HashSet<_>>()
20094 });
20095 if !languages_affected.is_empty() {
20096 self.refresh_inlay_hints(
20097 InlayHintRefreshReason::BufferEdited(languages_affected),
20098 cx,
20099 );
20100 }
20101 }
20102 }
20103
20104 let Some(project) = &self.project else { return };
20105 let (telemetry, is_via_ssh) = {
20106 let project = project.read(cx);
20107 let telemetry = project.client().telemetry().clone();
20108 let is_via_ssh = project.is_via_ssh();
20109 (telemetry, is_via_ssh)
20110 };
20111 refresh_linked_ranges(self, window, cx);
20112 telemetry.log_edit_event("editor", is_via_ssh);
20113 }
20114 multi_buffer::Event::ExcerptsAdded {
20115 buffer,
20116 predecessor,
20117 excerpts,
20118 } => {
20119 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20120 let buffer_id = buffer.read(cx).remote_id();
20121 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
20122 if let Some(project) = &self.project {
20123 update_uncommitted_diff_for_buffer(
20124 cx.entity(),
20125 project,
20126 [buffer.clone()],
20127 self.buffer.clone(),
20128 cx,
20129 )
20130 .detach();
20131 }
20132 }
20133 self.update_lsp_data(false, Some(buffer_id), window, cx);
20134 cx.emit(EditorEvent::ExcerptsAdded {
20135 buffer: buffer.clone(),
20136 predecessor: *predecessor,
20137 excerpts: excerpts.clone(),
20138 });
20139 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20140 }
20141 multi_buffer::Event::ExcerptsRemoved {
20142 ids,
20143 removed_buffer_ids,
20144 } => {
20145 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20146 let buffer = self.buffer.read(cx);
20147 self.registered_buffers
20148 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20149 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20150 cx.emit(EditorEvent::ExcerptsRemoved {
20151 ids: ids.clone(),
20152 removed_buffer_ids: removed_buffer_ids.clone(),
20153 });
20154 }
20155 multi_buffer::Event::ExcerptsEdited {
20156 excerpt_ids,
20157 buffer_ids,
20158 } => {
20159 self.display_map.update(cx, |map, cx| {
20160 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20161 });
20162 cx.emit(EditorEvent::ExcerptsEdited {
20163 ids: excerpt_ids.clone(),
20164 });
20165 }
20166 multi_buffer::Event::ExcerptsExpanded { ids } => {
20167 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20168 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20169 }
20170 multi_buffer::Event::Reparsed(buffer_id) => {
20171 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20172 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20173
20174 cx.emit(EditorEvent::Reparsed(*buffer_id));
20175 }
20176 multi_buffer::Event::DiffHunksToggled => {
20177 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20178 }
20179 multi_buffer::Event::LanguageChanged(buffer_id) => {
20180 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20181 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20182 cx.emit(EditorEvent::Reparsed(*buffer_id));
20183 cx.notify();
20184 }
20185 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20186 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20187 multi_buffer::Event::FileHandleChanged
20188 | multi_buffer::Event::Reloaded
20189 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20190 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20191 multi_buffer::Event::DiagnosticsUpdated => {
20192 self.update_diagnostics_state(window, cx);
20193 }
20194 _ => {}
20195 };
20196 }
20197
20198 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20199 if !self.diagnostics_enabled() {
20200 return;
20201 }
20202 self.refresh_active_diagnostics(cx);
20203 self.refresh_inline_diagnostics(true, window, cx);
20204 self.scrollbar_marker_state.dirty = true;
20205 cx.notify();
20206 }
20207
20208 pub fn start_temporary_diff_override(&mut self) {
20209 self.load_diff_task.take();
20210 self.temporary_diff_override = true;
20211 }
20212
20213 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20214 self.temporary_diff_override = false;
20215 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20216 self.buffer.update(cx, |buffer, cx| {
20217 buffer.set_all_diff_hunks_collapsed(cx);
20218 });
20219
20220 if let Some(project) = self.project.clone() {
20221 self.load_diff_task = Some(
20222 update_uncommitted_diff_for_buffer(
20223 cx.entity(),
20224 &project,
20225 self.buffer.read(cx).all_buffers(),
20226 self.buffer.clone(),
20227 cx,
20228 )
20229 .shared(),
20230 );
20231 }
20232 }
20233
20234 fn on_display_map_changed(
20235 &mut self,
20236 _: Entity<DisplayMap>,
20237 _: &mut Window,
20238 cx: &mut Context<Self>,
20239 ) {
20240 cx.notify();
20241 }
20242
20243 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20244 if self.diagnostics_enabled() {
20245 let new_severity = EditorSettings::get_global(cx)
20246 .diagnostics_max_severity
20247 .unwrap_or(DiagnosticSeverity::Hint);
20248 self.set_max_diagnostics_severity(new_severity, cx);
20249 }
20250 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20251 self.update_edit_prediction_settings(cx);
20252 self.refresh_edit_prediction(true, false, window, cx);
20253 self.refresh_inline_values(cx);
20254 self.refresh_inlay_hints(
20255 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20256 self.selections.newest_anchor().head(),
20257 &self.buffer.read(cx).snapshot(cx),
20258 cx,
20259 )),
20260 cx,
20261 );
20262
20263 let old_cursor_shape = self.cursor_shape;
20264 let old_show_breadcrumbs = self.show_breadcrumbs;
20265
20266 {
20267 let editor_settings = EditorSettings::get_global(cx);
20268 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20269 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20270 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20271 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20272 }
20273
20274 if old_cursor_shape != self.cursor_shape {
20275 cx.emit(EditorEvent::CursorShapeChanged);
20276 }
20277
20278 if old_show_breadcrumbs != self.show_breadcrumbs {
20279 cx.emit(EditorEvent::BreadcrumbsChanged);
20280 }
20281
20282 let project_settings = ProjectSettings::get_global(cx);
20283 self.serialize_dirty_buffers =
20284 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20285
20286 if self.mode.is_full() {
20287 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20288 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20289 if self.show_inline_diagnostics != show_inline_diagnostics {
20290 self.show_inline_diagnostics = show_inline_diagnostics;
20291 self.refresh_inline_diagnostics(false, window, cx);
20292 }
20293
20294 if self.git_blame_inline_enabled != inline_blame_enabled {
20295 self.toggle_git_blame_inline_internal(false, window, cx);
20296 }
20297
20298 let minimap_settings = EditorSettings::get_global(cx).minimap;
20299 if self.minimap_visibility != MinimapVisibility::Disabled {
20300 if self.minimap_visibility.settings_visibility()
20301 != minimap_settings.minimap_enabled()
20302 {
20303 self.set_minimap_visibility(
20304 MinimapVisibility::for_mode(self.mode(), cx),
20305 window,
20306 cx,
20307 );
20308 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20309 minimap_entity.update(cx, |minimap_editor, cx| {
20310 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20311 })
20312 }
20313 }
20314 }
20315
20316 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20317 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20318 }) {
20319 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20320 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20321 }
20322 self.refresh_colors(false, None, window, cx);
20323 }
20324
20325 cx.notify();
20326 }
20327
20328 pub fn set_searchable(&mut self, searchable: bool) {
20329 self.searchable = searchable;
20330 }
20331
20332 pub fn searchable(&self) -> bool {
20333 self.searchable
20334 }
20335
20336 fn open_proposed_changes_editor(
20337 &mut self,
20338 _: &OpenProposedChangesEditor,
20339 window: &mut Window,
20340 cx: &mut Context<Self>,
20341 ) {
20342 let Some(workspace) = self.workspace() else {
20343 cx.propagate();
20344 return;
20345 };
20346
20347 let selections = self.selections.all::<usize>(cx);
20348 let multi_buffer = self.buffer.read(cx);
20349 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20350 let mut new_selections_by_buffer = HashMap::default();
20351 for selection in selections {
20352 for (buffer, range, _) in
20353 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20354 {
20355 let mut range = range.to_point(buffer);
20356 range.start.column = 0;
20357 range.end.column = buffer.line_len(range.end.row);
20358 new_selections_by_buffer
20359 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20360 .or_insert(Vec::new())
20361 .push(range)
20362 }
20363 }
20364
20365 let proposed_changes_buffers = new_selections_by_buffer
20366 .into_iter()
20367 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20368 .collect::<Vec<_>>();
20369 let proposed_changes_editor = cx.new(|cx| {
20370 ProposedChangesEditor::new(
20371 "Proposed changes",
20372 proposed_changes_buffers,
20373 self.project.clone(),
20374 window,
20375 cx,
20376 )
20377 });
20378
20379 window.defer(cx, move |window, cx| {
20380 workspace.update(cx, |workspace, cx| {
20381 workspace.active_pane().update(cx, |pane, cx| {
20382 pane.add_item(
20383 Box::new(proposed_changes_editor),
20384 true,
20385 true,
20386 None,
20387 window,
20388 cx,
20389 );
20390 });
20391 });
20392 });
20393 }
20394
20395 pub fn open_excerpts_in_split(
20396 &mut self,
20397 _: &OpenExcerptsSplit,
20398 window: &mut Window,
20399 cx: &mut Context<Self>,
20400 ) {
20401 self.open_excerpts_common(None, true, window, cx)
20402 }
20403
20404 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20405 self.open_excerpts_common(None, false, window, cx)
20406 }
20407
20408 fn open_excerpts_common(
20409 &mut self,
20410 jump_data: Option<JumpData>,
20411 split: bool,
20412 window: &mut Window,
20413 cx: &mut Context<Self>,
20414 ) {
20415 let Some(workspace) = self.workspace() else {
20416 cx.propagate();
20417 return;
20418 };
20419
20420 if self.buffer.read(cx).is_singleton() {
20421 cx.propagate();
20422 return;
20423 }
20424
20425 let mut new_selections_by_buffer = HashMap::default();
20426 match &jump_data {
20427 Some(JumpData::MultiBufferPoint {
20428 excerpt_id,
20429 position,
20430 anchor,
20431 line_offset_from_top,
20432 }) => {
20433 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20434 if let Some(buffer) = multi_buffer_snapshot
20435 .buffer_id_for_excerpt(*excerpt_id)
20436 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20437 {
20438 let buffer_snapshot = buffer.read(cx).snapshot();
20439 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20440 language::ToPoint::to_point(anchor, &buffer_snapshot)
20441 } else {
20442 buffer_snapshot.clip_point(*position, Bias::Left)
20443 };
20444 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20445 new_selections_by_buffer.insert(
20446 buffer,
20447 (
20448 vec![jump_to_offset..jump_to_offset],
20449 Some(*line_offset_from_top),
20450 ),
20451 );
20452 }
20453 }
20454 Some(JumpData::MultiBufferRow {
20455 row,
20456 line_offset_from_top,
20457 }) => {
20458 let point = MultiBufferPoint::new(row.0, 0);
20459 if let Some((buffer, buffer_point, _)) =
20460 self.buffer.read(cx).point_to_buffer_point(point, cx)
20461 {
20462 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20463 new_selections_by_buffer
20464 .entry(buffer)
20465 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20466 .0
20467 .push(buffer_offset..buffer_offset)
20468 }
20469 }
20470 None => {
20471 let selections = self.selections.all::<usize>(cx);
20472 let multi_buffer = self.buffer.read(cx);
20473 for selection in selections {
20474 for (snapshot, range, _, anchor) in multi_buffer
20475 .snapshot(cx)
20476 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20477 {
20478 if let Some(anchor) = anchor {
20479 // selection is in a deleted hunk
20480 let Some(buffer_id) = anchor.buffer_id else {
20481 continue;
20482 };
20483 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20484 continue;
20485 };
20486 let offset = text::ToOffset::to_offset(
20487 &anchor.text_anchor,
20488 &buffer_handle.read(cx).snapshot(),
20489 );
20490 let range = offset..offset;
20491 new_selections_by_buffer
20492 .entry(buffer_handle)
20493 .or_insert((Vec::new(), None))
20494 .0
20495 .push(range)
20496 } else {
20497 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20498 else {
20499 continue;
20500 };
20501 new_selections_by_buffer
20502 .entry(buffer_handle)
20503 .or_insert((Vec::new(), None))
20504 .0
20505 .push(range)
20506 }
20507 }
20508 }
20509 }
20510 }
20511
20512 new_selections_by_buffer
20513 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20514
20515 if new_selections_by_buffer.is_empty() {
20516 return;
20517 }
20518
20519 // We defer the pane interaction because we ourselves are a workspace item
20520 // and activating a new item causes the pane to call a method on us reentrantly,
20521 // which panics if we're on the stack.
20522 window.defer(cx, move |window, cx| {
20523 workspace.update(cx, |workspace, cx| {
20524 let pane = if split {
20525 workspace.adjacent_pane(window, cx)
20526 } else {
20527 workspace.active_pane().clone()
20528 };
20529
20530 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20531 let editor = buffer
20532 .read(cx)
20533 .file()
20534 .is_none()
20535 .then(|| {
20536 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20537 // so `workspace.open_project_item` will never find them, always opening a new editor.
20538 // Instead, we try to activate the existing editor in the pane first.
20539 let (editor, pane_item_index) =
20540 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20541 let editor = item.downcast::<Editor>()?;
20542 let singleton_buffer =
20543 editor.read(cx).buffer().read(cx).as_singleton()?;
20544 if singleton_buffer == buffer {
20545 Some((editor, i))
20546 } else {
20547 None
20548 }
20549 })?;
20550 pane.update(cx, |pane, cx| {
20551 pane.activate_item(pane_item_index, true, true, window, cx)
20552 });
20553 Some(editor)
20554 })
20555 .flatten()
20556 .unwrap_or_else(|| {
20557 workspace.open_project_item::<Self>(
20558 pane.clone(),
20559 buffer,
20560 true,
20561 true,
20562 window,
20563 cx,
20564 )
20565 });
20566
20567 editor.update(cx, |editor, cx| {
20568 let autoscroll = match scroll_offset {
20569 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20570 None => Autoscroll::newest(),
20571 };
20572 let nav_history = editor.nav_history.take();
20573 editor.change_selections(
20574 SelectionEffects::scroll(autoscroll),
20575 window,
20576 cx,
20577 |s| {
20578 s.select_ranges(ranges);
20579 },
20580 );
20581 editor.nav_history = nav_history;
20582 });
20583 }
20584 })
20585 });
20586 }
20587
20588 // For now, don't allow opening excerpts in buffers that aren't backed by
20589 // regular project files.
20590 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20591 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20592 }
20593
20594 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20595 let snapshot = self.buffer.read(cx).read(cx);
20596 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20597 Some(
20598 ranges
20599 .iter()
20600 .map(move |range| {
20601 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20602 })
20603 .collect(),
20604 )
20605 }
20606
20607 fn selection_replacement_ranges(
20608 &self,
20609 range: Range<OffsetUtf16>,
20610 cx: &mut App,
20611 ) -> Vec<Range<OffsetUtf16>> {
20612 let selections = self.selections.all::<OffsetUtf16>(cx);
20613 let newest_selection = selections
20614 .iter()
20615 .max_by_key(|selection| selection.id)
20616 .unwrap();
20617 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20618 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20619 let snapshot = self.buffer.read(cx).read(cx);
20620 selections
20621 .into_iter()
20622 .map(|mut selection| {
20623 selection.start.0 =
20624 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20625 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20626 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20627 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20628 })
20629 .collect()
20630 }
20631
20632 fn report_editor_event(
20633 &self,
20634 reported_event: ReportEditorEvent,
20635 file_extension: Option<String>,
20636 cx: &App,
20637 ) {
20638 if cfg!(any(test, feature = "test-support")) {
20639 return;
20640 }
20641
20642 let Some(project) = &self.project else { return };
20643
20644 // If None, we are in a file without an extension
20645 let file = self
20646 .buffer
20647 .read(cx)
20648 .as_singleton()
20649 .and_then(|b| b.read(cx).file());
20650 let file_extension = file_extension.or(file
20651 .as_ref()
20652 .and_then(|file| Path::new(file.file_name(cx)).extension())
20653 .and_then(|e| e.to_str())
20654 .map(|a| a.to_string()));
20655
20656 let vim_mode = vim_enabled(cx);
20657
20658 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20659 let copilot_enabled = edit_predictions_provider
20660 == language::language_settings::EditPredictionProvider::Copilot;
20661 let copilot_enabled_for_language = self
20662 .buffer
20663 .read(cx)
20664 .language_settings(cx)
20665 .show_edit_predictions;
20666
20667 let project = project.read(cx);
20668 let event_type = reported_event.event_type();
20669
20670 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20671 telemetry::event!(
20672 event_type,
20673 type = if auto_saved {"autosave"} else {"manual"},
20674 file_extension,
20675 vim_mode,
20676 copilot_enabled,
20677 copilot_enabled_for_language,
20678 edit_predictions_provider,
20679 is_via_ssh = project.is_via_ssh(),
20680 );
20681 } else {
20682 telemetry::event!(
20683 event_type,
20684 file_extension,
20685 vim_mode,
20686 copilot_enabled,
20687 copilot_enabled_for_language,
20688 edit_predictions_provider,
20689 is_via_ssh = project.is_via_ssh(),
20690 );
20691 };
20692 }
20693
20694 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20695 /// with each line being an array of {text, highlight} objects.
20696 fn copy_highlight_json(
20697 &mut self,
20698 _: &CopyHighlightJson,
20699 window: &mut Window,
20700 cx: &mut Context<Self>,
20701 ) {
20702 #[derive(Serialize)]
20703 struct Chunk<'a> {
20704 text: String,
20705 highlight: Option<&'a str>,
20706 }
20707
20708 let snapshot = self.buffer.read(cx).snapshot(cx);
20709 let range = self
20710 .selected_text_range(false, window, cx)
20711 .and_then(|selection| {
20712 if selection.range.is_empty() {
20713 None
20714 } else {
20715 Some(selection.range)
20716 }
20717 })
20718 .unwrap_or_else(|| 0..snapshot.len());
20719
20720 let chunks = snapshot.chunks(range, true);
20721 let mut lines = Vec::new();
20722 let mut line: VecDeque<Chunk> = VecDeque::new();
20723
20724 let Some(style) = self.style.as_ref() else {
20725 return;
20726 };
20727
20728 for chunk in chunks {
20729 let highlight = chunk
20730 .syntax_highlight_id
20731 .and_then(|id| id.name(&style.syntax));
20732 let mut chunk_lines = chunk.text.split('\n').peekable();
20733 while let Some(text) = chunk_lines.next() {
20734 let mut merged_with_last_token = false;
20735 if let Some(last_token) = line.back_mut() {
20736 if last_token.highlight == highlight {
20737 last_token.text.push_str(text);
20738 merged_with_last_token = true;
20739 }
20740 }
20741
20742 if !merged_with_last_token {
20743 line.push_back(Chunk {
20744 text: text.into(),
20745 highlight,
20746 });
20747 }
20748
20749 if chunk_lines.peek().is_some() {
20750 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20751 line.pop_front();
20752 }
20753 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20754 line.pop_back();
20755 }
20756
20757 lines.push(mem::take(&mut line));
20758 }
20759 }
20760 }
20761
20762 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20763 return;
20764 };
20765 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20766 }
20767
20768 pub fn open_context_menu(
20769 &mut self,
20770 _: &OpenContextMenu,
20771 window: &mut Window,
20772 cx: &mut Context<Self>,
20773 ) {
20774 self.request_autoscroll(Autoscroll::newest(), cx);
20775 let position = self.selections.newest_display(cx).start;
20776 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20777 }
20778
20779 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20780 &self.inlay_hint_cache
20781 }
20782
20783 pub fn replay_insert_event(
20784 &mut self,
20785 text: &str,
20786 relative_utf16_range: Option<Range<isize>>,
20787 window: &mut Window,
20788 cx: &mut Context<Self>,
20789 ) {
20790 if !self.input_enabled {
20791 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20792 return;
20793 }
20794 if let Some(relative_utf16_range) = relative_utf16_range {
20795 let selections = self.selections.all::<OffsetUtf16>(cx);
20796 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20797 let new_ranges = selections.into_iter().map(|range| {
20798 let start = OffsetUtf16(
20799 range
20800 .head()
20801 .0
20802 .saturating_add_signed(relative_utf16_range.start),
20803 );
20804 let end = OffsetUtf16(
20805 range
20806 .head()
20807 .0
20808 .saturating_add_signed(relative_utf16_range.end),
20809 );
20810 start..end
20811 });
20812 s.select_ranges(new_ranges);
20813 });
20814 }
20815
20816 self.handle_input(text, window, cx);
20817 }
20818
20819 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20820 let Some(provider) = self.semantics_provider.as_ref() else {
20821 return false;
20822 };
20823
20824 let mut supports = false;
20825 self.buffer().update(cx, |this, cx| {
20826 this.for_each_buffer(|buffer| {
20827 supports |= provider.supports_inlay_hints(buffer, cx);
20828 });
20829 });
20830
20831 supports
20832 }
20833
20834 pub fn is_focused(&self, window: &Window) -> bool {
20835 self.focus_handle.is_focused(window)
20836 }
20837
20838 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20839 cx.emit(EditorEvent::Focused);
20840
20841 if let Some(descendant) = self
20842 .last_focused_descendant
20843 .take()
20844 .and_then(|descendant| descendant.upgrade())
20845 {
20846 window.focus(&descendant);
20847 } else {
20848 if let Some(blame) = self.blame.as_ref() {
20849 blame.update(cx, GitBlame::focus)
20850 }
20851
20852 self.blink_manager.update(cx, BlinkManager::enable);
20853 self.show_cursor_names(window, cx);
20854 self.buffer.update(cx, |buffer, cx| {
20855 buffer.finalize_last_transaction(cx);
20856 if self.leader_id.is_none() {
20857 buffer.set_active_selections(
20858 &self.selections.disjoint_anchors(),
20859 self.selections.line_mode,
20860 self.cursor_shape,
20861 cx,
20862 );
20863 }
20864 });
20865 }
20866 }
20867
20868 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20869 cx.emit(EditorEvent::FocusedIn)
20870 }
20871
20872 fn handle_focus_out(
20873 &mut self,
20874 event: FocusOutEvent,
20875 _window: &mut Window,
20876 cx: &mut Context<Self>,
20877 ) {
20878 if event.blurred != self.focus_handle {
20879 self.last_focused_descendant = Some(event.blurred);
20880 }
20881 self.selection_drag_state = SelectionDragState::None;
20882 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20883 }
20884
20885 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20886 self.blink_manager.update(cx, BlinkManager::disable);
20887 self.buffer
20888 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20889
20890 if let Some(blame) = self.blame.as_ref() {
20891 blame.update(cx, GitBlame::blur)
20892 }
20893 if !self.hover_state.focused(window, cx) {
20894 hide_hover(self, cx);
20895 }
20896 if !self
20897 .context_menu
20898 .borrow()
20899 .as_ref()
20900 .is_some_and(|context_menu| context_menu.focused(window, cx))
20901 {
20902 self.hide_context_menu(window, cx);
20903 }
20904 self.discard_edit_prediction(false, cx);
20905 cx.emit(EditorEvent::Blurred);
20906 cx.notify();
20907 }
20908
20909 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20910 let mut pending: String = window
20911 .pending_input_keystrokes()
20912 .into_iter()
20913 .flatten()
20914 .filter_map(|keystroke| {
20915 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20916 keystroke.key_char.clone()
20917 } else {
20918 None
20919 }
20920 })
20921 .collect();
20922
20923 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20924 pending = "".to_string();
20925 }
20926
20927 let existing_pending = self
20928 .text_highlights::<PendingInput>(cx)
20929 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20930 if existing_pending.is_none() && pending.is_empty() {
20931 return;
20932 }
20933 let transaction =
20934 self.transact(window, cx, |this, window, cx| {
20935 let selections = this.selections.all::<usize>(cx);
20936 let edits = selections
20937 .iter()
20938 .map(|selection| (selection.end..selection.end, pending.clone()));
20939 this.edit(edits, cx);
20940 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20941 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20942 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20943 }));
20944 });
20945 if let Some(existing_ranges) = existing_pending {
20946 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20947 this.edit(edits, cx);
20948 }
20949 });
20950
20951 let snapshot = self.snapshot(window, cx);
20952 let ranges = self
20953 .selections
20954 .all::<usize>(cx)
20955 .into_iter()
20956 .map(|selection| {
20957 snapshot.buffer_snapshot.anchor_after(selection.end)
20958 ..snapshot
20959 .buffer_snapshot
20960 .anchor_before(selection.end + pending.len())
20961 })
20962 .collect();
20963
20964 if pending.is_empty() {
20965 self.clear_highlights::<PendingInput>(cx);
20966 } else {
20967 self.highlight_text::<PendingInput>(
20968 ranges,
20969 HighlightStyle {
20970 underline: Some(UnderlineStyle {
20971 thickness: px(1.),
20972 color: None,
20973 wavy: false,
20974 }),
20975 ..Default::default()
20976 },
20977 cx,
20978 );
20979 }
20980
20981 self.ime_transaction = self.ime_transaction.or(transaction);
20982 if let Some(transaction) = self.ime_transaction {
20983 self.buffer.update(cx, |buffer, cx| {
20984 buffer.group_until_transaction(transaction, cx);
20985 });
20986 }
20987
20988 if self.text_highlights::<PendingInput>(cx).is_none() {
20989 self.ime_transaction.take();
20990 }
20991 }
20992
20993 pub fn register_action_renderer(
20994 &mut self,
20995 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20996 ) -> Subscription {
20997 let id = self.next_editor_action_id.post_inc();
20998 self.editor_actions
20999 .borrow_mut()
21000 .insert(id, Box::new(listener));
21001
21002 let editor_actions = self.editor_actions.clone();
21003 Subscription::new(move || {
21004 editor_actions.borrow_mut().remove(&id);
21005 })
21006 }
21007
21008 pub fn register_action<A: Action>(
21009 &mut self,
21010 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21011 ) -> Subscription {
21012 let id = self.next_editor_action_id.post_inc();
21013 let listener = Arc::new(listener);
21014 self.editor_actions.borrow_mut().insert(
21015 id,
21016 Box::new(move |_, window, _| {
21017 let listener = listener.clone();
21018 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21019 let action = action.downcast_ref().unwrap();
21020 if phase == DispatchPhase::Bubble {
21021 listener(action, window, cx)
21022 }
21023 })
21024 }),
21025 );
21026
21027 let editor_actions = self.editor_actions.clone();
21028 Subscription::new(move || {
21029 editor_actions.borrow_mut().remove(&id);
21030 })
21031 }
21032
21033 pub fn file_header_size(&self) -> u32 {
21034 FILE_HEADER_HEIGHT
21035 }
21036
21037 pub fn restore(
21038 &mut self,
21039 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21040 window: &mut Window,
21041 cx: &mut Context<Self>,
21042 ) {
21043 let workspace = self.workspace();
21044 let project = self.project();
21045 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21046 let mut tasks = Vec::new();
21047 for (buffer_id, changes) in revert_changes {
21048 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21049 buffer.update(cx, |buffer, cx| {
21050 buffer.edit(
21051 changes
21052 .into_iter()
21053 .map(|(range, text)| (range, text.to_string())),
21054 None,
21055 cx,
21056 );
21057 });
21058
21059 if let Some(project) =
21060 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21061 {
21062 project.update(cx, |project, cx| {
21063 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21064 })
21065 }
21066 }
21067 }
21068 tasks
21069 });
21070 cx.spawn_in(window, async move |_, cx| {
21071 for (buffer, task) in save_tasks {
21072 let result = task.await;
21073 if result.is_err() {
21074 let Some(path) = buffer
21075 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21076 .ok()
21077 else {
21078 continue;
21079 };
21080 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21081 let Some(task) = cx
21082 .update_window_entity(&workspace, |workspace, window, cx| {
21083 workspace
21084 .open_path_preview(path, None, false, false, false, window, cx)
21085 })
21086 .ok()
21087 else {
21088 continue;
21089 };
21090 task.await.log_err();
21091 }
21092 }
21093 }
21094 })
21095 .detach();
21096 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21097 selections.refresh()
21098 });
21099 }
21100
21101 pub fn to_pixel_point(
21102 &self,
21103 source: multi_buffer::Anchor,
21104 editor_snapshot: &EditorSnapshot,
21105 window: &mut Window,
21106 ) -> Option<gpui::Point<Pixels>> {
21107 let source_point = source.to_display_point(editor_snapshot);
21108 self.display_to_pixel_point(source_point, editor_snapshot, window)
21109 }
21110
21111 pub fn display_to_pixel_point(
21112 &self,
21113 source: DisplayPoint,
21114 editor_snapshot: &EditorSnapshot,
21115 window: &mut Window,
21116 ) -> Option<gpui::Point<Pixels>> {
21117 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21118 let text_layout_details = self.text_layout_details(window);
21119 let scroll_top = text_layout_details
21120 .scroll_anchor
21121 .scroll_position(editor_snapshot)
21122 .y;
21123
21124 if source.row().as_f32() < scroll_top.floor() {
21125 return None;
21126 }
21127 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21128 let source_y = line_height * (source.row().as_f32() - scroll_top);
21129 Some(gpui::Point::new(source_x, source_y))
21130 }
21131
21132 pub fn has_visible_completions_menu(&self) -> bool {
21133 !self.edit_prediction_preview_is_active()
21134 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
21135 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21136 })
21137 }
21138
21139 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21140 if self.mode.is_minimap() {
21141 return;
21142 }
21143 self.addons
21144 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21145 }
21146
21147 pub fn unregister_addon<T: Addon>(&mut self) {
21148 self.addons.remove(&std::any::TypeId::of::<T>());
21149 }
21150
21151 pub fn addon<T: Addon>(&self) -> Option<&T> {
21152 let type_id = std::any::TypeId::of::<T>();
21153 self.addons
21154 .get(&type_id)
21155 .and_then(|item| item.to_any().downcast_ref::<T>())
21156 }
21157
21158 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21159 let type_id = std::any::TypeId::of::<T>();
21160 self.addons
21161 .get_mut(&type_id)
21162 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21163 }
21164
21165 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21166 let text_layout_details = self.text_layout_details(window);
21167 let style = &text_layout_details.editor_style;
21168 let font_id = window.text_system().resolve_font(&style.text.font());
21169 let font_size = style.text.font_size.to_pixels(window.rem_size());
21170 let line_height = style.text.line_height_in_pixels(window.rem_size());
21171 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21172 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21173
21174 CharacterDimensions {
21175 em_width,
21176 em_advance,
21177 line_height,
21178 }
21179 }
21180
21181 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21182 self.load_diff_task.clone()
21183 }
21184
21185 fn read_metadata_from_db(
21186 &mut self,
21187 item_id: u64,
21188 workspace_id: WorkspaceId,
21189 window: &mut Window,
21190 cx: &mut Context<Editor>,
21191 ) {
21192 if self.is_singleton(cx)
21193 && !self.mode.is_minimap()
21194 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21195 {
21196 let buffer_snapshot = OnceCell::new();
21197
21198 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21199 if !folds.is_empty() {
21200 let snapshot =
21201 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21202 self.fold_ranges(
21203 folds
21204 .into_iter()
21205 .map(|(start, end)| {
21206 snapshot.clip_offset(start, Bias::Left)
21207 ..snapshot.clip_offset(end, Bias::Right)
21208 })
21209 .collect(),
21210 false,
21211 window,
21212 cx,
21213 );
21214 }
21215 }
21216
21217 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21218 if !selections.is_empty() {
21219 let snapshot =
21220 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21221 // skip adding the initial selection to selection history
21222 self.selection_history.mode = SelectionHistoryMode::Skipping;
21223 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21224 s.select_ranges(selections.into_iter().map(|(start, end)| {
21225 snapshot.clip_offset(start, Bias::Left)
21226 ..snapshot.clip_offset(end, Bias::Right)
21227 }));
21228 });
21229 self.selection_history.mode = SelectionHistoryMode::Normal;
21230 }
21231 };
21232 }
21233
21234 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21235 }
21236
21237 fn update_lsp_data(
21238 &mut self,
21239 ignore_cache: bool,
21240 for_buffer: Option<BufferId>,
21241 window: &mut Window,
21242 cx: &mut Context<'_, Self>,
21243 ) {
21244 self.pull_diagnostics(for_buffer, window, cx);
21245 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21246 }
21247}
21248
21249fn vim_enabled(cx: &App) -> bool {
21250 cx.global::<SettingsStore>()
21251 .raw_user_settings()
21252 .get("vim_mode")
21253 == Some(&serde_json::Value::Bool(true))
21254}
21255
21256fn process_completion_for_edit(
21257 completion: &Completion,
21258 intent: CompletionIntent,
21259 buffer: &Entity<Buffer>,
21260 cursor_position: &text::Anchor,
21261 cx: &mut Context<Editor>,
21262) -> CompletionEdit {
21263 let buffer = buffer.read(cx);
21264 let buffer_snapshot = buffer.snapshot();
21265 let (snippet, new_text) = if completion.is_snippet() {
21266 // Workaround for typescript language server issues so that methods don't expand within
21267 // strings and functions with type expressions. The previous point is used because the query
21268 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21269 let mut snippet_source = completion.new_text.clone();
21270 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21271 previous_point.column = previous_point.column.saturating_sub(1);
21272 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21273 if scope.prefers_label_for_snippet_in_completion() {
21274 if let Some(label) = completion.label() {
21275 if matches!(
21276 completion.kind(),
21277 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21278 ) {
21279 snippet_source = label;
21280 }
21281 }
21282 }
21283 }
21284 match Snippet::parse(&snippet_source).log_err() {
21285 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21286 None => (None, completion.new_text.clone()),
21287 }
21288 } else {
21289 (None, completion.new_text.clone())
21290 };
21291
21292 let mut range_to_replace = {
21293 let replace_range = &completion.replace_range;
21294 if let CompletionSource::Lsp {
21295 insert_range: Some(insert_range),
21296 ..
21297 } = &completion.source
21298 {
21299 debug_assert_eq!(
21300 insert_range.start, replace_range.start,
21301 "insert_range and replace_range should start at the same position"
21302 );
21303 debug_assert!(
21304 insert_range
21305 .start
21306 .cmp(&cursor_position, &buffer_snapshot)
21307 .is_le(),
21308 "insert_range should start before or at cursor position"
21309 );
21310 debug_assert!(
21311 replace_range
21312 .start
21313 .cmp(&cursor_position, &buffer_snapshot)
21314 .is_le(),
21315 "replace_range should start before or at cursor position"
21316 );
21317
21318 let should_replace = match intent {
21319 CompletionIntent::CompleteWithInsert => false,
21320 CompletionIntent::CompleteWithReplace => true,
21321 CompletionIntent::Complete | CompletionIntent::Compose => {
21322 let insert_mode =
21323 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21324 .completions
21325 .lsp_insert_mode;
21326 match insert_mode {
21327 LspInsertMode::Insert => false,
21328 LspInsertMode::Replace => true,
21329 LspInsertMode::ReplaceSubsequence => {
21330 let mut text_to_replace = buffer.chars_for_range(
21331 buffer.anchor_before(replace_range.start)
21332 ..buffer.anchor_after(replace_range.end),
21333 );
21334 let mut current_needle = text_to_replace.next();
21335 for haystack_ch in completion.label.text.chars() {
21336 if let Some(needle_ch) = current_needle {
21337 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21338 current_needle = text_to_replace.next();
21339 }
21340 }
21341 }
21342 current_needle.is_none()
21343 }
21344 LspInsertMode::ReplaceSuffix => {
21345 if replace_range
21346 .end
21347 .cmp(&cursor_position, &buffer_snapshot)
21348 .is_gt()
21349 {
21350 let range_after_cursor = *cursor_position..replace_range.end;
21351 let text_after_cursor = buffer
21352 .text_for_range(
21353 buffer.anchor_before(range_after_cursor.start)
21354 ..buffer.anchor_after(range_after_cursor.end),
21355 )
21356 .collect::<String>()
21357 .to_ascii_lowercase();
21358 completion
21359 .label
21360 .text
21361 .to_ascii_lowercase()
21362 .ends_with(&text_after_cursor)
21363 } else {
21364 true
21365 }
21366 }
21367 }
21368 }
21369 };
21370
21371 if should_replace {
21372 replace_range.clone()
21373 } else {
21374 insert_range.clone()
21375 }
21376 } else {
21377 replace_range.clone()
21378 }
21379 };
21380
21381 if range_to_replace
21382 .end
21383 .cmp(&cursor_position, &buffer_snapshot)
21384 .is_lt()
21385 {
21386 range_to_replace.end = *cursor_position;
21387 }
21388
21389 CompletionEdit {
21390 new_text,
21391 replace_range: range_to_replace.to_offset(&buffer),
21392 snippet,
21393 }
21394}
21395
21396struct CompletionEdit {
21397 new_text: String,
21398 replace_range: Range<usize>,
21399 snippet: Option<Snippet>,
21400}
21401
21402fn insert_extra_newline_brackets(
21403 buffer: &MultiBufferSnapshot,
21404 range: Range<usize>,
21405 language: &language::LanguageScope,
21406) -> bool {
21407 let leading_whitespace_len = buffer
21408 .reversed_chars_at(range.start)
21409 .take_while(|c| c.is_whitespace() && *c != '\n')
21410 .map(|c| c.len_utf8())
21411 .sum::<usize>();
21412 let trailing_whitespace_len = buffer
21413 .chars_at(range.end)
21414 .take_while(|c| c.is_whitespace() && *c != '\n')
21415 .map(|c| c.len_utf8())
21416 .sum::<usize>();
21417 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21418
21419 language.brackets().any(|(pair, enabled)| {
21420 let pair_start = pair.start.trim_end();
21421 let pair_end = pair.end.trim_start();
21422
21423 enabled
21424 && pair.newline
21425 && buffer.contains_str_at(range.end, pair_end)
21426 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21427 })
21428}
21429
21430fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21431 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21432 [(buffer, range, _)] => (*buffer, range.clone()),
21433 _ => return false,
21434 };
21435 let pair = {
21436 let mut result: Option<BracketMatch> = None;
21437
21438 for pair in buffer
21439 .all_bracket_ranges(range.clone())
21440 .filter(move |pair| {
21441 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21442 })
21443 {
21444 let len = pair.close_range.end - pair.open_range.start;
21445
21446 if let Some(existing) = &result {
21447 let existing_len = existing.close_range.end - existing.open_range.start;
21448 if len > existing_len {
21449 continue;
21450 }
21451 }
21452
21453 result = Some(pair);
21454 }
21455
21456 result
21457 };
21458 let Some(pair) = pair else {
21459 return false;
21460 };
21461 pair.newline_only
21462 && buffer
21463 .chars_for_range(pair.open_range.end..range.start)
21464 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21465 .all(|c| c.is_whitespace() && c != '\n')
21466}
21467
21468fn update_uncommitted_diff_for_buffer(
21469 editor: Entity<Editor>,
21470 project: &Entity<Project>,
21471 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21472 buffer: Entity<MultiBuffer>,
21473 cx: &mut App,
21474) -> Task<()> {
21475 let mut tasks = Vec::new();
21476 project.update(cx, |project, cx| {
21477 for buffer in buffers {
21478 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21479 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21480 }
21481 }
21482 });
21483 cx.spawn(async move |cx| {
21484 let diffs = future::join_all(tasks).await;
21485 if editor
21486 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21487 .unwrap_or(false)
21488 {
21489 return;
21490 }
21491
21492 buffer
21493 .update(cx, |buffer, cx| {
21494 for diff in diffs.into_iter().flatten() {
21495 buffer.add_diff(diff, cx);
21496 }
21497 })
21498 .ok();
21499 })
21500}
21501
21502fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21503 let tab_size = tab_size.get() as usize;
21504 let mut width = offset;
21505
21506 for ch in text.chars() {
21507 width += if ch == '\t' {
21508 tab_size - (width % tab_size)
21509 } else {
21510 1
21511 };
21512 }
21513
21514 width - offset
21515}
21516
21517#[cfg(test)]
21518mod tests {
21519 use super::*;
21520
21521 #[test]
21522 fn test_string_size_with_expanded_tabs() {
21523 let nz = |val| NonZeroU32::new(val).unwrap();
21524 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21525 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21526 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21527 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21528 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21529 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21530 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21531 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21532 }
21533}
21534
21535/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21536struct WordBreakingTokenizer<'a> {
21537 input: &'a str,
21538}
21539
21540impl<'a> WordBreakingTokenizer<'a> {
21541 fn new(input: &'a str) -> Self {
21542 Self { input }
21543 }
21544}
21545
21546fn is_char_ideographic(ch: char) -> bool {
21547 use unicode_script::Script::*;
21548 use unicode_script::UnicodeScript;
21549 matches!(ch.script(), Han | Tangut | Yi)
21550}
21551
21552fn is_grapheme_ideographic(text: &str) -> bool {
21553 text.chars().any(is_char_ideographic)
21554}
21555
21556fn is_grapheme_whitespace(text: &str) -> bool {
21557 text.chars().any(|x| x.is_whitespace())
21558}
21559
21560fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21561 text.chars().next().map_or(false, |ch| {
21562 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21563 })
21564}
21565
21566#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21567enum WordBreakToken<'a> {
21568 Word { token: &'a str, grapheme_len: usize },
21569 InlineWhitespace { token: &'a str, grapheme_len: usize },
21570 Newline,
21571}
21572
21573impl<'a> Iterator for WordBreakingTokenizer<'a> {
21574 /// Yields a span, the count of graphemes in the token, and whether it was
21575 /// whitespace. Note that it also breaks at word boundaries.
21576 type Item = WordBreakToken<'a>;
21577
21578 fn next(&mut self) -> Option<Self::Item> {
21579 use unicode_segmentation::UnicodeSegmentation;
21580 if self.input.is_empty() {
21581 return None;
21582 }
21583
21584 let mut iter = self.input.graphemes(true).peekable();
21585 let mut offset = 0;
21586 let mut grapheme_len = 0;
21587 if let Some(first_grapheme) = iter.next() {
21588 let is_newline = first_grapheme == "\n";
21589 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21590 offset += first_grapheme.len();
21591 grapheme_len += 1;
21592 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21593 if let Some(grapheme) = iter.peek().copied() {
21594 if should_stay_with_preceding_ideograph(grapheme) {
21595 offset += grapheme.len();
21596 grapheme_len += 1;
21597 }
21598 }
21599 } else {
21600 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21601 let mut next_word_bound = words.peek().copied();
21602 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21603 next_word_bound = words.next();
21604 }
21605 while let Some(grapheme) = iter.peek().copied() {
21606 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21607 break;
21608 };
21609 if is_grapheme_whitespace(grapheme) != is_whitespace
21610 || (grapheme == "\n") != is_newline
21611 {
21612 break;
21613 };
21614 offset += grapheme.len();
21615 grapheme_len += 1;
21616 iter.next();
21617 }
21618 }
21619 let token = &self.input[..offset];
21620 self.input = &self.input[offset..];
21621 if token == "\n" {
21622 Some(WordBreakToken::Newline)
21623 } else if is_whitespace {
21624 Some(WordBreakToken::InlineWhitespace {
21625 token,
21626 grapheme_len,
21627 })
21628 } else {
21629 Some(WordBreakToken::Word {
21630 token,
21631 grapheme_len,
21632 })
21633 }
21634 } else {
21635 None
21636 }
21637 }
21638}
21639
21640#[test]
21641fn test_word_breaking_tokenizer() {
21642 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21643 ("", &[]),
21644 (" ", &[whitespace(" ", 2)]),
21645 ("Ʒ", &[word("Ʒ", 1)]),
21646 ("Ǽ", &[word("Ǽ", 1)]),
21647 ("⋑", &[word("⋑", 1)]),
21648 ("⋑⋑", &[word("⋑⋑", 2)]),
21649 (
21650 "原理,进而",
21651 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21652 ),
21653 (
21654 "hello world",
21655 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21656 ),
21657 (
21658 "hello, world",
21659 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21660 ),
21661 (
21662 " hello world",
21663 &[
21664 whitespace(" ", 2),
21665 word("hello", 5),
21666 whitespace(" ", 1),
21667 word("world", 5),
21668 ],
21669 ),
21670 (
21671 "这是什么 \n 钢笔",
21672 &[
21673 word("这", 1),
21674 word("是", 1),
21675 word("什", 1),
21676 word("么", 1),
21677 whitespace(" ", 1),
21678 newline(),
21679 whitespace(" ", 1),
21680 word("钢", 1),
21681 word("笔", 1),
21682 ],
21683 ),
21684 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21685 ];
21686
21687 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21688 WordBreakToken::Word {
21689 token,
21690 grapheme_len,
21691 }
21692 }
21693
21694 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21695 WordBreakToken::InlineWhitespace {
21696 token,
21697 grapheme_len,
21698 }
21699 }
21700
21701 fn newline() -> WordBreakToken<'static> {
21702 WordBreakToken::Newline
21703 }
21704
21705 for (input, result) in tests {
21706 assert_eq!(
21707 WordBreakingTokenizer::new(input)
21708 .collect::<Vec<_>>()
21709 .as_slice(),
21710 *result,
21711 );
21712 }
21713}
21714
21715fn wrap_with_prefix(
21716 first_line_prefix: String,
21717 subsequent_lines_prefix: String,
21718 unwrapped_text: String,
21719 wrap_column: usize,
21720 tab_size: NonZeroU32,
21721 preserve_existing_whitespace: bool,
21722) -> String {
21723 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21724 let subsequent_lines_prefix_len =
21725 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21726 let mut wrapped_text = String::new();
21727 let mut current_line = first_line_prefix.clone();
21728 let mut is_first_line = true;
21729
21730 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21731 let mut current_line_len = first_line_prefix_len;
21732 let mut in_whitespace = false;
21733 for token in tokenizer {
21734 let have_preceding_whitespace = in_whitespace;
21735 match token {
21736 WordBreakToken::Word {
21737 token,
21738 grapheme_len,
21739 } => {
21740 in_whitespace = false;
21741 let current_prefix_len = if is_first_line {
21742 first_line_prefix_len
21743 } else {
21744 subsequent_lines_prefix_len
21745 };
21746 if current_line_len + grapheme_len > wrap_column
21747 && current_line_len != current_prefix_len
21748 {
21749 wrapped_text.push_str(current_line.trim_end());
21750 wrapped_text.push('\n');
21751 is_first_line = false;
21752 current_line = subsequent_lines_prefix.clone();
21753 current_line_len = subsequent_lines_prefix_len;
21754 }
21755 current_line.push_str(token);
21756 current_line_len += grapheme_len;
21757 }
21758 WordBreakToken::InlineWhitespace {
21759 mut token,
21760 mut grapheme_len,
21761 } => {
21762 in_whitespace = true;
21763 if have_preceding_whitespace && !preserve_existing_whitespace {
21764 continue;
21765 }
21766 if !preserve_existing_whitespace {
21767 token = " ";
21768 grapheme_len = 1;
21769 }
21770 let current_prefix_len = if is_first_line {
21771 first_line_prefix_len
21772 } else {
21773 subsequent_lines_prefix_len
21774 };
21775 if current_line_len + grapheme_len > wrap_column {
21776 wrapped_text.push_str(current_line.trim_end());
21777 wrapped_text.push('\n');
21778 is_first_line = false;
21779 current_line = subsequent_lines_prefix.clone();
21780 current_line_len = subsequent_lines_prefix_len;
21781 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21782 current_line.push_str(token);
21783 current_line_len += grapheme_len;
21784 }
21785 }
21786 WordBreakToken::Newline => {
21787 in_whitespace = true;
21788 let current_prefix_len = if is_first_line {
21789 first_line_prefix_len
21790 } else {
21791 subsequent_lines_prefix_len
21792 };
21793 if preserve_existing_whitespace {
21794 wrapped_text.push_str(current_line.trim_end());
21795 wrapped_text.push('\n');
21796 is_first_line = false;
21797 current_line = subsequent_lines_prefix.clone();
21798 current_line_len = subsequent_lines_prefix_len;
21799 } else if have_preceding_whitespace {
21800 continue;
21801 } else if current_line_len + 1 > wrap_column
21802 && current_line_len != current_prefix_len
21803 {
21804 wrapped_text.push_str(current_line.trim_end());
21805 wrapped_text.push('\n');
21806 is_first_line = false;
21807 current_line = subsequent_lines_prefix.clone();
21808 current_line_len = subsequent_lines_prefix_len;
21809 } else if current_line_len != current_prefix_len {
21810 current_line.push(' ');
21811 current_line_len += 1;
21812 }
21813 }
21814 }
21815 }
21816
21817 if !current_line.is_empty() {
21818 wrapped_text.push_str(¤t_line);
21819 }
21820 wrapped_text
21821}
21822
21823#[test]
21824fn test_wrap_with_prefix() {
21825 assert_eq!(
21826 wrap_with_prefix(
21827 "# ".to_string(),
21828 "# ".to_string(),
21829 "abcdefg".to_string(),
21830 4,
21831 NonZeroU32::new(4).unwrap(),
21832 false,
21833 ),
21834 "# abcdefg"
21835 );
21836 assert_eq!(
21837 wrap_with_prefix(
21838 "".to_string(),
21839 "".to_string(),
21840 "\thello world".to_string(),
21841 8,
21842 NonZeroU32::new(4).unwrap(),
21843 false,
21844 ),
21845 "hello\nworld"
21846 );
21847 assert_eq!(
21848 wrap_with_prefix(
21849 "// ".to_string(),
21850 "// ".to_string(),
21851 "xx \nyy zz aa bb cc".to_string(),
21852 12,
21853 NonZeroU32::new(4).unwrap(),
21854 false,
21855 ),
21856 "// xx yy zz\n// aa bb cc"
21857 );
21858 assert_eq!(
21859 wrap_with_prefix(
21860 String::new(),
21861 String::new(),
21862 "这是什么 \n 钢笔".to_string(),
21863 3,
21864 NonZeroU32::new(4).unwrap(),
21865 false,
21866 ),
21867 "这是什\n么 钢\n笔"
21868 );
21869}
21870
21871pub trait CollaborationHub {
21872 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21873 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21874 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21875}
21876
21877impl CollaborationHub for Entity<Project> {
21878 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21879 self.read(cx).collaborators()
21880 }
21881
21882 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21883 self.read(cx).user_store().read(cx).participant_indices()
21884 }
21885
21886 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21887 let this = self.read(cx);
21888 let user_ids = this.collaborators().values().map(|c| c.user_id);
21889 this.user_store().read(cx).participant_names(user_ids, cx)
21890 }
21891}
21892
21893pub trait SemanticsProvider {
21894 fn hover(
21895 &self,
21896 buffer: &Entity<Buffer>,
21897 position: text::Anchor,
21898 cx: &mut App,
21899 ) -> Option<Task<Vec<project::Hover>>>;
21900
21901 fn inline_values(
21902 &self,
21903 buffer_handle: Entity<Buffer>,
21904 range: Range<text::Anchor>,
21905 cx: &mut App,
21906 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21907
21908 fn inlay_hints(
21909 &self,
21910 buffer_handle: Entity<Buffer>,
21911 range: Range<text::Anchor>,
21912 cx: &mut App,
21913 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21914
21915 fn resolve_inlay_hint(
21916 &self,
21917 hint: InlayHint,
21918 buffer_handle: Entity<Buffer>,
21919 server_id: LanguageServerId,
21920 cx: &mut App,
21921 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21922
21923 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21924
21925 fn document_highlights(
21926 &self,
21927 buffer: &Entity<Buffer>,
21928 position: text::Anchor,
21929 cx: &mut App,
21930 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21931
21932 fn definitions(
21933 &self,
21934 buffer: &Entity<Buffer>,
21935 position: text::Anchor,
21936 kind: GotoDefinitionKind,
21937 cx: &mut App,
21938 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21939
21940 fn range_for_rename(
21941 &self,
21942 buffer: &Entity<Buffer>,
21943 position: text::Anchor,
21944 cx: &mut App,
21945 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21946
21947 fn perform_rename(
21948 &self,
21949 buffer: &Entity<Buffer>,
21950 position: text::Anchor,
21951 new_name: String,
21952 cx: &mut App,
21953 ) -> Option<Task<Result<ProjectTransaction>>>;
21954}
21955
21956pub trait CompletionProvider {
21957 fn completions(
21958 &self,
21959 excerpt_id: ExcerptId,
21960 buffer: &Entity<Buffer>,
21961 buffer_position: text::Anchor,
21962 trigger: CompletionContext,
21963 window: &mut Window,
21964 cx: &mut Context<Editor>,
21965 ) -> Task<Result<Vec<CompletionResponse>>>;
21966
21967 fn resolve_completions(
21968 &self,
21969 _buffer: Entity<Buffer>,
21970 _completion_indices: Vec<usize>,
21971 _completions: Rc<RefCell<Box<[Completion]>>>,
21972 _cx: &mut Context<Editor>,
21973 ) -> Task<Result<bool>> {
21974 Task::ready(Ok(false))
21975 }
21976
21977 fn apply_additional_edits_for_completion(
21978 &self,
21979 _buffer: Entity<Buffer>,
21980 _completions: Rc<RefCell<Box<[Completion]>>>,
21981 _completion_index: usize,
21982 _push_to_history: bool,
21983 _cx: &mut Context<Editor>,
21984 ) -> Task<Result<Option<language::Transaction>>> {
21985 Task::ready(Ok(None))
21986 }
21987
21988 fn is_completion_trigger(
21989 &self,
21990 buffer: &Entity<Buffer>,
21991 position: language::Anchor,
21992 text: &str,
21993 trigger_in_words: bool,
21994 menu_is_open: bool,
21995 cx: &mut Context<Editor>,
21996 ) -> bool;
21997
21998 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21999
22000 fn sort_completions(&self) -> bool {
22001 true
22002 }
22003
22004 fn filter_completions(&self) -> bool {
22005 true
22006 }
22007}
22008
22009pub trait CodeActionProvider {
22010 fn id(&self) -> Arc<str>;
22011
22012 fn code_actions(
22013 &self,
22014 buffer: &Entity<Buffer>,
22015 range: Range<text::Anchor>,
22016 window: &mut Window,
22017 cx: &mut App,
22018 ) -> Task<Result<Vec<CodeAction>>>;
22019
22020 fn apply_code_action(
22021 &self,
22022 buffer_handle: Entity<Buffer>,
22023 action: CodeAction,
22024 excerpt_id: ExcerptId,
22025 push_to_history: bool,
22026 window: &mut Window,
22027 cx: &mut App,
22028 ) -> Task<Result<ProjectTransaction>>;
22029}
22030
22031impl CodeActionProvider for Entity<Project> {
22032 fn id(&self) -> Arc<str> {
22033 "project".into()
22034 }
22035
22036 fn code_actions(
22037 &self,
22038 buffer: &Entity<Buffer>,
22039 range: Range<text::Anchor>,
22040 _window: &mut Window,
22041 cx: &mut App,
22042 ) -> Task<Result<Vec<CodeAction>>> {
22043 self.update(cx, |project, cx| {
22044 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22045 let code_actions = project.code_actions(buffer, range, None, cx);
22046 cx.background_spawn(async move {
22047 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22048 Ok(code_lens_actions
22049 .context("code lens fetch")?
22050 .into_iter()
22051 .chain(code_actions.context("code action fetch")?)
22052 .collect())
22053 })
22054 })
22055 }
22056
22057 fn apply_code_action(
22058 &self,
22059 buffer_handle: Entity<Buffer>,
22060 action: CodeAction,
22061 _excerpt_id: ExcerptId,
22062 push_to_history: bool,
22063 _window: &mut Window,
22064 cx: &mut App,
22065 ) -> Task<Result<ProjectTransaction>> {
22066 self.update(cx, |project, cx| {
22067 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22068 })
22069 }
22070}
22071
22072fn snippet_completions(
22073 project: &Project,
22074 buffer: &Entity<Buffer>,
22075 buffer_position: text::Anchor,
22076 cx: &mut App,
22077) -> Task<Result<CompletionResponse>> {
22078 let languages = buffer.read(cx).languages_at(buffer_position);
22079 let snippet_store = project.snippets().read(cx);
22080
22081 let scopes: Vec<_> = languages
22082 .iter()
22083 .filter_map(|language| {
22084 let language_name = language.lsp_id();
22085 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22086
22087 if snippets.is_empty() {
22088 None
22089 } else {
22090 Some((language.default_scope(), snippets))
22091 }
22092 })
22093 .collect();
22094
22095 if scopes.is_empty() {
22096 return Task::ready(Ok(CompletionResponse {
22097 completions: vec![],
22098 is_incomplete: false,
22099 }));
22100 }
22101
22102 let snapshot = buffer.read(cx).text_snapshot();
22103 let chars: String = snapshot
22104 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22105 .collect();
22106 let executor = cx.background_executor().clone();
22107
22108 cx.background_spawn(async move {
22109 let mut is_incomplete = false;
22110 let mut completions: Vec<Completion> = Vec::new();
22111 for (scope, snippets) in scopes.into_iter() {
22112 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22113 let mut last_word = chars
22114 .chars()
22115 .take_while(|c| classifier.is_word(*c))
22116 .collect::<String>();
22117 last_word = last_word.chars().rev().collect();
22118
22119 if last_word.is_empty() {
22120 return Ok(CompletionResponse {
22121 completions: vec![],
22122 is_incomplete: true,
22123 });
22124 }
22125
22126 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22127 let to_lsp = |point: &text::Anchor| {
22128 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22129 point_to_lsp(end)
22130 };
22131 let lsp_end = to_lsp(&buffer_position);
22132
22133 let candidates = snippets
22134 .iter()
22135 .enumerate()
22136 .flat_map(|(ix, snippet)| {
22137 snippet
22138 .prefix
22139 .iter()
22140 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
22141 })
22142 .collect::<Vec<StringMatchCandidate>>();
22143
22144 const MAX_RESULTS: usize = 100;
22145 let mut matches = fuzzy::match_strings(
22146 &candidates,
22147 &last_word,
22148 last_word.chars().any(|c| c.is_uppercase()),
22149 true,
22150 MAX_RESULTS,
22151 &Default::default(),
22152 executor.clone(),
22153 )
22154 .await;
22155
22156 if matches.len() >= MAX_RESULTS {
22157 is_incomplete = true;
22158 }
22159
22160 // Remove all candidates where the query's start does not match the start of any word in the candidate
22161 if let Some(query_start) = last_word.chars().next() {
22162 matches.retain(|string_match| {
22163 split_words(&string_match.string).any(|word| {
22164 // Check that the first codepoint of the word as lowercase matches the first
22165 // codepoint of the query as lowercase
22166 word.chars()
22167 .flat_map(|codepoint| codepoint.to_lowercase())
22168 .zip(query_start.to_lowercase())
22169 .all(|(word_cp, query_cp)| word_cp == query_cp)
22170 })
22171 });
22172 }
22173
22174 let matched_strings = matches
22175 .into_iter()
22176 .map(|m| m.string)
22177 .collect::<HashSet<_>>();
22178
22179 completions.extend(snippets.iter().filter_map(|snippet| {
22180 let matching_prefix = snippet
22181 .prefix
22182 .iter()
22183 .find(|prefix| matched_strings.contains(*prefix))?;
22184 let start = as_offset - last_word.len();
22185 let start = snapshot.anchor_before(start);
22186 let range = start..buffer_position;
22187 let lsp_start = to_lsp(&start);
22188 let lsp_range = lsp::Range {
22189 start: lsp_start,
22190 end: lsp_end,
22191 };
22192 Some(Completion {
22193 replace_range: range,
22194 new_text: snippet.body.clone(),
22195 source: CompletionSource::Lsp {
22196 insert_range: None,
22197 server_id: LanguageServerId(usize::MAX),
22198 resolved: true,
22199 lsp_completion: Box::new(lsp::CompletionItem {
22200 label: snippet.prefix.first().unwrap().clone(),
22201 kind: Some(CompletionItemKind::SNIPPET),
22202 label_details: snippet.description.as_ref().map(|description| {
22203 lsp::CompletionItemLabelDetails {
22204 detail: Some(description.clone()),
22205 description: None,
22206 }
22207 }),
22208 insert_text_format: Some(InsertTextFormat::SNIPPET),
22209 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22210 lsp::InsertReplaceEdit {
22211 new_text: snippet.body.clone(),
22212 insert: lsp_range,
22213 replace: lsp_range,
22214 },
22215 )),
22216 filter_text: Some(snippet.body.clone()),
22217 sort_text: Some(char::MAX.to_string()),
22218 ..lsp::CompletionItem::default()
22219 }),
22220 lsp_defaults: None,
22221 },
22222 label: CodeLabel {
22223 text: matching_prefix.clone(),
22224 runs: Vec::new(),
22225 filter_range: 0..matching_prefix.len(),
22226 },
22227 icon_path: None,
22228 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22229 single_line: snippet.name.clone().into(),
22230 plain_text: snippet
22231 .description
22232 .clone()
22233 .map(|description| description.into()),
22234 }),
22235 insert_text_mode: None,
22236 confirm: None,
22237 })
22238 }))
22239 }
22240
22241 Ok(CompletionResponse {
22242 completions,
22243 is_incomplete,
22244 })
22245 })
22246}
22247
22248impl CompletionProvider for Entity<Project> {
22249 fn completions(
22250 &self,
22251 _excerpt_id: ExcerptId,
22252 buffer: &Entity<Buffer>,
22253 buffer_position: text::Anchor,
22254 options: CompletionContext,
22255 _window: &mut Window,
22256 cx: &mut Context<Editor>,
22257 ) -> Task<Result<Vec<CompletionResponse>>> {
22258 self.update(cx, |project, cx| {
22259 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22260 let project_completions = project.completions(buffer, buffer_position, options, cx);
22261 cx.background_spawn(async move {
22262 let mut responses = project_completions.await?;
22263 let snippets = snippets.await?;
22264 if !snippets.completions.is_empty() {
22265 responses.push(snippets);
22266 }
22267 Ok(responses)
22268 })
22269 })
22270 }
22271
22272 fn resolve_completions(
22273 &self,
22274 buffer: Entity<Buffer>,
22275 completion_indices: Vec<usize>,
22276 completions: Rc<RefCell<Box<[Completion]>>>,
22277 cx: &mut Context<Editor>,
22278 ) -> Task<Result<bool>> {
22279 self.update(cx, |project, cx| {
22280 project.lsp_store().update(cx, |lsp_store, cx| {
22281 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22282 })
22283 })
22284 }
22285
22286 fn apply_additional_edits_for_completion(
22287 &self,
22288 buffer: Entity<Buffer>,
22289 completions: Rc<RefCell<Box<[Completion]>>>,
22290 completion_index: usize,
22291 push_to_history: bool,
22292 cx: &mut Context<Editor>,
22293 ) -> Task<Result<Option<language::Transaction>>> {
22294 self.update(cx, |project, cx| {
22295 project.lsp_store().update(cx, |lsp_store, cx| {
22296 lsp_store.apply_additional_edits_for_completion(
22297 buffer,
22298 completions,
22299 completion_index,
22300 push_to_history,
22301 cx,
22302 )
22303 })
22304 })
22305 }
22306
22307 fn is_completion_trigger(
22308 &self,
22309 buffer: &Entity<Buffer>,
22310 position: language::Anchor,
22311 text: &str,
22312 trigger_in_words: bool,
22313 menu_is_open: bool,
22314 cx: &mut Context<Editor>,
22315 ) -> bool {
22316 let mut chars = text.chars();
22317 let char = if let Some(char) = chars.next() {
22318 char
22319 } else {
22320 return false;
22321 };
22322 if chars.next().is_some() {
22323 return false;
22324 }
22325
22326 let buffer = buffer.read(cx);
22327 let snapshot = buffer.snapshot();
22328 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22329 return false;
22330 }
22331 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22332 if trigger_in_words && classifier.is_word(char) {
22333 return true;
22334 }
22335
22336 buffer.completion_triggers().contains(text)
22337 }
22338}
22339
22340impl SemanticsProvider for Entity<Project> {
22341 fn hover(
22342 &self,
22343 buffer: &Entity<Buffer>,
22344 position: text::Anchor,
22345 cx: &mut App,
22346 ) -> Option<Task<Vec<project::Hover>>> {
22347 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22348 }
22349
22350 fn document_highlights(
22351 &self,
22352 buffer: &Entity<Buffer>,
22353 position: text::Anchor,
22354 cx: &mut App,
22355 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22356 Some(self.update(cx, |project, cx| {
22357 project.document_highlights(buffer, position, cx)
22358 }))
22359 }
22360
22361 fn definitions(
22362 &self,
22363 buffer: &Entity<Buffer>,
22364 position: text::Anchor,
22365 kind: GotoDefinitionKind,
22366 cx: &mut App,
22367 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22368 Some(self.update(cx, |project, cx| match kind {
22369 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22370 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22371 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22372 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22373 }))
22374 }
22375
22376 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22377 self.update(cx, |project, cx| {
22378 if project
22379 .active_debug_session(cx)
22380 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22381 {
22382 return true;
22383 }
22384
22385 buffer.update(cx, |buffer, cx| {
22386 project.any_language_server_supports_inlay_hints(buffer, cx)
22387 })
22388 })
22389 }
22390
22391 fn inline_values(
22392 &self,
22393 buffer_handle: Entity<Buffer>,
22394 range: Range<text::Anchor>,
22395 cx: &mut App,
22396 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22397 self.update(cx, |project, cx| {
22398 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22399
22400 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22401 })
22402 }
22403
22404 fn inlay_hints(
22405 &self,
22406 buffer_handle: Entity<Buffer>,
22407 range: Range<text::Anchor>,
22408 cx: &mut App,
22409 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22410 Some(self.update(cx, |project, cx| {
22411 project.inlay_hints(buffer_handle, range, cx)
22412 }))
22413 }
22414
22415 fn resolve_inlay_hint(
22416 &self,
22417 hint: InlayHint,
22418 buffer_handle: Entity<Buffer>,
22419 server_id: LanguageServerId,
22420 cx: &mut App,
22421 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22422 Some(self.update(cx, |project, cx| {
22423 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22424 }))
22425 }
22426
22427 fn range_for_rename(
22428 &self,
22429 buffer: &Entity<Buffer>,
22430 position: text::Anchor,
22431 cx: &mut App,
22432 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22433 Some(self.update(cx, |project, cx| {
22434 let buffer = buffer.clone();
22435 let task = project.prepare_rename(buffer.clone(), position, cx);
22436 cx.spawn(async move |_, cx| {
22437 Ok(match task.await? {
22438 PrepareRenameResponse::Success(range) => Some(range),
22439 PrepareRenameResponse::InvalidPosition => None,
22440 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22441 // Fallback on using TreeSitter info to determine identifier range
22442 buffer.read_with(cx, |buffer, _| {
22443 let snapshot = buffer.snapshot();
22444 let (range, kind) = snapshot.surrounding_word(position, false);
22445 if kind != Some(CharKind::Word) {
22446 return None;
22447 }
22448 Some(
22449 snapshot.anchor_before(range.start)
22450 ..snapshot.anchor_after(range.end),
22451 )
22452 })?
22453 }
22454 })
22455 })
22456 }))
22457 }
22458
22459 fn perform_rename(
22460 &self,
22461 buffer: &Entity<Buffer>,
22462 position: text::Anchor,
22463 new_name: String,
22464 cx: &mut App,
22465 ) -> Option<Task<Result<ProjectTransaction>>> {
22466 Some(self.update(cx, |project, cx| {
22467 project.perform_rename(buffer.clone(), position, new_name, cx)
22468 }))
22469 }
22470}
22471
22472fn inlay_hint_settings(
22473 location: Anchor,
22474 snapshot: &MultiBufferSnapshot,
22475 cx: &mut Context<Editor>,
22476) -> InlayHintSettings {
22477 let file = snapshot.file_at(location);
22478 let language = snapshot.language_at(location).map(|l| l.name());
22479 language_settings(language, file, cx).inlay_hints
22480}
22481
22482fn consume_contiguous_rows(
22483 contiguous_row_selections: &mut Vec<Selection<Point>>,
22484 selection: &Selection<Point>,
22485 display_map: &DisplaySnapshot,
22486 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22487) -> (MultiBufferRow, MultiBufferRow) {
22488 contiguous_row_selections.push(selection.clone());
22489 let start_row = starting_row(selection, display_map);
22490 let mut end_row = ending_row(selection, display_map);
22491
22492 while let Some(next_selection) = selections.peek() {
22493 if next_selection.start.row <= end_row.0 {
22494 end_row = ending_row(next_selection, display_map);
22495 contiguous_row_selections.push(selections.next().unwrap().clone());
22496 } else {
22497 break;
22498 }
22499 }
22500 (start_row, end_row)
22501}
22502
22503fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22504 if selection.start.column > 0 {
22505 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22506 } else {
22507 MultiBufferRow(selection.start.row)
22508 }
22509}
22510
22511fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22512 if next_selection.end.column > 0 || next_selection.is_empty() {
22513 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22514 } else {
22515 MultiBufferRow(next_selection.end.row)
22516 }
22517}
22518
22519impl EditorSnapshot {
22520 pub fn remote_selections_in_range<'a>(
22521 &'a self,
22522 range: &'a Range<Anchor>,
22523 collaboration_hub: &dyn CollaborationHub,
22524 cx: &'a App,
22525 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22526 let participant_names = collaboration_hub.user_names(cx);
22527 let participant_indices = collaboration_hub.user_participant_indices(cx);
22528 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22529 let collaborators_by_replica_id = collaborators_by_peer_id
22530 .values()
22531 .map(|collaborator| (collaborator.replica_id, collaborator))
22532 .collect::<HashMap<_, _>>();
22533 self.buffer_snapshot
22534 .selections_in_range(range, false)
22535 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22536 if replica_id == AGENT_REPLICA_ID {
22537 Some(RemoteSelection {
22538 replica_id,
22539 selection,
22540 cursor_shape,
22541 line_mode,
22542 collaborator_id: CollaboratorId::Agent,
22543 user_name: Some("Agent".into()),
22544 color: cx.theme().players().agent(),
22545 })
22546 } else {
22547 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22548 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22549 let user_name = participant_names.get(&collaborator.user_id).cloned();
22550 Some(RemoteSelection {
22551 replica_id,
22552 selection,
22553 cursor_shape,
22554 line_mode,
22555 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22556 user_name,
22557 color: if let Some(index) = participant_index {
22558 cx.theme().players().color_for_participant(index.0)
22559 } else {
22560 cx.theme().players().absent()
22561 },
22562 })
22563 }
22564 })
22565 }
22566
22567 pub fn hunks_for_ranges(
22568 &self,
22569 ranges: impl IntoIterator<Item = Range<Point>>,
22570 ) -> Vec<MultiBufferDiffHunk> {
22571 let mut hunks = Vec::new();
22572 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22573 HashMap::default();
22574 for query_range in ranges {
22575 let query_rows =
22576 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22577 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22578 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22579 ) {
22580 // Include deleted hunks that are adjacent to the query range, because
22581 // otherwise they would be missed.
22582 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22583 if hunk.status().is_deleted() {
22584 intersects_range |= hunk.row_range.start == query_rows.end;
22585 intersects_range |= hunk.row_range.end == query_rows.start;
22586 }
22587 if intersects_range {
22588 if !processed_buffer_rows
22589 .entry(hunk.buffer_id)
22590 .or_default()
22591 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22592 {
22593 continue;
22594 }
22595 hunks.push(hunk);
22596 }
22597 }
22598 }
22599
22600 hunks
22601 }
22602
22603 fn display_diff_hunks_for_rows<'a>(
22604 &'a self,
22605 display_rows: Range<DisplayRow>,
22606 folded_buffers: &'a HashSet<BufferId>,
22607 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22608 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22609 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22610
22611 self.buffer_snapshot
22612 .diff_hunks_in_range(buffer_start..buffer_end)
22613 .filter_map(|hunk| {
22614 if folded_buffers.contains(&hunk.buffer_id) {
22615 return None;
22616 }
22617
22618 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22619 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22620
22621 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22622 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22623
22624 let display_hunk = if hunk_display_start.column() != 0 {
22625 DisplayDiffHunk::Folded {
22626 display_row: hunk_display_start.row(),
22627 }
22628 } else {
22629 let mut end_row = hunk_display_end.row();
22630 if hunk_display_end.column() > 0 {
22631 end_row.0 += 1;
22632 }
22633 let is_created_file = hunk.is_created_file();
22634 DisplayDiffHunk::Unfolded {
22635 status: hunk.status(),
22636 diff_base_byte_range: hunk.diff_base_byte_range,
22637 display_row_range: hunk_display_start.row()..end_row,
22638 multi_buffer_range: Anchor::range_in_buffer(
22639 hunk.excerpt_id,
22640 hunk.buffer_id,
22641 hunk.buffer_range,
22642 ),
22643 is_created_file,
22644 }
22645 };
22646
22647 Some(display_hunk)
22648 })
22649 }
22650
22651 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22652 self.display_snapshot.buffer_snapshot.language_at(position)
22653 }
22654
22655 pub fn is_focused(&self) -> bool {
22656 self.is_focused
22657 }
22658
22659 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22660 self.placeholder_text.as_ref()
22661 }
22662
22663 pub fn scroll_position(&self) -> gpui::Point<f32> {
22664 self.scroll_anchor.scroll_position(&self.display_snapshot)
22665 }
22666
22667 fn gutter_dimensions(
22668 &self,
22669 font_id: FontId,
22670 font_size: Pixels,
22671 max_line_number_width: Pixels,
22672 cx: &App,
22673 ) -> Option<GutterDimensions> {
22674 if !self.show_gutter {
22675 return None;
22676 }
22677
22678 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22679 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22680
22681 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22682 matches!(
22683 ProjectSettings::get_global(cx).git.git_gutter,
22684 Some(GitGutterSetting::TrackedFiles)
22685 )
22686 });
22687 let gutter_settings = EditorSettings::get_global(cx).gutter;
22688 let show_line_numbers = self
22689 .show_line_numbers
22690 .unwrap_or(gutter_settings.line_numbers);
22691 let line_gutter_width = if show_line_numbers {
22692 // Avoid flicker-like gutter resizes when the line number gains another digit by
22693 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22694 let min_width_for_number_on_gutter =
22695 ch_advance * gutter_settings.min_line_number_digits as f32;
22696 max_line_number_width.max(min_width_for_number_on_gutter)
22697 } else {
22698 0.0.into()
22699 };
22700
22701 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22702 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22703
22704 let git_blame_entries_width =
22705 self.git_blame_gutter_max_author_length
22706 .map(|max_author_length| {
22707 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22708 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22709
22710 /// The number of characters to dedicate to gaps and margins.
22711 const SPACING_WIDTH: usize = 4;
22712
22713 let max_char_count = max_author_length.min(renderer.max_author_length())
22714 + ::git::SHORT_SHA_LENGTH
22715 + MAX_RELATIVE_TIMESTAMP.len()
22716 + SPACING_WIDTH;
22717
22718 ch_advance * max_char_count
22719 });
22720
22721 let is_singleton = self.buffer_snapshot.is_singleton();
22722
22723 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22724 left_padding += if !is_singleton {
22725 ch_width * 4.0
22726 } else if show_runnables || show_breakpoints {
22727 ch_width * 3.0
22728 } else if show_git_gutter && show_line_numbers {
22729 ch_width * 2.0
22730 } else if show_git_gutter || show_line_numbers {
22731 ch_width
22732 } else {
22733 px(0.)
22734 };
22735
22736 let shows_folds = is_singleton && gutter_settings.folds;
22737
22738 let right_padding = if shows_folds && show_line_numbers {
22739 ch_width * 4.0
22740 } else if shows_folds || (!is_singleton && show_line_numbers) {
22741 ch_width * 3.0
22742 } else if show_line_numbers {
22743 ch_width
22744 } else {
22745 px(0.)
22746 };
22747
22748 Some(GutterDimensions {
22749 left_padding,
22750 right_padding,
22751 width: line_gutter_width + left_padding + right_padding,
22752 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22753 git_blame_entries_width,
22754 })
22755 }
22756
22757 pub fn render_crease_toggle(
22758 &self,
22759 buffer_row: MultiBufferRow,
22760 row_contains_cursor: bool,
22761 editor: Entity<Editor>,
22762 window: &mut Window,
22763 cx: &mut App,
22764 ) -> Option<AnyElement> {
22765 let folded = self.is_line_folded(buffer_row);
22766 let mut is_foldable = false;
22767
22768 if let Some(crease) = self
22769 .crease_snapshot
22770 .query_row(buffer_row, &self.buffer_snapshot)
22771 {
22772 is_foldable = true;
22773 match crease {
22774 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22775 if let Some(render_toggle) = render_toggle {
22776 let toggle_callback =
22777 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22778 if folded {
22779 editor.update(cx, |editor, cx| {
22780 editor.fold_at(buffer_row, window, cx)
22781 });
22782 } else {
22783 editor.update(cx, |editor, cx| {
22784 editor.unfold_at(buffer_row, window, cx)
22785 });
22786 }
22787 });
22788 return Some((render_toggle)(
22789 buffer_row,
22790 folded,
22791 toggle_callback,
22792 window,
22793 cx,
22794 ));
22795 }
22796 }
22797 }
22798 }
22799
22800 is_foldable |= self.starts_indent(buffer_row);
22801
22802 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22803 Some(
22804 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22805 .toggle_state(folded)
22806 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22807 if folded {
22808 this.unfold_at(buffer_row, window, cx);
22809 } else {
22810 this.fold_at(buffer_row, window, cx);
22811 }
22812 }))
22813 .into_any_element(),
22814 )
22815 } else {
22816 None
22817 }
22818 }
22819
22820 pub fn render_crease_trailer(
22821 &self,
22822 buffer_row: MultiBufferRow,
22823 window: &mut Window,
22824 cx: &mut App,
22825 ) -> Option<AnyElement> {
22826 let folded = self.is_line_folded(buffer_row);
22827 if let Crease::Inline { render_trailer, .. } = self
22828 .crease_snapshot
22829 .query_row(buffer_row, &self.buffer_snapshot)?
22830 {
22831 let render_trailer = render_trailer.as_ref()?;
22832 Some(render_trailer(buffer_row, folded, window, cx))
22833 } else {
22834 None
22835 }
22836 }
22837}
22838
22839impl Deref for EditorSnapshot {
22840 type Target = DisplaySnapshot;
22841
22842 fn deref(&self) -> &Self::Target {
22843 &self.display_snapshot
22844 }
22845}
22846
22847#[derive(Clone, Debug, PartialEq, Eq)]
22848pub enum EditorEvent {
22849 InputIgnored {
22850 text: Arc<str>,
22851 },
22852 InputHandled {
22853 utf16_range_to_replace: Option<Range<isize>>,
22854 text: Arc<str>,
22855 },
22856 ExcerptsAdded {
22857 buffer: Entity<Buffer>,
22858 predecessor: ExcerptId,
22859 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22860 },
22861 ExcerptsRemoved {
22862 ids: Vec<ExcerptId>,
22863 removed_buffer_ids: Vec<BufferId>,
22864 },
22865 BufferFoldToggled {
22866 ids: Vec<ExcerptId>,
22867 folded: bool,
22868 },
22869 ExcerptsEdited {
22870 ids: Vec<ExcerptId>,
22871 },
22872 ExcerptsExpanded {
22873 ids: Vec<ExcerptId>,
22874 },
22875 BufferEdited,
22876 Edited {
22877 transaction_id: clock::Lamport,
22878 },
22879 Reparsed(BufferId),
22880 Focused,
22881 FocusedIn,
22882 Blurred,
22883 DirtyChanged,
22884 Saved,
22885 TitleChanged,
22886 DiffBaseChanged,
22887 SelectionsChanged {
22888 local: bool,
22889 },
22890 ScrollPositionChanged {
22891 local: bool,
22892 autoscroll: bool,
22893 },
22894 Closed,
22895 TransactionUndone {
22896 transaction_id: clock::Lamport,
22897 },
22898 TransactionBegun {
22899 transaction_id: clock::Lamport,
22900 },
22901 Reloaded,
22902 CursorShapeChanged,
22903 BreadcrumbsChanged,
22904 PushedToNavHistory {
22905 anchor: Anchor,
22906 is_deactivate: bool,
22907 },
22908}
22909
22910impl EventEmitter<EditorEvent> for Editor {}
22911
22912impl Focusable for Editor {
22913 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22914 self.focus_handle.clone()
22915 }
22916}
22917
22918impl Render for Editor {
22919 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22920 let settings = ThemeSettings::get_global(cx);
22921
22922 let mut text_style = match self.mode {
22923 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22924 color: cx.theme().colors().editor_foreground,
22925 font_family: settings.ui_font.family.clone(),
22926 font_features: settings.ui_font.features.clone(),
22927 font_fallbacks: settings.ui_font.fallbacks.clone(),
22928 font_size: rems(0.875).into(),
22929 font_weight: settings.ui_font.weight,
22930 line_height: relative(settings.buffer_line_height.value()),
22931 ..Default::default()
22932 },
22933 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22934 color: cx.theme().colors().editor_foreground,
22935 font_family: settings.buffer_font.family.clone(),
22936 font_features: settings.buffer_font.features.clone(),
22937 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22938 font_size: settings.buffer_font_size(cx).into(),
22939 font_weight: settings.buffer_font.weight,
22940 line_height: relative(settings.buffer_line_height.value()),
22941 ..Default::default()
22942 },
22943 };
22944 if let Some(text_style_refinement) = &self.text_style_refinement {
22945 text_style.refine(text_style_refinement)
22946 }
22947
22948 let background = match self.mode {
22949 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22950 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22951 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22952 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22953 };
22954
22955 EditorElement::new(
22956 &cx.entity(),
22957 EditorStyle {
22958 background,
22959 border: cx.theme().colors().border,
22960 local_player: cx.theme().players().local(),
22961 text: text_style,
22962 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22963 syntax: cx.theme().syntax().clone(),
22964 status: cx.theme().status().clone(),
22965 inlay_hints_style: make_inlay_hints_style(cx),
22966 edit_prediction_styles: make_suggestion_styles(cx),
22967 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22968 show_underlines: self.diagnostics_enabled(),
22969 },
22970 )
22971 }
22972}
22973
22974impl EntityInputHandler for Editor {
22975 fn text_for_range(
22976 &mut self,
22977 range_utf16: Range<usize>,
22978 adjusted_range: &mut Option<Range<usize>>,
22979 _: &mut Window,
22980 cx: &mut Context<Self>,
22981 ) -> Option<String> {
22982 let snapshot = self.buffer.read(cx).read(cx);
22983 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22984 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22985 if (start.0..end.0) != range_utf16 {
22986 adjusted_range.replace(start.0..end.0);
22987 }
22988 Some(snapshot.text_for_range(start..end).collect())
22989 }
22990
22991 fn selected_text_range(
22992 &mut self,
22993 ignore_disabled_input: bool,
22994 _: &mut Window,
22995 cx: &mut Context<Self>,
22996 ) -> Option<UTF16Selection> {
22997 // Prevent the IME menu from appearing when holding down an alphabetic key
22998 // while input is disabled.
22999 if !ignore_disabled_input && !self.input_enabled {
23000 return None;
23001 }
23002
23003 let selection = self.selections.newest::<OffsetUtf16>(cx);
23004 let range = selection.range();
23005
23006 Some(UTF16Selection {
23007 range: range.start.0..range.end.0,
23008 reversed: selection.reversed,
23009 })
23010 }
23011
23012 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23013 let snapshot = self.buffer.read(cx).read(cx);
23014 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23015 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23016 }
23017
23018 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23019 self.clear_highlights::<InputComposition>(cx);
23020 self.ime_transaction.take();
23021 }
23022
23023 fn replace_text_in_range(
23024 &mut self,
23025 range_utf16: Option<Range<usize>>,
23026 text: &str,
23027 window: &mut Window,
23028 cx: &mut Context<Self>,
23029 ) {
23030 if !self.input_enabled {
23031 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23032 return;
23033 }
23034
23035 self.transact(window, cx, |this, window, cx| {
23036 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23037 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23038 Some(this.selection_replacement_ranges(range_utf16, cx))
23039 } else {
23040 this.marked_text_ranges(cx)
23041 };
23042
23043 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23044 let newest_selection_id = this.selections.newest_anchor().id;
23045 this.selections
23046 .all::<OffsetUtf16>(cx)
23047 .iter()
23048 .zip(ranges_to_replace.iter())
23049 .find_map(|(selection, range)| {
23050 if selection.id == newest_selection_id {
23051 Some(
23052 (range.start.0 as isize - selection.head().0 as isize)
23053 ..(range.end.0 as isize - selection.head().0 as isize),
23054 )
23055 } else {
23056 None
23057 }
23058 })
23059 });
23060
23061 cx.emit(EditorEvent::InputHandled {
23062 utf16_range_to_replace: range_to_replace,
23063 text: text.into(),
23064 });
23065
23066 if let Some(new_selected_ranges) = new_selected_ranges {
23067 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23068 selections.select_ranges(new_selected_ranges)
23069 });
23070 this.backspace(&Default::default(), window, cx);
23071 }
23072
23073 this.handle_input(text, window, cx);
23074 });
23075
23076 if let Some(transaction) = self.ime_transaction {
23077 self.buffer.update(cx, |buffer, cx| {
23078 buffer.group_until_transaction(transaction, cx);
23079 });
23080 }
23081
23082 self.unmark_text(window, cx);
23083 }
23084
23085 fn replace_and_mark_text_in_range(
23086 &mut self,
23087 range_utf16: Option<Range<usize>>,
23088 text: &str,
23089 new_selected_range_utf16: Option<Range<usize>>,
23090 window: &mut Window,
23091 cx: &mut Context<Self>,
23092 ) {
23093 if !self.input_enabled {
23094 return;
23095 }
23096
23097 let transaction = self.transact(window, cx, |this, window, cx| {
23098 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23099 let snapshot = this.buffer.read(cx).read(cx);
23100 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23101 for marked_range in &mut marked_ranges {
23102 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23103 marked_range.start.0 += relative_range_utf16.start;
23104 marked_range.start =
23105 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23106 marked_range.end =
23107 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23108 }
23109 }
23110 Some(marked_ranges)
23111 } else if let Some(range_utf16) = range_utf16 {
23112 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23113 Some(this.selection_replacement_ranges(range_utf16, cx))
23114 } else {
23115 None
23116 };
23117
23118 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23119 let newest_selection_id = this.selections.newest_anchor().id;
23120 this.selections
23121 .all::<OffsetUtf16>(cx)
23122 .iter()
23123 .zip(ranges_to_replace.iter())
23124 .find_map(|(selection, range)| {
23125 if selection.id == newest_selection_id {
23126 Some(
23127 (range.start.0 as isize - selection.head().0 as isize)
23128 ..(range.end.0 as isize - selection.head().0 as isize),
23129 )
23130 } else {
23131 None
23132 }
23133 })
23134 });
23135
23136 cx.emit(EditorEvent::InputHandled {
23137 utf16_range_to_replace: range_to_replace,
23138 text: text.into(),
23139 });
23140
23141 if let Some(ranges) = ranges_to_replace {
23142 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23143 s.select_ranges(ranges)
23144 });
23145 }
23146
23147 let marked_ranges = {
23148 let snapshot = this.buffer.read(cx).read(cx);
23149 this.selections
23150 .disjoint_anchors()
23151 .iter()
23152 .map(|selection| {
23153 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23154 })
23155 .collect::<Vec<_>>()
23156 };
23157
23158 if text.is_empty() {
23159 this.unmark_text(window, cx);
23160 } else {
23161 this.highlight_text::<InputComposition>(
23162 marked_ranges.clone(),
23163 HighlightStyle {
23164 underline: Some(UnderlineStyle {
23165 thickness: px(1.),
23166 color: None,
23167 wavy: false,
23168 }),
23169 ..Default::default()
23170 },
23171 cx,
23172 );
23173 }
23174
23175 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23176 let use_autoclose = this.use_autoclose;
23177 let use_auto_surround = this.use_auto_surround;
23178 this.set_use_autoclose(false);
23179 this.set_use_auto_surround(false);
23180 this.handle_input(text, window, cx);
23181 this.set_use_autoclose(use_autoclose);
23182 this.set_use_auto_surround(use_auto_surround);
23183
23184 if let Some(new_selected_range) = new_selected_range_utf16 {
23185 let snapshot = this.buffer.read(cx).read(cx);
23186 let new_selected_ranges = marked_ranges
23187 .into_iter()
23188 .map(|marked_range| {
23189 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23190 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23191 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23192 snapshot.clip_offset_utf16(new_start, Bias::Left)
23193 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23194 })
23195 .collect::<Vec<_>>();
23196
23197 drop(snapshot);
23198 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23199 selections.select_ranges(new_selected_ranges)
23200 });
23201 }
23202 });
23203
23204 self.ime_transaction = self.ime_transaction.or(transaction);
23205 if let Some(transaction) = self.ime_transaction {
23206 self.buffer.update(cx, |buffer, cx| {
23207 buffer.group_until_transaction(transaction, cx);
23208 });
23209 }
23210
23211 if self.text_highlights::<InputComposition>(cx).is_none() {
23212 self.ime_transaction.take();
23213 }
23214 }
23215
23216 fn bounds_for_range(
23217 &mut self,
23218 range_utf16: Range<usize>,
23219 element_bounds: gpui::Bounds<Pixels>,
23220 window: &mut Window,
23221 cx: &mut Context<Self>,
23222 ) -> Option<gpui::Bounds<Pixels>> {
23223 let text_layout_details = self.text_layout_details(window);
23224 let CharacterDimensions {
23225 em_width,
23226 em_advance,
23227 line_height,
23228 } = self.character_dimensions(window);
23229
23230 let snapshot = self.snapshot(window, cx);
23231 let scroll_position = snapshot.scroll_position();
23232 let scroll_left = scroll_position.x * em_advance;
23233
23234 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23235 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23236 + self.gutter_dimensions.full_width();
23237 let y = line_height * (start.row().as_f32() - scroll_position.y);
23238
23239 Some(Bounds {
23240 origin: element_bounds.origin + point(x, y),
23241 size: size(em_width, line_height),
23242 })
23243 }
23244
23245 fn character_index_for_point(
23246 &mut self,
23247 point: gpui::Point<Pixels>,
23248 _window: &mut Window,
23249 _cx: &mut Context<Self>,
23250 ) -> Option<usize> {
23251 let position_map = self.last_position_map.as_ref()?;
23252 if !position_map.text_hitbox.contains(&point) {
23253 return None;
23254 }
23255 let display_point = position_map.point_for_position(point).previous_valid;
23256 let anchor = position_map
23257 .snapshot
23258 .display_point_to_anchor(display_point, Bias::Left);
23259 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23260 Some(utf16_offset.0)
23261 }
23262}
23263
23264trait SelectionExt {
23265 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23266 fn spanned_rows(
23267 &self,
23268 include_end_if_at_line_start: bool,
23269 map: &DisplaySnapshot,
23270 ) -> Range<MultiBufferRow>;
23271}
23272
23273impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23274 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23275 let start = self
23276 .start
23277 .to_point(&map.buffer_snapshot)
23278 .to_display_point(map);
23279 let end = self
23280 .end
23281 .to_point(&map.buffer_snapshot)
23282 .to_display_point(map);
23283 if self.reversed {
23284 end..start
23285 } else {
23286 start..end
23287 }
23288 }
23289
23290 fn spanned_rows(
23291 &self,
23292 include_end_if_at_line_start: bool,
23293 map: &DisplaySnapshot,
23294 ) -> Range<MultiBufferRow> {
23295 let start = self.start.to_point(&map.buffer_snapshot);
23296 let mut end = self.end.to_point(&map.buffer_snapshot);
23297 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23298 end.row -= 1;
23299 }
23300
23301 let buffer_start = map.prev_line_boundary(start).0;
23302 let buffer_end = map.next_line_boundary(end).0;
23303 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23304 }
23305}
23306
23307impl<T: InvalidationRegion> InvalidationStack<T> {
23308 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23309 where
23310 S: Clone + ToOffset,
23311 {
23312 while let Some(region) = self.last() {
23313 let all_selections_inside_invalidation_ranges =
23314 if selections.len() == region.ranges().len() {
23315 selections
23316 .iter()
23317 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23318 .all(|(selection, invalidation_range)| {
23319 let head = selection.head().to_offset(buffer);
23320 invalidation_range.start <= head && invalidation_range.end >= head
23321 })
23322 } else {
23323 false
23324 };
23325
23326 if all_selections_inside_invalidation_ranges {
23327 break;
23328 } else {
23329 self.pop();
23330 }
23331 }
23332 }
23333}
23334
23335impl<T> Default for InvalidationStack<T> {
23336 fn default() -> Self {
23337 Self(Default::default())
23338 }
23339}
23340
23341impl<T> Deref for InvalidationStack<T> {
23342 type Target = Vec<T>;
23343
23344 fn deref(&self) -> &Self::Target {
23345 &self.0
23346 }
23347}
23348
23349impl<T> DerefMut for InvalidationStack<T> {
23350 fn deref_mut(&mut self) -> &mut Self::Target {
23351 &mut self.0
23352 }
23353}
23354
23355impl InvalidationRegion for SnippetState {
23356 fn ranges(&self) -> &[Range<Anchor>] {
23357 &self.ranges[self.active_index]
23358 }
23359}
23360
23361fn edit_prediction_edit_text(
23362 current_snapshot: &BufferSnapshot,
23363 edits: &[(Range<Anchor>, String)],
23364 edit_preview: &EditPreview,
23365 include_deletions: bool,
23366 cx: &App,
23367) -> HighlightedText {
23368 let edits = edits
23369 .iter()
23370 .map(|(anchor, text)| {
23371 (
23372 anchor.start.text_anchor..anchor.end.text_anchor,
23373 text.clone(),
23374 )
23375 })
23376 .collect::<Vec<_>>();
23377
23378 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23379}
23380
23381fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23382 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23383 // Just show the raw edit text with basic styling
23384 let mut text = String::new();
23385 let mut highlights = Vec::new();
23386
23387 let insertion_highlight_style = HighlightStyle {
23388 color: Some(cx.theme().colors().text),
23389 ..Default::default()
23390 };
23391
23392 for (_, edit_text) in edits {
23393 let start_offset = text.len();
23394 text.push_str(edit_text);
23395 let end_offset = text.len();
23396
23397 if start_offset < end_offset {
23398 highlights.push((start_offset..end_offset, insertion_highlight_style));
23399 }
23400 }
23401
23402 HighlightedText {
23403 text: text.into(),
23404 highlights,
23405 }
23406}
23407
23408pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23409 match severity {
23410 lsp::DiagnosticSeverity::ERROR => colors.error,
23411 lsp::DiagnosticSeverity::WARNING => colors.warning,
23412 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23413 lsp::DiagnosticSeverity::HINT => colors.info,
23414 _ => colors.ignored,
23415 }
23416}
23417
23418pub fn styled_runs_for_code_label<'a>(
23419 label: &'a CodeLabel,
23420 syntax_theme: &'a theme::SyntaxTheme,
23421) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23422 let fade_out = HighlightStyle {
23423 fade_out: Some(0.35),
23424 ..Default::default()
23425 };
23426
23427 let mut prev_end = label.filter_range.end;
23428 label
23429 .runs
23430 .iter()
23431 .enumerate()
23432 .flat_map(move |(ix, (range, highlight_id))| {
23433 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23434 style
23435 } else {
23436 return Default::default();
23437 };
23438 let mut muted_style = style;
23439 muted_style.highlight(fade_out);
23440
23441 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23442 if range.start >= label.filter_range.end {
23443 if range.start > prev_end {
23444 runs.push((prev_end..range.start, fade_out));
23445 }
23446 runs.push((range.clone(), muted_style));
23447 } else if range.end <= label.filter_range.end {
23448 runs.push((range.clone(), style));
23449 } else {
23450 runs.push((range.start..label.filter_range.end, style));
23451 runs.push((label.filter_range.end..range.end, muted_style));
23452 }
23453 prev_end = cmp::max(prev_end, range.end);
23454
23455 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23456 runs.push((prev_end..label.text.len(), fade_out));
23457 }
23458
23459 runs
23460 })
23461}
23462
23463pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23464 let mut prev_index = 0;
23465 let mut prev_codepoint: Option<char> = None;
23466 text.char_indices()
23467 .chain([(text.len(), '\0')])
23468 .filter_map(move |(index, codepoint)| {
23469 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23470 let is_boundary = index == text.len()
23471 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23472 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23473 if is_boundary {
23474 let chunk = &text[prev_index..index];
23475 prev_index = index;
23476 Some(chunk)
23477 } else {
23478 None
23479 }
23480 })
23481}
23482
23483pub trait RangeToAnchorExt: Sized {
23484 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23485
23486 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23487 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23488 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23489 }
23490}
23491
23492impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23493 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23494 let start_offset = self.start.to_offset(snapshot);
23495 let end_offset = self.end.to_offset(snapshot);
23496 if start_offset == end_offset {
23497 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23498 } else {
23499 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23500 }
23501 }
23502}
23503
23504pub trait RowExt {
23505 fn as_f32(&self) -> f32;
23506
23507 fn next_row(&self) -> Self;
23508
23509 fn previous_row(&self) -> Self;
23510
23511 fn minus(&self, other: Self) -> u32;
23512}
23513
23514impl RowExt for DisplayRow {
23515 fn as_f32(&self) -> f32 {
23516 self.0 as f32
23517 }
23518
23519 fn next_row(&self) -> Self {
23520 Self(self.0 + 1)
23521 }
23522
23523 fn previous_row(&self) -> Self {
23524 Self(self.0.saturating_sub(1))
23525 }
23526
23527 fn minus(&self, other: Self) -> u32 {
23528 self.0 - other.0
23529 }
23530}
23531
23532impl RowExt for MultiBufferRow {
23533 fn as_f32(&self) -> f32 {
23534 self.0 as f32
23535 }
23536
23537 fn next_row(&self) -> Self {
23538 Self(self.0 + 1)
23539 }
23540
23541 fn previous_row(&self) -> Self {
23542 Self(self.0.saturating_sub(1))
23543 }
23544
23545 fn minus(&self, other: Self) -> u32 {
23546 self.0 - other.0
23547 }
23548}
23549
23550trait RowRangeExt {
23551 type Row;
23552
23553 fn len(&self) -> usize;
23554
23555 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23556}
23557
23558impl RowRangeExt for Range<MultiBufferRow> {
23559 type Row = MultiBufferRow;
23560
23561 fn len(&self) -> usize {
23562 (self.end.0 - self.start.0) as usize
23563 }
23564
23565 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23566 (self.start.0..self.end.0).map(MultiBufferRow)
23567 }
23568}
23569
23570impl RowRangeExt for Range<DisplayRow> {
23571 type Row = DisplayRow;
23572
23573 fn len(&self) -> usize {
23574 (self.end.0 - self.start.0) as usize
23575 }
23576
23577 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23578 (self.start.0..self.end.0).map(DisplayRow)
23579 }
23580}
23581
23582/// If select range has more than one line, we
23583/// just point the cursor to range.start.
23584fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23585 if range.start.row == range.end.row {
23586 range
23587 } else {
23588 range.start..range.start
23589 }
23590}
23591pub struct KillRing(ClipboardItem);
23592impl Global for KillRing {}
23593
23594const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23595
23596enum BreakpointPromptEditAction {
23597 Log,
23598 Condition,
23599 HitCondition,
23600}
23601
23602struct BreakpointPromptEditor {
23603 pub(crate) prompt: Entity<Editor>,
23604 editor: WeakEntity<Editor>,
23605 breakpoint_anchor: Anchor,
23606 breakpoint: Breakpoint,
23607 edit_action: BreakpointPromptEditAction,
23608 block_ids: HashSet<CustomBlockId>,
23609 editor_margins: Arc<Mutex<EditorMargins>>,
23610 _subscriptions: Vec<Subscription>,
23611}
23612
23613impl BreakpointPromptEditor {
23614 const MAX_LINES: u8 = 4;
23615
23616 fn new(
23617 editor: WeakEntity<Editor>,
23618 breakpoint_anchor: Anchor,
23619 breakpoint: Breakpoint,
23620 edit_action: BreakpointPromptEditAction,
23621 window: &mut Window,
23622 cx: &mut Context<Self>,
23623 ) -> Self {
23624 let base_text = match edit_action {
23625 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23626 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23627 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23628 }
23629 .map(|msg| msg.to_string())
23630 .unwrap_or_default();
23631
23632 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23633 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23634
23635 let prompt = cx.new(|cx| {
23636 let mut prompt = Editor::new(
23637 EditorMode::AutoHeight {
23638 min_lines: 1,
23639 max_lines: Some(Self::MAX_LINES as usize),
23640 },
23641 buffer,
23642 None,
23643 window,
23644 cx,
23645 );
23646 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23647 prompt.set_show_cursor_when_unfocused(false, cx);
23648 prompt.set_placeholder_text(
23649 match edit_action {
23650 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23651 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23652 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23653 },
23654 cx,
23655 );
23656
23657 prompt
23658 });
23659
23660 Self {
23661 prompt,
23662 editor,
23663 breakpoint_anchor,
23664 breakpoint,
23665 edit_action,
23666 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23667 block_ids: Default::default(),
23668 _subscriptions: vec![],
23669 }
23670 }
23671
23672 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23673 self.block_ids.extend(block_ids)
23674 }
23675
23676 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23677 if let Some(editor) = self.editor.upgrade() {
23678 let message = self
23679 .prompt
23680 .read(cx)
23681 .buffer
23682 .read(cx)
23683 .as_singleton()
23684 .expect("A multi buffer in breakpoint prompt isn't possible")
23685 .read(cx)
23686 .as_rope()
23687 .to_string();
23688
23689 editor.update(cx, |editor, cx| {
23690 editor.edit_breakpoint_at_anchor(
23691 self.breakpoint_anchor,
23692 self.breakpoint.clone(),
23693 match self.edit_action {
23694 BreakpointPromptEditAction::Log => {
23695 BreakpointEditAction::EditLogMessage(message.into())
23696 }
23697 BreakpointPromptEditAction::Condition => {
23698 BreakpointEditAction::EditCondition(message.into())
23699 }
23700 BreakpointPromptEditAction::HitCondition => {
23701 BreakpointEditAction::EditHitCondition(message.into())
23702 }
23703 },
23704 cx,
23705 );
23706
23707 editor.remove_blocks(self.block_ids.clone(), None, cx);
23708 cx.focus_self(window);
23709 });
23710 }
23711 }
23712
23713 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23714 self.editor
23715 .update(cx, |editor, cx| {
23716 editor.remove_blocks(self.block_ids.clone(), None, cx);
23717 window.focus(&editor.focus_handle);
23718 })
23719 .log_err();
23720 }
23721
23722 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23723 let settings = ThemeSettings::get_global(cx);
23724 let text_style = TextStyle {
23725 color: if self.prompt.read(cx).read_only(cx) {
23726 cx.theme().colors().text_disabled
23727 } else {
23728 cx.theme().colors().text
23729 },
23730 font_family: settings.buffer_font.family.clone(),
23731 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23732 font_size: settings.buffer_font_size(cx).into(),
23733 font_weight: settings.buffer_font.weight,
23734 line_height: relative(settings.buffer_line_height.value()),
23735 ..Default::default()
23736 };
23737 EditorElement::new(
23738 &self.prompt,
23739 EditorStyle {
23740 background: cx.theme().colors().editor_background,
23741 local_player: cx.theme().players().local(),
23742 text: text_style,
23743 ..Default::default()
23744 },
23745 )
23746 }
23747}
23748
23749impl Render for BreakpointPromptEditor {
23750 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23751 let editor_margins = *self.editor_margins.lock();
23752 let gutter_dimensions = editor_margins.gutter;
23753 h_flex()
23754 .key_context("Editor")
23755 .bg(cx.theme().colors().editor_background)
23756 .border_y_1()
23757 .border_color(cx.theme().status().info_border)
23758 .size_full()
23759 .py(window.line_height() / 2.5)
23760 .on_action(cx.listener(Self::confirm))
23761 .on_action(cx.listener(Self::cancel))
23762 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23763 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23764 }
23765}
23766
23767impl Focusable for BreakpointPromptEditor {
23768 fn focus_handle(&self, cx: &App) -> FocusHandle {
23769 self.prompt.focus_handle(cx)
23770 }
23771}
23772
23773fn all_edits_insertions_or_deletions(
23774 edits: &Vec<(Range<Anchor>, String)>,
23775 snapshot: &MultiBufferSnapshot,
23776) -> bool {
23777 let mut all_insertions = true;
23778 let mut all_deletions = true;
23779
23780 for (range, new_text) in edits.iter() {
23781 let range_is_empty = range.to_offset(&snapshot).is_empty();
23782 let text_is_empty = new_text.is_empty();
23783
23784 if range_is_empty != text_is_empty {
23785 if range_is_empty {
23786 all_deletions = false;
23787 } else {
23788 all_insertions = false;
23789 }
23790 } else {
23791 return false;
23792 }
23793
23794 if !all_insertions && !all_deletions {
23795 return false;
23796 }
23797 }
23798 all_insertions || all_deletions
23799}
23800
23801struct MissingEditPredictionKeybindingTooltip;
23802
23803impl Render for MissingEditPredictionKeybindingTooltip {
23804 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23805 ui::tooltip_container(window, cx, |container, _, cx| {
23806 container
23807 .flex_shrink_0()
23808 .max_w_80()
23809 .min_h(rems_from_px(124.))
23810 .justify_between()
23811 .child(
23812 v_flex()
23813 .flex_1()
23814 .text_ui_sm(cx)
23815 .child(Label::new("Conflict with Accept Keybinding"))
23816 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23817 )
23818 .child(
23819 h_flex()
23820 .pb_1()
23821 .gap_1()
23822 .items_end()
23823 .w_full()
23824 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23825 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23826 }))
23827 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23828 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23829 })),
23830 )
23831 })
23832 }
23833}
23834
23835#[derive(Debug, Clone, Copy, PartialEq)]
23836pub struct LineHighlight {
23837 pub background: Background,
23838 pub border: Option<gpui::Hsla>,
23839 pub include_gutter: bool,
23840 pub type_id: Option<TypeId>,
23841}
23842
23843struct LineManipulationResult {
23844 pub new_text: String,
23845 pub line_count_before: usize,
23846 pub line_count_after: usize,
23847}
23848
23849fn render_diff_hunk_controls(
23850 row: u32,
23851 status: &DiffHunkStatus,
23852 hunk_range: Range<Anchor>,
23853 is_created_file: bool,
23854 line_height: Pixels,
23855 editor: &Entity<Editor>,
23856 _window: &mut Window,
23857 cx: &mut App,
23858) -> AnyElement {
23859 h_flex()
23860 .h(line_height)
23861 .mr_1()
23862 .gap_1()
23863 .px_0p5()
23864 .pb_1()
23865 .border_x_1()
23866 .border_b_1()
23867 .border_color(cx.theme().colors().border_variant)
23868 .rounded_b_lg()
23869 .bg(cx.theme().colors().editor_background)
23870 .gap_1()
23871 .block_mouse_except_scroll()
23872 .shadow_md()
23873 .child(if status.has_secondary_hunk() {
23874 Button::new(("stage", row as u64), "Stage")
23875 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23876 .tooltip({
23877 let focus_handle = editor.focus_handle(cx);
23878 move |window, cx| {
23879 Tooltip::for_action_in(
23880 "Stage Hunk",
23881 &::git::ToggleStaged,
23882 &focus_handle,
23883 window,
23884 cx,
23885 )
23886 }
23887 })
23888 .on_click({
23889 let editor = editor.clone();
23890 move |_event, _window, cx| {
23891 editor.update(cx, |editor, cx| {
23892 editor.stage_or_unstage_diff_hunks(
23893 true,
23894 vec![hunk_range.start..hunk_range.start],
23895 cx,
23896 );
23897 });
23898 }
23899 })
23900 } else {
23901 Button::new(("unstage", row as u64), "Unstage")
23902 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23903 .tooltip({
23904 let focus_handle = editor.focus_handle(cx);
23905 move |window, cx| {
23906 Tooltip::for_action_in(
23907 "Unstage Hunk",
23908 &::git::ToggleStaged,
23909 &focus_handle,
23910 window,
23911 cx,
23912 )
23913 }
23914 })
23915 .on_click({
23916 let editor = editor.clone();
23917 move |_event, _window, cx| {
23918 editor.update(cx, |editor, cx| {
23919 editor.stage_or_unstage_diff_hunks(
23920 false,
23921 vec![hunk_range.start..hunk_range.start],
23922 cx,
23923 );
23924 });
23925 }
23926 })
23927 })
23928 .child(
23929 Button::new(("restore", row as u64), "Restore")
23930 .tooltip({
23931 let focus_handle = editor.focus_handle(cx);
23932 move |window, cx| {
23933 Tooltip::for_action_in(
23934 "Restore Hunk",
23935 &::git::Restore,
23936 &focus_handle,
23937 window,
23938 cx,
23939 )
23940 }
23941 })
23942 .on_click({
23943 let editor = editor.clone();
23944 move |_event, window, cx| {
23945 editor.update(cx, |editor, cx| {
23946 let snapshot = editor.snapshot(window, cx);
23947 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23948 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23949 });
23950 }
23951 })
23952 .disabled(is_created_file),
23953 )
23954 .when(
23955 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23956 |el| {
23957 el.child(
23958 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23959 .shape(IconButtonShape::Square)
23960 .icon_size(IconSize::Small)
23961 // .disabled(!has_multiple_hunks)
23962 .tooltip({
23963 let focus_handle = editor.focus_handle(cx);
23964 move |window, cx| {
23965 Tooltip::for_action_in(
23966 "Next Hunk",
23967 &GoToHunk,
23968 &focus_handle,
23969 window,
23970 cx,
23971 )
23972 }
23973 })
23974 .on_click({
23975 let editor = editor.clone();
23976 move |_event, window, cx| {
23977 editor.update(cx, |editor, cx| {
23978 let snapshot = editor.snapshot(window, cx);
23979 let position =
23980 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23981 editor.go_to_hunk_before_or_after_position(
23982 &snapshot,
23983 position,
23984 Direction::Next,
23985 window,
23986 cx,
23987 );
23988 editor.expand_selected_diff_hunks(cx);
23989 });
23990 }
23991 }),
23992 )
23993 .child(
23994 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23995 .shape(IconButtonShape::Square)
23996 .icon_size(IconSize::Small)
23997 // .disabled(!has_multiple_hunks)
23998 .tooltip({
23999 let focus_handle = editor.focus_handle(cx);
24000 move |window, cx| {
24001 Tooltip::for_action_in(
24002 "Previous Hunk",
24003 &GoToPreviousHunk,
24004 &focus_handle,
24005 window,
24006 cx,
24007 )
24008 }
24009 })
24010 .on_click({
24011 let editor = editor.clone();
24012 move |_event, window, cx| {
24013 editor.update(cx, |editor, cx| {
24014 let snapshot = editor.snapshot(window, cx);
24015 let point =
24016 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24017 editor.go_to_hunk_before_or_after_position(
24018 &snapshot,
24019 point,
24020 Direction::Prev,
24021 window,
24022 cx,
24023 );
24024 editor.expand_selected_diff_hunks(cx);
24025 });
24026 }
24027 }),
24028 )
24029 },
24030 )
24031 .into_any_element()
24032}