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