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,
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, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
152 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
153 ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use rpc::{ErrorCode, ErrorExt, proto::PeerId};
167use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
168use selections_collection::{
169 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
170};
171use serde::{Deserialize, Serialize};
172use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
173use smallvec::{SmallVec, smallvec};
174use snippet::Snippet;
175use std::{
176 any::{Any, TypeId},
177 borrow::Cow,
178 cell::{OnceCell, RefCell},
179 cmp::{self, Ordering, Reverse},
180 iter::Peekable,
181 mem,
182 num::NonZeroU32,
183 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
184 path::{Path, PathBuf},
185 rc::Rc,
186 sync::Arc,
187 time::{Duration, Instant},
188};
189use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
190use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
191use theme::{
192 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
193 observe_buffer_font_size_adjustment,
194};
195use ui::{
196 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
197 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
198};
199use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
200use workspace::{
201 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
202 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
203 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
204 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
205 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
206 searchable::SearchEvent,
207};
208
209use crate::{
210 code_context_menus::CompletionsMenuSource,
211 editor_settings::MultiCursorModifier,
212 hover_links::{find_url, find_url_from_range},
213 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
214};
215
216pub const FILE_HEADER_HEIGHT: u32 = 2;
217pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
218const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
219const MAX_LINE_LEN: usize = 1024;
220const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
221const MAX_SELECTION_HISTORY_LEN: usize = 1024;
222pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
223#[doc(hidden)]
224pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
225pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
226
227pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248enum ReportEditorEvent {
249 Saved { auto_saved: bool },
250 EditorOpened,
251 Closed,
252}
253
254impl ReportEditorEvent {
255 pub fn event_type(&self) -> &'static str {
256 match self {
257 Self::Saved { .. } => "Editor Saved",
258 Self::EditorOpened => "Editor Opened",
259 Self::Closed => "Editor Closed",
260 }
261 }
262}
263
264struct InlineValueCache {
265 enabled: bool,
266 inlays: Vec<InlayId>,
267 refresh_task: Task<Option<()>>,
268}
269
270impl InlineValueCache {
271 fn new(enabled: bool) -> Self {
272 Self {
273 enabled,
274 inlays: Vec::new(),
275 refresh_task: Task::ready(None),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum InlayId {
282 EditPrediction(usize),
283 DebuggerValue(usize),
284 // LSP
285 Hint(usize),
286 Color(usize),
287}
288
289impl InlayId {
290 fn id(&self) -> usize {
291 match self {
292 Self::EditPrediction(id) => *id,
293 Self::DebuggerValue(id) => *id,
294 Self::Hint(id) => *id,
295 Self::Color(id) => *id,
296 }
297 }
298}
299
300pub enum ActiveDebugLine {}
301pub enum DebugStackFrameLine {}
302enum DocumentHighlightRead {}
303enum DocumentHighlightWrite {}
304enum InputComposition {}
305pub enum PendingInput {}
306enum SelectedTextHighlight {}
307
308pub enum ConflictsOuter {}
309pub enum ConflictsOurs {}
310pub enum ConflictsTheirs {}
311pub enum ConflictsOursMarker {}
312pub enum ConflictsTheirsMarker {}
313
314#[derive(Debug, Copy, Clone, PartialEq, Eq)]
315pub enum Navigated {
316 Yes,
317 No,
318}
319
320impl Navigated {
321 pub fn from_bool(yes: bool) -> Navigated {
322 if yes { Navigated::Yes } else { Navigated::No }
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum DisplayDiffHunk {
328 Folded {
329 display_row: DisplayRow,
330 },
331 Unfolded {
332 is_created_file: bool,
333 diff_base_byte_range: Range<usize>,
334 display_row_range: Range<DisplayRow>,
335 multi_buffer_range: Range<Anchor>,
336 status: DiffHunkStatus,
337 },
338}
339
340pub enum HideMouseCursorOrigin {
341 TypingAction,
342 MovementAction,
343}
344
345pub fn init_settings(cx: &mut App) {
346 EditorSettings::register(cx);
347}
348
349pub fn init(cx: &mut App) {
350 init_settings(cx);
351
352 cx.set_global(GlobalBlameRenderer(Arc::new(())));
353
354 workspace::register_project_item::<Editor>(cx);
355 workspace::FollowableViewRegistry::register::<Editor>(cx);
356 workspace::register_serializable_item::<Editor>(cx);
357
358 cx.observe_new(
359 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
360 workspace.register_action(Editor::new_file);
361 workspace.register_action(Editor::new_file_vertical);
362 workspace.register_action(Editor::new_file_horizontal);
363 workspace.register_action(Editor::cancel_language_server_work);
364 workspace.register_action(Editor::toggle_focus);
365 },
366 )
367 .detach();
368
369 cx.on_action(move |_: &workspace::NewFile, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 Editor::new_file(workspace, &Default::default(), window, cx)
378 },
379 )
380 .detach();
381 }
382 });
383 cx.on_action(move |_: &workspace::NewWindow, cx| {
384 let app_state = workspace::AppState::global(cx);
385 if let Some(app_state) = app_state.upgrade() {
386 workspace::open_new(
387 Default::default(),
388 app_state,
389 cx,
390 |workspace, window, cx| {
391 cx.activate(true);
392 Editor::new_file(workspace, &Default::default(), window, cx)
393 },
394 )
395 .detach();
396 }
397 });
398}
399
400pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
401 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
402}
403
404pub trait DiagnosticRenderer {
405 fn render_group(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntry<Point>>,
408 buffer_id: BufferId,
409 snapshot: EditorSnapshot,
410 editor: WeakEntity<Editor>,
411 cx: &mut App,
412 ) -> Vec<BlockProperties<Anchor>>;
413
414 fn render_hover(
415 &self,
416 diagnostic_group: Vec<DiagnosticEntry<Point>>,
417 range: Range<Point>,
418 buffer_id: BufferId,
419 cx: &mut App,
420 ) -> Option<Entity<markdown::Markdown>>;
421
422 fn open_link(
423 &self,
424 editor: &mut Editor,
425 link: SharedString,
426 window: &mut Window,
427 cx: &mut Context<Editor>,
428 );
429}
430
431pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
432
433impl GlobalDiagnosticRenderer {
434 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
435 cx.try_global::<Self>().map(|g| g.0.clone())
436 }
437}
438
439impl gpui::Global for GlobalDiagnosticRenderer {}
440pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
441 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
442}
443
444pub struct SearchWithinRange;
445
446trait InvalidationRegion {
447 fn ranges(&self) -> &[Range<Anchor>];
448}
449
450#[derive(Clone, Debug, PartialEq)]
451pub enum SelectPhase {
452 Begin {
453 position: DisplayPoint,
454 add: bool,
455 click_count: usize,
456 },
457 BeginColumnar {
458 position: DisplayPoint,
459 reset: bool,
460 mode: ColumnarMode,
461 goal_column: u32,
462 },
463 Extend {
464 position: DisplayPoint,
465 click_count: usize,
466 },
467 Update {
468 position: DisplayPoint,
469 goal_column: u32,
470 scroll_delta: gpui::Point<f32>,
471 },
472 End,
473}
474
475#[derive(Clone, Debug, PartialEq)]
476pub enum ColumnarMode {
477 FromMouse,
478 FromSelection,
479}
480
481#[derive(Clone, Debug)]
482pub enum SelectMode {
483 Character,
484 Word(Range<Anchor>),
485 Line(Range<Anchor>),
486 All,
487}
488
489#[derive(Clone, PartialEq, Eq, Debug)]
490pub enum EditorMode {
491 SingleLine,
492 AutoHeight {
493 min_lines: usize,
494 max_lines: Option<usize>,
495 },
496 Full {
497 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
498 scale_ui_elements_with_buffer_font_size: bool,
499 /// When set to `true`, the editor will render a background for the active line.
500 show_active_line_background: bool,
501 /// When set to `true`, the editor's height will be determined by its content.
502 sized_by_content: bool,
503 },
504 Minimap {
505 parent: WeakEntity<Editor>,
506 },
507}
508
509impl EditorMode {
510 pub fn full() -> Self {
511 Self::Full {
512 scale_ui_elements_with_buffer_font_size: true,
513 show_active_line_background: true,
514 sized_by_content: false,
515 }
516 }
517
518 #[inline]
519 pub fn is_full(&self) -> bool {
520 matches!(self, Self::Full { .. })
521 }
522
523 #[inline]
524 pub fn is_single_line(&self) -> bool {
525 matches!(self, Self::SingleLine { .. })
526 }
527
528 #[inline]
529 fn is_minimap(&self) -> bool {
530 matches!(self, Self::Minimap { .. })
531 }
532}
533
534#[derive(Copy, Clone, Debug)]
535pub enum SoftWrap {
536 /// Prefer not to wrap at all.
537 ///
538 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
539 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
540 GitDiff,
541 /// Prefer a single line generally, unless an overly long line is encountered.
542 None,
543 /// Soft wrap lines that exceed the editor width.
544 EditorWidth,
545 /// Soft wrap lines at the preferred line length.
546 Column(u32),
547 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
548 Bounded(u32),
549}
550
551#[derive(Clone)]
552pub struct EditorStyle {
553 pub background: Hsla,
554 pub border: Hsla,
555 pub local_player: PlayerColor,
556 pub text: TextStyle,
557 pub scrollbar_width: Pixels,
558 pub syntax: Arc<SyntaxTheme>,
559 pub status: StatusColors,
560 pub inlay_hints_style: HighlightStyle,
561 pub edit_prediction_styles: EditPredictionStyles,
562 pub unnecessary_code_fade: f32,
563 pub show_underlines: bool,
564}
565
566impl Default for EditorStyle {
567 fn default() -> Self {
568 Self {
569 background: Hsla::default(),
570 border: Hsla::default(),
571 local_player: PlayerColor::default(),
572 text: TextStyle::default(),
573 scrollbar_width: Pixels::default(),
574 syntax: Default::default(),
575 // HACK: Status colors don't have a real default.
576 // We should look into removing the status colors from the editor
577 // style and retrieve them directly from the theme.
578 status: StatusColors::dark(),
579 inlay_hints_style: HighlightStyle::default(),
580 edit_prediction_styles: EditPredictionStyles {
581 insertion: HighlightStyle::default(),
582 whitespace: HighlightStyle::default(),
583 },
584 unnecessary_code_fade: Default::default(),
585 show_underlines: true,
586 }
587 }
588}
589
590pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
591 let show_background = language_settings::language_settings(None, None, cx)
592 .inlay_hints
593 .show_background;
594
595 HighlightStyle {
596 color: Some(cx.theme().status().hint),
597 background_color: show_background.then(|| cx.theme().status().hint_background),
598 ..HighlightStyle::default()
599 }
600}
601
602pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
603 EditPredictionStyles {
604 insertion: HighlightStyle {
605 color: Some(cx.theme().status().predictive),
606 ..HighlightStyle::default()
607 },
608 whitespace: HighlightStyle {
609 background_color: Some(cx.theme().status().created_background),
610 ..HighlightStyle::default()
611 },
612 }
613}
614
615type CompletionId = usize;
616
617pub(crate) enum EditDisplayMode {
618 TabAccept,
619 DiffPopover,
620 Inline,
621}
622
623enum EditPrediction {
624 Edit {
625 edits: Vec<(Range<Anchor>, String)>,
626 edit_preview: Option<EditPreview>,
627 display_mode: EditDisplayMode,
628 snapshot: BufferSnapshot,
629 },
630 Move {
631 target: Anchor,
632 snapshot: BufferSnapshot,
633 },
634}
635
636struct EditPredictionState {
637 inlay_ids: Vec<InlayId>,
638 completion: EditPrediction,
639 completion_id: Option<SharedString>,
640 invalidation_range: Range<Anchor>,
641}
642
643enum EditPredictionSettings {
644 Disabled,
645 Enabled {
646 show_in_menu: bool,
647 preview_requires_modifier: bool,
648 },
649}
650
651enum EditPredictionHighlight {}
652
653#[derive(Debug, Clone)]
654struct InlineDiagnostic {
655 message: SharedString,
656 group_id: usize,
657 is_primary: bool,
658 start: Point,
659 severity: lsp::DiagnosticSeverity,
660}
661
662pub enum MenuEditPredictionsPolicy {
663 Never,
664 ByProvider,
665}
666
667pub enum EditPredictionPreview {
668 /// Modifier is not pressed
669 Inactive { released_too_fast: bool },
670 /// Modifier pressed
671 Active {
672 since: Instant,
673 previous_scroll_position: Option<ScrollAnchor>,
674 },
675}
676
677impl EditPredictionPreview {
678 pub fn released_too_fast(&self) -> bool {
679 match self {
680 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
681 EditPredictionPreview::Active { .. } => false,
682 }
683 }
684
685 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
686 if let EditPredictionPreview::Active {
687 previous_scroll_position,
688 ..
689 } = self
690 {
691 *previous_scroll_position = scroll_position;
692 }
693 }
694}
695
696pub struct ContextMenuOptions {
697 pub min_entries_visible: usize,
698 pub max_entries_visible: usize,
699 pub placement: Option<ContextMenuPlacement>,
700}
701
702#[derive(Debug, Clone, PartialEq, Eq)]
703pub enum ContextMenuPlacement {
704 Above,
705 Below,
706}
707
708#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
709struct EditorActionId(usize);
710
711impl EditorActionId {
712 pub fn post_inc(&mut self) -> Self {
713 let answer = self.0;
714
715 *self = Self(answer + 1);
716
717 Self(answer)
718 }
719}
720
721// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
722// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
723
724type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
725type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
726
727#[derive(Default)]
728struct ScrollbarMarkerState {
729 scrollbar_size: Size<Pixels>,
730 dirty: bool,
731 markers: Arc<[PaintQuad]>,
732 pending_refresh: Option<Task<Result<()>>>,
733}
734
735impl ScrollbarMarkerState {
736 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
737 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
738 }
739}
740
741#[derive(Clone, Copy, PartialEq, Eq)]
742pub enum MinimapVisibility {
743 Disabled,
744 Enabled {
745 /// The configuration currently present in the users settings.
746 setting_configuration: bool,
747 /// Whether to override the currently set visibility from the users setting.
748 toggle_override: bool,
749 },
750}
751
752impl MinimapVisibility {
753 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
754 if mode.is_full() {
755 Self::Enabled {
756 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
757 toggle_override: false,
758 }
759 } else {
760 Self::Disabled
761 }
762 }
763
764 fn hidden(&self) -> Self {
765 match *self {
766 Self::Enabled {
767 setting_configuration,
768 ..
769 } => Self::Enabled {
770 setting_configuration,
771 toggle_override: setting_configuration,
772 },
773 Self::Disabled => Self::Disabled,
774 }
775 }
776
777 fn disabled(&self) -> bool {
778 matches!(*self, Self::Disabled)
779 }
780
781 fn settings_visibility(&self) -> bool {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => setting_configuration,
787 _ => false,
788 }
789 }
790
791 fn visible(&self) -> bool {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 toggle_override,
796 } => setting_configuration ^ toggle_override,
797 _ => false,
798 }
799 }
800
801 fn toggle_visibility(&self) -> Self {
802 match *self {
803 Self::Enabled {
804 toggle_override,
805 setting_configuration,
806 } => Self::Enabled {
807 setting_configuration,
808 toggle_override: !toggle_override,
809 },
810 Self::Disabled => Self::Disabled,
811 }
812 }
813}
814
815#[derive(Clone, Debug)]
816struct RunnableTasks {
817 templates: Vec<(TaskSourceKind, TaskTemplate)>,
818 offset: multi_buffer::Anchor,
819 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
820 column: u32,
821 // Values of all named captures, including those starting with '_'
822 extra_variables: HashMap<String, String>,
823 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
824 context_range: Range<BufferOffset>,
825}
826
827impl RunnableTasks {
828 fn resolve<'a>(
829 &'a self,
830 cx: &'a task::TaskContext,
831 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
832 self.templates.iter().filter_map(|(kind, template)| {
833 template
834 .resolve_task(&kind.to_id_base(), cx)
835 .map(|task| (kind.clone(), task))
836 })
837 }
838}
839
840#[derive(Clone)]
841pub struct ResolvedTasks {
842 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
843 position: Anchor,
844}
845
846#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
847struct BufferOffset(usize);
848
849/// Addons allow storing per-editor state in other crates (e.g. Vim)
850pub trait Addon: 'static {
851 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
852
853 fn render_buffer_header_controls(
854 &self,
855 _: &ExcerptInfo,
856 _: &Window,
857 _: &App,
858 ) -> Option<AnyElement> {
859 None
860 }
861
862 fn to_any(&self) -> &dyn std::any::Any;
863
864 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
865 None
866 }
867}
868
869struct ChangeLocation {
870 current: Option<Vec<Anchor>>,
871 original: Vec<Anchor>,
872}
873impl ChangeLocation {
874 fn locations(&self) -> &[Anchor] {
875 self.current.as_ref().unwrap_or(&self.original)
876 }
877}
878
879/// A set of caret positions, registered when the editor was edited.
880pub struct ChangeList {
881 changes: Vec<ChangeLocation>,
882 /// Currently "selected" change.
883 position: Option<usize>,
884}
885
886impl ChangeList {
887 pub fn new() -> Self {
888 Self {
889 changes: Vec::new(),
890 position: None,
891 }
892 }
893
894 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
895 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
896 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
897 if self.changes.is_empty() {
898 return None;
899 }
900
901 let prev = self.position.unwrap_or(self.changes.len());
902 let next = if direction == Direction::Prev {
903 prev.saturating_sub(count)
904 } else {
905 (prev + count).min(self.changes.len() - 1)
906 };
907 self.position = Some(next);
908 self.changes.get(next).map(|change| change.locations())
909 }
910
911 /// Adds a new change to the list, resetting the change list position.
912 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
913 self.position.take();
914 if let Some(last) = self.changes.last_mut()
915 && group
916 {
917 last.current = Some(new_positions)
918 } else {
919 self.changes.push(ChangeLocation {
920 original: new_positions,
921 current: None,
922 });
923 }
924 }
925
926 pub fn last(&self) -> Option<&[Anchor]> {
927 self.changes.last().map(|change| change.locations())
928 }
929
930 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
931 self.changes.last().map(|change| change.original.as_slice())
932 }
933
934 pub fn invert_last_group(&mut self) {
935 if let Some(last) = self.changes.last_mut()
936 && let Some(current) = last.current.as_mut()
937 {
938 mem::swap(&mut last.original, current);
939 }
940 }
941}
942
943#[derive(Clone)]
944struct InlineBlamePopoverState {
945 scroll_handle: ScrollHandle,
946 commit_message: Option<ParsedCommitMessage>,
947 markdown: Entity<Markdown>,
948}
949
950struct InlineBlamePopover {
951 position: gpui::Point<Pixels>,
952 hide_task: Option<Task<()>>,
953 popover_bounds: Option<Bounds<Pixels>>,
954 popover_state: InlineBlamePopoverState,
955 keyboard_grace: bool,
956}
957
958enum SelectionDragState {
959 /// State when no drag related activity is detected.
960 None,
961 /// State when the mouse is down on a selection that is about to be dragged.
962 ReadyToDrag {
963 selection: Selection<Anchor>,
964 click_position: gpui::Point<Pixels>,
965 mouse_down_time: Instant,
966 },
967 /// State when the mouse is dragging the selection in the editor.
968 Dragging {
969 selection: Selection<Anchor>,
970 drop_cursor: Selection<Anchor>,
971 hide_drop_cursor: bool,
972 },
973}
974
975enum ColumnarSelectionState {
976 FromMouse {
977 selection_tail: Anchor,
978 display_point: Option<DisplayPoint>,
979 },
980 FromSelection {
981 selection_tail: Anchor,
982 },
983}
984
985/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
986/// a breakpoint on them.
987#[derive(Clone, Copy, Debug, PartialEq, Eq)]
988struct PhantomBreakpointIndicator {
989 display_row: DisplayRow,
990 /// There's a small debounce between hovering over the line and showing the indicator.
991 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
992 is_active: bool,
993 collides_with_existing_breakpoint: bool,
994}
995
996/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
997///
998/// See the [module level documentation](self) for more information.
999pub struct Editor {
1000 focus_handle: FocusHandle,
1001 last_focused_descendant: Option<WeakFocusHandle>,
1002 /// The text buffer being edited
1003 buffer: Entity<MultiBuffer>,
1004 /// Map of how text in the buffer should be displayed.
1005 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1006 pub display_map: Entity<DisplayMap>,
1007 placeholder_display_map: Option<Entity<DisplayMap>>,
1008 pub selections: SelectionsCollection,
1009 pub scroll_manager: ScrollManager,
1010 /// When inline assist editors are linked, they all render cursors because
1011 /// typing enters text into each of them, even the ones that aren't focused.
1012 pub(crate) show_cursor_when_unfocused: bool,
1013 columnar_selection_state: Option<ColumnarSelectionState>,
1014 add_selections_state: Option<AddSelectionsState>,
1015 select_next_state: Option<SelectNextState>,
1016 select_prev_state: Option<SelectNextState>,
1017 selection_history: SelectionHistory,
1018 defer_selection_effects: bool,
1019 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1020 autoclose_regions: Vec<AutocloseRegion>,
1021 snippet_stack: InvalidationStack<SnippetState>,
1022 select_syntax_node_history: SelectSyntaxNodeHistory,
1023 ime_transaction: Option<TransactionId>,
1024 pub diagnostics_max_severity: DiagnosticSeverity,
1025 active_diagnostics: ActiveDiagnostic,
1026 show_inline_diagnostics: bool,
1027 inline_diagnostics_update: Task<()>,
1028 inline_diagnostics_enabled: bool,
1029 diagnostics_enabled: bool,
1030 word_completions_enabled: bool,
1031 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1032 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1033 hard_wrap: Option<usize>,
1034 project: Option<Entity<Project>>,
1035 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1036 completion_provider: Option<Rc<dyn CompletionProvider>>,
1037 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1038 blink_manager: Entity<BlinkManager>,
1039 show_cursor_names: bool,
1040 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1041 pub show_local_selections: bool,
1042 mode: EditorMode,
1043 show_breadcrumbs: bool,
1044 show_gutter: bool,
1045 show_scrollbars: ScrollbarAxes,
1046 minimap_visibility: MinimapVisibility,
1047 offset_content: bool,
1048 disable_expand_excerpt_buttons: bool,
1049 show_line_numbers: Option<bool>,
1050 use_relative_line_numbers: Option<bool>,
1051 show_git_diff_gutter: Option<bool>,
1052 show_code_actions: Option<bool>,
1053 show_runnables: Option<bool>,
1054 show_breakpoints: Option<bool>,
1055 show_wrap_guides: Option<bool>,
1056 show_indent_guides: Option<bool>,
1057 highlight_order: usize,
1058 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1059 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1060 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1061 scrollbar_marker_state: ScrollbarMarkerState,
1062 active_indent_guides_state: ActiveIndentGuidesState,
1063 nav_history: Option<ItemNavHistory>,
1064 context_menu: RefCell<Option<CodeContextMenu>>,
1065 context_menu_options: Option<ContextMenuOptions>,
1066 mouse_context_menu: Option<MouseContextMenu>,
1067 completion_tasks: Vec<(CompletionId, Task<()>)>,
1068 inline_blame_popover: Option<InlineBlamePopover>,
1069 inline_blame_popover_show_task: Option<Task<()>>,
1070 signature_help_state: SignatureHelpState,
1071 auto_signature_help: Option<bool>,
1072 find_all_references_task_sources: Vec<Anchor>,
1073 next_completion_id: CompletionId,
1074 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1075 code_actions_task: Option<Task<Result<()>>>,
1076 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1077 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1078 document_highlights_task: Option<Task<()>>,
1079 linked_editing_range_task: Option<Task<Option<()>>>,
1080 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1081 pending_rename: Option<RenameState>,
1082 searchable: bool,
1083 cursor_shape: CursorShape,
1084 current_line_highlight: Option<CurrentLineHighlight>,
1085 collapse_matches: bool,
1086 autoindent_mode: Option<AutoindentMode>,
1087 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1088 input_enabled: bool,
1089 use_modal_editing: bool,
1090 read_only: bool,
1091 leader_id: Option<CollaboratorId>,
1092 remote_id: Option<ViewId>,
1093 pub hover_state: HoverState,
1094 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1095 gutter_hovered: bool,
1096 hovered_link_state: Option<HoveredLinkState>,
1097 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1098 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1099 active_edit_prediction: Option<EditPredictionState>,
1100 /// Used to prevent flickering as the user types while the menu is open
1101 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1102 edit_prediction_settings: EditPredictionSettings,
1103 edit_predictions_hidden_for_vim_mode: bool,
1104 show_edit_predictions_override: Option<bool>,
1105 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1106 edit_prediction_preview: EditPredictionPreview,
1107 edit_prediction_indent_conflict: bool,
1108 edit_prediction_requires_modifier_in_indent_conflict: bool,
1109 inlay_hint_cache: InlayHintCache,
1110 next_inlay_id: usize,
1111 _subscriptions: Vec<Subscription>,
1112 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1113 gutter_dimensions: GutterDimensions,
1114 style: Option<EditorStyle>,
1115 text_style_refinement: Option<TextStyleRefinement>,
1116 next_editor_action_id: EditorActionId,
1117 editor_actions: Rc<
1118 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1119 >,
1120 use_autoclose: bool,
1121 use_auto_surround: bool,
1122 auto_replace_emoji_shortcode: bool,
1123 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1124 show_git_blame_gutter: bool,
1125 show_git_blame_inline: bool,
1126 show_git_blame_inline_delay_task: Option<Task<()>>,
1127 git_blame_inline_enabled: bool,
1128 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1129 serialize_dirty_buffers: bool,
1130 show_selection_menu: Option<bool>,
1131 blame: Option<Entity<GitBlame>>,
1132 blame_subscription: Option<Subscription>,
1133 custom_context_menu: Option<
1134 Box<
1135 dyn 'static
1136 + Fn(
1137 &mut Self,
1138 DisplayPoint,
1139 &mut Window,
1140 &mut Context<Self>,
1141 ) -> Option<Entity<ui::ContextMenu>>,
1142 >,
1143 >,
1144 last_bounds: Option<Bounds<Pixels>>,
1145 last_position_map: Option<Rc<PositionMap>>,
1146 expect_bounds_change: Option<Bounds<Pixels>>,
1147 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1148 tasks_update_task: Option<Task<()>>,
1149 breakpoint_store: Option<Entity<BreakpointStore>>,
1150 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1151 hovered_diff_hunk_row: Option<DisplayRow>,
1152 pull_diagnostics_task: Task<()>,
1153 in_project_search: bool,
1154 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1155 breadcrumb_header: Option<String>,
1156 focused_block: Option<FocusedBlock>,
1157 next_scroll_position: NextScrollCursorCenterTopBottom,
1158 addons: HashMap<TypeId, Box<dyn Addon>>,
1159 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1160 load_diff_task: Option<Shared<Task<()>>>,
1161 /// Whether we are temporarily displaying a diff other than git's
1162 temporary_diff_override: bool,
1163 selection_mark_mode: bool,
1164 toggle_fold_multiple_buffers: Task<()>,
1165 _scroll_cursor_center_top_bottom_task: Task<()>,
1166 serialize_selections: Task<()>,
1167 serialize_folds: Task<()>,
1168 mouse_cursor_hidden: bool,
1169 minimap: Option<Entity<Self>>,
1170 hide_mouse_mode: HideMouseMode,
1171 pub change_list: ChangeList,
1172 inline_value_cache: InlineValueCache,
1173 selection_drag_state: SelectionDragState,
1174 next_color_inlay_id: usize,
1175 colors: Option<LspColorData>,
1176 folding_newlines: Task<()>,
1177 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1178}
1179
1180#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1181enum NextScrollCursorCenterTopBottom {
1182 #[default]
1183 Center,
1184 Top,
1185 Bottom,
1186}
1187
1188impl NextScrollCursorCenterTopBottom {
1189 fn next(&self) -> Self {
1190 match self {
1191 Self::Center => Self::Top,
1192 Self::Top => Self::Bottom,
1193 Self::Bottom => Self::Center,
1194 }
1195 }
1196}
1197
1198#[derive(Clone)]
1199pub struct EditorSnapshot {
1200 pub mode: EditorMode,
1201 show_gutter: bool,
1202 show_line_numbers: Option<bool>,
1203 show_git_diff_gutter: Option<bool>,
1204 show_code_actions: Option<bool>,
1205 show_runnables: Option<bool>,
1206 show_breakpoints: Option<bool>,
1207 git_blame_gutter_max_author_length: Option<usize>,
1208 pub display_snapshot: DisplaySnapshot,
1209 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1210 is_focused: bool,
1211 scroll_anchor: ScrollAnchor,
1212 ongoing_scroll: OngoingScroll,
1213 current_line_highlight: CurrentLineHighlight,
1214 gutter_hovered: bool,
1215}
1216
1217#[derive(Default, Debug, Clone, Copy)]
1218pub struct GutterDimensions {
1219 pub left_padding: Pixels,
1220 pub right_padding: Pixels,
1221 pub width: Pixels,
1222 pub margin: Pixels,
1223 pub git_blame_entries_width: Option<Pixels>,
1224}
1225
1226impl GutterDimensions {
1227 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1228 Self {
1229 margin: Self::default_gutter_margin(font_id, font_size, cx),
1230 ..Default::default()
1231 }
1232 }
1233
1234 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1235 -cx.text_system().descent(font_id, font_size)
1236 }
1237 /// The full width of the space taken up by the gutter.
1238 pub fn full_width(&self) -> Pixels {
1239 self.margin + self.width
1240 }
1241
1242 /// The width of the space reserved for the fold indicators,
1243 /// use alongside 'justify_end' and `gutter_width` to
1244 /// right align content with the line numbers
1245 pub fn fold_area_width(&self) -> Pixels {
1246 self.margin + self.right_padding
1247 }
1248}
1249
1250struct CharacterDimensions {
1251 em_width: Pixels,
1252 em_advance: Pixels,
1253 line_height: Pixels,
1254}
1255
1256#[derive(Debug)]
1257pub struct RemoteSelection {
1258 pub replica_id: ReplicaId,
1259 pub selection: Selection<Anchor>,
1260 pub cursor_shape: CursorShape,
1261 pub collaborator_id: CollaboratorId,
1262 pub line_mode: bool,
1263 pub user_name: Option<SharedString>,
1264 pub color: PlayerColor,
1265}
1266
1267#[derive(Clone, Debug)]
1268struct SelectionHistoryEntry {
1269 selections: Arc<[Selection<Anchor>]>,
1270 select_next_state: Option<SelectNextState>,
1271 select_prev_state: Option<SelectNextState>,
1272 add_selections_state: Option<AddSelectionsState>,
1273}
1274
1275#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1276enum SelectionHistoryMode {
1277 Normal,
1278 Undoing,
1279 Redoing,
1280 Skipping,
1281}
1282
1283#[derive(Clone, PartialEq, Eq, Hash)]
1284struct HoveredCursor {
1285 replica_id: u16,
1286 selection_id: usize,
1287}
1288
1289impl Default for SelectionHistoryMode {
1290 fn default() -> Self {
1291 Self::Normal
1292 }
1293}
1294
1295#[derive(Debug)]
1296/// SelectionEffects controls the side-effects of updating the selection.
1297///
1298/// The default behaviour does "what you mostly want":
1299/// - it pushes to the nav history if the cursor moved by >10 lines
1300/// - it re-triggers completion requests
1301/// - it scrolls to fit
1302///
1303/// You might want to modify these behaviours. For example when doing a "jump"
1304/// like go to definition, we always want to add to nav history; but when scrolling
1305/// in vim mode we never do.
1306///
1307/// Similarly, you might want to disable scrolling if you don't want the viewport to
1308/// move.
1309#[derive(Clone)]
1310pub struct SelectionEffects {
1311 nav_history: Option<bool>,
1312 completions: bool,
1313 scroll: Option<Autoscroll>,
1314}
1315
1316impl Default for SelectionEffects {
1317 fn default() -> Self {
1318 Self {
1319 nav_history: None,
1320 completions: true,
1321 scroll: Some(Autoscroll::fit()),
1322 }
1323 }
1324}
1325impl SelectionEffects {
1326 pub fn scroll(scroll: Autoscroll) -> Self {
1327 Self {
1328 scroll: Some(scroll),
1329 ..Default::default()
1330 }
1331 }
1332
1333 pub fn no_scroll() -> Self {
1334 Self {
1335 scroll: None,
1336 ..Default::default()
1337 }
1338 }
1339
1340 pub fn completions(self, completions: bool) -> Self {
1341 Self {
1342 completions,
1343 ..self
1344 }
1345 }
1346
1347 pub fn nav_history(self, nav_history: bool) -> Self {
1348 Self {
1349 nav_history: Some(nav_history),
1350 ..self
1351 }
1352 }
1353}
1354
1355struct DeferredSelectionEffectsState {
1356 changed: bool,
1357 effects: SelectionEffects,
1358 old_cursor_position: Anchor,
1359 history_entry: SelectionHistoryEntry,
1360}
1361
1362#[derive(Default)]
1363struct SelectionHistory {
1364 #[allow(clippy::type_complexity)]
1365 selections_by_transaction:
1366 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1367 mode: SelectionHistoryMode,
1368 undo_stack: VecDeque<SelectionHistoryEntry>,
1369 redo_stack: VecDeque<SelectionHistoryEntry>,
1370}
1371
1372impl SelectionHistory {
1373 #[track_caller]
1374 fn insert_transaction(
1375 &mut self,
1376 transaction_id: TransactionId,
1377 selections: Arc<[Selection<Anchor>]>,
1378 ) {
1379 if selections.is_empty() {
1380 log::error!(
1381 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1382 std::panic::Location::caller()
1383 );
1384 return;
1385 }
1386 self.selections_by_transaction
1387 .insert(transaction_id, (selections, None));
1388 }
1389
1390 #[allow(clippy::type_complexity)]
1391 fn transaction(
1392 &self,
1393 transaction_id: TransactionId,
1394 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1395 self.selections_by_transaction.get(&transaction_id)
1396 }
1397
1398 #[allow(clippy::type_complexity)]
1399 fn transaction_mut(
1400 &mut self,
1401 transaction_id: TransactionId,
1402 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1403 self.selections_by_transaction.get_mut(&transaction_id)
1404 }
1405
1406 fn push(&mut self, entry: SelectionHistoryEntry) {
1407 if !entry.selections.is_empty() {
1408 match self.mode {
1409 SelectionHistoryMode::Normal => {
1410 self.push_undo(entry);
1411 self.redo_stack.clear();
1412 }
1413 SelectionHistoryMode::Undoing => self.push_redo(entry),
1414 SelectionHistoryMode::Redoing => self.push_undo(entry),
1415 SelectionHistoryMode::Skipping => {}
1416 }
1417 }
1418 }
1419
1420 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1421 if self
1422 .undo_stack
1423 .back()
1424 .is_none_or(|e| e.selections != entry.selections)
1425 {
1426 self.undo_stack.push_back(entry);
1427 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1428 self.undo_stack.pop_front();
1429 }
1430 }
1431 }
1432
1433 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1434 if self
1435 .redo_stack
1436 .back()
1437 .is_none_or(|e| e.selections != entry.selections)
1438 {
1439 self.redo_stack.push_back(entry);
1440 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1441 self.redo_stack.pop_front();
1442 }
1443 }
1444 }
1445}
1446
1447#[derive(Clone, Copy)]
1448pub struct RowHighlightOptions {
1449 pub autoscroll: bool,
1450 pub include_gutter: bool,
1451}
1452
1453impl Default for RowHighlightOptions {
1454 fn default() -> Self {
1455 Self {
1456 autoscroll: Default::default(),
1457 include_gutter: true,
1458 }
1459 }
1460}
1461
1462struct RowHighlight {
1463 index: usize,
1464 range: Range<Anchor>,
1465 color: Hsla,
1466 options: RowHighlightOptions,
1467 type_id: TypeId,
1468}
1469
1470#[derive(Clone, Debug)]
1471struct AddSelectionsState {
1472 groups: Vec<AddSelectionsGroup>,
1473}
1474
1475#[derive(Clone, Debug)]
1476struct AddSelectionsGroup {
1477 above: bool,
1478 stack: Vec<usize>,
1479}
1480
1481#[derive(Clone)]
1482struct SelectNextState {
1483 query: AhoCorasick,
1484 wordwise: bool,
1485 done: bool,
1486}
1487
1488impl std::fmt::Debug for SelectNextState {
1489 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1490 f.debug_struct(std::any::type_name::<Self>())
1491 .field("wordwise", &self.wordwise)
1492 .field("done", &self.done)
1493 .finish()
1494 }
1495}
1496
1497#[derive(Debug)]
1498struct AutocloseRegion {
1499 selection_id: usize,
1500 range: Range<Anchor>,
1501 pair: BracketPair,
1502}
1503
1504#[derive(Debug)]
1505struct SnippetState {
1506 ranges: Vec<Vec<Range<Anchor>>>,
1507 active_index: usize,
1508 choices: Vec<Option<Vec<String>>>,
1509}
1510
1511#[doc(hidden)]
1512pub struct RenameState {
1513 pub range: Range<Anchor>,
1514 pub old_name: Arc<str>,
1515 pub editor: Entity<Editor>,
1516 block_id: CustomBlockId,
1517}
1518
1519struct InvalidationStack<T>(Vec<T>);
1520
1521struct RegisteredEditPredictionProvider {
1522 provider: Arc<dyn EditPredictionProviderHandle>,
1523 _subscription: Subscription,
1524}
1525
1526#[derive(Debug, PartialEq, Eq)]
1527pub struct ActiveDiagnosticGroup {
1528 pub active_range: Range<Anchor>,
1529 pub active_message: String,
1530 pub group_id: usize,
1531 pub blocks: HashSet<CustomBlockId>,
1532}
1533
1534#[derive(Debug, PartialEq, Eq)]
1535
1536pub(crate) enum ActiveDiagnostic {
1537 None,
1538 All,
1539 Group(ActiveDiagnosticGroup),
1540}
1541
1542#[derive(Serialize, Deserialize, Clone, Debug)]
1543pub struct ClipboardSelection {
1544 /// The number of bytes in this selection.
1545 pub len: usize,
1546 /// Whether this was a full-line selection.
1547 pub is_entire_line: bool,
1548 /// The indentation of the first line when this content was originally copied.
1549 pub first_line_indent: u32,
1550}
1551
1552// selections, scroll behavior, was newest selection reversed
1553type SelectSyntaxNodeHistoryState = (
1554 Box<[Selection<usize>]>,
1555 SelectSyntaxNodeScrollBehavior,
1556 bool,
1557);
1558
1559#[derive(Default)]
1560struct SelectSyntaxNodeHistory {
1561 stack: Vec<SelectSyntaxNodeHistoryState>,
1562 // disable temporarily to allow changing selections without losing the stack
1563 pub disable_clearing: bool,
1564}
1565
1566impl SelectSyntaxNodeHistory {
1567 pub fn try_clear(&mut self) {
1568 if !self.disable_clearing {
1569 self.stack.clear();
1570 }
1571 }
1572
1573 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1574 self.stack.push(selection);
1575 }
1576
1577 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1578 self.stack.pop()
1579 }
1580}
1581
1582enum SelectSyntaxNodeScrollBehavior {
1583 CursorTop,
1584 FitSelection,
1585 CursorBottom,
1586}
1587
1588#[derive(Debug)]
1589pub(crate) struct NavigationData {
1590 cursor_anchor: Anchor,
1591 cursor_position: Point,
1592 scroll_anchor: ScrollAnchor,
1593 scroll_top_row: u32,
1594}
1595
1596#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1597pub enum GotoDefinitionKind {
1598 Symbol,
1599 Declaration,
1600 Type,
1601 Implementation,
1602}
1603
1604#[derive(Debug, Clone)]
1605enum InlayHintRefreshReason {
1606 ModifiersChanged(bool),
1607 Toggle(bool),
1608 SettingsChange(InlayHintSettings),
1609 NewLinesShown,
1610 BufferEdited(HashSet<Arc<Language>>),
1611 RefreshRequested,
1612 ExcerptsRemoved(Vec<ExcerptId>),
1613}
1614
1615impl InlayHintRefreshReason {
1616 fn description(&self) -> &'static str {
1617 match self {
1618 Self::ModifiersChanged(_) => "modifiers changed",
1619 Self::Toggle(_) => "toggle",
1620 Self::SettingsChange(_) => "settings change",
1621 Self::NewLinesShown => "new lines shown",
1622 Self::BufferEdited(_) => "buffer edited",
1623 Self::RefreshRequested => "refresh requested",
1624 Self::ExcerptsRemoved(_) => "excerpts removed",
1625 }
1626 }
1627}
1628
1629pub enum FormatTarget {
1630 Buffers(HashSet<Entity<Buffer>>),
1631 Ranges(Vec<Range<MultiBufferPoint>>),
1632}
1633
1634pub(crate) struct FocusedBlock {
1635 id: BlockId,
1636 focus_handle: WeakFocusHandle,
1637}
1638
1639#[derive(Clone)]
1640enum JumpData {
1641 MultiBufferRow {
1642 row: MultiBufferRow,
1643 line_offset_from_top: u32,
1644 },
1645 MultiBufferPoint {
1646 excerpt_id: ExcerptId,
1647 position: Point,
1648 anchor: text::Anchor,
1649 line_offset_from_top: u32,
1650 },
1651}
1652
1653pub enum MultibufferSelectionMode {
1654 First,
1655 All,
1656}
1657
1658#[derive(Clone, Copy, Debug, Default)]
1659pub struct RewrapOptions {
1660 pub override_language_settings: bool,
1661 pub preserve_existing_whitespace: bool,
1662}
1663
1664impl Editor {
1665 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1666 let buffer = cx.new(|cx| Buffer::local("", cx));
1667 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1668 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1669 }
1670
1671 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1672 let buffer = cx.new(|cx| Buffer::local("", cx));
1673 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1674 Self::new(EditorMode::full(), buffer, None, window, cx)
1675 }
1676
1677 pub fn auto_height(
1678 min_lines: usize,
1679 max_lines: usize,
1680 window: &mut Window,
1681 cx: &mut Context<Self>,
1682 ) -> Self {
1683 let buffer = cx.new(|cx| Buffer::local("", cx));
1684 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1685 Self::new(
1686 EditorMode::AutoHeight {
1687 min_lines,
1688 max_lines: Some(max_lines),
1689 },
1690 buffer,
1691 None,
1692 window,
1693 cx,
1694 )
1695 }
1696
1697 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1698 /// The editor grows as tall as needed to fit its content.
1699 pub fn auto_height_unbounded(
1700 min_lines: usize,
1701 window: &mut Window,
1702 cx: &mut Context<Self>,
1703 ) -> Self {
1704 let buffer = cx.new(|cx| Buffer::local("", cx));
1705 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1706 Self::new(
1707 EditorMode::AutoHeight {
1708 min_lines,
1709 max_lines: None,
1710 },
1711 buffer,
1712 None,
1713 window,
1714 cx,
1715 )
1716 }
1717
1718 pub fn for_buffer(
1719 buffer: Entity<Buffer>,
1720 project: Option<Entity<Project>>,
1721 window: &mut Window,
1722 cx: &mut Context<Self>,
1723 ) -> Self {
1724 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1725 Self::new(EditorMode::full(), buffer, project, window, cx)
1726 }
1727
1728 pub fn for_multibuffer(
1729 buffer: Entity<MultiBuffer>,
1730 project: Option<Entity<Project>>,
1731 window: &mut Window,
1732 cx: &mut Context<Self>,
1733 ) -> Self {
1734 Self::new(EditorMode::full(), buffer, project, window, cx)
1735 }
1736
1737 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1738 let mut clone = Self::new(
1739 self.mode.clone(),
1740 self.buffer.clone(),
1741 self.project.clone(),
1742 window,
1743 cx,
1744 );
1745 self.display_map.update(cx, |display_map, cx| {
1746 let snapshot = display_map.snapshot(cx);
1747 clone.display_map.update(cx, |display_map, cx| {
1748 display_map.set_state(&snapshot, cx);
1749 });
1750 });
1751 clone.folds_did_change(cx);
1752 clone.selections.clone_state(&self.selections);
1753 clone.scroll_manager.clone_state(&self.scroll_manager);
1754 clone.searchable = self.searchable;
1755 clone.read_only = self.read_only;
1756 clone
1757 }
1758
1759 pub fn new(
1760 mode: EditorMode,
1761 buffer: Entity<MultiBuffer>,
1762 project: Option<Entity<Project>>,
1763 window: &mut Window,
1764 cx: &mut Context<Self>,
1765 ) -> Self {
1766 Editor::new_internal(mode, buffer, project, None, window, cx)
1767 }
1768
1769 fn new_internal(
1770 mode: EditorMode,
1771 buffer: Entity<MultiBuffer>,
1772 project: Option<Entity<Project>>,
1773 display_map: Option<Entity<DisplayMap>>,
1774 window: &mut Window,
1775 cx: &mut Context<Self>,
1776 ) -> Self {
1777 debug_assert!(
1778 display_map.is_none() || mode.is_minimap(),
1779 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1780 );
1781
1782 let full_mode = mode.is_full();
1783 let is_minimap = mode.is_minimap();
1784 let diagnostics_max_severity = if full_mode {
1785 EditorSettings::get_global(cx)
1786 .diagnostics_max_severity
1787 .unwrap_or(DiagnosticSeverity::Hint)
1788 } else {
1789 DiagnosticSeverity::Off
1790 };
1791 let style = window.text_style();
1792 let font_size = style.font_size.to_pixels(window.rem_size());
1793 let editor = cx.entity().downgrade();
1794 let fold_placeholder = FoldPlaceholder {
1795 constrain_width: false,
1796 render: Arc::new(move |fold_id, fold_range, cx| {
1797 let editor = editor.clone();
1798 div()
1799 .id(fold_id)
1800 .bg(cx.theme().colors().ghost_element_background)
1801 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1802 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1803 .rounded_xs()
1804 .size_full()
1805 .cursor_pointer()
1806 .child("⋯")
1807 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1808 .on_click(move |_, _window, cx| {
1809 editor
1810 .update(cx, |editor, cx| {
1811 editor.unfold_ranges(
1812 &[fold_range.start..fold_range.end],
1813 true,
1814 false,
1815 cx,
1816 );
1817 cx.stop_propagation();
1818 })
1819 .ok();
1820 })
1821 .into_any()
1822 }),
1823 merge_adjacent: true,
1824 ..FoldPlaceholder::default()
1825 };
1826 let display_map = display_map.unwrap_or_else(|| {
1827 cx.new(|cx| {
1828 DisplayMap::new(
1829 buffer.clone(),
1830 style.font(),
1831 font_size,
1832 None,
1833 FILE_HEADER_HEIGHT,
1834 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1835 fold_placeholder,
1836 diagnostics_max_severity,
1837 cx,
1838 )
1839 })
1840 });
1841
1842 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1843
1844 let blink_manager = cx.new(|cx| {
1845 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1846 if is_minimap {
1847 blink_manager.disable(cx);
1848 }
1849 blink_manager
1850 });
1851
1852 let soft_wrap_mode_override =
1853 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1854
1855 let mut project_subscriptions = Vec::new();
1856 if full_mode && let Some(project) = project.as_ref() {
1857 project_subscriptions.push(cx.subscribe_in(
1858 project,
1859 window,
1860 |editor, _, event, window, cx| match event {
1861 project::Event::RefreshCodeLens => {
1862 // we always query lens with actions, without storing them, always refreshing them
1863 }
1864 project::Event::RefreshInlayHints => {
1865 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1866 }
1867 project::Event::LanguageServerAdded(..)
1868 | project::Event::LanguageServerRemoved(..) => {
1869 if editor.tasks_update_task.is_none() {
1870 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1871 }
1872 }
1873 project::Event::SnippetEdit(id, snippet_edits) => {
1874 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1875 let focus_handle = editor.focus_handle(cx);
1876 if focus_handle.is_focused(window) {
1877 let snapshot = buffer.read(cx).snapshot();
1878 for (range, snippet) in snippet_edits {
1879 let editor_range =
1880 language::range_from_lsp(*range).to_offset(&snapshot);
1881 editor
1882 .insert_snippet(
1883 &[editor_range],
1884 snippet.clone(),
1885 window,
1886 cx,
1887 )
1888 .ok();
1889 }
1890 }
1891 }
1892 }
1893 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1894 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1895 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1896 }
1897 }
1898
1899 project::Event::EntryRenamed(transaction) => {
1900 let Some(workspace) = editor.workspace() else {
1901 return;
1902 };
1903 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1904 else {
1905 return;
1906 };
1907 if active_editor.entity_id() == cx.entity_id() {
1908 let edited_buffers_already_open = {
1909 let other_editors: Vec<Entity<Editor>> = workspace
1910 .read(cx)
1911 .panes()
1912 .iter()
1913 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1914 .filter(|editor| editor.entity_id() != cx.entity_id())
1915 .collect();
1916
1917 transaction.0.keys().all(|buffer| {
1918 other_editors.iter().any(|editor| {
1919 let multi_buffer = editor.read(cx).buffer();
1920 multi_buffer.read(cx).is_singleton()
1921 && multi_buffer.read(cx).as_singleton().map_or(
1922 false,
1923 |singleton| {
1924 singleton.entity_id() == buffer.entity_id()
1925 },
1926 )
1927 })
1928 })
1929 };
1930
1931 if !edited_buffers_already_open {
1932 let workspace = workspace.downgrade();
1933 let transaction = transaction.clone();
1934 cx.defer_in(window, move |_, window, cx| {
1935 cx.spawn_in(window, async move |editor, cx| {
1936 Self::open_project_transaction(
1937 &editor,
1938 workspace,
1939 transaction,
1940 "Rename".to_string(),
1941 cx,
1942 )
1943 .await
1944 .ok()
1945 })
1946 .detach();
1947 });
1948 }
1949 }
1950 }
1951
1952 _ => {}
1953 },
1954 ));
1955 if let Some(task_inventory) = project
1956 .read(cx)
1957 .task_store()
1958 .read(cx)
1959 .task_inventory()
1960 .cloned()
1961 {
1962 project_subscriptions.push(cx.observe_in(
1963 &task_inventory,
1964 window,
1965 |editor, _, window, cx| {
1966 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1967 },
1968 ));
1969 };
1970
1971 project_subscriptions.push(cx.subscribe_in(
1972 &project.read(cx).breakpoint_store(),
1973 window,
1974 |editor, _, event, window, cx| match event {
1975 BreakpointStoreEvent::ClearDebugLines => {
1976 editor.clear_row_highlights::<ActiveDebugLine>();
1977 editor.refresh_inline_values(cx);
1978 }
1979 BreakpointStoreEvent::SetDebugLine => {
1980 if editor.go_to_active_debug_line(window, cx) {
1981 cx.stop_propagation();
1982 }
1983
1984 editor.refresh_inline_values(cx);
1985 }
1986 _ => {}
1987 },
1988 ));
1989 let git_store = project.read(cx).git_store().clone();
1990 let project = project.clone();
1991 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1992 if let GitStoreEvent::RepositoryUpdated(
1993 _,
1994 RepositoryEvent::Updated {
1995 new_instance: true, ..
1996 },
1997 _,
1998 ) = event
1999 {
2000 this.load_diff_task = Some(
2001 update_uncommitted_diff_for_buffer(
2002 cx.entity(),
2003 &project,
2004 this.buffer.read(cx).all_buffers(),
2005 this.buffer.clone(),
2006 cx,
2007 )
2008 .shared(),
2009 );
2010 }
2011 }));
2012 }
2013
2014 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2015
2016 let inlay_hint_settings =
2017 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2018 let focus_handle = cx.focus_handle();
2019 if !is_minimap {
2020 cx.on_focus(&focus_handle, window, Self::handle_focus)
2021 .detach();
2022 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2023 .detach();
2024 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2025 .detach();
2026 cx.on_blur(&focus_handle, window, Self::handle_blur)
2027 .detach();
2028 cx.observe_pending_input(window, Self::observe_pending_input)
2029 .detach();
2030 }
2031
2032 let show_indent_guides =
2033 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2034 Some(false)
2035 } else {
2036 None
2037 };
2038
2039 let breakpoint_store = match (&mode, project.as_ref()) {
2040 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2041 _ => None,
2042 };
2043
2044 let mut code_action_providers = Vec::new();
2045 let mut load_uncommitted_diff = None;
2046 if let Some(project) = project.clone() {
2047 load_uncommitted_diff = Some(
2048 update_uncommitted_diff_for_buffer(
2049 cx.entity(),
2050 &project,
2051 buffer.read(cx).all_buffers(),
2052 buffer.clone(),
2053 cx,
2054 )
2055 .shared(),
2056 );
2057 code_action_providers.push(Rc::new(project) as Rc<_>);
2058 }
2059
2060 let mut editor = Self {
2061 focus_handle,
2062 show_cursor_when_unfocused: false,
2063 last_focused_descendant: None,
2064 buffer: buffer.clone(),
2065 display_map: display_map.clone(),
2066 placeholder_display_map: None,
2067 selections,
2068 scroll_manager: ScrollManager::new(cx),
2069 columnar_selection_state: None,
2070 add_selections_state: None,
2071 select_next_state: None,
2072 select_prev_state: None,
2073 selection_history: SelectionHistory::default(),
2074 defer_selection_effects: false,
2075 deferred_selection_effects_state: None,
2076 autoclose_regions: Vec::new(),
2077 snippet_stack: InvalidationStack::default(),
2078 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2079 ime_transaction: None,
2080 active_diagnostics: ActiveDiagnostic::None,
2081 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2082 inline_diagnostics_update: Task::ready(()),
2083 inline_diagnostics: Vec::new(),
2084 soft_wrap_mode_override,
2085 diagnostics_max_severity,
2086 hard_wrap: None,
2087 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2088 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2089 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2090 project,
2091 blink_manager: blink_manager.clone(),
2092 show_local_selections: true,
2093 show_scrollbars: ScrollbarAxes {
2094 horizontal: full_mode,
2095 vertical: full_mode,
2096 },
2097 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2098 offset_content: !matches!(mode, EditorMode::SingleLine),
2099 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2100 show_gutter: full_mode,
2101 show_line_numbers: (!full_mode).then_some(false),
2102 use_relative_line_numbers: None,
2103 disable_expand_excerpt_buttons: !full_mode,
2104 show_git_diff_gutter: None,
2105 show_code_actions: None,
2106 show_runnables: None,
2107 show_breakpoints: None,
2108 show_wrap_guides: None,
2109 show_indent_guides,
2110 highlight_order: 0,
2111 highlighted_rows: HashMap::default(),
2112 background_highlights: HashMap::default(),
2113 gutter_highlights: HashMap::default(),
2114 scrollbar_marker_state: ScrollbarMarkerState::default(),
2115 active_indent_guides_state: ActiveIndentGuidesState::default(),
2116 nav_history: None,
2117 context_menu: RefCell::new(None),
2118 context_menu_options: None,
2119 mouse_context_menu: None,
2120 completion_tasks: Vec::new(),
2121 inline_blame_popover: None,
2122 inline_blame_popover_show_task: None,
2123 signature_help_state: SignatureHelpState::default(),
2124 auto_signature_help: None,
2125 find_all_references_task_sources: Vec::new(),
2126 next_completion_id: 0,
2127 next_inlay_id: 0,
2128 code_action_providers,
2129 available_code_actions: None,
2130 code_actions_task: None,
2131 quick_selection_highlight_task: None,
2132 debounced_selection_highlight_task: None,
2133 document_highlights_task: None,
2134 linked_editing_range_task: None,
2135 pending_rename: None,
2136 searchable: !is_minimap,
2137 cursor_shape: EditorSettings::get_global(cx)
2138 .cursor_shape
2139 .unwrap_or_default(),
2140 current_line_highlight: None,
2141 autoindent_mode: Some(AutoindentMode::EachLine),
2142 collapse_matches: false,
2143 workspace: None,
2144 input_enabled: !is_minimap,
2145 use_modal_editing: full_mode,
2146 read_only: is_minimap,
2147 use_autoclose: true,
2148 use_auto_surround: true,
2149 auto_replace_emoji_shortcode: false,
2150 jsx_tag_auto_close_enabled_in_any_buffer: false,
2151 leader_id: None,
2152 remote_id: None,
2153 hover_state: HoverState::default(),
2154 pending_mouse_down: None,
2155 hovered_link_state: None,
2156 edit_prediction_provider: None,
2157 active_edit_prediction: None,
2158 stale_edit_prediction_in_menu: None,
2159 edit_prediction_preview: EditPredictionPreview::Inactive {
2160 released_too_fast: false,
2161 },
2162 inline_diagnostics_enabled: full_mode,
2163 diagnostics_enabled: full_mode,
2164 word_completions_enabled: full_mode,
2165 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2166 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2167 gutter_hovered: false,
2168 pixel_position_of_newest_cursor: None,
2169 last_bounds: None,
2170 last_position_map: None,
2171 expect_bounds_change: None,
2172 gutter_dimensions: GutterDimensions::default(),
2173 style: None,
2174 show_cursor_names: false,
2175 hovered_cursors: HashMap::default(),
2176 next_editor_action_id: EditorActionId::default(),
2177 editor_actions: Rc::default(),
2178 edit_predictions_hidden_for_vim_mode: false,
2179 show_edit_predictions_override: None,
2180 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2181 edit_prediction_settings: EditPredictionSettings::Disabled,
2182 edit_prediction_indent_conflict: false,
2183 edit_prediction_requires_modifier_in_indent_conflict: true,
2184 custom_context_menu: None,
2185 show_git_blame_gutter: false,
2186 show_git_blame_inline: false,
2187 show_selection_menu: None,
2188 show_git_blame_inline_delay_task: None,
2189 git_blame_inline_enabled: full_mode
2190 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2191 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2192 serialize_dirty_buffers: !is_minimap
2193 && ProjectSettings::get_global(cx)
2194 .session
2195 .restore_unsaved_buffers,
2196 blame: None,
2197 blame_subscription: None,
2198 tasks: BTreeMap::default(),
2199
2200 breakpoint_store,
2201 gutter_breakpoint_indicator: (None, None),
2202 hovered_diff_hunk_row: None,
2203 _subscriptions: (!is_minimap)
2204 .then(|| {
2205 vec![
2206 cx.observe(&buffer, Self::on_buffer_changed),
2207 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2208 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2209 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2210 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2211 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2212 cx.observe_window_activation(window, |editor, window, cx| {
2213 let active = window.is_window_active();
2214 editor.blink_manager.update(cx, |blink_manager, cx| {
2215 if active {
2216 blink_manager.enable(cx);
2217 } else {
2218 blink_manager.disable(cx);
2219 }
2220 });
2221 if active {
2222 editor.show_mouse_cursor(cx);
2223 }
2224 }),
2225 ]
2226 })
2227 .unwrap_or_default(),
2228 tasks_update_task: None,
2229 pull_diagnostics_task: Task::ready(()),
2230 colors: None,
2231 next_color_inlay_id: 0,
2232 linked_edit_ranges: Default::default(),
2233 in_project_search: false,
2234 previous_search_ranges: None,
2235 breadcrumb_header: None,
2236 focused_block: None,
2237 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2238 addons: HashMap::default(),
2239 registered_buffers: HashMap::default(),
2240 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2241 selection_mark_mode: false,
2242 toggle_fold_multiple_buffers: Task::ready(()),
2243 serialize_selections: Task::ready(()),
2244 serialize_folds: Task::ready(()),
2245 text_style_refinement: None,
2246 load_diff_task: load_uncommitted_diff,
2247 temporary_diff_override: false,
2248 mouse_cursor_hidden: false,
2249 minimap: None,
2250 hide_mouse_mode: EditorSettings::get_global(cx)
2251 .hide_mouse
2252 .unwrap_or_default(),
2253 change_list: ChangeList::new(),
2254 mode,
2255 selection_drag_state: SelectionDragState::None,
2256 folding_newlines: Task::ready(()),
2257 lookup_key: None,
2258 };
2259
2260 if is_minimap {
2261 return editor;
2262 }
2263
2264 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2265 editor
2266 ._subscriptions
2267 .push(cx.observe(breakpoints, |_, _, cx| {
2268 cx.notify();
2269 }));
2270 }
2271 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2272 editor._subscriptions.extend(project_subscriptions);
2273
2274 editor._subscriptions.push(cx.subscribe_in(
2275 &cx.entity(),
2276 window,
2277 |editor, _, e: &EditorEvent, window, cx| match e {
2278 EditorEvent::ScrollPositionChanged { local, .. } => {
2279 if *local {
2280 let new_anchor = editor.scroll_manager.anchor();
2281 let snapshot = editor.snapshot(window, cx);
2282 editor.update_restoration_data(cx, move |data| {
2283 data.scroll_position = (
2284 new_anchor.top_row(&snapshot.buffer_snapshot),
2285 new_anchor.offset,
2286 );
2287 });
2288 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2289 editor.inline_blame_popover.take();
2290 }
2291 }
2292 EditorEvent::Edited { .. } => {
2293 if !vim_enabled(cx) {
2294 let (map, selections) = editor.selections.all_adjusted_display(cx);
2295 let pop_state = editor
2296 .change_list
2297 .last()
2298 .map(|previous| {
2299 previous.len() == selections.len()
2300 && previous.iter().enumerate().all(|(ix, p)| {
2301 p.to_display_point(&map).row()
2302 == selections[ix].head().row()
2303 })
2304 })
2305 .unwrap_or(false);
2306 let new_positions = selections
2307 .into_iter()
2308 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2309 .collect();
2310 editor
2311 .change_list
2312 .push_to_change_list(pop_state, new_positions);
2313 }
2314 }
2315 _ => (),
2316 },
2317 ));
2318
2319 if let Some(dap_store) = editor
2320 .project
2321 .as_ref()
2322 .map(|project| project.read(cx).dap_store())
2323 {
2324 let weak_editor = cx.weak_entity();
2325
2326 editor
2327 ._subscriptions
2328 .push(
2329 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2330 let session_entity = cx.entity();
2331 weak_editor
2332 .update(cx, |editor, cx| {
2333 editor._subscriptions.push(
2334 cx.subscribe(&session_entity, Self::on_debug_session_event),
2335 );
2336 })
2337 .ok();
2338 }),
2339 );
2340
2341 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2342 editor
2343 ._subscriptions
2344 .push(cx.subscribe(&session, Self::on_debug_session_event));
2345 }
2346 }
2347
2348 // skip adding the initial selection to selection history
2349 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2350 editor.end_selection(window, cx);
2351 editor.selection_history.mode = SelectionHistoryMode::Normal;
2352
2353 editor.scroll_manager.show_scrollbars(window, cx);
2354 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2355
2356 if full_mode {
2357 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2358 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2359
2360 if editor.git_blame_inline_enabled {
2361 editor.start_git_blame_inline(false, window, cx);
2362 }
2363
2364 editor.go_to_active_debug_line(window, cx);
2365
2366 if let Some(buffer) = buffer.read(cx).as_singleton()
2367 && let Some(project) = editor.project()
2368 {
2369 let handle = project.update(cx, |project, cx| {
2370 project.register_buffer_with_language_servers(&buffer, cx)
2371 });
2372 editor
2373 .registered_buffers
2374 .insert(buffer.read(cx).remote_id(), handle);
2375 }
2376
2377 editor.minimap =
2378 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2379 editor.colors = Some(LspColorData::new(cx));
2380 editor.update_lsp_data(false, None, window, cx);
2381 }
2382
2383 if editor.mode.is_full() {
2384 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2385 }
2386
2387 editor
2388 }
2389
2390 pub fn deploy_mouse_context_menu(
2391 &mut self,
2392 position: gpui::Point<Pixels>,
2393 context_menu: Entity<ContextMenu>,
2394 window: &mut Window,
2395 cx: &mut Context<Self>,
2396 ) {
2397 self.mouse_context_menu = Some(MouseContextMenu::new(
2398 self,
2399 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2400 context_menu,
2401 window,
2402 cx,
2403 ));
2404 }
2405
2406 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2407 self.mouse_context_menu
2408 .as_ref()
2409 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2410 }
2411
2412 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2413 if self
2414 .selections
2415 .pending_anchor()
2416 .is_some_and(|pending_selection| {
2417 let snapshot = self.buffer().read(cx).snapshot(cx);
2418 pending_selection.range().includes(range, &snapshot)
2419 })
2420 {
2421 return true;
2422 }
2423
2424 self.selections
2425 .disjoint_in_range::<usize>(range.clone(), cx)
2426 .into_iter()
2427 .any(|selection| {
2428 // This is needed to cover a corner case, if we just check for an existing
2429 // selection in the fold range, having a cursor at the start of the fold
2430 // marks it as selected. Non-empty selections don't cause this.
2431 let length = selection.end - selection.start;
2432 length > 0
2433 })
2434 }
2435
2436 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2437 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2438 }
2439
2440 fn key_context_internal(
2441 &self,
2442 has_active_edit_prediction: bool,
2443 window: &Window,
2444 cx: &App,
2445 ) -> KeyContext {
2446 let mut key_context = KeyContext::new_with_defaults();
2447 key_context.add("Editor");
2448 let mode = match self.mode {
2449 EditorMode::SingleLine => "single_line",
2450 EditorMode::AutoHeight { .. } => "auto_height",
2451 EditorMode::Minimap { .. } => "minimap",
2452 EditorMode::Full { .. } => "full",
2453 };
2454
2455 if EditorSettings::jupyter_enabled(cx) {
2456 key_context.add("jupyter");
2457 }
2458
2459 key_context.set("mode", mode);
2460 if self.pending_rename.is_some() {
2461 key_context.add("renaming");
2462 }
2463
2464 match self.context_menu.borrow().as_ref() {
2465 Some(CodeContextMenu::Completions(menu)) => {
2466 if menu.visible() {
2467 key_context.add("menu");
2468 key_context.add("showing_completions");
2469 }
2470 }
2471 Some(CodeContextMenu::CodeActions(menu)) => {
2472 if menu.visible() {
2473 key_context.add("menu");
2474 key_context.add("showing_code_actions")
2475 }
2476 }
2477 None => {}
2478 }
2479
2480 if self.signature_help_state.has_multiple_signatures() {
2481 key_context.add("showing_signature_help");
2482 }
2483
2484 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2485 if !self.focus_handle(cx).contains_focused(window, cx)
2486 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2487 {
2488 for addon in self.addons.values() {
2489 addon.extend_key_context(&mut key_context, cx)
2490 }
2491 }
2492
2493 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2494 if let Some(extension) = singleton_buffer
2495 .read(cx)
2496 .file()
2497 .and_then(|file| file.path().extension()?.to_str())
2498 {
2499 key_context.set("extension", extension.to_string());
2500 }
2501 } else {
2502 key_context.add("multibuffer");
2503 }
2504
2505 if has_active_edit_prediction {
2506 if self.edit_prediction_in_conflict() {
2507 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2508 } else {
2509 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2510 key_context.add("copilot_suggestion");
2511 }
2512 }
2513
2514 if self.selection_mark_mode {
2515 key_context.add("selection_mode");
2516 }
2517
2518 key_context
2519 }
2520
2521 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2522 if self.mouse_cursor_hidden {
2523 self.mouse_cursor_hidden = false;
2524 cx.notify();
2525 }
2526 }
2527
2528 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2529 let hide_mouse_cursor = match origin {
2530 HideMouseCursorOrigin::TypingAction => {
2531 matches!(
2532 self.hide_mouse_mode,
2533 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2534 )
2535 }
2536 HideMouseCursorOrigin::MovementAction => {
2537 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2538 }
2539 };
2540 if self.mouse_cursor_hidden != hide_mouse_cursor {
2541 self.mouse_cursor_hidden = hide_mouse_cursor;
2542 cx.notify();
2543 }
2544 }
2545
2546 pub fn edit_prediction_in_conflict(&self) -> bool {
2547 if !self.show_edit_predictions_in_menu() {
2548 return false;
2549 }
2550
2551 let showing_completions = self
2552 .context_menu
2553 .borrow()
2554 .as_ref()
2555 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2556
2557 showing_completions
2558 || self.edit_prediction_requires_modifier()
2559 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2560 // bindings to insert tab characters.
2561 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2562 }
2563
2564 pub fn accept_edit_prediction_keybind(
2565 &self,
2566 accept_partial: bool,
2567 window: &Window,
2568 cx: &App,
2569 ) -> AcceptEditPredictionBinding {
2570 let key_context = self.key_context_internal(true, window, cx);
2571 let in_conflict = self.edit_prediction_in_conflict();
2572
2573 let bindings = if accept_partial {
2574 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2575 } else {
2576 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2577 };
2578
2579 // TODO: if the binding contains multiple keystrokes, display all of them, not
2580 // just the first one.
2581 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2582 !in_conflict
2583 || binding
2584 .keystrokes()
2585 .first()
2586 .is_some_and(|keystroke| keystroke.modifiers().modified())
2587 }))
2588 }
2589
2590 pub fn new_file(
2591 workspace: &mut Workspace,
2592 _: &workspace::NewFile,
2593 window: &mut Window,
2594 cx: &mut Context<Workspace>,
2595 ) {
2596 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2597 "Failed to create buffer",
2598 window,
2599 cx,
2600 |e, _, _| match e.error_code() {
2601 ErrorCode::RemoteUpgradeRequired => Some(format!(
2602 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2603 e.error_tag("required").unwrap_or("the latest version")
2604 )),
2605 _ => None,
2606 },
2607 );
2608 }
2609
2610 pub fn new_in_workspace(
2611 workspace: &mut Workspace,
2612 window: &mut Window,
2613 cx: &mut Context<Workspace>,
2614 ) -> Task<Result<Entity<Editor>>> {
2615 let project = workspace.project().clone();
2616 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2617
2618 cx.spawn_in(window, async move |workspace, cx| {
2619 let buffer = create.await?;
2620 workspace.update_in(cx, |workspace, window, cx| {
2621 let editor =
2622 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2623 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2624 editor
2625 })
2626 })
2627 }
2628
2629 fn new_file_vertical(
2630 workspace: &mut Workspace,
2631 _: &workspace::NewFileSplitVertical,
2632 window: &mut Window,
2633 cx: &mut Context<Workspace>,
2634 ) {
2635 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2636 }
2637
2638 fn new_file_horizontal(
2639 workspace: &mut Workspace,
2640 _: &workspace::NewFileSplitHorizontal,
2641 window: &mut Window,
2642 cx: &mut Context<Workspace>,
2643 ) {
2644 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2645 }
2646
2647 fn new_file_in_direction(
2648 workspace: &mut Workspace,
2649 direction: SplitDirection,
2650 window: &mut Window,
2651 cx: &mut Context<Workspace>,
2652 ) {
2653 let project = workspace.project().clone();
2654 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2655
2656 cx.spawn_in(window, async move |workspace, cx| {
2657 let buffer = create.await?;
2658 workspace.update_in(cx, move |workspace, window, cx| {
2659 workspace.split_item(
2660 direction,
2661 Box::new(
2662 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2663 ),
2664 window,
2665 cx,
2666 )
2667 })?;
2668 anyhow::Ok(())
2669 })
2670 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2671 match e.error_code() {
2672 ErrorCode::RemoteUpgradeRequired => Some(format!(
2673 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2674 e.error_tag("required").unwrap_or("the latest version")
2675 )),
2676 _ => None,
2677 }
2678 });
2679 }
2680
2681 pub fn leader_id(&self) -> Option<CollaboratorId> {
2682 self.leader_id
2683 }
2684
2685 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2686 &self.buffer
2687 }
2688
2689 pub fn project(&self) -> Option<&Entity<Project>> {
2690 self.project.as_ref()
2691 }
2692
2693 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2694 self.workspace.as_ref()?.0.upgrade()
2695 }
2696
2697 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2698 self.buffer().read(cx).title(cx)
2699 }
2700
2701 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2702 let git_blame_gutter_max_author_length = self
2703 .render_git_blame_gutter(cx)
2704 .then(|| {
2705 if let Some(blame) = self.blame.as_ref() {
2706 let max_author_length =
2707 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2708 Some(max_author_length)
2709 } else {
2710 None
2711 }
2712 })
2713 .flatten();
2714
2715 EditorSnapshot {
2716 mode: self.mode.clone(),
2717 show_gutter: self.show_gutter,
2718 show_line_numbers: self.show_line_numbers,
2719 show_git_diff_gutter: self.show_git_diff_gutter,
2720 show_code_actions: self.show_code_actions,
2721 show_runnables: self.show_runnables,
2722 show_breakpoints: self.show_breakpoints,
2723 git_blame_gutter_max_author_length,
2724 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2725 placeholder_display_snapshot: self
2726 .placeholder_display_map
2727 .as_ref()
2728 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2729 scroll_anchor: self.scroll_manager.anchor(),
2730 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2731 is_focused: self.focus_handle.is_focused(window),
2732 current_line_highlight: self
2733 .current_line_highlight
2734 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2735 gutter_hovered: self.gutter_hovered,
2736 }
2737 }
2738
2739 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2740 self.buffer.read(cx).language_at(point, cx)
2741 }
2742
2743 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2744 self.buffer.read(cx).read(cx).file_at(point).cloned()
2745 }
2746
2747 pub fn active_excerpt(
2748 &self,
2749 cx: &App,
2750 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2751 self.buffer
2752 .read(cx)
2753 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2754 }
2755
2756 pub fn mode(&self) -> &EditorMode {
2757 &self.mode
2758 }
2759
2760 pub fn set_mode(&mut self, mode: EditorMode) {
2761 self.mode = mode;
2762 }
2763
2764 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2765 self.collaboration_hub.as_deref()
2766 }
2767
2768 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2769 self.collaboration_hub = Some(hub);
2770 }
2771
2772 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2773 self.in_project_search = in_project_search;
2774 }
2775
2776 pub fn set_custom_context_menu(
2777 &mut self,
2778 f: impl 'static
2779 + Fn(
2780 &mut Self,
2781 DisplayPoint,
2782 &mut Window,
2783 &mut Context<Self>,
2784 ) -> Option<Entity<ui::ContextMenu>>,
2785 ) {
2786 self.custom_context_menu = Some(Box::new(f))
2787 }
2788
2789 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2790 self.completion_provider = provider;
2791 }
2792
2793 #[cfg(any(test, feature = "test-support"))]
2794 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2795 self.completion_provider.clone()
2796 }
2797
2798 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2799 self.semantics_provider.clone()
2800 }
2801
2802 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2803 self.semantics_provider = provider;
2804 }
2805
2806 pub fn set_edit_prediction_provider<T>(
2807 &mut self,
2808 provider: Option<Entity<T>>,
2809 window: &mut Window,
2810 cx: &mut Context<Self>,
2811 ) where
2812 T: EditPredictionProvider,
2813 {
2814 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2815 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2816 if this.focus_handle.is_focused(window) {
2817 this.update_visible_edit_prediction(window, cx);
2818 }
2819 }),
2820 provider: Arc::new(provider),
2821 });
2822 self.update_edit_prediction_settings(cx);
2823 self.refresh_edit_prediction(false, false, window, cx);
2824 }
2825
2826 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2827 self.placeholder_display_map
2828 .as_ref()
2829 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2830 }
2831
2832 pub fn set_placeholder_text(
2833 &mut self,
2834 placeholder_text: &str,
2835 window: &mut Window,
2836 cx: &mut Context<Self>,
2837 ) {
2838 let multibuffer = cx
2839 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2840
2841 let style = window.text_style();
2842
2843 self.placeholder_display_map = Some(cx.new(|cx| {
2844 DisplayMap::new(
2845 multibuffer,
2846 style.font(),
2847 style.font_size.to_pixels(window.rem_size()),
2848 None,
2849 FILE_HEADER_HEIGHT,
2850 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2851 Default::default(),
2852 DiagnosticSeverity::Off,
2853 cx,
2854 )
2855 }));
2856 cx.notify();
2857 }
2858
2859 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2860 self.cursor_shape = cursor_shape;
2861
2862 // Disrupt blink for immediate user feedback that the cursor shape has changed
2863 self.blink_manager.update(cx, BlinkManager::show_cursor);
2864
2865 cx.notify();
2866 }
2867
2868 pub fn set_current_line_highlight(
2869 &mut self,
2870 current_line_highlight: Option<CurrentLineHighlight>,
2871 ) {
2872 self.current_line_highlight = current_line_highlight;
2873 }
2874
2875 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2876 self.collapse_matches = collapse_matches;
2877 }
2878
2879 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2880 let buffers = self.buffer.read(cx).all_buffers();
2881 let Some(project) = self.project.as_ref() else {
2882 return;
2883 };
2884 project.update(cx, |project, cx| {
2885 for buffer in buffers {
2886 self.registered_buffers
2887 .entry(buffer.read(cx).remote_id())
2888 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2889 }
2890 })
2891 }
2892
2893 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2894 if self.collapse_matches {
2895 return range.start..range.start;
2896 }
2897 range.clone()
2898 }
2899
2900 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2901 if self.display_map.read(cx).clip_at_line_ends != clip {
2902 self.display_map
2903 .update(cx, |map, _| map.clip_at_line_ends = clip);
2904 }
2905 }
2906
2907 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2908 self.input_enabled = input_enabled;
2909 }
2910
2911 pub fn set_edit_predictions_hidden_for_vim_mode(
2912 &mut self,
2913 hidden: bool,
2914 window: &mut Window,
2915 cx: &mut Context<Self>,
2916 ) {
2917 if hidden != self.edit_predictions_hidden_for_vim_mode {
2918 self.edit_predictions_hidden_for_vim_mode = hidden;
2919 if hidden {
2920 self.update_visible_edit_prediction(window, cx);
2921 } else {
2922 self.refresh_edit_prediction(true, false, window, cx);
2923 }
2924 }
2925 }
2926
2927 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2928 self.menu_edit_predictions_policy = value;
2929 }
2930
2931 pub fn set_autoindent(&mut self, autoindent: bool) {
2932 if autoindent {
2933 self.autoindent_mode = Some(AutoindentMode::EachLine);
2934 } else {
2935 self.autoindent_mode = None;
2936 }
2937 }
2938
2939 pub fn read_only(&self, cx: &App) -> bool {
2940 self.read_only || self.buffer.read(cx).read_only()
2941 }
2942
2943 pub fn set_read_only(&mut self, read_only: bool) {
2944 self.read_only = read_only;
2945 }
2946
2947 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2948 self.use_autoclose = autoclose;
2949 }
2950
2951 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2952 self.use_auto_surround = auto_surround;
2953 }
2954
2955 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2956 self.auto_replace_emoji_shortcode = auto_replace;
2957 }
2958
2959 pub fn toggle_edit_predictions(
2960 &mut self,
2961 _: &ToggleEditPrediction,
2962 window: &mut Window,
2963 cx: &mut Context<Self>,
2964 ) {
2965 if self.show_edit_predictions_override.is_some() {
2966 self.set_show_edit_predictions(None, window, cx);
2967 } else {
2968 let show_edit_predictions = !self.edit_predictions_enabled();
2969 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2970 }
2971 }
2972
2973 pub fn set_show_edit_predictions(
2974 &mut self,
2975 show_edit_predictions: Option<bool>,
2976 window: &mut Window,
2977 cx: &mut Context<Self>,
2978 ) {
2979 self.show_edit_predictions_override = show_edit_predictions;
2980 self.update_edit_prediction_settings(cx);
2981
2982 if let Some(false) = show_edit_predictions {
2983 self.discard_edit_prediction(false, cx);
2984 } else {
2985 self.refresh_edit_prediction(false, true, window, cx);
2986 }
2987 }
2988
2989 fn edit_predictions_disabled_in_scope(
2990 &self,
2991 buffer: &Entity<Buffer>,
2992 buffer_position: language::Anchor,
2993 cx: &App,
2994 ) -> bool {
2995 let snapshot = buffer.read(cx).snapshot();
2996 let settings = snapshot.settings_at(buffer_position, cx);
2997
2998 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2999 return false;
3000 };
3001
3002 scope.override_name().is_some_and(|scope_name| {
3003 settings
3004 .edit_predictions_disabled_in
3005 .iter()
3006 .any(|s| s == scope_name)
3007 })
3008 }
3009
3010 pub fn set_use_modal_editing(&mut self, to: bool) {
3011 self.use_modal_editing = to;
3012 }
3013
3014 pub fn use_modal_editing(&self) -> bool {
3015 self.use_modal_editing
3016 }
3017
3018 fn selections_did_change(
3019 &mut self,
3020 local: bool,
3021 old_cursor_position: &Anchor,
3022 effects: SelectionEffects,
3023 window: &mut Window,
3024 cx: &mut Context<Self>,
3025 ) {
3026 window.invalidate_character_coordinates();
3027
3028 // Copy selections to primary selection buffer
3029 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3030 if local {
3031 let selections = self.selections.all::<usize>(cx);
3032 let buffer_handle = self.buffer.read(cx).read(cx);
3033
3034 let mut text = String::new();
3035 for (index, selection) in selections.iter().enumerate() {
3036 let text_for_selection = buffer_handle
3037 .text_for_range(selection.start..selection.end)
3038 .collect::<String>();
3039
3040 text.push_str(&text_for_selection);
3041 if index != selections.len() - 1 {
3042 text.push('\n');
3043 }
3044 }
3045
3046 if !text.is_empty() {
3047 cx.write_to_primary(ClipboardItem::new_string(text));
3048 }
3049 }
3050
3051 let selection_anchors = self.selections.disjoint_anchors_arc();
3052
3053 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3054 self.buffer.update(cx, |buffer, cx| {
3055 buffer.set_active_selections(
3056 &selection_anchors,
3057 self.selections.line_mode,
3058 self.cursor_shape,
3059 cx,
3060 )
3061 });
3062 }
3063 let display_map = self
3064 .display_map
3065 .update(cx, |display_map, cx| display_map.snapshot(cx));
3066 let buffer = &display_map.buffer_snapshot;
3067 if self.selections.count() == 1 {
3068 self.add_selections_state = None;
3069 }
3070 self.select_next_state = None;
3071 self.select_prev_state = None;
3072 self.select_syntax_node_history.try_clear();
3073 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3074 self.snippet_stack.invalidate(&selection_anchors, buffer);
3075 self.take_rename(false, window, cx);
3076
3077 let newest_selection = self.selections.newest_anchor();
3078 let new_cursor_position = newest_selection.head();
3079 let selection_start = newest_selection.start;
3080
3081 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3082 self.push_to_nav_history(
3083 *old_cursor_position,
3084 Some(new_cursor_position.to_point(buffer)),
3085 false,
3086 effects.nav_history == Some(true),
3087 cx,
3088 );
3089 }
3090
3091 if local {
3092 if let Some(buffer_id) = new_cursor_position.buffer_id
3093 && !self.registered_buffers.contains_key(&buffer_id)
3094 && let Some(project) = self.project.as_ref()
3095 {
3096 project.update(cx, |project, cx| {
3097 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3098 return;
3099 };
3100 self.registered_buffers.insert(
3101 buffer_id,
3102 project.register_buffer_with_language_servers(&buffer, cx),
3103 );
3104 })
3105 }
3106
3107 let mut context_menu = self.context_menu.borrow_mut();
3108 let completion_menu = match context_menu.as_ref() {
3109 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3110 Some(CodeContextMenu::CodeActions(_)) => {
3111 *context_menu = None;
3112 None
3113 }
3114 None => None,
3115 };
3116 let completion_position = completion_menu.map(|menu| menu.initial_position);
3117 drop(context_menu);
3118
3119 if effects.completions
3120 && let Some(completion_position) = completion_position
3121 {
3122 let start_offset = selection_start.to_offset(buffer);
3123 let position_matches = start_offset == completion_position.to_offset(buffer);
3124 let continue_showing = if position_matches {
3125 if self.snippet_stack.is_empty() {
3126 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3127 } else {
3128 // Snippet choices can be shown even when the cursor is in whitespace.
3129 // Dismissing the menu with actions like backspace is handled by
3130 // invalidation regions.
3131 true
3132 }
3133 } else {
3134 false
3135 };
3136
3137 if continue_showing {
3138 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3139 } else {
3140 self.hide_context_menu(window, cx);
3141 }
3142 }
3143
3144 hide_hover(self, cx);
3145
3146 if old_cursor_position.to_display_point(&display_map).row()
3147 != new_cursor_position.to_display_point(&display_map).row()
3148 {
3149 self.available_code_actions.take();
3150 }
3151 self.refresh_code_actions(window, cx);
3152 self.refresh_document_highlights(cx);
3153 self.refresh_selected_text_highlights(false, window, cx);
3154 refresh_matching_bracket_highlights(self, window, cx);
3155 self.update_visible_edit_prediction(window, cx);
3156 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3157 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3158 self.inline_blame_popover.take();
3159 if self.git_blame_inline_enabled {
3160 self.start_inline_blame_timer(window, cx);
3161 }
3162 }
3163
3164 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3165 cx.emit(EditorEvent::SelectionsChanged { local });
3166
3167 let selections = &self.selections.disjoint_anchors_arc();
3168 if selections.len() == 1 {
3169 cx.emit(SearchEvent::ActiveMatchChanged)
3170 }
3171 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3172 let inmemory_selections = selections
3173 .iter()
3174 .map(|s| {
3175 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3176 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3177 })
3178 .collect();
3179 self.update_restoration_data(cx, |data| {
3180 data.selections = inmemory_selections;
3181 });
3182
3183 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3184 && let Some(workspace_id) =
3185 self.workspace.as_ref().and_then(|workspace| workspace.1)
3186 {
3187 let snapshot = self.buffer().read(cx).snapshot(cx);
3188 let selections = selections.clone();
3189 let background_executor = cx.background_executor().clone();
3190 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3191 self.serialize_selections = cx.background_spawn(async move {
3192 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3193 let db_selections = selections
3194 .iter()
3195 .map(|selection| {
3196 (
3197 selection.start.to_offset(&snapshot),
3198 selection.end.to_offset(&snapshot),
3199 )
3200 })
3201 .collect();
3202
3203 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3204 .await
3205 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3206 .log_err();
3207 });
3208 }
3209 }
3210
3211 cx.notify();
3212 }
3213
3214 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3215 use text::ToOffset as _;
3216 use text::ToPoint as _;
3217
3218 if self.mode.is_minimap()
3219 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3220 {
3221 return;
3222 }
3223
3224 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3225 return;
3226 };
3227
3228 let snapshot = singleton.read(cx).snapshot();
3229 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3230 let display_snapshot = display_map.snapshot(cx);
3231
3232 display_snapshot
3233 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3234 .map(|fold| {
3235 fold.range.start.text_anchor.to_point(&snapshot)
3236 ..fold.range.end.text_anchor.to_point(&snapshot)
3237 })
3238 .collect()
3239 });
3240 self.update_restoration_data(cx, |data| {
3241 data.folds = inmemory_folds;
3242 });
3243
3244 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3245 return;
3246 };
3247 let background_executor = cx.background_executor().clone();
3248 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3249 let db_folds = self.display_map.update(cx, |display_map, cx| {
3250 display_map
3251 .snapshot(cx)
3252 .folds_in_range(0..snapshot.len())
3253 .map(|fold| {
3254 (
3255 fold.range.start.text_anchor.to_offset(&snapshot),
3256 fold.range.end.text_anchor.to_offset(&snapshot),
3257 )
3258 })
3259 .collect()
3260 });
3261 self.serialize_folds = cx.background_spawn(async move {
3262 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3263 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3264 .await
3265 .with_context(|| {
3266 format!(
3267 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3268 )
3269 })
3270 .log_err();
3271 });
3272 }
3273
3274 pub fn sync_selections(
3275 &mut self,
3276 other: Entity<Editor>,
3277 cx: &mut Context<Self>,
3278 ) -> gpui::Subscription {
3279 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3280 self.selections.change_with(cx, |selections| {
3281 selections.select_anchors(other_selections);
3282 });
3283
3284 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3285 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3286 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3287 if other_selections.is_empty() {
3288 return;
3289 }
3290 this.selections.change_with(cx, |selections| {
3291 selections.select_anchors(other_selections);
3292 });
3293 }
3294 });
3295
3296 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3297 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3298 let these_selections = this.selections.disjoint_anchors().to_vec();
3299 if these_selections.is_empty() {
3300 return;
3301 }
3302 other.update(cx, |other_editor, cx| {
3303 other_editor.selections.change_with(cx, |selections| {
3304 selections.select_anchors(these_selections);
3305 })
3306 });
3307 }
3308 });
3309
3310 Subscription::join(other_subscription, this_subscription)
3311 }
3312
3313 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3314 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3315 /// effects of selection change occur at the end of the transaction.
3316 pub fn change_selections<R>(
3317 &mut self,
3318 effects: SelectionEffects,
3319 window: &mut Window,
3320 cx: &mut Context<Self>,
3321 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3322 ) -> R {
3323 if let Some(state) = &mut self.deferred_selection_effects_state {
3324 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3325 state.effects.completions = effects.completions;
3326 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3327 let (changed, result) = self.selections.change_with(cx, change);
3328 state.changed |= changed;
3329 return result;
3330 }
3331 let mut state = DeferredSelectionEffectsState {
3332 changed: false,
3333 effects,
3334 old_cursor_position: self.selections.newest_anchor().head(),
3335 history_entry: SelectionHistoryEntry {
3336 selections: self.selections.disjoint_anchors_arc(),
3337 select_next_state: self.select_next_state.clone(),
3338 select_prev_state: self.select_prev_state.clone(),
3339 add_selections_state: self.add_selections_state.clone(),
3340 },
3341 };
3342 let (changed, result) = self.selections.change_with(cx, change);
3343 state.changed = state.changed || changed;
3344 if self.defer_selection_effects {
3345 self.deferred_selection_effects_state = Some(state);
3346 } else {
3347 self.apply_selection_effects(state, window, cx);
3348 }
3349 result
3350 }
3351
3352 /// Defers the effects of selection change, so that the effects of multiple calls to
3353 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3354 /// to selection history and the state of popovers based on selection position aren't
3355 /// erroneously updated.
3356 pub fn with_selection_effects_deferred<R>(
3357 &mut self,
3358 window: &mut Window,
3359 cx: &mut Context<Self>,
3360 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3361 ) -> R {
3362 let already_deferred = self.defer_selection_effects;
3363 self.defer_selection_effects = true;
3364 let result = update(self, window, cx);
3365 if !already_deferred {
3366 self.defer_selection_effects = false;
3367 if let Some(state) = self.deferred_selection_effects_state.take() {
3368 self.apply_selection_effects(state, window, cx);
3369 }
3370 }
3371 result
3372 }
3373
3374 fn apply_selection_effects(
3375 &mut self,
3376 state: DeferredSelectionEffectsState,
3377 window: &mut Window,
3378 cx: &mut Context<Self>,
3379 ) {
3380 if state.changed {
3381 self.selection_history.push(state.history_entry);
3382
3383 if let Some(autoscroll) = state.effects.scroll {
3384 self.request_autoscroll(autoscroll, cx);
3385 }
3386
3387 let old_cursor_position = &state.old_cursor_position;
3388
3389 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3390
3391 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3392 self.show_signature_help(&ShowSignatureHelp, window, cx);
3393 }
3394 }
3395 }
3396
3397 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3398 where
3399 I: IntoIterator<Item = (Range<S>, T)>,
3400 S: ToOffset,
3401 T: Into<Arc<str>>,
3402 {
3403 if self.read_only(cx) {
3404 return;
3405 }
3406
3407 self.buffer
3408 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3409 }
3410
3411 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3412 where
3413 I: IntoIterator<Item = (Range<S>, T)>,
3414 S: ToOffset,
3415 T: Into<Arc<str>>,
3416 {
3417 if self.read_only(cx) {
3418 return;
3419 }
3420
3421 self.buffer.update(cx, |buffer, cx| {
3422 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3423 });
3424 }
3425
3426 pub fn edit_with_block_indent<I, S, T>(
3427 &mut self,
3428 edits: I,
3429 original_indent_columns: Vec<Option<u32>>,
3430 cx: &mut Context<Self>,
3431 ) where
3432 I: IntoIterator<Item = (Range<S>, T)>,
3433 S: ToOffset,
3434 T: Into<Arc<str>>,
3435 {
3436 if self.read_only(cx) {
3437 return;
3438 }
3439
3440 self.buffer.update(cx, |buffer, cx| {
3441 buffer.edit(
3442 edits,
3443 Some(AutoindentMode::Block {
3444 original_indent_columns,
3445 }),
3446 cx,
3447 )
3448 });
3449 }
3450
3451 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3452 self.hide_context_menu(window, cx);
3453
3454 match phase {
3455 SelectPhase::Begin {
3456 position,
3457 add,
3458 click_count,
3459 } => self.begin_selection(position, add, click_count, window, cx),
3460 SelectPhase::BeginColumnar {
3461 position,
3462 goal_column,
3463 reset,
3464 mode,
3465 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3466 SelectPhase::Extend {
3467 position,
3468 click_count,
3469 } => self.extend_selection(position, click_count, window, cx),
3470 SelectPhase::Update {
3471 position,
3472 goal_column,
3473 scroll_delta,
3474 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3475 SelectPhase::End => self.end_selection(window, cx),
3476 }
3477 }
3478
3479 fn extend_selection(
3480 &mut self,
3481 position: DisplayPoint,
3482 click_count: usize,
3483 window: &mut Window,
3484 cx: &mut Context<Self>,
3485 ) {
3486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3487 let tail = self.selections.newest::<usize>(cx).tail();
3488 self.begin_selection(position, false, click_count, window, cx);
3489
3490 let position = position.to_offset(&display_map, Bias::Left);
3491 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3492
3493 let mut pending_selection = self
3494 .selections
3495 .pending_anchor()
3496 .cloned()
3497 .expect("extend_selection not called with pending selection");
3498 if position >= tail {
3499 pending_selection.start = tail_anchor;
3500 } else {
3501 pending_selection.end = tail_anchor;
3502 pending_selection.reversed = true;
3503 }
3504
3505 let mut pending_mode = self.selections.pending_mode().unwrap();
3506 match &mut pending_mode {
3507 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3508 _ => {}
3509 }
3510
3511 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3512 SelectionEffects::scroll(Autoscroll::fit())
3513 } else {
3514 SelectionEffects::no_scroll()
3515 };
3516
3517 self.change_selections(effects, window, cx, |s| {
3518 s.set_pending(pending_selection.clone(), pending_mode)
3519 });
3520 }
3521
3522 fn begin_selection(
3523 &mut self,
3524 position: DisplayPoint,
3525 add: bool,
3526 click_count: usize,
3527 window: &mut Window,
3528 cx: &mut Context<Self>,
3529 ) {
3530 if !self.focus_handle.is_focused(window) {
3531 self.last_focused_descendant = None;
3532 window.focus(&self.focus_handle);
3533 }
3534
3535 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3536 let buffer = &display_map.buffer_snapshot;
3537 let position = display_map.clip_point(position, Bias::Left);
3538
3539 let start;
3540 let end;
3541 let mode;
3542 let mut auto_scroll;
3543 match click_count {
3544 1 => {
3545 start = buffer.anchor_before(position.to_point(&display_map));
3546 end = start;
3547 mode = SelectMode::Character;
3548 auto_scroll = true;
3549 }
3550 2 => {
3551 let position = display_map
3552 .clip_point(position, Bias::Left)
3553 .to_offset(&display_map, Bias::Left);
3554 let (range, _) = buffer.surrounding_word(position, false);
3555 start = buffer.anchor_before(range.start);
3556 end = buffer.anchor_before(range.end);
3557 mode = SelectMode::Word(start..end);
3558 auto_scroll = true;
3559 }
3560 3 => {
3561 let position = display_map
3562 .clip_point(position, Bias::Left)
3563 .to_point(&display_map);
3564 let line_start = display_map.prev_line_boundary(position).0;
3565 let next_line_start = buffer.clip_point(
3566 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3567 Bias::Left,
3568 );
3569 start = buffer.anchor_before(line_start);
3570 end = buffer.anchor_before(next_line_start);
3571 mode = SelectMode::Line(start..end);
3572 auto_scroll = true;
3573 }
3574 _ => {
3575 start = buffer.anchor_before(0);
3576 end = buffer.anchor_before(buffer.len());
3577 mode = SelectMode::All;
3578 auto_scroll = false;
3579 }
3580 }
3581 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3582
3583 let point_to_delete: Option<usize> = {
3584 let selected_points: Vec<Selection<Point>> =
3585 self.selections.disjoint_in_range(start..end, cx);
3586
3587 if !add || click_count > 1 {
3588 None
3589 } else if !selected_points.is_empty() {
3590 Some(selected_points[0].id)
3591 } else {
3592 let clicked_point_already_selected =
3593 self.selections.disjoint_anchors().iter().find(|selection| {
3594 selection.start.to_point(buffer) == start.to_point(buffer)
3595 || selection.end.to_point(buffer) == end.to_point(buffer)
3596 });
3597
3598 clicked_point_already_selected.map(|selection| selection.id)
3599 }
3600 };
3601
3602 let selections_count = self.selections.count();
3603 let effects = if auto_scroll {
3604 SelectionEffects::default()
3605 } else {
3606 SelectionEffects::no_scroll()
3607 };
3608
3609 self.change_selections(effects, window, cx, |s| {
3610 if let Some(point_to_delete) = point_to_delete {
3611 s.delete(point_to_delete);
3612
3613 if selections_count == 1 {
3614 s.set_pending_anchor_range(start..end, mode);
3615 }
3616 } else {
3617 if !add {
3618 s.clear_disjoint();
3619 }
3620
3621 s.set_pending_anchor_range(start..end, mode);
3622 }
3623 });
3624 }
3625
3626 fn begin_columnar_selection(
3627 &mut self,
3628 position: DisplayPoint,
3629 goal_column: u32,
3630 reset: bool,
3631 mode: ColumnarMode,
3632 window: &mut Window,
3633 cx: &mut Context<Self>,
3634 ) {
3635 if !self.focus_handle.is_focused(window) {
3636 self.last_focused_descendant = None;
3637 window.focus(&self.focus_handle);
3638 }
3639
3640 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3641
3642 if reset {
3643 let pointer_position = display_map
3644 .buffer_snapshot
3645 .anchor_before(position.to_point(&display_map));
3646
3647 self.change_selections(
3648 SelectionEffects::scroll(Autoscroll::newest()),
3649 window,
3650 cx,
3651 |s| {
3652 s.clear_disjoint();
3653 s.set_pending_anchor_range(
3654 pointer_position..pointer_position,
3655 SelectMode::Character,
3656 );
3657 },
3658 );
3659 };
3660
3661 let tail = self.selections.newest::<Point>(cx).tail();
3662 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3663 self.columnar_selection_state = match mode {
3664 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3665 selection_tail: selection_anchor,
3666 display_point: if reset {
3667 if position.column() != goal_column {
3668 Some(DisplayPoint::new(position.row(), goal_column))
3669 } else {
3670 None
3671 }
3672 } else {
3673 None
3674 },
3675 }),
3676 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3677 selection_tail: selection_anchor,
3678 }),
3679 };
3680
3681 if !reset {
3682 self.select_columns(position, goal_column, &display_map, window, cx);
3683 }
3684 }
3685
3686 fn update_selection(
3687 &mut self,
3688 position: DisplayPoint,
3689 goal_column: u32,
3690 scroll_delta: gpui::Point<f32>,
3691 window: &mut Window,
3692 cx: &mut Context<Self>,
3693 ) {
3694 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3695
3696 if self.columnar_selection_state.is_some() {
3697 self.select_columns(position, goal_column, &display_map, window, cx);
3698 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3699 let buffer = &display_map.buffer_snapshot;
3700 let head;
3701 let tail;
3702 let mode = self.selections.pending_mode().unwrap();
3703 match &mode {
3704 SelectMode::Character => {
3705 head = position.to_point(&display_map);
3706 tail = pending.tail().to_point(buffer);
3707 }
3708 SelectMode::Word(original_range) => {
3709 let offset = display_map
3710 .clip_point(position, Bias::Left)
3711 .to_offset(&display_map, Bias::Left);
3712 let original_range = original_range.to_offset(buffer);
3713
3714 let head_offset = if buffer.is_inside_word(offset, false)
3715 || original_range.contains(&offset)
3716 {
3717 let (word_range, _) = buffer.surrounding_word(offset, false);
3718 if word_range.start < original_range.start {
3719 word_range.start
3720 } else {
3721 word_range.end
3722 }
3723 } else {
3724 offset
3725 };
3726
3727 head = head_offset.to_point(buffer);
3728 if head_offset <= original_range.start {
3729 tail = original_range.end.to_point(buffer);
3730 } else {
3731 tail = original_range.start.to_point(buffer);
3732 }
3733 }
3734 SelectMode::Line(original_range) => {
3735 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3736
3737 let position = display_map
3738 .clip_point(position, Bias::Left)
3739 .to_point(&display_map);
3740 let line_start = display_map.prev_line_boundary(position).0;
3741 let next_line_start = buffer.clip_point(
3742 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3743 Bias::Left,
3744 );
3745
3746 if line_start < original_range.start {
3747 head = line_start
3748 } else {
3749 head = next_line_start
3750 }
3751
3752 if head <= original_range.start {
3753 tail = original_range.end;
3754 } else {
3755 tail = original_range.start;
3756 }
3757 }
3758 SelectMode::All => {
3759 return;
3760 }
3761 };
3762
3763 if head < tail {
3764 pending.start = buffer.anchor_before(head);
3765 pending.end = buffer.anchor_before(tail);
3766 pending.reversed = true;
3767 } else {
3768 pending.start = buffer.anchor_before(tail);
3769 pending.end = buffer.anchor_before(head);
3770 pending.reversed = false;
3771 }
3772
3773 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3774 s.set_pending(pending.clone(), mode);
3775 });
3776 } else {
3777 log::error!("update_selection dispatched with no pending selection");
3778 return;
3779 }
3780
3781 self.apply_scroll_delta(scroll_delta, window, cx);
3782 cx.notify();
3783 }
3784
3785 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3786 self.columnar_selection_state.take();
3787 if self.selections.pending_anchor().is_some() {
3788 let selections = self.selections.all::<usize>(cx);
3789 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3790 s.select(selections);
3791 s.clear_pending();
3792 });
3793 }
3794 }
3795
3796 fn select_columns(
3797 &mut self,
3798 head: DisplayPoint,
3799 goal_column: u32,
3800 display_map: &DisplaySnapshot,
3801 window: &mut Window,
3802 cx: &mut Context<Self>,
3803 ) {
3804 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3805 return;
3806 };
3807
3808 let tail = match columnar_state {
3809 ColumnarSelectionState::FromMouse {
3810 selection_tail,
3811 display_point,
3812 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3813 ColumnarSelectionState::FromSelection { selection_tail } => {
3814 selection_tail.to_display_point(display_map)
3815 }
3816 };
3817
3818 let start_row = cmp::min(tail.row(), head.row());
3819 let end_row = cmp::max(tail.row(), head.row());
3820 let start_column = cmp::min(tail.column(), goal_column);
3821 let end_column = cmp::max(tail.column(), goal_column);
3822 let reversed = start_column < tail.column();
3823
3824 let selection_ranges = (start_row.0..=end_row.0)
3825 .map(DisplayRow)
3826 .filter_map(|row| {
3827 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3828 || start_column <= display_map.line_len(row))
3829 && !display_map.is_block_line(row)
3830 {
3831 let start = display_map
3832 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3833 .to_point(display_map);
3834 let end = display_map
3835 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3836 .to_point(display_map);
3837 if reversed {
3838 Some(end..start)
3839 } else {
3840 Some(start..end)
3841 }
3842 } else {
3843 None
3844 }
3845 })
3846 .collect::<Vec<_>>();
3847
3848 let ranges = match columnar_state {
3849 ColumnarSelectionState::FromMouse { .. } => {
3850 let mut non_empty_ranges = selection_ranges
3851 .iter()
3852 .filter(|selection_range| selection_range.start != selection_range.end)
3853 .peekable();
3854 if non_empty_ranges.peek().is_some() {
3855 non_empty_ranges.cloned().collect()
3856 } else {
3857 selection_ranges
3858 }
3859 }
3860 _ => selection_ranges,
3861 };
3862
3863 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3864 s.select_ranges(ranges);
3865 });
3866 cx.notify();
3867 }
3868
3869 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3870 self.selections
3871 .all_adjusted(cx)
3872 .iter()
3873 .any(|selection| !selection.is_empty())
3874 }
3875
3876 pub fn has_pending_nonempty_selection(&self) -> bool {
3877 let pending_nonempty_selection = match self.selections.pending_anchor() {
3878 Some(Selection { start, end, .. }) => start != end,
3879 None => false,
3880 };
3881
3882 pending_nonempty_selection
3883 || (self.columnar_selection_state.is_some()
3884 && self.selections.disjoint_anchors().len() > 1)
3885 }
3886
3887 pub fn has_pending_selection(&self) -> bool {
3888 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3889 }
3890
3891 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3892 self.selection_mark_mode = false;
3893 self.selection_drag_state = SelectionDragState::None;
3894
3895 if self.clear_expanded_diff_hunks(cx) {
3896 cx.notify();
3897 return;
3898 }
3899 if self.dismiss_menus_and_popups(true, window, cx) {
3900 return;
3901 }
3902
3903 if self.mode.is_full()
3904 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3905 {
3906 return;
3907 }
3908
3909 cx.propagate();
3910 }
3911
3912 pub fn dismiss_menus_and_popups(
3913 &mut self,
3914 is_user_requested: bool,
3915 window: &mut Window,
3916 cx: &mut Context<Self>,
3917 ) -> bool {
3918 if self.take_rename(false, window, cx).is_some() {
3919 return true;
3920 }
3921
3922 if hide_hover(self, cx) {
3923 return true;
3924 }
3925
3926 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3927 return true;
3928 }
3929
3930 if self.hide_context_menu(window, cx).is_some() {
3931 return true;
3932 }
3933
3934 if self.mouse_context_menu.take().is_some() {
3935 return true;
3936 }
3937
3938 if is_user_requested && self.discard_edit_prediction(true, cx) {
3939 return true;
3940 }
3941
3942 if self.snippet_stack.pop().is_some() {
3943 return true;
3944 }
3945
3946 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3947 self.dismiss_diagnostics(cx);
3948 return true;
3949 }
3950
3951 false
3952 }
3953
3954 fn linked_editing_ranges_for(
3955 &self,
3956 selection: Range<text::Anchor>,
3957 cx: &App,
3958 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3959 if self.linked_edit_ranges.is_empty() {
3960 return None;
3961 }
3962 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3963 selection.end.buffer_id.and_then(|end_buffer_id| {
3964 if selection.start.buffer_id != Some(end_buffer_id) {
3965 return None;
3966 }
3967 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3968 let snapshot = buffer.read(cx).snapshot();
3969 self.linked_edit_ranges
3970 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3971 .map(|ranges| (ranges, snapshot, buffer))
3972 })?;
3973 use text::ToOffset as TO;
3974 // find offset from the start of current range to current cursor position
3975 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3976
3977 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3978 let start_difference = start_offset - start_byte_offset;
3979 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3980 let end_difference = end_offset - start_byte_offset;
3981 // Current range has associated linked ranges.
3982 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3983 for range in linked_ranges.iter() {
3984 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3985 let end_offset = start_offset + end_difference;
3986 let start_offset = start_offset + start_difference;
3987 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3988 continue;
3989 }
3990 if self.selections.disjoint_anchor_ranges().any(|s| {
3991 if s.start.buffer_id != selection.start.buffer_id
3992 || s.end.buffer_id != selection.end.buffer_id
3993 {
3994 return false;
3995 }
3996 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3997 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3998 }) {
3999 continue;
4000 }
4001 let start = buffer_snapshot.anchor_after(start_offset);
4002 let end = buffer_snapshot.anchor_after(end_offset);
4003 linked_edits
4004 .entry(buffer.clone())
4005 .or_default()
4006 .push(start..end);
4007 }
4008 Some(linked_edits)
4009 }
4010
4011 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4012 let text: Arc<str> = text.into();
4013
4014 if self.read_only(cx) {
4015 return;
4016 }
4017
4018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4019
4020 let selections = self.selections.all_adjusted(cx);
4021 let mut bracket_inserted = false;
4022 let mut edits = Vec::new();
4023 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4024 let mut new_selections = Vec::with_capacity(selections.len());
4025 let mut new_autoclose_regions = Vec::new();
4026 let snapshot = self.buffer.read(cx).read(cx);
4027 let mut clear_linked_edit_ranges = false;
4028
4029 for (selection, autoclose_region) in
4030 self.selections_with_autoclose_regions(selections, &snapshot)
4031 {
4032 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4033 // Determine if the inserted text matches the opening or closing
4034 // bracket of any of this language's bracket pairs.
4035 let mut bracket_pair = None;
4036 let mut is_bracket_pair_start = false;
4037 let mut is_bracket_pair_end = false;
4038 if !text.is_empty() {
4039 let mut bracket_pair_matching_end = None;
4040 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4041 // and they are removing the character that triggered IME popup.
4042 for (pair, enabled) in scope.brackets() {
4043 if !pair.close && !pair.surround {
4044 continue;
4045 }
4046
4047 if enabled && pair.start.ends_with(text.as_ref()) {
4048 let prefix_len = pair.start.len() - text.len();
4049 let preceding_text_matches_prefix = prefix_len == 0
4050 || (selection.start.column >= (prefix_len as u32)
4051 && snapshot.contains_str_at(
4052 Point::new(
4053 selection.start.row,
4054 selection.start.column - (prefix_len as u32),
4055 ),
4056 &pair.start[..prefix_len],
4057 ));
4058 if preceding_text_matches_prefix {
4059 bracket_pair = Some(pair.clone());
4060 is_bracket_pair_start = true;
4061 break;
4062 }
4063 }
4064 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4065 {
4066 // take first bracket pair matching end, but don't break in case a later bracket
4067 // pair matches start
4068 bracket_pair_matching_end = Some(pair.clone());
4069 }
4070 }
4071 if let Some(end) = bracket_pair_matching_end
4072 && bracket_pair.is_none()
4073 {
4074 bracket_pair = Some(end);
4075 is_bracket_pair_end = true;
4076 }
4077 }
4078
4079 if let Some(bracket_pair) = bracket_pair {
4080 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4081 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4082 let auto_surround =
4083 self.use_auto_surround && snapshot_settings.use_auto_surround;
4084 if selection.is_empty() {
4085 if is_bracket_pair_start {
4086 // If the inserted text is a suffix of an opening bracket and the
4087 // selection is preceded by the rest of the opening bracket, then
4088 // insert the closing bracket.
4089 let following_text_allows_autoclose = snapshot
4090 .chars_at(selection.start)
4091 .next()
4092 .is_none_or(|c| scope.should_autoclose_before(c));
4093
4094 let preceding_text_allows_autoclose = selection.start.column == 0
4095 || snapshot
4096 .reversed_chars_at(selection.start)
4097 .next()
4098 .is_none_or(|c| {
4099 bracket_pair.start != bracket_pair.end
4100 || !snapshot
4101 .char_classifier_at(selection.start)
4102 .is_word(c)
4103 });
4104
4105 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4106 && bracket_pair.start.len() == 1
4107 {
4108 let target = bracket_pair.start.chars().next().unwrap();
4109 let current_line_count = snapshot
4110 .reversed_chars_at(selection.start)
4111 .take_while(|&c| c != '\n')
4112 .filter(|&c| c == target)
4113 .count();
4114 current_line_count % 2 == 1
4115 } else {
4116 false
4117 };
4118
4119 if autoclose
4120 && bracket_pair.close
4121 && following_text_allows_autoclose
4122 && preceding_text_allows_autoclose
4123 && !is_closing_quote
4124 {
4125 let anchor = snapshot.anchor_before(selection.end);
4126 new_selections.push((selection.map(|_| anchor), text.len()));
4127 new_autoclose_regions.push((
4128 anchor,
4129 text.len(),
4130 selection.id,
4131 bracket_pair.clone(),
4132 ));
4133 edits.push((
4134 selection.range(),
4135 format!("{}{}", text, bracket_pair.end).into(),
4136 ));
4137 bracket_inserted = true;
4138 continue;
4139 }
4140 }
4141
4142 if let Some(region) = autoclose_region {
4143 // If the selection is followed by an auto-inserted closing bracket,
4144 // then don't insert that closing bracket again; just move the selection
4145 // past the closing bracket.
4146 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4147 && text.as_ref() == region.pair.end.as_str()
4148 && snapshot.contains_str_at(region.range.end, text.as_ref());
4149 if should_skip {
4150 let anchor = snapshot.anchor_after(selection.end);
4151 new_selections
4152 .push((selection.map(|_| anchor), region.pair.end.len()));
4153 continue;
4154 }
4155 }
4156
4157 let always_treat_brackets_as_autoclosed = snapshot
4158 .language_settings_at(selection.start, cx)
4159 .always_treat_brackets_as_autoclosed;
4160 if always_treat_brackets_as_autoclosed
4161 && is_bracket_pair_end
4162 && snapshot.contains_str_at(selection.end, text.as_ref())
4163 {
4164 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4165 // and the inserted text is a closing bracket and the selection is followed
4166 // by the closing bracket then move the selection past the closing bracket.
4167 let anchor = snapshot.anchor_after(selection.end);
4168 new_selections.push((selection.map(|_| anchor), text.len()));
4169 continue;
4170 }
4171 }
4172 // If an opening bracket is 1 character long and is typed while
4173 // text is selected, then surround that text with the bracket pair.
4174 else if auto_surround
4175 && bracket_pair.surround
4176 && is_bracket_pair_start
4177 && bracket_pair.start.chars().count() == 1
4178 {
4179 edits.push((selection.start..selection.start, text.clone()));
4180 edits.push((
4181 selection.end..selection.end,
4182 bracket_pair.end.as_str().into(),
4183 ));
4184 bracket_inserted = true;
4185 new_selections.push((
4186 Selection {
4187 id: selection.id,
4188 start: snapshot.anchor_after(selection.start),
4189 end: snapshot.anchor_before(selection.end),
4190 reversed: selection.reversed,
4191 goal: selection.goal,
4192 },
4193 0,
4194 ));
4195 continue;
4196 }
4197 }
4198 }
4199
4200 if self.auto_replace_emoji_shortcode
4201 && selection.is_empty()
4202 && text.as_ref().ends_with(':')
4203 && let Some(possible_emoji_short_code) =
4204 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4205 && !possible_emoji_short_code.is_empty()
4206 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4207 {
4208 let emoji_shortcode_start = Point::new(
4209 selection.start.row,
4210 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4211 );
4212
4213 // Remove shortcode from buffer
4214 edits.push((
4215 emoji_shortcode_start..selection.start,
4216 "".to_string().into(),
4217 ));
4218 new_selections.push((
4219 Selection {
4220 id: selection.id,
4221 start: snapshot.anchor_after(emoji_shortcode_start),
4222 end: snapshot.anchor_before(selection.start),
4223 reversed: selection.reversed,
4224 goal: selection.goal,
4225 },
4226 0,
4227 ));
4228
4229 // Insert emoji
4230 let selection_start_anchor = snapshot.anchor_after(selection.start);
4231 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4232 edits.push((selection.start..selection.end, emoji.to_string().into()));
4233
4234 continue;
4235 }
4236
4237 // If not handling any auto-close operation, then just replace the selected
4238 // text with the given input and move the selection to the end of the
4239 // newly inserted text.
4240 let anchor = snapshot.anchor_after(selection.end);
4241 if !self.linked_edit_ranges.is_empty() {
4242 let start_anchor = snapshot.anchor_before(selection.start);
4243
4244 let is_word_char = text.chars().next().is_none_or(|char| {
4245 let classifier = snapshot
4246 .char_classifier_at(start_anchor.to_offset(&snapshot))
4247 .ignore_punctuation(true);
4248 classifier.is_word(char)
4249 });
4250
4251 if is_word_char {
4252 if let Some(ranges) = self
4253 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4254 {
4255 for (buffer, edits) in ranges {
4256 linked_edits
4257 .entry(buffer.clone())
4258 .or_default()
4259 .extend(edits.into_iter().map(|range| (range, text.clone())));
4260 }
4261 }
4262 } else {
4263 clear_linked_edit_ranges = true;
4264 }
4265 }
4266
4267 new_selections.push((selection.map(|_| anchor), 0));
4268 edits.push((selection.start..selection.end, text.clone()));
4269 }
4270
4271 drop(snapshot);
4272
4273 self.transact(window, cx, |this, window, cx| {
4274 if clear_linked_edit_ranges {
4275 this.linked_edit_ranges.clear();
4276 }
4277 let initial_buffer_versions =
4278 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4279
4280 this.buffer.update(cx, |buffer, cx| {
4281 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4282 });
4283 for (buffer, edits) in linked_edits {
4284 buffer.update(cx, |buffer, cx| {
4285 let snapshot = buffer.snapshot();
4286 let edits = edits
4287 .into_iter()
4288 .map(|(range, text)| {
4289 use text::ToPoint as TP;
4290 let end_point = TP::to_point(&range.end, &snapshot);
4291 let start_point = TP::to_point(&range.start, &snapshot);
4292 (start_point..end_point, text)
4293 })
4294 .sorted_by_key(|(range, _)| range.start);
4295 buffer.edit(edits, None, cx);
4296 })
4297 }
4298 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4299 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4300 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4301 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4302 .zip(new_selection_deltas)
4303 .map(|(selection, delta)| Selection {
4304 id: selection.id,
4305 start: selection.start + delta,
4306 end: selection.end + delta,
4307 reversed: selection.reversed,
4308 goal: SelectionGoal::None,
4309 })
4310 .collect::<Vec<_>>();
4311
4312 let mut i = 0;
4313 for (position, delta, selection_id, pair) in new_autoclose_regions {
4314 let position = position.to_offset(&map.buffer_snapshot) + delta;
4315 let start = map.buffer_snapshot.anchor_before(position);
4316 let end = map.buffer_snapshot.anchor_after(position);
4317 while let Some(existing_state) = this.autoclose_regions.get(i) {
4318 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4319 Ordering::Less => i += 1,
4320 Ordering::Greater => break,
4321 Ordering::Equal => {
4322 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4323 Ordering::Less => i += 1,
4324 Ordering::Equal => break,
4325 Ordering::Greater => break,
4326 }
4327 }
4328 }
4329 }
4330 this.autoclose_regions.insert(
4331 i,
4332 AutocloseRegion {
4333 selection_id,
4334 range: start..end,
4335 pair,
4336 },
4337 );
4338 }
4339
4340 let had_active_edit_prediction = this.has_active_edit_prediction();
4341 this.change_selections(
4342 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4343 window,
4344 cx,
4345 |s| s.select(new_selections),
4346 );
4347
4348 if !bracket_inserted
4349 && let Some(on_type_format_task) =
4350 this.trigger_on_type_formatting(text.to_string(), window, cx)
4351 {
4352 on_type_format_task.detach_and_log_err(cx);
4353 }
4354
4355 let editor_settings = EditorSettings::get_global(cx);
4356 if bracket_inserted
4357 && (editor_settings.auto_signature_help
4358 || editor_settings.show_signature_help_after_edits)
4359 {
4360 this.show_signature_help(&ShowSignatureHelp, window, cx);
4361 }
4362
4363 let trigger_in_words =
4364 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4365 if this.hard_wrap.is_some() {
4366 let latest: Range<Point> = this.selections.newest(cx).range();
4367 if latest.is_empty()
4368 && this
4369 .buffer()
4370 .read(cx)
4371 .snapshot(cx)
4372 .line_len(MultiBufferRow(latest.start.row))
4373 == latest.start.column
4374 {
4375 this.rewrap_impl(
4376 RewrapOptions {
4377 override_language_settings: true,
4378 preserve_existing_whitespace: true,
4379 },
4380 cx,
4381 )
4382 }
4383 }
4384 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4385 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4386 this.refresh_edit_prediction(true, false, window, cx);
4387 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4388 });
4389 }
4390
4391 fn find_possible_emoji_shortcode_at_position(
4392 snapshot: &MultiBufferSnapshot,
4393 position: Point,
4394 ) -> Option<String> {
4395 let mut chars = Vec::new();
4396 let mut found_colon = false;
4397 for char in snapshot.reversed_chars_at(position).take(100) {
4398 // Found a possible emoji shortcode in the middle of the buffer
4399 if found_colon {
4400 if char.is_whitespace() {
4401 chars.reverse();
4402 return Some(chars.iter().collect());
4403 }
4404 // If the previous character is not a whitespace, we are in the middle of a word
4405 // and we only want to complete the shortcode if the word is made up of other emojis
4406 let mut containing_word = String::new();
4407 for ch in snapshot
4408 .reversed_chars_at(position)
4409 .skip(chars.len() + 1)
4410 .take(100)
4411 {
4412 if ch.is_whitespace() {
4413 break;
4414 }
4415 containing_word.push(ch);
4416 }
4417 let containing_word = containing_word.chars().rev().collect::<String>();
4418 if util::word_consists_of_emojis(containing_word.as_str()) {
4419 chars.reverse();
4420 return Some(chars.iter().collect());
4421 }
4422 }
4423
4424 if char.is_whitespace() || !char.is_ascii() {
4425 return None;
4426 }
4427 if char == ':' {
4428 found_colon = true;
4429 } else {
4430 chars.push(char);
4431 }
4432 }
4433 // Found a possible emoji shortcode at the beginning of the buffer
4434 chars.reverse();
4435 Some(chars.iter().collect())
4436 }
4437
4438 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4439 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4440 self.transact(window, cx, |this, window, cx| {
4441 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4442 let selections = this.selections.all::<usize>(cx);
4443 let multi_buffer = this.buffer.read(cx);
4444 let buffer = multi_buffer.snapshot(cx);
4445 selections
4446 .iter()
4447 .map(|selection| {
4448 let start_point = selection.start.to_point(&buffer);
4449 let mut existing_indent =
4450 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4451 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4452 let start = selection.start;
4453 let end = selection.end;
4454 let selection_is_empty = start == end;
4455 let language_scope = buffer.language_scope_at(start);
4456 let (
4457 comment_delimiter,
4458 doc_delimiter,
4459 insert_extra_newline,
4460 indent_on_newline,
4461 indent_on_extra_newline,
4462 ) = if let Some(language) = &language_scope {
4463 let mut insert_extra_newline =
4464 insert_extra_newline_brackets(&buffer, start..end, language)
4465 || insert_extra_newline_tree_sitter(&buffer, start..end);
4466
4467 // Comment extension on newline is allowed only for cursor selections
4468 let comment_delimiter = maybe!({
4469 if !selection_is_empty {
4470 return None;
4471 }
4472
4473 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4474 return None;
4475 }
4476
4477 let delimiters = language.line_comment_prefixes();
4478 let max_len_of_delimiter =
4479 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4480 let (snapshot, range) =
4481 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4482
4483 let num_of_whitespaces = snapshot
4484 .chars_for_range(range.clone())
4485 .take_while(|c| c.is_whitespace())
4486 .count();
4487 let comment_candidate = snapshot
4488 .chars_for_range(range.clone())
4489 .skip(num_of_whitespaces)
4490 .take(max_len_of_delimiter)
4491 .collect::<String>();
4492 let (delimiter, trimmed_len) = delimiters
4493 .iter()
4494 .filter_map(|delimiter| {
4495 let prefix = delimiter.trim_end();
4496 if comment_candidate.starts_with(prefix) {
4497 Some((delimiter, prefix.len()))
4498 } else {
4499 None
4500 }
4501 })
4502 .max_by_key(|(_, len)| *len)?;
4503
4504 if let Some(BlockCommentConfig {
4505 start: block_start, ..
4506 }) = language.block_comment()
4507 {
4508 let block_start_trimmed = block_start.trim_end();
4509 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4510 let line_content = snapshot
4511 .chars_for_range(range)
4512 .skip(num_of_whitespaces)
4513 .take(block_start_trimmed.len())
4514 .collect::<String>();
4515
4516 if line_content.starts_with(block_start_trimmed) {
4517 return None;
4518 }
4519 }
4520 }
4521
4522 let cursor_is_placed_after_comment_marker =
4523 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4524 if cursor_is_placed_after_comment_marker {
4525 Some(delimiter.clone())
4526 } else {
4527 None
4528 }
4529 });
4530
4531 let mut indent_on_newline = IndentSize::spaces(0);
4532 let mut indent_on_extra_newline = IndentSize::spaces(0);
4533
4534 let doc_delimiter = maybe!({
4535 if !selection_is_empty {
4536 return None;
4537 }
4538
4539 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4540 return None;
4541 }
4542
4543 let BlockCommentConfig {
4544 start: start_tag,
4545 end: end_tag,
4546 prefix: delimiter,
4547 tab_size: len,
4548 } = language.documentation_comment()?;
4549 let is_within_block_comment = buffer
4550 .language_scope_at(start_point)
4551 .is_some_and(|scope| scope.override_name() == Some("comment"));
4552 if !is_within_block_comment {
4553 return None;
4554 }
4555
4556 let (snapshot, range) =
4557 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4558
4559 let num_of_whitespaces = snapshot
4560 .chars_for_range(range.clone())
4561 .take_while(|c| c.is_whitespace())
4562 .count();
4563
4564 // 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.
4565 let column = start_point.column;
4566 let cursor_is_after_start_tag = {
4567 let start_tag_len = start_tag.len();
4568 let start_tag_line = snapshot
4569 .chars_for_range(range.clone())
4570 .skip(num_of_whitespaces)
4571 .take(start_tag_len)
4572 .collect::<String>();
4573 if start_tag_line.starts_with(start_tag.as_ref()) {
4574 num_of_whitespaces + start_tag_len <= column as usize
4575 } else {
4576 false
4577 }
4578 };
4579
4580 let cursor_is_after_delimiter = {
4581 let delimiter_trim = delimiter.trim_end();
4582 let delimiter_line = snapshot
4583 .chars_for_range(range.clone())
4584 .skip(num_of_whitespaces)
4585 .take(delimiter_trim.len())
4586 .collect::<String>();
4587 if delimiter_line.starts_with(delimiter_trim) {
4588 num_of_whitespaces + delimiter_trim.len() <= column as usize
4589 } else {
4590 false
4591 }
4592 };
4593
4594 let cursor_is_before_end_tag_if_exists = {
4595 let mut char_position = 0u32;
4596 let mut end_tag_offset = None;
4597
4598 'outer: for chunk in snapshot.text_for_range(range) {
4599 if let Some(byte_pos) = chunk.find(&**end_tag) {
4600 let chars_before_match =
4601 chunk[..byte_pos].chars().count() as u32;
4602 end_tag_offset =
4603 Some(char_position + chars_before_match);
4604 break 'outer;
4605 }
4606 char_position += chunk.chars().count() as u32;
4607 }
4608
4609 if let Some(end_tag_offset) = end_tag_offset {
4610 let cursor_is_before_end_tag = column <= end_tag_offset;
4611 if cursor_is_after_start_tag {
4612 if cursor_is_before_end_tag {
4613 insert_extra_newline = true;
4614 }
4615 let cursor_is_at_start_of_end_tag =
4616 column == end_tag_offset;
4617 if cursor_is_at_start_of_end_tag {
4618 indent_on_extra_newline.len = *len;
4619 }
4620 }
4621 cursor_is_before_end_tag
4622 } else {
4623 true
4624 }
4625 };
4626
4627 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4628 && cursor_is_before_end_tag_if_exists
4629 {
4630 if cursor_is_after_start_tag {
4631 indent_on_newline.len = *len;
4632 }
4633 Some(delimiter.clone())
4634 } else {
4635 None
4636 }
4637 });
4638
4639 (
4640 comment_delimiter,
4641 doc_delimiter,
4642 insert_extra_newline,
4643 indent_on_newline,
4644 indent_on_extra_newline,
4645 )
4646 } else {
4647 (
4648 None,
4649 None,
4650 false,
4651 IndentSize::default(),
4652 IndentSize::default(),
4653 )
4654 };
4655
4656 let prevent_auto_indent = doc_delimiter.is_some();
4657 let delimiter = comment_delimiter.or(doc_delimiter);
4658
4659 let capacity_for_delimiter =
4660 delimiter.as_deref().map(str::len).unwrap_or_default();
4661 let mut new_text = String::with_capacity(
4662 1 + capacity_for_delimiter
4663 + existing_indent.len as usize
4664 + indent_on_newline.len as usize
4665 + indent_on_extra_newline.len as usize,
4666 );
4667 new_text.push('\n');
4668 new_text.extend(existing_indent.chars());
4669 new_text.extend(indent_on_newline.chars());
4670
4671 if let Some(delimiter) = &delimiter {
4672 new_text.push_str(delimiter);
4673 }
4674
4675 if insert_extra_newline {
4676 new_text.push('\n');
4677 new_text.extend(existing_indent.chars());
4678 new_text.extend(indent_on_extra_newline.chars());
4679 }
4680
4681 let anchor = buffer.anchor_after(end);
4682 let new_selection = selection.map(|_| anchor);
4683 (
4684 ((start..end, new_text), prevent_auto_indent),
4685 (insert_extra_newline, new_selection),
4686 )
4687 })
4688 .unzip()
4689 };
4690
4691 let mut auto_indent_edits = Vec::new();
4692 let mut edits = Vec::new();
4693 for (edit, prevent_auto_indent) in edits_with_flags {
4694 if prevent_auto_indent {
4695 edits.push(edit);
4696 } else {
4697 auto_indent_edits.push(edit);
4698 }
4699 }
4700 if !edits.is_empty() {
4701 this.edit(edits, cx);
4702 }
4703 if !auto_indent_edits.is_empty() {
4704 this.edit_with_autoindent(auto_indent_edits, cx);
4705 }
4706
4707 let buffer = this.buffer.read(cx).snapshot(cx);
4708 let new_selections = selection_info
4709 .into_iter()
4710 .map(|(extra_newline_inserted, new_selection)| {
4711 let mut cursor = new_selection.end.to_point(&buffer);
4712 if extra_newline_inserted {
4713 cursor.row -= 1;
4714 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4715 }
4716 new_selection.map(|_| cursor)
4717 })
4718 .collect();
4719
4720 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4721 this.refresh_edit_prediction(true, false, window, cx);
4722 });
4723 }
4724
4725 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4726 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4727
4728 let buffer = self.buffer.read(cx);
4729 let snapshot = buffer.snapshot(cx);
4730
4731 let mut edits = Vec::new();
4732 let mut rows = Vec::new();
4733
4734 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4735 let cursor = selection.head();
4736 let row = cursor.row;
4737
4738 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4739
4740 let newline = "\n".to_string();
4741 edits.push((start_of_line..start_of_line, newline));
4742
4743 rows.push(row + rows_inserted as u32);
4744 }
4745
4746 self.transact(window, cx, |editor, window, cx| {
4747 editor.edit(edits, cx);
4748
4749 editor.change_selections(Default::default(), window, cx, |s| {
4750 let mut index = 0;
4751 s.move_cursors_with(|map, _, _| {
4752 let row = rows[index];
4753 index += 1;
4754
4755 let point = Point::new(row, 0);
4756 let boundary = map.next_line_boundary(point).1;
4757 let clipped = map.clip_point(boundary, Bias::Left);
4758
4759 (clipped, SelectionGoal::None)
4760 });
4761 });
4762
4763 let mut indent_edits = Vec::new();
4764 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4765 for row in rows {
4766 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4767 for (row, indent) in indents {
4768 if indent.len == 0 {
4769 continue;
4770 }
4771
4772 let text = match indent.kind {
4773 IndentKind::Space => " ".repeat(indent.len as usize),
4774 IndentKind::Tab => "\t".repeat(indent.len as usize),
4775 };
4776 let point = Point::new(row.0, 0);
4777 indent_edits.push((point..point, text));
4778 }
4779 }
4780 editor.edit(indent_edits, cx);
4781 });
4782 }
4783
4784 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4786
4787 let buffer = self.buffer.read(cx);
4788 let snapshot = buffer.snapshot(cx);
4789
4790 let mut edits = Vec::new();
4791 let mut rows = Vec::new();
4792 let mut rows_inserted = 0;
4793
4794 for selection in self.selections.all_adjusted(cx) {
4795 let cursor = selection.head();
4796 let row = cursor.row;
4797
4798 let point = Point::new(row + 1, 0);
4799 let start_of_line = snapshot.clip_point(point, Bias::Left);
4800
4801 let newline = "\n".to_string();
4802 edits.push((start_of_line..start_of_line, newline));
4803
4804 rows_inserted += 1;
4805 rows.push(row + rows_inserted);
4806 }
4807
4808 self.transact(window, cx, |editor, window, cx| {
4809 editor.edit(edits, cx);
4810
4811 editor.change_selections(Default::default(), window, cx, |s| {
4812 let mut index = 0;
4813 s.move_cursors_with(|map, _, _| {
4814 let row = rows[index];
4815 index += 1;
4816
4817 let point = Point::new(row, 0);
4818 let boundary = map.next_line_boundary(point).1;
4819 let clipped = map.clip_point(boundary, Bias::Left);
4820
4821 (clipped, SelectionGoal::None)
4822 });
4823 });
4824
4825 let mut indent_edits = Vec::new();
4826 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4827 for row in rows {
4828 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4829 for (row, indent) in indents {
4830 if indent.len == 0 {
4831 continue;
4832 }
4833
4834 let text = match indent.kind {
4835 IndentKind::Space => " ".repeat(indent.len as usize),
4836 IndentKind::Tab => "\t".repeat(indent.len as usize),
4837 };
4838 let point = Point::new(row.0, 0);
4839 indent_edits.push((point..point, text));
4840 }
4841 }
4842 editor.edit(indent_edits, cx);
4843 });
4844 }
4845
4846 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4847 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4848 original_indent_columns: Vec::new(),
4849 });
4850 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4851 }
4852
4853 fn insert_with_autoindent_mode(
4854 &mut self,
4855 text: &str,
4856 autoindent_mode: Option<AutoindentMode>,
4857 window: &mut Window,
4858 cx: &mut Context<Self>,
4859 ) {
4860 if self.read_only(cx) {
4861 return;
4862 }
4863
4864 let text: Arc<str> = text.into();
4865 self.transact(window, cx, |this, window, cx| {
4866 let old_selections = this.selections.all_adjusted(cx);
4867 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4868 let anchors = {
4869 let snapshot = buffer.read(cx);
4870 old_selections
4871 .iter()
4872 .map(|s| {
4873 let anchor = snapshot.anchor_after(s.head());
4874 s.map(|_| anchor)
4875 })
4876 .collect::<Vec<_>>()
4877 };
4878 buffer.edit(
4879 old_selections
4880 .iter()
4881 .map(|s| (s.start..s.end, text.clone())),
4882 autoindent_mode,
4883 cx,
4884 );
4885 anchors
4886 });
4887
4888 this.change_selections(Default::default(), window, cx, |s| {
4889 s.select_anchors(selection_anchors);
4890 });
4891
4892 cx.notify();
4893 });
4894 }
4895
4896 fn trigger_completion_on_input(
4897 &mut self,
4898 text: &str,
4899 trigger_in_words: bool,
4900 window: &mut Window,
4901 cx: &mut Context<Self>,
4902 ) {
4903 let completions_source = self
4904 .context_menu
4905 .borrow()
4906 .as_ref()
4907 .and_then(|menu| match menu {
4908 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4909 CodeContextMenu::CodeActions(_) => None,
4910 });
4911
4912 match completions_source {
4913 Some(CompletionsMenuSource::Words { .. }) => {
4914 self.open_or_update_completions_menu(
4915 Some(CompletionsMenuSource::Words {
4916 ignore_threshold: false,
4917 }),
4918 None,
4919 window,
4920 cx,
4921 );
4922 }
4923 Some(CompletionsMenuSource::Normal)
4924 | Some(CompletionsMenuSource::SnippetChoices)
4925 | None
4926 if self.is_completion_trigger(
4927 text,
4928 trigger_in_words,
4929 completions_source.is_some(),
4930 cx,
4931 ) =>
4932 {
4933 self.show_completions(
4934 &ShowCompletions {
4935 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4936 },
4937 window,
4938 cx,
4939 )
4940 }
4941 _ => {
4942 self.hide_context_menu(window, cx);
4943 }
4944 }
4945 }
4946
4947 fn is_completion_trigger(
4948 &self,
4949 text: &str,
4950 trigger_in_words: bool,
4951 menu_is_open: bool,
4952 cx: &mut Context<Self>,
4953 ) -> bool {
4954 let position = self.selections.newest_anchor().head();
4955 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4956 return false;
4957 };
4958
4959 if let Some(completion_provider) = &self.completion_provider {
4960 completion_provider.is_completion_trigger(
4961 &buffer,
4962 position.text_anchor,
4963 text,
4964 trigger_in_words,
4965 menu_is_open,
4966 cx,
4967 )
4968 } else {
4969 false
4970 }
4971 }
4972
4973 /// If any empty selections is touching the start of its innermost containing autoclose
4974 /// region, expand it to select the brackets.
4975 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4976 let selections = self.selections.all::<usize>(cx);
4977 let buffer = self.buffer.read(cx).read(cx);
4978 let new_selections = self
4979 .selections_with_autoclose_regions(selections, &buffer)
4980 .map(|(mut selection, region)| {
4981 if !selection.is_empty() {
4982 return selection;
4983 }
4984
4985 if let Some(region) = region {
4986 let mut range = region.range.to_offset(&buffer);
4987 if selection.start == range.start && range.start >= region.pair.start.len() {
4988 range.start -= region.pair.start.len();
4989 if buffer.contains_str_at(range.start, ®ion.pair.start)
4990 && buffer.contains_str_at(range.end, ®ion.pair.end)
4991 {
4992 range.end += region.pair.end.len();
4993 selection.start = range.start;
4994 selection.end = range.end;
4995
4996 return selection;
4997 }
4998 }
4999 }
5000
5001 let always_treat_brackets_as_autoclosed = buffer
5002 .language_settings_at(selection.start, cx)
5003 .always_treat_brackets_as_autoclosed;
5004
5005 if !always_treat_brackets_as_autoclosed {
5006 return selection;
5007 }
5008
5009 if let Some(scope) = buffer.language_scope_at(selection.start) {
5010 for (pair, enabled) in scope.brackets() {
5011 if !enabled || !pair.close {
5012 continue;
5013 }
5014
5015 if buffer.contains_str_at(selection.start, &pair.end) {
5016 let pair_start_len = pair.start.len();
5017 if buffer.contains_str_at(
5018 selection.start.saturating_sub(pair_start_len),
5019 &pair.start,
5020 ) {
5021 selection.start -= pair_start_len;
5022 selection.end += pair.end.len();
5023
5024 return selection;
5025 }
5026 }
5027 }
5028 }
5029
5030 selection
5031 })
5032 .collect();
5033
5034 drop(buffer);
5035 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5036 selections.select(new_selections)
5037 });
5038 }
5039
5040 /// Iterate the given selections, and for each one, find the smallest surrounding
5041 /// autoclose region. This uses the ordering of the selections and the autoclose
5042 /// regions to avoid repeated comparisons.
5043 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5044 &'a self,
5045 selections: impl IntoIterator<Item = Selection<D>>,
5046 buffer: &'a MultiBufferSnapshot,
5047 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5048 let mut i = 0;
5049 let mut regions = self.autoclose_regions.as_slice();
5050 selections.into_iter().map(move |selection| {
5051 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5052
5053 let mut enclosing = None;
5054 while let Some(pair_state) = regions.get(i) {
5055 if pair_state.range.end.to_offset(buffer) < range.start {
5056 regions = ®ions[i + 1..];
5057 i = 0;
5058 } else if pair_state.range.start.to_offset(buffer) > range.end {
5059 break;
5060 } else {
5061 if pair_state.selection_id == selection.id {
5062 enclosing = Some(pair_state);
5063 }
5064 i += 1;
5065 }
5066 }
5067
5068 (selection, enclosing)
5069 })
5070 }
5071
5072 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5073 fn invalidate_autoclose_regions(
5074 &mut self,
5075 mut selections: &[Selection<Anchor>],
5076 buffer: &MultiBufferSnapshot,
5077 ) {
5078 self.autoclose_regions.retain(|state| {
5079 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5080 return false;
5081 }
5082
5083 let mut i = 0;
5084 while let Some(selection) = selections.get(i) {
5085 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5086 selections = &selections[1..];
5087 continue;
5088 }
5089 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5090 break;
5091 }
5092 if selection.id == state.selection_id {
5093 return true;
5094 } else {
5095 i += 1;
5096 }
5097 }
5098 false
5099 });
5100 }
5101
5102 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5103 let offset = position.to_offset(buffer);
5104 let (word_range, kind) = buffer.surrounding_word(offset, true);
5105 if offset > word_range.start && kind == Some(CharKind::Word) {
5106 Some(
5107 buffer
5108 .text_for_range(word_range.start..offset)
5109 .collect::<String>(),
5110 )
5111 } else {
5112 None
5113 }
5114 }
5115
5116 pub fn toggle_inline_values(
5117 &mut self,
5118 _: &ToggleInlineValues,
5119 _: &mut Window,
5120 cx: &mut Context<Self>,
5121 ) {
5122 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5123
5124 self.refresh_inline_values(cx);
5125 }
5126
5127 pub fn toggle_inlay_hints(
5128 &mut self,
5129 _: &ToggleInlayHints,
5130 _: &mut Window,
5131 cx: &mut Context<Self>,
5132 ) {
5133 self.refresh_inlay_hints(
5134 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5135 cx,
5136 );
5137 }
5138
5139 pub fn inlay_hints_enabled(&self) -> bool {
5140 self.inlay_hint_cache.enabled
5141 }
5142
5143 pub fn inline_values_enabled(&self) -> bool {
5144 self.inline_value_cache.enabled
5145 }
5146
5147 #[cfg(any(test, feature = "test-support"))]
5148 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5149 self.display_map
5150 .read(cx)
5151 .current_inlays()
5152 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5153 .cloned()
5154 .collect()
5155 }
5156
5157 #[cfg(any(test, feature = "test-support"))]
5158 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5159 self.display_map
5160 .read(cx)
5161 .current_inlays()
5162 .cloned()
5163 .collect()
5164 }
5165
5166 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5167 if self.semantics_provider.is_none() || !self.mode.is_full() {
5168 return;
5169 }
5170
5171 let reason_description = reason.description();
5172 let ignore_debounce = matches!(
5173 reason,
5174 InlayHintRefreshReason::SettingsChange(_)
5175 | InlayHintRefreshReason::Toggle(_)
5176 | InlayHintRefreshReason::ExcerptsRemoved(_)
5177 | InlayHintRefreshReason::ModifiersChanged(_)
5178 );
5179 let (invalidate_cache, required_languages) = match reason {
5180 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5181 match self.inlay_hint_cache.modifiers_override(enabled) {
5182 Some(enabled) => {
5183 if enabled {
5184 (InvalidationStrategy::RefreshRequested, None)
5185 } else {
5186 self.splice_inlays(
5187 &self
5188 .visible_inlay_hints(cx)
5189 .iter()
5190 .map(|inlay| inlay.id)
5191 .collect::<Vec<InlayId>>(),
5192 Vec::new(),
5193 cx,
5194 );
5195 return;
5196 }
5197 }
5198 None => return,
5199 }
5200 }
5201 InlayHintRefreshReason::Toggle(enabled) => {
5202 if self.inlay_hint_cache.toggle(enabled) {
5203 if enabled {
5204 (InvalidationStrategy::RefreshRequested, None)
5205 } else {
5206 self.splice_inlays(
5207 &self
5208 .visible_inlay_hints(cx)
5209 .iter()
5210 .map(|inlay| inlay.id)
5211 .collect::<Vec<InlayId>>(),
5212 Vec::new(),
5213 cx,
5214 );
5215 return;
5216 }
5217 } else {
5218 return;
5219 }
5220 }
5221 InlayHintRefreshReason::SettingsChange(new_settings) => {
5222 match self.inlay_hint_cache.update_settings(
5223 &self.buffer,
5224 new_settings,
5225 self.visible_inlay_hints(cx),
5226 cx,
5227 ) {
5228 ControlFlow::Break(Some(InlaySplice {
5229 to_remove,
5230 to_insert,
5231 })) => {
5232 self.splice_inlays(&to_remove, to_insert, cx);
5233 return;
5234 }
5235 ControlFlow::Break(None) => return,
5236 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5237 }
5238 }
5239 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5240 if let Some(InlaySplice {
5241 to_remove,
5242 to_insert,
5243 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5244 {
5245 self.splice_inlays(&to_remove, to_insert, cx);
5246 }
5247 self.display_map.update(cx, |display_map, _| {
5248 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5249 });
5250 return;
5251 }
5252 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5253 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5254 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5255 }
5256 InlayHintRefreshReason::RefreshRequested => {
5257 (InvalidationStrategy::RefreshRequested, None)
5258 }
5259 };
5260
5261 if let Some(InlaySplice {
5262 to_remove,
5263 to_insert,
5264 }) = self.inlay_hint_cache.spawn_hint_refresh(
5265 reason_description,
5266 self.visible_excerpts(required_languages.as_ref(), cx),
5267 invalidate_cache,
5268 ignore_debounce,
5269 cx,
5270 ) {
5271 self.splice_inlays(&to_remove, to_insert, cx);
5272 }
5273 }
5274
5275 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5276 self.display_map
5277 .read(cx)
5278 .current_inlays()
5279 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5280 .cloned()
5281 .collect()
5282 }
5283
5284 pub fn visible_excerpts(
5285 &self,
5286 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5287 cx: &mut Context<Editor>,
5288 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5289 let Some(project) = self.project() else {
5290 return HashMap::default();
5291 };
5292 let project = project.read(cx);
5293 let multi_buffer = self.buffer().read(cx);
5294 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5295 let multi_buffer_visible_start = self
5296 .scroll_manager
5297 .anchor()
5298 .anchor
5299 .to_point(&multi_buffer_snapshot);
5300 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5301 multi_buffer_visible_start
5302 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5303 Bias::Left,
5304 );
5305 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5306 multi_buffer_snapshot
5307 .range_to_buffer_ranges(multi_buffer_visible_range)
5308 .into_iter()
5309 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5310 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5311 let buffer_file = project::File::from_dyn(buffer.file())?;
5312 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5313 let worktree_entry = buffer_worktree
5314 .read(cx)
5315 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5316 if worktree_entry.is_ignored {
5317 return None;
5318 }
5319
5320 let language = buffer.language()?;
5321 if let Some(restrict_to_languages) = restrict_to_languages
5322 && !restrict_to_languages.contains(language)
5323 {
5324 return None;
5325 }
5326 Some((
5327 excerpt_id,
5328 (
5329 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5330 buffer.version().clone(),
5331 excerpt_visible_range,
5332 ),
5333 ))
5334 })
5335 .collect()
5336 }
5337
5338 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5339 TextLayoutDetails {
5340 text_system: window.text_system().clone(),
5341 editor_style: self.style.clone().unwrap(),
5342 rem_size: window.rem_size(),
5343 scroll_anchor: self.scroll_manager.anchor(),
5344 visible_rows: self.visible_line_count(),
5345 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5346 }
5347 }
5348
5349 pub fn splice_inlays(
5350 &self,
5351 to_remove: &[InlayId],
5352 to_insert: Vec<Inlay>,
5353 cx: &mut Context<Self>,
5354 ) {
5355 self.display_map.update(cx, |display_map, cx| {
5356 display_map.splice_inlays(to_remove, to_insert, cx)
5357 });
5358 cx.notify();
5359 }
5360
5361 fn trigger_on_type_formatting(
5362 &self,
5363 input: String,
5364 window: &mut Window,
5365 cx: &mut Context<Self>,
5366 ) -> Option<Task<Result<()>>> {
5367 if input.len() != 1 {
5368 return None;
5369 }
5370
5371 let project = self.project()?;
5372 let position = self.selections.newest_anchor().head();
5373 let (buffer, buffer_position) = self
5374 .buffer
5375 .read(cx)
5376 .text_anchor_for_position(position, cx)?;
5377
5378 let settings = language_settings::language_settings(
5379 buffer
5380 .read(cx)
5381 .language_at(buffer_position)
5382 .map(|l| l.name()),
5383 buffer.read(cx).file(),
5384 cx,
5385 );
5386 if !settings.use_on_type_format {
5387 return None;
5388 }
5389
5390 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5391 // hence we do LSP request & edit on host side only — add formats to host's history.
5392 let push_to_lsp_host_history = true;
5393 // If this is not the host, append its history with new edits.
5394 let push_to_client_history = project.read(cx).is_via_collab();
5395
5396 let on_type_formatting = project.update(cx, |project, cx| {
5397 project.on_type_format(
5398 buffer.clone(),
5399 buffer_position,
5400 input,
5401 push_to_lsp_host_history,
5402 cx,
5403 )
5404 });
5405 Some(cx.spawn_in(window, async move |editor, cx| {
5406 if let Some(transaction) = on_type_formatting.await? {
5407 if push_to_client_history {
5408 buffer
5409 .update(cx, |buffer, _| {
5410 buffer.push_transaction(transaction, Instant::now());
5411 buffer.finalize_last_transaction();
5412 })
5413 .ok();
5414 }
5415 editor.update(cx, |editor, cx| {
5416 editor.refresh_document_highlights(cx);
5417 })?;
5418 }
5419 Ok(())
5420 }))
5421 }
5422
5423 pub fn show_word_completions(
5424 &mut self,
5425 _: &ShowWordCompletions,
5426 window: &mut Window,
5427 cx: &mut Context<Self>,
5428 ) {
5429 self.open_or_update_completions_menu(
5430 Some(CompletionsMenuSource::Words {
5431 ignore_threshold: true,
5432 }),
5433 None,
5434 window,
5435 cx,
5436 );
5437 }
5438
5439 pub fn show_completions(
5440 &mut self,
5441 options: &ShowCompletions,
5442 window: &mut Window,
5443 cx: &mut Context<Self>,
5444 ) {
5445 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5446 }
5447
5448 fn open_or_update_completions_menu(
5449 &mut self,
5450 requested_source: Option<CompletionsMenuSource>,
5451 trigger: Option<&str>,
5452 window: &mut Window,
5453 cx: &mut Context<Self>,
5454 ) {
5455 if self.pending_rename.is_some() {
5456 return;
5457 }
5458
5459 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5460
5461 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5462 // inserted and selected. To handle that case, the start of the selection is used so that
5463 // the menu starts with all choices.
5464 let position = self
5465 .selections
5466 .newest_anchor()
5467 .start
5468 .bias_right(&multibuffer_snapshot);
5469 if position.diff_base_anchor.is_some() {
5470 return;
5471 }
5472 let buffer_position = multibuffer_snapshot.anchor_before(position);
5473 let Some(buffer) = buffer_position
5474 .buffer_id
5475 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5476 else {
5477 return;
5478 };
5479 let buffer_snapshot = buffer.read(cx).snapshot();
5480
5481 let query: Option<Arc<String>> =
5482 Self::completion_query(&multibuffer_snapshot, buffer_position)
5483 .map(|query| query.into());
5484
5485 drop(multibuffer_snapshot);
5486
5487 // Hide the current completions menu when query is empty. Without this, cached
5488 // completions from before the trigger char may be reused (#32774).
5489 if query.is_none() {
5490 let menu_is_open = matches!(
5491 self.context_menu.borrow().as_ref(),
5492 Some(CodeContextMenu::Completions(_))
5493 );
5494 if menu_is_open {
5495 self.hide_context_menu(window, cx);
5496 }
5497 }
5498
5499 let mut ignore_word_threshold = false;
5500 let provider = match requested_source {
5501 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5502 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5503 ignore_word_threshold = ignore_threshold;
5504 None
5505 }
5506 Some(CompletionsMenuSource::SnippetChoices) => {
5507 log::error!("bug: SnippetChoices requested_source is not handled");
5508 None
5509 }
5510 };
5511
5512 let sort_completions = provider
5513 .as_ref()
5514 .is_some_and(|provider| provider.sort_completions());
5515
5516 let filter_completions = provider
5517 .as_ref()
5518 .is_none_or(|provider| provider.filter_completions());
5519
5520 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5521 if filter_completions {
5522 menu.filter(query.clone(), provider.clone(), window, cx);
5523 }
5524 // When `is_incomplete` is false, no need to re-query completions when the current query
5525 // is a suffix of the initial query.
5526 if !menu.is_incomplete {
5527 // If the new query is a suffix of the old query (typing more characters) and
5528 // the previous result was complete, the existing completions can be filtered.
5529 //
5530 // Note that this is always true for snippet completions.
5531 let query_matches = match (&menu.initial_query, &query) {
5532 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5533 (None, _) => true,
5534 _ => false,
5535 };
5536 if query_matches {
5537 let position_matches = if menu.initial_position == position {
5538 true
5539 } else {
5540 let snapshot = self.buffer.read(cx).read(cx);
5541 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5542 };
5543 if position_matches {
5544 return;
5545 }
5546 }
5547 }
5548 };
5549
5550 let trigger_kind = match trigger {
5551 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5552 CompletionTriggerKind::TRIGGER_CHARACTER
5553 }
5554 _ => CompletionTriggerKind::INVOKED,
5555 };
5556 let completion_context = CompletionContext {
5557 trigger_character: trigger.and_then(|trigger| {
5558 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5559 Some(String::from(trigger))
5560 } else {
5561 None
5562 }
5563 }),
5564 trigger_kind,
5565 };
5566
5567 let Anchor {
5568 excerpt_id: buffer_excerpt_id,
5569 text_anchor: buffer_position,
5570 ..
5571 } = buffer_position;
5572
5573 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5574 buffer_snapshot.surrounding_word(buffer_position, false)
5575 {
5576 let word_to_exclude = buffer_snapshot
5577 .text_for_range(word_range.clone())
5578 .collect::<String>();
5579 (
5580 buffer_snapshot.anchor_before(word_range.start)
5581 ..buffer_snapshot.anchor_after(buffer_position),
5582 Some(word_to_exclude),
5583 )
5584 } else {
5585 (buffer_position..buffer_position, None)
5586 };
5587
5588 let language = buffer_snapshot
5589 .language_at(buffer_position)
5590 .map(|language| language.name());
5591
5592 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5593 .completions
5594 .clone();
5595
5596 let show_completion_documentation = buffer_snapshot
5597 .settings_at(buffer_position, cx)
5598 .show_completion_documentation;
5599
5600 // The document can be large, so stay in reasonable bounds when searching for words,
5601 // otherwise completion pop-up might be slow to appear.
5602 const WORD_LOOKUP_ROWS: u32 = 5_000;
5603 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5604 let min_word_search = buffer_snapshot.clip_point(
5605 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5606 Bias::Left,
5607 );
5608 let max_word_search = buffer_snapshot.clip_point(
5609 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5610 Bias::Right,
5611 );
5612 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5613 ..buffer_snapshot.point_to_offset(max_word_search);
5614
5615 let skip_digits = query
5616 .as_ref()
5617 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5618
5619 let omit_word_completions = !self.word_completions_enabled
5620 || (!ignore_word_threshold
5621 && match &query {
5622 Some(query) => query.chars().count() < completion_settings.words_min_length,
5623 None => completion_settings.words_min_length != 0,
5624 });
5625
5626 let (mut words, provider_responses) = match &provider {
5627 Some(provider) => {
5628 let provider_responses = provider.completions(
5629 buffer_excerpt_id,
5630 &buffer,
5631 buffer_position,
5632 completion_context,
5633 window,
5634 cx,
5635 );
5636
5637 let words = match (omit_word_completions, completion_settings.words) {
5638 (true, _) | (_, WordsCompletionMode::Disabled) => {
5639 Task::ready(BTreeMap::default())
5640 }
5641 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5642 .background_spawn(async move {
5643 buffer_snapshot.words_in_range(WordsQuery {
5644 fuzzy_contents: None,
5645 range: word_search_range,
5646 skip_digits,
5647 })
5648 }),
5649 };
5650
5651 (words, provider_responses)
5652 }
5653 None => {
5654 let words = if omit_word_completions {
5655 Task::ready(BTreeMap::default())
5656 } else {
5657 cx.background_spawn(async move {
5658 buffer_snapshot.words_in_range(WordsQuery {
5659 fuzzy_contents: None,
5660 range: word_search_range,
5661 skip_digits,
5662 })
5663 })
5664 };
5665 (words, Task::ready(Ok(Vec::new())))
5666 }
5667 };
5668
5669 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5670
5671 let id = post_inc(&mut self.next_completion_id);
5672 let task = cx.spawn_in(window, async move |editor, cx| {
5673 let Ok(()) = editor.update(cx, |this, _| {
5674 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5675 }) else {
5676 return;
5677 };
5678
5679 // TODO: Ideally completions from different sources would be selectively re-queried, so
5680 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5681 let mut completions = Vec::new();
5682 let mut is_incomplete = false;
5683 let mut display_options: Option<CompletionDisplayOptions> = None;
5684 if let Some(provider_responses) = provider_responses.await.log_err()
5685 && !provider_responses.is_empty()
5686 {
5687 for response in provider_responses {
5688 completions.extend(response.completions);
5689 is_incomplete = is_incomplete || response.is_incomplete;
5690 match display_options.as_mut() {
5691 None => {
5692 display_options = Some(response.display_options);
5693 }
5694 Some(options) => options.merge(&response.display_options),
5695 }
5696 }
5697 if completion_settings.words == WordsCompletionMode::Fallback {
5698 words = Task::ready(BTreeMap::default());
5699 }
5700 }
5701 let display_options = display_options.unwrap_or_default();
5702
5703 let mut words = words.await;
5704 if let Some(word_to_exclude) = &word_to_exclude {
5705 words.remove(word_to_exclude);
5706 }
5707 for lsp_completion in &completions {
5708 words.remove(&lsp_completion.new_text);
5709 }
5710 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5711 replace_range: word_replace_range.clone(),
5712 new_text: word.clone(),
5713 label: CodeLabel::plain(word, None),
5714 icon_path: None,
5715 documentation: None,
5716 source: CompletionSource::BufferWord {
5717 word_range,
5718 resolved: false,
5719 },
5720 insert_text_mode: Some(InsertTextMode::AS_IS),
5721 confirm: None,
5722 }));
5723
5724 let menu = if completions.is_empty() {
5725 None
5726 } else {
5727 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5728 let languages = editor
5729 .workspace
5730 .as_ref()
5731 .and_then(|(workspace, _)| workspace.upgrade())
5732 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5733 let menu = CompletionsMenu::new(
5734 id,
5735 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5736 sort_completions,
5737 show_completion_documentation,
5738 position,
5739 query.clone(),
5740 is_incomplete,
5741 buffer.clone(),
5742 completions.into(),
5743 display_options,
5744 snippet_sort_order,
5745 languages,
5746 language,
5747 cx,
5748 );
5749
5750 let query = if filter_completions { query } else { None };
5751 let matches_task = if let Some(query) = query {
5752 menu.do_async_filtering(query, cx)
5753 } else {
5754 Task::ready(menu.unfiltered_matches())
5755 };
5756 (menu, matches_task)
5757 }) else {
5758 return;
5759 };
5760
5761 let matches = matches_task.await;
5762
5763 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5764 // Newer menu already set, so exit.
5765 if let Some(CodeContextMenu::Completions(prev_menu)) =
5766 editor.context_menu.borrow().as_ref()
5767 && prev_menu.id > id
5768 {
5769 return;
5770 };
5771
5772 // Only valid to take prev_menu because it the new menu is immediately set
5773 // below, or the menu is hidden.
5774 if let Some(CodeContextMenu::Completions(prev_menu)) =
5775 editor.context_menu.borrow_mut().take()
5776 {
5777 let position_matches =
5778 if prev_menu.initial_position == menu.initial_position {
5779 true
5780 } else {
5781 let snapshot = editor.buffer.read(cx).read(cx);
5782 prev_menu.initial_position.to_offset(&snapshot)
5783 == menu.initial_position.to_offset(&snapshot)
5784 };
5785 if position_matches {
5786 // Preserve markdown cache before `set_filter_results` because it will
5787 // try to populate the documentation cache.
5788 menu.preserve_markdown_cache(prev_menu);
5789 }
5790 };
5791
5792 menu.set_filter_results(matches, provider, window, cx);
5793 }) else {
5794 return;
5795 };
5796
5797 menu.visible().then_some(menu)
5798 };
5799
5800 editor
5801 .update_in(cx, |editor, window, cx| {
5802 if editor.focus_handle.is_focused(window)
5803 && let Some(menu) = menu
5804 {
5805 *editor.context_menu.borrow_mut() =
5806 Some(CodeContextMenu::Completions(menu));
5807
5808 crate::hover_popover::hide_hover(editor, cx);
5809 if editor.show_edit_predictions_in_menu() {
5810 editor.update_visible_edit_prediction(window, cx);
5811 } else {
5812 editor.discard_edit_prediction(false, cx);
5813 }
5814
5815 cx.notify();
5816 return;
5817 }
5818
5819 if editor.completion_tasks.len() <= 1 {
5820 // If there are no more completion tasks and the last menu was empty, we should hide it.
5821 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5822 // If it was already hidden and we don't show edit predictions in the menu,
5823 // we should also show the edit prediction when available.
5824 if was_hidden && editor.show_edit_predictions_in_menu() {
5825 editor.update_visible_edit_prediction(window, cx);
5826 }
5827 }
5828 })
5829 .ok();
5830 });
5831
5832 self.completion_tasks.push((id, task));
5833 }
5834
5835 #[cfg(feature = "test-support")]
5836 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5837 let menu = self.context_menu.borrow();
5838 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5839 let completions = menu.completions.borrow();
5840 Some(completions.to_vec())
5841 } else {
5842 None
5843 }
5844 }
5845
5846 pub fn with_completions_menu_matching_id<R>(
5847 &self,
5848 id: CompletionId,
5849 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5850 ) -> R {
5851 let mut context_menu = self.context_menu.borrow_mut();
5852 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5853 return f(None);
5854 };
5855 if completions_menu.id != id {
5856 return f(None);
5857 }
5858 f(Some(completions_menu))
5859 }
5860
5861 pub fn confirm_completion(
5862 &mut self,
5863 action: &ConfirmCompletion,
5864 window: &mut Window,
5865 cx: &mut Context<Self>,
5866 ) -> Option<Task<Result<()>>> {
5867 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5868 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5869 }
5870
5871 pub fn confirm_completion_insert(
5872 &mut self,
5873 _: &ConfirmCompletionInsert,
5874 window: &mut Window,
5875 cx: &mut Context<Self>,
5876 ) -> Option<Task<Result<()>>> {
5877 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5878 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5879 }
5880
5881 pub fn confirm_completion_replace(
5882 &mut self,
5883 _: &ConfirmCompletionReplace,
5884 window: &mut Window,
5885 cx: &mut Context<Self>,
5886 ) -> Option<Task<Result<()>>> {
5887 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5888 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5889 }
5890
5891 pub fn compose_completion(
5892 &mut self,
5893 action: &ComposeCompletion,
5894 window: &mut Window,
5895 cx: &mut Context<Self>,
5896 ) -> Option<Task<Result<()>>> {
5897 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5898 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5899 }
5900
5901 fn do_completion(
5902 &mut self,
5903 item_ix: Option<usize>,
5904 intent: CompletionIntent,
5905 window: &mut Window,
5906 cx: &mut Context<Editor>,
5907 ) -> Option<Task<Result<()>>> {
5908 use language::ToOffset as _;
5909
5910 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5911 else {
5912 return None;
5913 };
5914
5915 let candidate_id = {
5916 let entries = completions_menu.entries.borrow();
5917 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5918 if self.show_edit_predictions_in_menu() {
5919 self.discard_edit_prediction(true, cx);
5920 }
5921 mat.candidate_id
5922 };
5923
5924 let completion = completions_menu
5925 .completions
5926 .borrow()
5927 .get(candidate_id)?
5928 .clone();
5929 cx.stop_propagation();
5930
5931 let buffer_handle = completions_menu.buffer.clone();
5932
5933 let CompletionEdit {
5934 new_text,
5935 snippet,
5936 replace_range,
5937 } = process_completion_for_edit(
5938 &completion,
5939 intent,
5940 &buffer_handle,
5941 &completions_menu.initial_position.text_anchor,
5942 cx,
5943 );
5944
5945 let buffer = buffer_handle.read(cx);
5946 let snapshot = self.buffer.read(cx).snapshot(cx);
5947 let newest_anchor = self.selections.newest_anchor();
5948 let replace_range_multibuffer = {
5949 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5950 let multibuffer_anchor = snapshot
5951 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5952 .unwrap()
5953 ..snapshot
5954 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5955 .unwrap();
5956 multibuffer_anchor.start.to_offset(&snapshot)
5957 ..multibuffer_anchor.end.to_offset(&snapshot)
5958 };
5959 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5960 return None;
5961 }
5962
5963 let old_text = buffer
5964 .text_for_range(replace_range.clone())
5965 .collect::<String>();
5966 let lookbehind = newest_anchor
5967 .start
5968 .text_anchor
5969 .to_offset(buffer)
5970 .saturating_sub(replace_range.start);
5971 let lookahead = replace_range
5972 .end
5973 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5974 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5975 let suffix = &old_text[lookbehind.min(old_text.len())..];
5976
5977 let selections = self.selections.all::<usize>(cx);
5978 let mut ranges = Vec::new();
5979 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5980
5981 for selection in &selections {
5982 let range = if selection.id == newest_anchor.id {
5983 replace_range_multibuffer.clone()
5984 } else {
5985 let mut range = selection.range();
5986
5987 // if prefix is present, don't duplicate it
5988 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5989 range.start = range.start.saturating_sub(lookbehind);
5990
5991 // if suffix is also present, mimic the newest cursor and replace it
5992 if selection.id != newest_anchor.id
5993 && snapshot.contains_str_at(range.end, suffix)
5994 {
5995 range.end += lookahead;
5996 }
5997 }
5998 range
5999 };
6000
6001 ranges.push(range.clone());
6002
6003 if !self.linked_edit_ranges.is_empty() {
6004 let start_anchor = snapshot.anchor_before(range.start);
6005 let end_anchor = snapshot.anchor_after(range.end);
6006 if let Some(ranges) = self
6007 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6008 {
6009 for (buffer, edits) in ranges {
6010 linked_edits
6011 .entry(buffer.clone())
6012 .or_default()
6013 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6014 }
6015 }
6016 }
6017 }
6018
6019 let common_prefix_len = old_text
6020 .chars()
6021 .zip(new_text.chars())
6022 .take_while(|(a, b)| a == b)
6023 .map(|(a, _)| a.len_utf8())
6024 .sum::<usize>();
6025
6026 cx.emit(EditorEvent::InputHandled {
6027 utf16_range_to_replace: None,
6028 text: new_text[common_prefix_len..].into(),
6029 });
6030
6031 self.transact(window, cx, |editor, window, cx| {
6032 if let Some(mut snippet) = snippet {
6033 snippet.text = new_text.to_string();
6034 editor
6035 .insert_snippet(&ranges, snippet, window, cx)
6036 .log_err();
6037 } else {
6038 editor.buffer.update(cx, |multi_buffer, cx| {
6039 let auto_indent = match completion.insert_text_mode {
6040 Some(InsertTextMode::AS_IS) => None,
6041 _ => editor.autoindent_mode.clone(),
6042 };
6043 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6044 multi_buffer.edit(edits, auto_indent, cx);
6045 });
6046 }
6047 for (buffer, edits) in linked_edits {
6048 buffer.update(cx, |buffer, cx| {
6049 let snapshot = buffer.snapshot();
6050 let edits = edits
6051 .into_iter()
6052 .map(|(range, text)| {
6053 use text::ToPoint as TP;
6054 let end_point = TP::to_point(&range.end, &snapshot);
6055 let start_point = TP::to_point(&range.start, &snapshot);
6056 (start_point..end_point, text)
6057 })
6058 .sorted_by_key(|(range, _)| range.start);
6059 buffer.edit(edits, None, cx);
6060 })
6061 }
6062
6063 editor.refresh_edit_prediction(true, false, window, cx);
6064 });
6065 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6066
6067 let show_new_completions_on_confirm = completion
6068 .confirm
6069 .as_ref()
6070 .is_some_and(|confirm| confirm(intent, window, cx));
6071 if show_new_completions_on_confirm {
6072 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6073 }
6074
6075 let provider = self.completion_provider.as_ref()?;
6076 drop(completion);
6077 let apply_edits = provider.apply_additional_edits_for_completion(
6078 buffer_handle,
6079 completions_menu.completions.clone(),
6080 candidate_id,
6081 true,
6082 cx,
6083 );
6084
6085 let editor_settings = EditorSettings::get_global(cx);
6086 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6087 // After the code completion is finished, users often want to know what signatures are needed.
6088 // so we should automatically call signature_help
6089 self.show_signature_help(&ShowSignatureHelp, window, cx);
6090 }
6091
6092 Some(cx.foreground_executor().spawn(async move {
6093 apply_edits.await?;
6094 Ok(())
6095 }))
6096 }
6097
6098 pub fn toggle_code_actions(
6099 &mut self,
6100 action: &ToggleCodeActions,
6101 window: &mut Window,
6102 cx: &mut Context<Self>,
6103 ) {
6104 let quick_launch = action.quick_launch;
6105 let mut context_menu = self.context_menu.borrow_mut();
6106 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6107 if code_actions.deployed_from == action.deployed_from {
6108 // Toggle if we're selecting the same one
6109 *context_menu = None;
6110 cx.notify();
6111 return;
6112 } else {
6113 // Otherwise, clear it and start a new one
6114 *context_menu = None;
6115 cx.notify();
6116 }
6117 }
6118 drop(context_menu);
6119 let snapshot = self.snapshot(window, cx);
6120 let deployed_from = action.deployed_from.clone();
6121 let action = action.clone();
6122 self.completion_tasks.clear();
6123 self.discard_edit_prediction(false, cx);
6124
6125 let multibuffer_point = match &action.deployed_from {
6126 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6127 DisplayPoint::new(*row, 0).to_point(&snapshot)
6128 }
6129 _ => self.selections.newest::<Point>(cx).head(),
6130 };
6131 let Some((buffer, buffer_row)) = snapshot
6132 .buffer_snapshot
6133 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6134 .and_then(|(buffer_snapshot, range)| {
6135 self.buffer()
6136 .read(cx)
6137 .buffer(buffer_snapshot.remote_id())
6138 .map(|buffer| (buffer, range.start.row))
6139 })
6140 else {
6141 return;
6142 };
6143 let buffer_id = buffer.read(cx).remote_id();
6144 let tasks = self
6145 .tasks
6146 .get(&(buffer_id, buffer_row))
6147 .map(|t| Arc::new(t.to_owned()));
6148
6149 if !self.focus_handle.is_focused(window) {
6150 return;
6151 }
6152 let project = self.project.clone();
6153
6154 let code_actions_task = match deployed_from {
6155 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6156 _ => self.code_actions(buffer_row, window, cx),
6157 };
6158
6159 let runnable_task = match deployed_from {
6160 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6161 _ => {
6162 let mut task_context_task = Task::ready(None);
6163 if let Some(tasks) = &tasks
6164 && let Some(project) = project
6165 {
6166 task_context_task =
6167 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6168 }
6169
6170 cx.spawn_in(window, {
6171 let buffer = buffer.clone();
6172 async move |editor, cx| {
6173 let task_context = task_context_task.await;
6174
6175 let resolved_tasks =
6176 tasks
6177 .zip(task_context.clone())
6178 .map(|(tasks, task_context)| ResolvedTasks {
6179 templates: tasks.resolve(&task_context).collect(),
6180 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6181 multibuffer_point.row,
6182 tasks.column,
6183 )),
6184 });
6185 let debug_scenarios = editor
6186 .update(cx, |editor, cx| {
6187 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6188 })?
6189 .await;
6190 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6191 }
6192 })
6193 }
6194 };
6195
6196 cx.spawn_in(window, async move |editor, cx| {
6197 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6198 let code_actions = code_actions_task.await;
6199 let spawn_straight_away = quick_launch
6200 && resolved_tasks
6201 .as_ref()
6202 .is_some_and(|tasks| tasks.templates.len() == 1)
6203 && code_actions
6204 .as_ref()
6205 .is_none_or(|actions| actions.is_empty())
6206 && debug_scenarios.is_empty();
6207
6208 editor.update_in(cx, |editor, window, cx| {
6209 crate::hover_popover::hide_hover(editor, cx);
6210 let actions = CodeActionContents::new(
6211 resolved_tasks,
6212 code_actions,
6213 debug_scenarios,
6214 task_context.unwrap_or_default(),
6215 );
6216
6217 // Don't show the menu if there are no actions available
6218 if actions.is_empty() {
6219 cx.notify();
6220 return Task::ready(Ok(()));
6221 }
6222
6223 *editor.context_menu.borrow_mut() =
6224 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6225 buffer,
6226 actions,
6227 selected_item: Default::default(),
6228 scroll_handle: UniformListScrollHandle::default(),
6229 deployed_from,
6230 }));
6231 cx.notify();
6232 if spawn_straight_away
6233 && let Some(task) = editor.confirm_code_action(
6234 &ConfirmCodeAction { item_ix: Some(0) },
6235 window,
6236 cx,
6237 )
6238 {
6239 return task;
6240 }
6241
6242 Task::ready(Ok(()))
6243 })
6244 })
6245 .detach_and_log_err(cx);
6246 }
6247
6248 fn debug_scenarios(
6249 &mut self,
6250 resolved_tasks: &Option<ResolvedTasks>,
6251 buffer: &Entity<Buffer>,
6252 cx: &mut App,
6253 ) -> Task<Vec<task::DebugScenario>> {
6254 maybe!({
6255 let project = self.project()?;
6256 let dap_store = project.read(cx).dap_store();
6257 let mut scenarios = vec![];
6258 let resolved_tasks = resolved_tasks.as_ref()?;
6259 let buffer = buffer.read(cx);
6260 let language = buffer.language()?;
6261 let file = buffer.file();
6262 let debug_adapter = language_settings(language.name().into(), file, cx)
6263 .debuggers
6264 .first()
6265 .map(SharedString::from)
6266 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6267
6268 dap_store.update(cx, |dap_store, cx| {
6269 for (_, task) in &resolved_tasks.templates {
6270 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6271 task.original_task().clone(),
6272 debug_adapter.clone().into(),
6273 task.display_label().to_owned().into(),
6274 cx,
6275 );
6276 scenarios.push(maybe_scenario);
6277 }
6278 });
6279 Some(cx.background_spawn(async move {
6280 futures::future::join_all(scenarios)
6281 .await
6282 .into_iter()
6283 .flatten()
6284 .collect::<Vec<_>>()
6285 }))
6286 })
6287 .unwrap_or_else(|| Task::ready(vec![]))
6288 }
6289
6290 fn code_actions(
6291 &mut self,
6292 buffer_row: u32,
6293 window: &mut Window,
6294 cx: &mut Context<Self>,
6295 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6296 let mut task = self.code_actions_task.take();
6297 cx.spawn_in(window, async move |editor, cx| {
6298 while let Some(prev_task) = task {
6299 prev_task.await.log_err();
6300 task = editor
6301 .update(cx, |this, _| this.code_actions_task.take())
6302 .ok()?;
6303 }
6304
6305 editor
6306 .update(cx, |editor, cx| {
6307 editor
6308 .available_code_actions
6309 .clone()
6310 .and_then(|(location, code_actions)| {
6311 let snapshot = location.buffer.read(cx).snapshot();
6312 let point_range = location.range.to_point(&snapshot);
6313 let point_range = point_range.start.row..=point_range.end.row;
6314 if point_range.contains(&buffer_row) {
6315 Some(code_actions)
6316 } else {
6317 None
6318 }
6319 })
6320 })
6321 .ok()
6322 .flatten()
6323 })
6324 }
6325
6326 pub fn confirm_code_action(
6327 &mut self,
6328 action: &ConfirmCodeAction,
6329 window: &mut Window,
6330 cx: &mut Context<Self>,
6331 ) -> Option<Task<Result<()>>> {
6332 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6333
6334 let actions_menu =
6335 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6336 menu
6337 } else {
6338 return None;
6339 };
6340
6341 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6342 let action = actions_menu.actions.get(action_ix)?;
6343 let title = action.label();
6344 let buffer = actions_menu.buffer;
6345 let workspace = self.workspace()?;
6346
6347 match action {
6348 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6349 workspace.update(cx, |workspace, cx| {
6350 workspace.schedule_resolved_task(
6351 task_source_kind,
6352 resolved_task,
6353 false,
6354 window,
6355 cx,
6356 );
6357
6358 Some(Task::ready(Ok(())))
6359 })
6360 }
6361 CodeActionsItem::CodeAction {
6362 excerpt_id,
6363 action,
6364 provider,
6365 } => {
6366 let apply_code_action =
6367 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6368 let workspace = workspace.downgrade();
6369 Some(cx.spawn_in(window, async move |editor, cx| {
6370 let project_transaction = apply_code_action.await?;
6371 Self::open_project_transaction(
6372 &editor,
6373 workspace,
6374 project_transaction,
6375 title,
6376 cx,
6377 )
6378 .await
6379 }))
6380 }
6381 CodeActionsItem::DebugScenario(scenario) => {
6382 let context = actions_menu.actions.context;
6383
6384 workspace.update(cx, |workspace, cx| {
6385 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6386 workspace.start_debug_session(
6387 scenario,
6388 context,
6389 Some(buffer),
6390 None,
6391 window,
6392 cx,
6393 );
6394 });
6395 Some(Task::ready(Ok(())))
6396 }
6397 }
6398 }
6399
6400 pub async fn open_project_transaction(
6401 editor: &WeakEntity<Editor>,
6402 workspace: WeakEntity<Workspace>,
6403 transaction: ProjectTransaction,
6404 title: String,
6405 cx: &mut AsyncWindowContext,
6406 ) -> Result<()> {
6407 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6408 cx.update(|_, cx| {
6409 entries.sort_unstable_by_key(|(buffer, _)| {
6410 buffer.read(cx).file().map(|f| f.path().clone())
6411 });
6412 })?;
6413
6414 // If the project transaction's edits are all contained within this editor, then
6415 // avoid opening a new editor to display them.
6416
6417 if let Some((buffer, transaction)) = entries.first() {
6418 if entries.len() == 1 {
6419 let excerpt = editor.update(cx, |editor, cx| {
6420 editor
6421 .buffer()
6422 .read(cx)
6423 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6424 })?;
6425 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6426 && excerpted_buffer == *buffer
6427 {
6428 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6429 let excerpt_range = excerpt_range.to_offset(buffer);
6430 buffer
6431 .edited_ranges_for_transaction::<usize>(transaction)
6432 .all(|range| {
6433 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6434 })
6435 })?;
6436
6437 if all_edits_within_excerpt {
6438 return Ok(());
6439 }
6440 }
6441 }
6442 } else {
6443 return Ok(());
6444 }
6445
6446 let mut ranges_to_highlight = Vec::new();
6447 let excerpt_buffer = cx.new(|cx| {
6448 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6449 for (buffer_handle, transaction) in &entries {
6450 let edited_ranges = buffer_handle
6451 .read(cx)
6452 .edited_ranges_for_transaction::<Point>(transaction)
6453 .collect::<Vec<_>>();
6454 let (ranges, _) = multibuffer.set_excerpts_for_path(
6455 PathKey::for_buffer(buffer_handle, cx),
6456 buffer_handle.clone(),
6457 edited_ranges,
6458 multibuffer_context_lines(cx),
6459 cx,
6460 );
6461
6462 ranges_to_highlight.extend(ranges);
6463 }
6464 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6465 multibuffer
6466 })?;
6467
6468 workspace.update_in(cx, |workspace, window, cx| {
6469 let project = workspace.project().clone();
6470 let editor =
6471 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6472 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6473 editor.update(cx, |editor, cx| {
6474 editor.highlight_background::<Self>(
6475 &ranges_to_highlight,
6476 |theme| theme.colors().editor_highlighted_line_background,
6477 cx,
6478 );
6479 });
6480 })?;
6481
6482 Ok(())
6483 }
6484
6485 pub fn clear_code_action_providers(&mut self) {
6486 self.code_action_providers.clear();
6487 self.available_code_actions.take();
6488 }
6489
6490 pub fn add_code_action_provider(
6491 &mut self,
6492 provider: Rc<dyn CodeActionProvider>,
6493 window: &mut Window,
6494 cx: &mut Context<Self>,
6495 ) {
6496 if self
6497 .code_action_providers
6498 .iter()
6499 .any(|existing_provider| existing_provider.id() == provider.id())
6500 {
6501 return;
6502 }
6503
6504 self.code_action_providers.push(provider);
6505 self.refresh_code_actions(window, cx);
6506 }
6507
6508 pub fn remove_code_action_provider(
6509 &mut self,
6510 id: Arc<str>,
6511 window: &mut Window,
6512 cx: &mut Context<Self>,
6513 ) {
6514 self.code_action_providers
6515 .retain(|provider| provider.id() != id);
6516 self.refresh_code_actions(window, cx);
6517 }
6518
6519 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6520 !self.code_action_providers.is_empty()
6521 && EditorSettings::get_global(cx).toolbar.code_actions
6522 }
6523
6524 pub fn has_available_code_actions(&self) -> bool {
6525 self.available_code_actions
6526 .as_ref()
6527 .is_some_and(|(_, actions)| !actions.is_empty())
6528 }
6529
6530 fn render_inline_code_actions(
6531 &self,
6532 icon_size: ui::IconSize,
6533 display_row: DisplayRow,
6534 is_active: bool,
6535 cx: &mut Context<Self>,
6536 ) -> AnyElement {
6537 let show_tooltip = !self.context_menu_visible();
6538 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6539 .icon_size(icon_size)
6540 .shape(ui::IconButtonShape::Square)
6541 .icon_color(ui::Color::Hidden)
6542 .toggle_state(is_active)
6543 .when(show_tooltip, |this| {
6544 this.tooltip({
6545 let focus_handle = self.focus_handle.clone();
6546 move |window, cx| {
6547 Tooltip::for_action_in(
6548 "Toggle Code Actions",
6549 &ToggleCodeActions {
6550 deployed_from: None,
6551 quick_launch: false,
6552 },
6553 &focus_handle,
6554 window,
6555 cx,
6556 )
6557 }
6558 })
6559 })
6560 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6561 window.focus(&editor.focus_handle(cx));
6562 editor.toggle_code_actions(
6563 &crate::actions::ToggleCodeActions {
6564 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6565 display_row,
6566 )),
6567 quick_launch: false,
6568 },
6569 window,
6570 cx,
6571 );
6572 }))
6573 .into_any_element()
6574 }
6575
6576 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6577 &self.context_menu
6578 }
6579
6580 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6581 let newest_selection = self.selections.newest_anchor().clone();
6582 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6583 let buffer = self.buffer.read(cx);
6584 if newest_selection.head().diff_base_anchor.is_some() {
6585 return None;
6586 }
6587 let (start_buffer, start) =
6588 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6589 let (end_buffer, end) =
6590 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6591 if start_buffer != end_buffer {
6592 return None;
6593 }
6594
6595 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6596 cx.background_executor()
6597 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6598 .await;
6599
6600 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6601 let providers = this.code_action_providers.clone();
6602 let tasks = this
6603 .code_action_providers
6604 .iter()
6605 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6606 .collect::<Vec<_>>();
6607 (providers, tasks)
6608 })?;
6609
6610 let mut actions = Vec::new();
6611 for (provider, provider_actions) in
6612 providers.into_iter().zip(future::join_all(tasks).await)
6613 {
6614 if let Some(provider_actions) = provider_actions.log_err() {
6615 actions.extend(provider_actions.into_iter().map(|action| {
6616 AvailableCodeAction {
6617 excerpt_id: newest_selection.start.excerpt_id,
6618 action,
6619 provider: provider.clone(),
6620 }
6621 }));
6622 }
6623 }
6624
6625 this.update(cx, |this, cx| {
6626 this.available_code_actions = if actions.is_empty() {
6627 None
6628 } else {
6629 Some((
6630 Location {
6631 buffer: start_buffer,
6632 range: start..end,
6633 },
6634 actions.into(),
6635 ))
6636 };
6637 cx.notify();
6638 })
6639 }));
6640 None
6641 }
6642
6643 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6644 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6645 self.show_git_blame_inline = false;
6646
6647 self.show_git_blame_inline_delay_task =
6648 Some(cx.spawn_in(window, async move |this, cx| {
6649 cx.background_executor().timer(delay).await;
6650
6651 this.update(cx, |this, cx| {
6652 this.show_git_blame_inline = true;
6653 cx.notify();
6654 })
6655 .log_err();
6656 }));
6657 }
6658 }
6659
6660 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6661 let snapshot = self.snapshot(window, cx);
6662 let cursor = self.selections.newest::<Point>(cx).head();
6663 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6664 else {
6665 return;
6666 };
6667
6668 let Some(blame) = self.blame.as_ref() else {
6669 return;
6670 };
6671
6672 let row_info = RowInfo {
6673 buffer_id: Some(buffer.remote_id()),
6674 buffer_row: Some(point.row),
6675 ..Default::default()
6676 };
6677 let Some((buffer, blame_entry)) = blame
6678 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6679 .flatten()
6680 else {
6681 return;
6682 };
6683
6684 let anchor = self.selections.newest_anchor().head();
6685 let position = self.to_pixel_point(anchor, &snapshot, window);
6686 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6687 self.show_blame_popover(
6688 buffer,
6689 &blame_entry,
6690 position + last_bounds.origin,
6691 true,
6692 cx,
6693 );
6694 };
6695 }
6696
6697 fn show_blame_popover(
6698 &mut self,
6699 buffer: BufferId,
6700 blame_entry: &BlameEntry,
6701 position: gpui::Point<Pixels>,
6702 ignore_timeout: bool,
6703 cx: &mut Context<Self>,
6704 ) {
6705 if let Some(state) = &mut self.inline_blame_popover {
6706 state.hide_task.take();
6707 } else {
6708 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6709 let blame_entry = blame_entry.clone();
6710 let show_task = cx.spawn(async move |editor, cx| {
6711 if !ignore_timeout {
6712 cx.background_executor()
6713 .timer(std::time::Duration::from_millis(blame_popover_delay))
6714 .await;
6715 }
6716 editor
6717 .update(cx, |editor, cx| {
6718 editor.inline_blame_popover_show_task.take();
6719 let Some(blame) = editor.blame.as_ref() else {
6720 return;
6721 };
6722 let blame = blame.read(cx);
6723 let details = blame.details_for_entry(buffer, &blame_entry);
6724 let markdown = cx.new(|cx| {
6725 Markdown::new(
6726 details
6727 .as_ref()
6728 .map(|message| message.message.clone())
6729 .unwrap_or_default(),
6730 None,
6731 None,
6732 cx,
6733 )
6734 });
6735 editor.inline_blame_popover = Some(InlineBlamePopover {
6736 position,
6737 hide_task: None,
6738 popover_bounds: None,
6739 popover_state: InlineBlamePopoverState {
6740 scroll_handle: ScrollHandle::new(),
6741 commit_message: details,
6742 markdown,
6743 },
6744 keyboard_grace: ignore_timeout,
6745 });
6746 cx.notify();
6747 })
6748 .ok();
6749 });
6750 self.inline_blame_popover_show_task = Some(show_task);
6751 }
6752 }
6753
6754 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6755 self.inline_blame_popover_show_task.take();
6756 if let Some(state) = &mut self.inline_blame_popover {
6757 let hide_task = cx.spawn(async move |editor, cx| {
6758 cx.background_executor()
6759 .timer(std::time::Duration::from_millis(100))
6760 .await;
6761 editor
6762 .update(cx, |editor, cx| {
6763 editor.inline_blame_popover.take();
6764 cx.notify();
6765 })
6766 .ok();
6767 });
6768 state.hide_task = Some(hide_task);
6769 }
6770 }
6771
6772 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6773 if self.pending_rename.is_some() {
6774 return None;
6775 }
6776
6777 let provider = self.semantics_provider.clone()?;
6778 let buffer = self.buffer.read(cx);
6779 let newest_selection = self.selections.newest_anchor().clone();
6780 let cursor_position = newest_selection.head();
6781 let (cursor_buffer, cursor_buffer_position) =
6782 buffer.text_anchor_for_position(cursor_position, cx)?;
6783 let (tail_buffer, tail_buffer_position) =
6784 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6785 if cursor_buffer != tail_buffer {
6786 return None;
6787 }
6788
6789 let snapshot = cursor_buffer.read(cx).snapshot();
6790 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6791 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6792 if start_word_range != end_word_range {
6793 self.document_highlights_task.take();
6794 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6795 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6796 return None;
6797 }
6798
6799 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6800 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6801 cx.background_executor()
6802 .timer(Duration::from_millis(debounce))
6803 .await;
6804
6805 let highlights = if let Some(highlights) = cx
6806 .update(|cx| {
6807 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6808 })
6809 .ok()
6810 .flatten()
6811 {
6812 highlights.await.log_err()
6813 } else {
6814 None
6815 };
6816
6817 if let Some(highlights) = highlights {
6818 this.update(cx, |this, cx| {
6819 if this.pending_rename.is_some() {
6820 return;
6821 }
6822
6823 let buffer = this.buffer.read(cx);
6824 if buffer
6825 .text_anchor_for_position(cursor_position, cx)
6826 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6827 {
6828 return;
6829 }
6830
6831 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6832 let mut write_ranges = Vec::new();
6833 let mut read_ranges = Vec::new();
6834 for highlight in highlights {
6835 let buffer_id = cursor_buffer.read(cx).remote_id();
6836 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6837 {
6838 let start = highlight
6839 .range
6840 .start
6841 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6842 let end = highlight
6843 .range
6844 .end
6845 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6846 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6847 continue;
6848 }
6849
6850 let range = Anchor {
6851 buffer_id: Some(buffer_id),
6852 excerpt_id,
6853 text_anchor: start,
6854 diff_base_anchor: None,
6855 }..Anchor {
6856 buffer_id: Some(buffer_id),
6857 excerpt_id,
6858 text_anchor: end,
6859 diff_base_anchor: None,
6860 };
6861 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6862 write_ranges.push(range);
6863 } else {
6864 read_ranges.push(range);
6865 }
6866 }
6867 }
6868
6869 this.highlight_background::<DocumentHighlightRead>(
6870 &read_ranges,
6871 |theme| theme.colors().editor_document_highlight_read_background,
6872 cx,
6873 );
6874 this.highlight_background::<DocumentHighlightWrite>(
6875 &write_ranges,
6876 |theme| theme.colors().editor_document_highlight_write_background,
6877 cx,
6878 );
6879 cx.notify();
6880 })
6881 .log_err();
6882 }
6883 }));
6884 None
6885 }
6886
6887 fn prepare_highlight_query_from_selection(
6888 &mut self,
6889 cx: &mut Context<Editor>,
6890 ) -> Option<(String, Range<Anchor>)> {
6891 if matches!(self.mode, EditorMode::SingleLine) {
6892 return None;
6893 }
6894 if !EditorSettings::get_global(cx).selection_highlight {
6895 return None;
6896 }
6897 if self.selections.count() != 1 || self.selections.line_mode {
6898 return None;
6899 }
6900 let selection = self.selections.newest::<Point>(cx);
6901 if selection.is_empty() || selection.start.row != selection.end.row {
6902 return None;
6903 }
6904 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6905 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6906 let query = multi_buffer_snapshot
6907 .text_for_range(selection_anchor_range.clone())
6908 .collect::<String>();
6909 if query.trim().is_empty() {
6910 return None;
6911 }
6912 Some((query, selection_anchor_range))
6913 }
6914
6915 fn update_selection_occurrence_highlights(
6916 &mut self,
6917 query_text: String,
6918 query_range: Range<Anchor>,
6919 multi_buffer_range_to_query: Range<Point>,
6920 use_debounce: bool,
6921 window: &mut Window,
6922 cx: &mut Context<Editor>,
6923 ) -> Task<()> {
6924 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6925 cx.spawn_in(window, async move |editor, cx| {
6926 if use_debounce {
6927 cx.background_executor()
6928 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6929 .await;
6930 }
6931 let match_task = cx.background_spawn(async move {
6932 let buffer_ranges = multi_buffer_snapshot
6933 .range_to_buffer_ranges(multi_buffer_range_to_query)
6934 .into_iter()
6935 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6936 let mut match_ranges = Vec::new();
6937 let Ok(regex) = project::search::SearchQuery::text(
6938 query_text.clone(),
6939 false,
6940 false,
6941 false,
6942 Default::default(),
6943 Default::default(),
6944 false,
6945 None,
6946 ) else {
6947 return Vec::default();
6948 };
6949 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6950 match_ranges.extend(
6951 regex
6952 .search(buffer_snapshot, Some(search_range.clone()))
6953 .await
6954 .into_iter()
6955 .filter_map(|match_range| {
6956 let match_start = buffer_snapshot
6957 .anchor_after(search_range.start + match_range.start);
6958 let match_end = buffer_snapshot
6959 .anchor_before(search_range.start + match_range.end);
6960 let match_anchor_range = Anchor::range_in_buffer(
6961 excerpt_id,
6962 buffer_snapshot.remote_id(),
6963 match_start..match_end,
6964 );
6965 (match_anchor_range != query_range).then_some(match_anchor_range)
6966 }),
6967 );
6968 }
6969 match_ranges
6970 });
6971 let match_ranges = match_task.await;
6972 editor
6973 .update_in(cx, |editor, _, cx| {
6974 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6975 if !match_ranges.is_empty() {
6976 editor.highlight_background::<SelectedTextHighlight>(
6977 &match_ranges,
6978 |theme| theme.colors().editor_document_highlight_bracket_background,
6979 cx,
6980 )
6981 }
6982 })
6983 .log_err();
6984 })
6985 }
6986
6987 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6988 struct NewlineFold;
6989 let type_id = std::any::TypeId::of::<NewlineFold>();
6990 if !self.mode.is_single_line() {
6991 return;
6992 }
6993 let snapshot = self.snapshot(window, cx);
6994 if snapshot.buffer_snapshot.max_point().row == 0 {
6995 return;
6996 }
6997 let task = cx.background_spawn(async move {
6998 let new_newlines = snapshot
6999 .buffer_chars_at(0)
7000 .filter_map(|(c, i)| {
7001 if c == '\n' {
7002 Some(
7003 snapshot.buffer_snapshot.anchor_after(i)
7004 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7005 )
7006 } else {
7007 None
7008 }
7009 })
7010 .collect::<Vec<_>>();
7011 let existing_newlines = snapshot
7012 .folds_in_range(0..snapshot.buffer_snapshot.len())
7013 .filter_map(|fold| {
7014 if fold.placeholder.type_tag == Some(type_id) {
7015 Some(fold.range.start..fold.range.end)
7016 } else {
7017 None
7018 }
7019 })
7020 .collect::<Vec<_>>();
7021
7022 (new_newlines, existing_newlines)
7023 });
7024 self.folding_newlines = cx.spawn(async move |this, cx| {
7025 let (new_newlines, existing_newlines) = task.await;
7026 if new_newlines == existing_newlines {
7027 return;
7028 }
7029 let placeholder = FoldPlaceholder {
7030 render: Arc::new(move |_, _, cx| {
7031 div()
7032 .bg(cx.theme().status().hint_background)
7033 .border_b_1()
7034 .size_full()
7035 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7036 .border_color(cx.theme().status().hint)
7037 .child("\\n")
7038 .into_any()
7039 }),
7040 constrain_width: false,
7041 merge_adjacent: false,
7042 type_tag: Some(type_id),
7043 };
7044 let creases = new_newlines
7045 .into_iter()
7046 .map(|range| Crease::simple(range, placeholder.clone()))
7047 .collect();
7048 this.update(cx, |this, cx| {
7049 this.display_map.update(cx, |display_map, cx| {
7050 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7051 display_map.fold(creases, cx);
7052 });
7053 })
7054 .ok();
7055 });
7056 }
7057
7058 fn refresh_selected_text_highlights(
7059 &mut self,
7060 on_buffer_edit: bool,
7061 window: &mut Window,
7062 cx: &mut Context<Editor>,
7063 ) {
7064 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7065 else {
7066 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7067 self.quick_selection_highlight_task.take();
7068 self.debounced_selection_highlight_task.take();
7069 return;
7070 };
7071 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7072 if on_buffer_edit
7073 || self
7074 .quick_selection_highlight_task
7075 .as_ref()
7076 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7077 {
7078 let multi_buffer_visible_start = self
7079 .scroll_manager
7080 .anchor()
7081 .anchor
7082 .to_point(&multi_buffer_snapshot);
7083 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7084 multi_buffer_visible_start
7085 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7086 Bias::Left,
7087 );
7088 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7089 self.quick_selection_highlight_task = Some((
7090 query_range.clone(),
7091 self.update_selection_occurrence_highlights(
7092 query_text.clone(),
7093 query_range.clone(),
7094 multi_buffer_visible_range,
7095 false,
7096 window,
7097 cx,
7098 ),
7099 ));
7100 }
7101 if on_buffer_edit
7102 || self
7103 .debounced_selection_highlight_task
7104 .as_ref()
7105 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7106 {
7107 let multi_buffer_start = multi_buffer_snapshot
7108 .anchor_before(0)
7109 .to_point(&multi_buffer_snapshot);
7110 let multi_buffer_end = multi_buffer_snapshot
7111 .anchor_after(multi_buffer_snapshot.len())
7112 .to_point(&multi_buffer_snapshot);
7113 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7114 self.debounced_selection_highlight_task = Some((
7115 query_range.clone(),
7116 self.update_selection_occurrence_highlights(
7117 query_text,
7118 query_range,
7119 multi_buffer_full_range,
7120 true,
7121 window,
7122 cx,
7123 ),
7124 ));
7125 }
7126 }
7127
7128 pub fn refresh_edit_prediction(
7129 &mut self,
7130 debounce: bool,
7131 user_requested: bool,
7132 window: &mut Window,
7133 cx: &mut Context<Self>,
7134 ) -> Option<()> {
7135 if DisableAiSettings::get_global(cx).disable_ai {
7136 return None;
7137 }
7138
7139 let provider = self.edit_prediction_provider()?;
7140 let cursor = self.selections.newest_anchor().head();
7141 let (buffer, cursor_buffer_position) =
7142 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7143
7144 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7145 self.discard_edit_prediction(false, cx);
7146 return None;
7147 }
7148
7149 if !user_requested
7150 && (!self.should_show_edit_predictions()
7151 || !self.is_focused(window)
7152 || buffer.read(cx).is_empty())
7153 {
7154 self.discard_edit_prediction(false, cx);
7155 return None;
7156 }
7157
7158 self.update_visible_edit_prediction(window, cx);
7159 provider.refresh(
7160 self.project.clone(),
7161 buffer,
7162 cursor_buffer_position,
7163 debounce,
7164 cx,
7165 );
7166 Some(())
7167 }
7168
7169 fn show_edit_predictions_in_menu(&self) -> bool {
7170 match self.edit_prediction_settings {
7171 EditPredictionSettings::Disabled => false,
7172 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7173 }
7174 }
7175
7176 pub fn edit_predictions_enabled(&self) -> bool {
7177 match self.edit_prediction_settings {
7178 EditPredictionSettings::Disabled => false,
7179 EditPredictionSettings::Enabled { .. } => true,
7180 }
7181 }
7182
7183 fn edit_prediction_requires_modifier(&self) -> bool {
7184 match self.edit_prediction_settings {
7185 EditPredictionSettings::Disabled => false,
7186 EditPredictionSettings::Enabled {
7187 preview_requires_modifier,
7188 ..
7189 } => preview_requires_modifier,
7190 }
7191 }
7192
7193 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7194 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7195 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7196 self.discard_edit_prediction(false, cx);
7197 } else {
7198 let selection = self.selections.newest_anchor();
7199 let cursor = selection.head();
7200
7201 if let Some((buffer, cursor_buffer_position)) =
7202 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7203 {
7204 self.edit_prediction_settings =
7205 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7206 }
7207 }
7208 }
7209
7210 fn edit_prediction_settings_at_position(
7211 &self,
7212 buffer: &Entity<Buffer>,
7213 buffer_position: language::Anchor,
7214 cx: &App,
7215 ) -> EditPredictionSettings {
7216 if !self.mode.is_full()
7217 || !self.show_edit_predictions_override.unwrap_or(true)
7218 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7219 {
7220 return EditPredictionSettings::Disabled;
7221 }
7222
7223 let buffer = buffer.read(cx);
7224
7225 let file = buffer.file();
7226
7227 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7228 return EditPredictionSettings::Disabled;
7229 };
7230
7231 let by_provider = matches!(
7232 self.menu_edit_predictions_policy,
7233 MenuEditPredictionsPolicy::ByProvider
7234 );
7235
7236 let show_in_menu = by_provider
7237 && self
7238 .edit_prediction_provider
7239 .as_ref()
7240 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7241
7242 let preview_requires_modifier =
7243 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7244
7245 EditPredictionSettings::Enabled {
7246 show_in_menu,
7247 preview_requires_modifier,
7248 }
7249 }
7250
7251 fn should_show_edit_predictions(&self) -> bool {
7252 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7253 }
7254
7255 pub fn edit_prediction_preview_is_active(&self) -> bool {
7256 matches!(
7257 self.edit_prediction_preview,
7258 EditPredictionPreview::Active { .. }
7259 )
7260 }
7261
7262 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7263 let cursor = self.selections.newest_anchor().head();
7264 if let Some((buffer, cursor_position)) =
7265 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7266 {
7267 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7268 } else {
7269 false
7270 }
7271 }
7272
7273 pub fn supports_minimap(&self, cx: &App) -> bool {
7274 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7275 }
7276
7277 fn edit_predictions_enabled_in_buffer(
7278 &self,
7279 buffer: &Entity<Buffer>,
7280 buffer_position: language::Anchor,
7281 cx: &App,
7282 ) -> bool {
7283 maybe!({
7284 if self.read_only(cx) {
7285 return Some(false);
7286 }
7287 let provider = self.edit_prediction_provider()?;
7288 if !provider.is_enabled(buffer, buffer_position, cx) {
7289 return Some(false);
7290 }
7291 let buffer = buffer.read(cx);
7292 let Some(file) = buffer.file() else {
7293 return Some(true);
7294 };
7295 let settings = all_language_settings(Some(file), cx);
7296 Some(settings.edit_predictions_enabled_for_file(file, cx))
7297 })
7298 .unwrap_or(false)
7299 }
7300
7301 fn cycle_edit_prediction(
7302 &mut self,
7303 direction: Direction,
7304 window: &mut Window,
7305 cx: &mut Context<Self>,
7306 ) -> Option<()> {
7307 let provider = self.edit_prediction_provider()?;
7308 let cursor = self.selections.newest_anchor().head();
7309 let (buffer, cursor_buffer_position) =
7310 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7311 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7312 return None;
7313 }
7314
7315 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7316 self.update_visible_edit_prediction(window, cx);
7317
7318 Some(())
7319 }
7320
7321 pub fn show_edit_prediction(
7322 &mut self,
7323 _: &ShowEditPrediction,
7324 window: &mut Window,
7325 cx: &mut Context<Self>,
7326 ) {
7327 if !self.has_active_edit_prediction() {
7328 self.refresh_edit_prediction(false, true, window, cx);
7329 return;
7330 }
7331
7332 self.update_visible_edit_prediction(window, cx);
7333 }
7334
7335 pub fn display_cursor_names(
7336 &mut self,
7337 _: &DisplayCursorNames,
7338 window: &mut Window,
7339 cx: &mut Context<Self>,
7340 ) {
7341 self.show_cursor_names(window, cx);
7342 }
7343
7344 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7345 self.show_cursor_names = true;
7346 cx.notify();
7347 cx.spawn_in(window, async move |this, cx| {
7348 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7349 this.update(cx, |this, cx| {
7350 this.show_cursor_names = false;
7351 cx.notify()
7352 })
7353 .ok()
7354 })
7355 .detach();
7356 }
7357
7358 pub fn next_edit_prediction(
7359 &mut self,
7360 _: &NextEditPrediction,
7361 window: &mut Window,
7362 cx: &mut Context<Self>,
7363 ) {
7364 if self.has_active_edit_prediction() {
7365 self.cycle_edit_prediction(Direction::Next, window, cx);
7366 } else {
7367 let is_copilot_disabled = self
7368 .refresh_edit_prediction(false, true, window, cx)
7369 .is_none();
7370 if is_copilot_disabled {
7371 cx.propagate();
7372 }
7373 }
7374 }
7375
7376 pub fn previous_edit_prediction(
7377 &mut self,
7378 _: &PreviousEditPrediction,
7379 window: &mut Window,
7380 cx: &mut Context<Self>,
7381 ) {
7382 if self.has_active_edit_prediction() {
7383 self.cycle_edit_prediction(Direction::Prev, window, cx);
7384 } else {
7385 let is_copilot_disabled = self
7386 .refresh_edit_prediction(false, true, window, cx)
7387 .is_none();
7388 if is_copilot_disabled {
7389 cx.propagate();
7390 }
7391 }
7392 }
7393
7394 pub fn accept_edit_prediction(
7395 &mut self,
7396 _: &AcceptEditPrediction,
7397 window: &mut Window,
7398 cx: &mut Context<Self>,
7399 ) {
7400 if self.show_edit_predictions_in_menu() {
7401 self.hide_context_menu(window, cx);
7402 }
7403
7404 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7405 return;
7406 };
7407
7408 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7409
7410 match &active_edit_prediction.completion {
7411 EditPrediction::Move { target, .. } => {
7412 let target = *target;
7413
7414 if let Some(position_map) = &self.last_position_map {
7415 if position_map
7416 .visible_row_range
7417 .contains(&target.to_display_point(&position_map.snapshot).row())
7418 || !self.edit_prediction_requires_modifier()
7419 {
7420 self.unfold_ranges(&[target..target], true, false, cx);
7421 // Note that this is also done in vim's handler of the Tab action.
7422 self.change_selections(
7423 SelectionEffects::scroll(Autoscroll::newest()),
7424 window,
7425 cx,
7426 |selections| {
7427 selections.select_anchor_ranges([target..target]);
7428 },
7429 );
7430 self.clear_row_highlights::<EditPredictionPreview>();
7431
7432 self.edit_prediction_preview
7433 .set_previous_scroll_position(None);
7434 } else {
7435 self.edit_prediction_preview
7436 .set_previous_scroll_position(Some(
7437 position_map.snapshot.scroll_anchor,
7438 ));
7439
7440 self.highlight_rows::<EditPredictionPreview>(
7441 target..target,
7442 cx.theme().colors().editor_highlighted_line_background,
7443 RowHighlightOptions {
7444 autoscroll: true,
7445 ..Default::default()
7446 },
7447 cx,
7448 );
7449 self.request_autoscroll(Autoscroll::fit(), cx);
7450 }
7451 }
7452 }
7453 EditPrediction::Edit { edits, .. } => {
7454 if let Some(provider) = self.edit_prediction_provider() {
7455 provider.accept(cx);
7456 }
7457
7458 // Store the transaction ID and selections before applying the edit
7459 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7460
7461 let snapshot = self.buffer.read(cx).snapshot(cx);
7462 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7463
7464 self.buffer.update(cx, |buffer, cx| {
7465 buffer.edit(edits.iter().cloned(), None, cx)
7466 });
7467
7468 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7469 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7470 });
7471
7472 let selections = self.selections.disjoint_anchors_arc();
7473 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7474 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7475 if has_new_transaction {
7476 self.selection_history
7477 .insert_transaction(transaction_id_now, selections);
7478 }
7479 }
7480
7481 self.update_visible_edit_prediction(window, cx);
7482 if self.active_edit_prediction.is_none() {
7483 self.refresh_edit_prediction(true, true, window, cx);
7484 }
7485
7486 cx.notify();
7487 }
7488 }
7489
7490 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7491 }
7492
7493 pub fn accept_partial_edit_prediction(
7494 &mut self,
7495 _: &AcceptPartialEditPrediction,
7496 window: &mut Window,
7497 cx: &mut Context<Self>,
7498 ) {
7499 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7500 return;
7501 };
7502 if self.selections.count() != 1 {
7503 return;
7504 }
7505
7506 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7507
7508 match &active_edit_prediction.completion {
7509 EditPrediction::Move { target, .. } => {
7510 let target = *target;
7511 self.change_selections(
7512 SelectionEffects::scroll(Autoscroll::newest()),
7513 window,
7514 cx,
7515 |selections| {
7516 selections.select_anchor_ranges([target..target]);
7517 },
7518 );
7519 }
7520 EditPrediction::Edit { edits, .. } => {
7521 // Find an insertion that starts at the cursor position.
7522 let snapshot = self.buffer.read(cx).snapshot(cx);
7523 let cursor_offset = self.selections.newest::<usize>(cx).head();
7524 let insertion = edits.iter().find_map(|(range, text)| {
7525 let range = range.to_offset(&snapshot);
7526 if range.is_empty() && range.start == cursor_offset {
7527 Some(text)
7528 } else {
7529 None
7530 }
7531 });
7532
7533 if let Some(text) = insertion {
7534 let mut partial_completion = text
7535 .chars()
7536 .by_ref()
7537 .take_while(|c| c.is_alphabetic())
7538 .collect::<String>();
7539 if partial_completion.is_empty() {
7540 partial_completion = text
7541 .chars()
7542 .by_ref()
7543 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7544 .collect::<String>();
7545 }
7546
7547 cx.emit(EditorEvent::InputHandled {
7548 utf16_range_to_replace: None,
7549 text: partial_completion.clone().into(),
7550 });
7551
7552 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7553
7554 self.refresh_edit_prediction(true, true, window, cx);
7555 cx.notify();
7556 } else {
7557 self.accept_edit_prediction(&Default::default(), window, cx);
7558 }
7559 }
7560 }
7561 }
7562
7563 fn discard_edit_prediction(
7564 &mut self,
7565 should_report_edit_prediction_event: bool,
7566 cx: &mut Context<Self>,
7567 ) -> bool {
7568 if should_report_edit_prediction_event {
7569 let completion_id = self
7570 .active_edit_prediction
7571 .as_ref()
7572 .and_then(|active_completion| active_completion.completion_id.clone());
7573
7574 self.report_edit_prediction_event(completion_id, false, cx);
7575 }
7576
7577 if let Some(provider) = self.edit_prediction_provider() {
7578 provider.discard(cx);
7579 }
7580
7581 self.take_active_edit_prediction(cx)
7582 }
7583
7584 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7585 let Some(provider) = self.edit_prediction_provider() else {
7586 return;
7587 };
7588
7589 let Some((_, buffer, _)) = self
7590 .buffer
7591 .read(cx)
7592 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7593 else {
7594 return;
7595 };
7596
7597 let extension = buffer
7598 .read(cx)
7599 .file()
7600 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7601
7602 let event_type = match accepted {
7603 true => "Edit Prediction Accepted",
7604 false => "Edit Prediction Discarded",
7605 };
7606 telemetry::event!(
7607 event_type,
7608 provider = provider.name(),
7609 prediction_id = id,
7610 suggestion_accepted = accepted,
7611 file_extension = extension,
7612 );
7613 }
7614
7615 pub fn has_active_edit_prediction(&self) -> bool {
7616 self.active_edit_prediction.is_some()
7617 }
7618
7619 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7620 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7621 return false;
7622 };
7623
7624 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7625 self.clear_highlights::<EditPredictionHighlight>(cx);
7626 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7627 true
7628 }
7629
7630 /// Returns true when we're displaying the edit prediction popover below the cursor
7631 /// like we are not previewing and the LSP autocomplete menu is visible
7632 /// or we are in `when_holding_modifier` mode.
7633 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7634 if self.edit_prediction_preview_is_active()
7635 || !self.show_edit_predictions_in_menu()
7636 || !self.edit_predictions_enabled()
7637 {
7638 return false;
7639 }
7640
7641 if self.has_visible_completions_menu() {
7642 return true;
7643 }
7644
7645 has_completion && self.edit_prediction_requires_modifier()
7646 }
7647
7648 fn handle_modifiers_changed(
7649 &mut self,
7650 modifiers: Modifiers,
7651 position_map: &PositionMap,
7652 window: &mut Window,
7653 cx: &mut Context<Self>,
7654 ) {
7655 if self.show_edit_predictions_in_menu() {
7656 self.update_edit_prediction_preview(&modifiers, window, cx);
7657 }
7658
7659 self.update_selection_mode(&modifiers, position_map, window, cx);
7660
7661 let mouse_position = window.mouse_position();
7662 if !position_map.text_hitbox.is_hovered(window) {
7663 return;
7664 }
7665
7666 self.update_hovered_link(
7667 position_map.point_for_position(mouse_position),
7668 &position_map.snapshot,
7669 modifiers,
7670 window,
7671 cx,
7672 )
7673 }
7674
7675 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7676 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7677 if invert {
7678 match multi_cursor_setting {
7679 MultiCursorModifier::Alt => modifiers.alt,
7680 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7681 }
7682 } else {
7683 match multi_cursor_setting {
7684 MultiCursorModifier::Alt => modifiers.secondary(),
7685 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7686 }
7687 }
7688 }
7689
7690 fn columnar_selection_mode(
7691 modifiers: &Modifiers,
7692 cx: &mut Context<Self>,
7693 ) -> Option<ColumnarMode> {
7694 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7695 if Self::multi_cursor_modifier(false, modifiers, cx) {
7696 Some(ColumnarMode::FromMouse)
7697 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7698 Some(ColumnarMode::FromSelection)
7699 } else {
7700 None
7701 }
7702 } else {
7703 None
7704 }
7705 }
7706
7707 fn update_selection_mode(
7708 &mut self,
7709 modifiers: &Modifiers,
7710 position_map: &PositionMap,
7711 window: &mut Window,
7712 cx: &mut Context<Self>,
7713 ) {
7714 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7715 return;
7716 };
7717 if self.selections.pending_anchor().is_none() {
7718 return;
7719 }
7720
7721 let mouse_position = window.mouse_position();
7722 let point_for_position = position_map.point_for_position(mouse_position);
7723 let position = point_for_position.previous_valid;
7724
7725 self.select(
7726 SelectPhase::BeginColumnar {
7727 position,
7728 reset: false,
7729 mode,
7730 goal_column: point_for_position.exact_unclipped.column(),
7731 },
7732 window,
7733 cx,
7734 );
7735 }
7736
7737 fn update_edit_prediction_preview(
7738 &mut self,
7739 modifiers: &Modifiers,
7740 window: &mut Window,
7741 cx: &mut Context<Self>,
7742 ) {
7743 let mut modifiers_held = false;
7744 if let Some(accept_keystroke) = self
7745 .accept_edit_prediction_keybind(false, window, cx)
7746 .keystroke()
7747 {
7748 modifiers_held = modifiers_held
7749 || (accept_keystroke.modifiers() == modifiers
7750 && accept_keystroke.modifiers().modified());
7751 };
7752 if let Some(accept_partial_keystroke) = self
7753 .accept_edit_prediction_keybind(true, window, cx)
7754 .keystroke()
7755 {
7756 modifiers_held = modifiers_held
7757 || (accept_partial_keystroke.modifiers() == modifiers
7758 && accept_partial_keystroke.modifiers().modified());
7759 }
7760
7761 if modifiers_held {
7762 if matches!(
7763 self.edit_prediction_preview,
7764 EditPredictionPreview::Inactive { .. }
7765 ) {
7766 self.edit_prediction_preview = EditPredictionPreview::Active {
7767 previous_scroll_position: None,
7768 since: Instant::now(),
7769 };
7770
7771 self.update_visible_edit_prediction(window, cx);
7772 cx.notify();
7773 }
7774 } else if let EditPredictionPreview::Active {
7775 previous_scroll_position,
7776 since,
7777 } = self.edit_prediction_preview
7778 {
7779 if let (Some(previous_scroll_position), Some(position_map)) =
7780 (previous_scroll_position, self.last_position_map.as_ref())
7781 {
7782 self.set_scroll_position(
7783 previous_scroll_position
7784 .scroll_position(&position_map.snapshot.display_snapshot),
7785 window,
7786 cx,
7787 );
7788 }
7789
7790 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7791 released_too_fast: since.elapsed() < Duration::from_millis(200),
7792 };
7793 self.clear_row_highlights::<EditPredictionPreview>();
7794 self.update_visible_edit_prediction(window, cx);
7795 cx.notify();
7796 }
7797 }
7798
7799 fn update_visible_edit_prediction(
7800 &mut self,
7801 _window: &mut Window,
7802 cx: &mut Context<Self>,
7803 ) -> Option<()> {
7804 if DisableAiSettings::get_global(cx).disable_ai {
7805 return None;
7806 }
7807
7808 if self.ime_transaction.is_some() {
7809 self.discard_edit_prediction(false, cx);
7810 return None;
7811 }
7812
7813 let selection = self.selections.newest_anchor();
7814 let cursor = selection.head();
7815 let multibuffer = self.buffer.read(cx).snapshot(cx);
7816 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7817 let excerpt_id = cursor.excerpt_id;
7818
7819 let show_in_menu = self.show_edit_predictions_in_menu();
7820 let completions_menu_has_precedence = !show_in_menu
7821 && (self.context_menu.borrow().is_some()
7822 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7823
7824 if completions_menu_has_precedence
7825 || !offset_selection.is_empty()
7826 || self
7827 .active_edit_prediction
7828 .as_ref()
7829 .is_some_and(|completion| {
7830 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7831 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7832 !invalidation_range.contains(&offset_selection.head())
7833 })
7834 {
7835 self.discard_edit_prediction(false, cx);
7836 return None;
7837 }
7838
7839 self.take_active_edit_prediction(cx);
7840 let Some(provider) = self.edit_prediction_provider() else {
7841 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7842 return None;
7843 };
7844
7845 let (buffer, cursor_buffer_position) =
7846 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7847
7848 self.edit_prediction_settings =
7849 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7850
7851 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7852 self.discard_edit_prediction(false, cx);
7853 return None;
7854 };
7855
7856 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7857
7858 if self.edit_prediction_indent_conflict {
7859 let cursor_point = cursor.to_point(&multibuffer);
7860
7861 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7862
7863 if let Some((_, indent)) = indents.iter().next()
7864 && indent.len == cursor_point.column
7865 {
7866 self.edit_prediction_indent_conflict = false;
7867 }
7868 }
7869
7870 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7871 let edits = edit_prediction
7872 .edits
7873 .into_iter()
7874 .flat_map(|(range, new_text)| {
7875 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7876 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7877 Some((start..end, new_text))
7878 })
7879 .collect::<Vec<_>>();
7880 if edits.is_empty() {
7881 return None;
7882 }
7883
7884 let first_edit_start = edits.first().unwrap().0.start;
7885 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7886 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7887
7888 let last_edit_end = edits.last().unwrap().0.end;
7889 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7890 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7891
7892 let cursor_row = cursor.to_point(&multibuffer).row;
7893
7894 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7895
7896 let mut inlay_ids = Vec::new();
7897 let invalidation_row_range;
7898 let move_invalidation_row_range = if cursor_row < edit_start_row {
7899 Some(cursor_row..edit_end_row)
7900 } else if cursor_row > edit_end_row {
7901 Some(edit_start_row..cursor_row)
7902 } else {
7903 None
7904 };
7905 let supports_jump = self
7906 .edit_prediction_provider
7907 .as_ref()
7908 .map(|provider| provider.provider.supports_jump_to_edit())
7909 .unwrap_or(true);
7910
7911 let is_move = supports_jump
7912 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7913 let completion = if is_move {
7914 invalidation_row_range =
7915 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7916 let target = first_edit_start;
7917 EditPrediction::Move { target, snapshot }
7918 } else {
7919 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7920 && !self.edit_predictions_hidden_for_vim_mode;
7921
7922 if show_completions_in_buffer {
7923 if edits
7924 .iter()
7925 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7926 {
7927 let mut inlays = Vec::new();
7928 for (range, new_text) in &edits {
7929 let inlay = Inlay::edit_prediction(
7930 post_inc(&mut self.next_inlay_id),
7931 range.start,
7932 new_text.as_str(),
7933 );
7934 inlay_ids.push(inlay.id);
7935 inlays.push(inlay);
7936 }
7937
7938 self.splice_inlays(&[], inlays, cx);
7939 } else {
7940 let background_color = cx.theme().status().deleted_background;
7941 self.highlight_text::<EditPredictionHighlight>(
7942 edits.iter().map(|(range, _)| range.clone()).collect(),
7943 HighlightStyle {
7944 background_color: Some(background_color),
7945 ..Default::default()
7946 },
7947 cx,
7948 );
7949 }
7950 }
7951
7952 invalidation_row_range = edit_start_row..edit_end_row;
7953
7954 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7955 if provider.show_tab_accept_marker() {
7956 EditDisplayMode::TabAccept
7957 } else {
7958 EditDisplayMode::Inline
7959 }
7960 } else {
7961 EditDisplayMode::DiffPopover
7962 };
7963
7964 EditPrediction::Edit {
7965 edits,
7966 edit_preview: edit_prediction.edit_preview,
7967 display_mode,
7968 snapshot,
7969 }
7970 };
7971
7972 let invalidation_range = multibuffer
7973 .anchor_before(Point::new(invalidation_row_range.start, 0))
7974 ..multibuffer.anchor_after(Point::new(
7975 invalidation_row_range.end,
7976 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7977 ));
7978
7979 self.stale_edit_prediction_in_menu = None;
7980 self.active_edit_prediction = Some(EditPredictionState {
7981 inlay_ids,
7982 completion,
7983 completion_id: edit_prediction.id,
7984 invalidation_range,
7985 });
7986
7987 cx.notify();
7988
7989 Some(())
7990 }
7991
7992 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7993 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7994 }
7995
7996 fn clear_tasks(&mut self) {
7997 self.tasks.clear()
7998 }
7999
8000 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8001 if self.tasks.insert(key, value).is_some() {
8002 // This case should hopefully be rare, but just in case...
8003 log::error!(
8004 "multiple different run targets found on a single line, only the last target will be rendered"
8005 )
8006 }
8007 }
8008
8009 /// Get all display points of breakpoints that will be rendered within editor
8010 ///
8011 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8012 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8013 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8014 fn active_breakpoints(
8015 &self,
8016 range: Range<DisplayRow>,
8017 window: &mut Window,
8018 cx: &mut Context<Self>,
8019 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8020 let mut breakpoint_display_points = HashMap::default();
8021
8022 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8023 return breakpoint_display_points;
8024 };
8025
8026 let snapshot = self.snapshot(window, cx);
8027
8028 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8029 let Some(project) = self.project() else {
8030 return breakpoint_display_points;
8031 };
8032
8033 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8034 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8035
8036 for (buffer_snapshot, range, excerpt_id) in
8037 multi_buffer_snapshot.range_to_buffer_ranges(range)
8038 {
8039 let Some(buffer) = project
8040 .read(cx)
8041 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8042 else {
8043 continue;
8044 };
8045 let breakpoints = breakpoint_store.read(cx).breakpoints(
8046 &buffer,
8047 Some(
8048 buffer_snapshot.anchor_before(range.start)
8049 ..buffer_snapshot.anchor_after(range.end),
8050 ),
8051 buffer_snapshot,
8052 cx,
8053 );
8054 for (breakpoint, state) in breakpoints {
8055 let multi_buffer_anchor =
8056 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8057 let position = multi_buffer_anchor
8058 .to_point(multi_buffer_snapshot)
8059 .to_display_point(&snapshot);
8060
8061 breakpoint_display_points.insert(
8062 position.row(),
8063 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8064 );
8065 }
8066 }
8067
8068 breakpoint_display_points
8069 }
8070
8071 fn breakpoint_context_menu(
8072 &self,
8073 anchor: Anchor,
8074 window: &mut Window,
8075 cx: &mut Context<Self>,
8076 ) -> Entity<ui::ContextMenu> {
8077 let weak_editor = cx.weak_entity();
8078 let focus_handle = self.focus_handle(cx);
8079
8080 let row = self
8081 .buffer
8082 .read(cx)
8083 .snapshot(cx)
8084 .summary_for_anchor::<Point>(&anchor)
8085 .row;
8086
8087 let breakpoint = self
8088 .breakpoint_at_row(row, window, cx)
8089 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8090
8091 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8092 "Edit Log Breakpoint"
8093 } else {
8094 "Set Log Breakpoint"
8095 };
8096
8097 let condition_breakpoint_msg = if breakpoint
8098 .as_ref()
8099 .is_some_and(|bp| bp.1.condition.is_some())
8100 {
8101 "Edit Condition Breakpoint"
8102 } else {
8103 "Set Condition Breakpoint"
8104 };
8105
8106 let hit_condition_breakpoint_msg = if breakpoint
8107 .as_ref()
8108 .is_some_and(|bp| bp.1.hit_condition.is_some())
8109 {
8110 "Edit Hit Condition Breakpoint"
8111 } else {
8112 "Set Hit Condition Breakpoint"
8113 };
8114
8115 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8116 "Unset Breakpoint"
8117 } else {
8118 "Set Breakpoint"
8119 };
8120
8121 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8122
8123 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8124 BreakpointState::Enabled => Some("Disable"),
8125 BreakpointState::Disabled => Some("Enable"),
8126 });
8127
8128 let (anchor, breakpoint) =
8129 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8130
8131 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8132 menu.on_blur_subscription(Subscription::new(|| {}))
8133 .context(focus_handle)
8134 .when(run_to_cursor, |this| {
8135 let weak_editor = weak_editor.clone();
8136 this.entry("Run to cursor", None, move |window, cx| {
8137 weak_editor
8138 .update(cx, |editor, cx| {
8139 editor.change_selections(
8140 SelectionEffects::no_scroll(),
8141 window,
8142 cx,
8143 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8144 );
8145 })
8146 .ok();
8147
8148 window.dispatch_action(Box::new(RunToCursor), cx);
8149 })
8150 .separator()
8151 })
8152 .when_some(toggle_state_msg, |this, msg| {
8153 this.entry(msg, None, {
8154 let weak_editor = weak_editor.clone();
8155 let breakpoint = breakpoint.clone();
8156 move |_window, cx| {
8157 weak_editor
8158 .update(cx, |this, cx| {
8159 this.edit_breakpoint_at_anchor(
8160 anchor,
8161 breakpoint.as_ref().clone(),
8162 BreakpointEditAction::InvertState,
8163 cx,
8164 );
8165 })
8166 .log_err();
8167 }
8168 })
8169 })
8170 .entry(set_breakpoint_msg, None, {
8171 let weak_editor = weak_editor.clone();
8172 let breakpoint = breakpoint.clone();
8173 move |_window, cx| {
8174 weak_editor
8175 .update(cx, |this, cx| {
8176 this.edit_breakpoint_at_anchor(
8177 anchor,
8178 breakpoint.as_ref().clone(),
8179 BreakpointEditAction::Toggle,
8180 cx,
8181 );
8182 })
8183 .log_err();
8184 }
8185 })
8186 .entry(log_breakpoint_msg, None, {
8187 let breakpoint = breakpoint.clone();
8188 let weak_editor = weak_editor.clone();
8189 move |window, cx| {
8190 weak_editor
8191 .update(cx, |this, cx| {
8192 this.add_edit_breakpoint_block(
8193 anchor,
8194 breakpoint.as_ref(),
8195 BreakpointPromptEditAction::Log,
8196 window,
8197 cx,
8198 );
8199 })
8200 .log_err();
8201 }
8202 })
8203 .entry(condition_breakpoint_msg, None, {
8204 let breakpoint = breakpoint.clone();
8205 let weak_editor = weak_editor.clone();
8206 move |window, cx| {
8207 weak_editor
8208 .update(cx, |this, cx| {
8209 this.add_edit_breakpoint_block(
8210 anchor,
8211 breakpoint.as_ref(),
8212 BreakpointPromptEditAction::Condition,
8213 window,
8214 cx,
8215 );
8216 })
8217 .log_err();
8218 }
8219 })
8220 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8221 weak_editor
8222 .update(cx, |this, cx| {
8223 this.add_edit_breakpoint_block(
8224 anchor,
8225 breakpoint.as_ref(),
8226 BreakpointPromptEditAction::HitCondition,
8227 window,
8228 cx,
8229 );
8230 })
8231 .log_err();
8232 })
8233 })
8234 }
8235
8236 fn render_breakpoint(
8237 &self,
8238 position: Anchor,
8239 row: DisplayRow,
8240 breakpoint: &Breakpoint,
8241 state: Option<BreakpointSessionState>,
8242 cx: &mut Context<Self>,
8243 ) -> IconButton {
8244 let is_rejected = state.is_some_and(|s| !s.verified);
8245 // Is it a breakpoint that shows up when hovering over gutter?
8246 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8247 (false, false),
8248 |PhantomBreakpointIndicator {
8249 is_active,
8250 display_row,
8251 collides_with_existing_breakpoint,
8252 }| {
8253 (
8254 is_active && display_row == row,
8255 collides_with_existing_breakpoint,
8256 )
8257 },
8258 );
8259
8260 let (color, icon) = {
8261 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8262 (false, false) => ui::IconName::DebugBreakpoint,
8263 (true, false) => ui::IconName::DebugLogBreakpoint,
8264 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8265 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8266 };
8267
8268 let color = if is_phantom {
8269 Color::Hint
8270 } else if is_rejected {
8271 Color::Disabled
8272 } else {
8273 Color::Debugger
8274 };
8275
8276 (color, icon)
8277 };
8278
8279 let breakpoint = Arc::from(breakpoint.clone());
8280
8281 let alt_as_text = gpui::Keystroke {
8282 modifiers: Modifiers::secondary_key(),
8283 ..Default::default()
8284 };
8285 let primary_action_text = if breakpoint.is_disabled() {
8286 "Enable breakpoint"
8287 } else if is_phantom && !collides_with_existing {
8288 "Set breakpoint"
8289 } else {
8290 "Unset breakpoint"
8291 };
8292 let focus_handle = self.focus_handle.clone();
8293
8294 let meta = if is_rejected {
8295 SharedString::from("No executable code is associated with this line.")
8296 } else if collides_with_existing && !breakpoint.is_disabled() {
8297 SharedString::from(format!(
8298 "{alt_as_text}-click to disable,\nright-click for more options."
8299 ))
8300 } else {
8301 SharedString::from("Right-click for more options.")
8302 };
8303 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8304 .icon_size(IconSize::XSmall)
8305 .size(ui::ButtonSize::None)
8306 .when(is_rejected, |this| {
8307 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8308 })
8309 .icon_color(color)
8310 .style(ButtonStyle::Transparent)
8311 .on_click(cx.listener({
8312 move |editor, event: &ClickEvent, window, cx| {
8313 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8314 BreakpointEditAction::InvertState
8315 } else {
8316 BreakpointEditAction::Toggle
8317 };
8318
8319 window.focus(&editor.focus_handle(cx));
8320 editor.edit_breakpoint_at_anchor(
8321 position,
8322 breakpoint.as_ref().clone(),
8323 edit_action,
8324 cx,
8325 );
8326 }
8327 }))
8328 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8329 editor.set_breakpoint_context_menu(
8330 row,
8331 Some(position),
8332 event.position(),
8333 window,
8334 cx,
8335 );
8336 }))
8337 .tooltip(move |window, cx| {
8338 Tooltip::with_meta_in(
8339 primary_action_text,
8340 Some(&ToggleBreakpoint),
8341 meta.clone(),
8342 &focus_handle,
8343 window,
8344 cx,
8345 )
8346 })
8347 }
8348
8349 fn build_tasks_context(
8350 project: &Entity<Project>,
8351 buffer: &Entity<Buffer>,
8352 buffer_row: u32,
8353 tasks: &Arc<RunnableTasks>,
8354 cx: &mut Context<Self>,
8355 ) -> Task<Option<task::TaskContext>> {
8356 let position = Point::new(buffer_row, tasks.column);
8357 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8358 let location = Location {
8359 buffer: buffer.clone(),
8360 range: range_start..range_start,
8361 };
8362 // Fill in the environmental variables from the tree-sitter captures
8363 let mut captured_task_variables = TaskVariables::default();
8364 for (capture_name, value) in tasks.extra_variables.clone() {
8365 captured_task_variables.insert(
8366 task::VariableName::Custom(capture_name.into()),
8367 value.clone(),
8368 );
8369 }
8370 project.update(cx, |project, cx| {
8371 project.task_store().update(cx, |task_store, cx| {
8372 task_store.task_context_for_location(captured_task_variables, location, cx)
8373 })
8374 })
8375 }
8376
8377 pub fn spawn_nearest_task(
8378 &mut self,
8379 action: &SpawnNearestTask,
8380 window: &mut Window,
8381 cx: &mut Context<Self>,
8382 ) {
8383 let Some((workspace, _)) = self.workspace.clone() else {
8384 return;
8385 };
8386 let Some(project) = self.project.clone() else {
8387 return;
8388 };
8389
8390 // Try to find a closest, enclosing node using tree-sitter that has a task
8391 let Some((buffer, buffer_row, tasks)) = self
8392 .find_enclosing_node_task(cx)
8393 // Or find the task that's closest in row-distance.
8394 .or_else(|| self.find_closest_task(cx))
8395 else {
8396 return;
8397 };
8398
8399 let reveal_strategy = action.reveal;
8400 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8401 cx.spawn_in(window, async move |_, cx| {
8402 let context = task_context.await?;
8403 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8404
8405 let resolved = &mut resolved_task.resolved;
8406 resolved.reveal = reveal_strategy;
8407
8408 workspace
8409 .update_in(cx, |workspace, window, cx| {
8410 workspace.schedule_resolved_task(
8411 task_source_kind,
8412 resolved_task,
8413 false,
8414 window,
8415 cx,
8416 );
8417 })
8418 .ok()
8419 })
8420 .detach();
8421 }
8422
8423 fn find_closest_task(
8424 &mut self,
8425 cx: &mut Context<Self>,
8426 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8427 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8428
8429 let ((buffer_id, row), tasks) = self
8430 .tasks
8431 .iter()
8432 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8433
8434 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8435 let tasks = Arc::new(tasks.to_owned());
8436 Some((buffer, *row, tasks))
8437 }
8438
8439 fn find_enclosing_node_task(
8440 &mut self,
8441 cx: &mut Context<Self>,
8442 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8443 let snapshot = self.buffer.read(cx).snapshot(cx);
8444 let offset = self.selections.newest::<usize>(cx).head();
8445 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8446 let buffer_id = excerpt.buffer().remote_id();
8447
8448 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8449 let mut cursor = layer.node().walk();
8450
8451 while cursor.goto_first_child_for_byte(offset).is_some() {
8452 if cursor.node().end_byte() == offset {
8453 cursor.goto_next_sibling();
8454 }
8455 }
8456
8457 // Ascend to the smallest ancestor that contains the range and has a task.
8458 loop {
8459 let node = cursor.node();
8460 let node_range = node.byte_range();
8461 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8462
8463 // Check if this node contains our offset
8464 if node_range.start <= offset && node_range.end >= offset {
8465 // If it contains offset, check for task
8466 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8467 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8468 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8469 }
8470 }
8471
8472 if !cursor.goto_parent() {
8473 break;
8474 }
8475 }
8476 None
8477 }
8478
8479 fn render_run_indicator(
8480 &self,
8481 _style: &EditorStyle,
8482 is_active: bool,
8483 row: DisplayRow,
8484 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8485 cx: &mut Context<Self>,
8486 ) -> IconButton {
8487 let color = Color::Muted;
8488 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8489
8490 IconButton::new(
8491 ("run_indicator", row.0 as usize),
8492 ui::IconName::PlayOutlined,
8493 )
8494 .shape(ui::IconButtonShape::Square)
8495 .icon_size(IconSize::XSmall)
8496 .icon_color(color)
8497 .toggle_state(is_active)
8498 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8499 let quick_launch = match e {
8500 ClickEvent::Keyboard(_) => true,
8501 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8502 };
8503
8504 window.focus(&editor.focus_handle(cx));
8505 editor.toggle_code_actions(
8506 &ToggleCodeActions {
8507 deployed_from: Some(CodeActionSource::RunMenu(row)),
8508 quick_launch,
8509 },
8510 window,
8511 cx,
8512 );
8513 }))
8514 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8515 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8516 }))
8517 }
8518
8519 pub fn context_menu_visible(&self) -> bool {
8520 !self.edit_prediction_preview_is_active()
8521 && self
8522 .context_menu
8523 .borrow()
8524 .as_ref()
8525 .is_some_and(|menu| menu.visible())
8526 }
8527
8528 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8529 self.context_menu
8530 .borrow()
8531 .as_ref()
8532 .map(|menu| menu.origin())
8533 }
8534
8535 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8536 self.context_menu_options = Some(options);
8537 }
8538
8539 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8540 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8541
8542 fn render_edit_prediction_popover(
8543 &mut self,
8544 text_bounds: &Bounds<Pixels>,
8545 content_origin: gpui::Point<Pixels>,
8546 right_margin: Pixels,
8547 editor_snapshot: &EditorSnapshot,
8548 visible_row_range: Range<DisplayRow>,
8549 scroll_top: f32,
8550 scroll_bottom: f32,
8551 line_layouts: &[LineWithInvisibles],
8552 line_height: Pixels,
8553 scroll_pixel_position: gpui::Point<Pixels>,
8554 newest_selection_head: Option<DisplayPoint>,
8555 editor_width: Pixels,
8556 style: &EditorStyle,
8557 window: &mut Window,
8558 cx: &mut App,
8559 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8560 if self.mode().is_minimap() {
8561 return None;
8562 }
8563 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8564
8565 if self.edit_prediction_visible_in_cursor_popover(true) {
8566 return None;
8567 }
8568
8569 match &active_edit_prediction.completion {
8570 EditPrediction::Move { target, .. } => {
8571 let target_display_point = target.to_display_point(editor_snapshot);
8572
8573 if self.edit_prediction_requires_modifier() {
8574 if !self.edit_prediction_preview_is_active() {
8575 return None;
8576 }
8577
8578 self.render_edit_prediction_modifier_jump_popover(
8579 text_bounds,
8580 content_origin,
8581 visible_row_range,
8582 line_layouts,
8583 line_height,
8584 scroll_pixel_position,
8585 newest_selection_head,
8586 target_display_point,
8587 window,
8588 cx,
8589 )
8590 } else {
8591 self.render_edit_prediction_eager_jump_popover(
8592 text_bounds,
8593 content_origin,
8594 editor_snapshot,
8595 visible_row_range,
8596 scroll_top,
8597 scroll_bottom,
8598 line_height,
8599 scroll_pixel_position,
8600 target_display_point,
8601 editor_width,
8602 window,
8603 cx,
8604 )
8605 }
8606 }
8607 EditPrediction::Edit {
8608 display_mode: EditDisplayMode::Inline,
8609 ..
8610 } => None,
8611 EditPrediction::Edit {
8612 display_mode: EditDisplayMode::TabAccept,
8613 edits,
8614 ..
8615 } => {
8616 let range = &edits.first()?.0;
8617 let target_display_point = range.end.to_display_point(editor_snapshot);
8618
8619 self.render_edit_prediction_end_of_line_popover(
8620 "Accept",
8621 editor_snapshot,
8622 visible_row_range,
8623 target_display_point,
8624 line_height,
8625 scroll_pixel_position,
8626 content_origin,
8627 editor_width,
8628 window,
8629 cx,
8630 )
8631 }
8632 EditPrediction::Edit {
8633 edits,
8634 edit_preview,
8635 display_mode: EditDisplayMode::DiffPopover,
8636 snapshot,
8637 } => self.render_edit_prediction_diff_popover(
8638 text_bounds,
8639 content_origin,
8640 right_margin,
8641 editor_snapshot,
8642 visible_row_range,
8643 line_layouts,
8644 line_height,
8645 scroll_pixel_position,
8646 newest_selection_head,
8647 editor_width,
8648 style,
8649 edits,
8650 edit_preview,
8651 snapshot,
8652 window,
8653 cx,
8654 ),
8655 }
8656 }
8657
8658 fn render_edit_prediction_modifier_jump_popover(
8659 &mut self,
8660 text_bounds: &Bounds<Pixels>,
8661 content_origin: gpui::Point<Pixels>,
8662 visible_row_range: Range<DisplayRow>,
8663 line_layouts: &[LineWithInvisibles],
8664 line_height: Pixels,
8665 scroll_pixel_position: gpui::Point<Pixels>,
8666 newest_selection_head: Option<DisplayPoint>,
8667 target_display_point: DisplayPoint,
8668 window: &mut Window,
8669 cx: &mut App,
8670 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8671 let scrolled_content_origin =
8672 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8673
8674 const SCROLL_PADDING_Y: Pixels = px(12.);
8675
8676 if target_display_point.row() < visible_row_range.start {
8677 return self.render_edit_prediction_scroll_popover(
8678 |_| SCROLL_PADDING_Y,
8679 IconName::ArrowUp,
8680 visible_row_range,
8681 line_layouts,
8682 newest_selection_head,
8683 scrolled_content_origin,
8684 window,
8685 cx,
8686 );
8687 } else if target_display_point.row() >= visible_row_range.end {
8688 return self.render_edit_prediction_scroll_popover(
8689 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8690 IconName::ArrowDown,
8691 visible_row_range,
8692 line_layouts,
8693 newest_selection_head,
8694 scrolled_content_origin,
8695 window,
8696 cx,
8697 );
8698 }
8699
8700 const POLE_WIDTH: Pixels = px(2.);
8701
8702 let line_layout =
8703 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8704 let target_column = target_display_point.column() as usize;
8705
8706 let target_x = line_layout.x_for_index(target_column);
8707 let target_y =
8708 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8709
8710 let flag_on_right = target_x < text_bounds.size.width / 2.;
8711
8712 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8713 border_color.l += 0.001;
8714
8715 let mut element = v_flex()
8716 .items_end()
8717 .when(flag_on_right, |el| el.items_start())
8718 .child(if flag_on_right {
8719 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8720 .rounded_bl(px(0.))
8721 .rounded_tl(px(0.))
8722 .border_l_2()
8723 .border_color(border_color)
8724 } else {
8725 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8726 .rounded_br(px(0.))
8727 .rounded_tr(px(0.))
8728 .border_r_2()
8729 .border_color(border_color)
8730 })
8731 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8732 .into_any();
8733
8734 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8735
8736 let mut origin = scrolled_content_origin + point(target_x, target_y)
8737 - point(
8738 if flag_on_right {
8739 POLE_WIDTH
8740 } else {
8741 size.width - POLE_WIDTH
8742 },
8743 size.height - line_height,
8744 );
8745
8746 origin.x = origin.x.max(content_origin.x);
8747
8748 element.prepaint_at(origin, window, cx);
8749
8750 Some((element, origin))
8751 }
8752
8753 fn render_edit_prediction_scroll_popover(
8754 &mut self,
8755 to_y: impl Fn(Size<Pixels>) -> Pixels,
8756 scroll_icon: IconName,
8757 visible_row_range: Range<DisplayRow>,
8758 line_layouts: &[LineWithInvisibles],
8759 newest_selection_head: Option<DisplayPoint>,
8760 scrolled_content_origin: gpui::Point<Pixels>,
8761 window: &mut Window,
8762 cx: &mut App,
8763 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8764 let mut element = self
8765 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8766 .into_any();
8767
8768 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8769
8770 let cursor = newest_selection_head?;
8771 let cursor_row_layout =
8772 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8773 let cursor_column = cursor.column() as usize;
8774
8775 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8776
8777 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8778
8779 element.prepaint_at(origin, window, cx);
8780 Some((element, origin))
8781 }
8782
8783 fn render_edit_prediction_eager_jump_popover(
8784 &mut self,
8785 text_bounds: &Bounds<Pixels>,
8786 content_origin: gpui::Point<Pixels>,
8787 editor_snapshot: &EditorSnapshot,
8788 visible_row_range: Range<DisplayRow>,
8789 scroll_top: f32,
8790 scroll_bottom: f32,
8791 line_height: Pixels,
8792 scroll_pixel_position: gpui::Point<Pixels>,
8793 target_display_point: DisplayPoint,
8794 editor_width: Pixels,
8795 window: &mut Window,
8796 cx: &mut App,
8797 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8798 if target_display_point.row().as_f32() < scroll_top {
8799 let mut element = self
8800 .render_edit_prediction_line_popover(
8801 "Jump to Edit",
8802 Some(IconName::ArrowUp),
8803 window,
8804 cx,
8805 )?
8806 .into_any();
8807
8808 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8809 let offset = point(
8810 (text_bounds.size.width - size.width) / 2.,
8811 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8812 );
8813
8814 let origin = text_bounds.origin + offset;
8815 element.prepaint_at(origin, window, cx);
8816 Some((element, origin))
8817 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8818 let mut element = self
8819 .render_edit_prediction_line_popover(
8820 "Jump to Edit",
8821 Some(IconName::ArrowDown),
8822 window,
8823 cx,
8824 )?
8825 .into_any();
8826
8827 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8828 let offset = point(
8829 (text_bounds.size.width - size.width) / 2.,
8830 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8831 );
8832
8833 let origin = text_bounds.origin + offset;
8834 element.prepaint_at(origin, window, cx);
8835 Some((element, origin))
8836 } else {
8837 self.render_edit_prediction_end_of_line_popover(
8838 "Jump to Edit",
8839 editor_snapshot,
8840 visible_row_range,
8841 target_display_point,
8842 line_height,
8843 scroll_pixel_position,
8844 content_origin,
8845 editor_width,
8846 window,
8847 cx,
8848 )
8849 }
8850 }
8851
8852 fn render_edit_prediction_end_of_line_popover(
8853 self: &mut Editor,
8854 label: &'static str,
8855 editor_snapshot: &EditorSnapshot,
8856 visible_row_range: Range<DisplayRow>,
8857 target_display_point: DisplayPoint,
8858 line_height: Pixels,
8859 scroll_pixel_position: gpui::Point<Pixels>,
8860 content_origin: gpui::Point<Pixels>,
8861 editor_width: Pixels,
8862 window: &mut Window,
8863 cx: &mut App,
8864 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8865 let target_line_end = DisplayPoint::new(
8866 target_display_point.row(),
8867 editor_snapshot.line_len(target_display_point.row()),
8868 );
8869
8870 let mut element = self
8871 .render_edit_prediction_line_popover(label, None, window, cx)?
8872 .into_any();
8873
8874 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8875
8876 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8877
8878 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8879 let mut origin = start_point
8880 + line_origin
8881 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8882 origin.x = origin.x.max(content_origin.x);
8883
8884 let max_x = content_origin.x + editor_width - size.width;
8885
8886 if origin.x > max_x {
8887 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8888
8889 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8890 origin.y += offset;
8891 IconName::ArrowUp
8892 } else {
8893 origin.y -= offset;
8894 IconName::ArrowDown
8895 };
8896
8897 element = self
8898 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8899 .into_any();
8900
8901 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8902
8903 origin.x = content_origin.x + editor_width - size.width - px(2.);
8904 }
8905
8906 element.prepaint_at(origin, window, cx);
8907 Some((element, origin))
8908 }
8909
8910 fn render_edit_prediction_diff_popover(
8911 self: &Editor,
8912 text_bounds: &Bounds<Pixels>,
8913 content_origin: gpui::Point<Pixels>,
8914 right_margin: Pixels,
8915 editor_snapshot: &EditorSnapshot,
8916 visible_row_range: Range<DisplayRow>,
8917 line_layouts: &[LineWithInvisibles],
8918 line_height: Pixels,
8919 scroll_pixel_position: gpui::Point<Pixels>,
8920 newest_selection_head: Option<DisplayPoint>,
8921 editor_width: Pixels,
8922 style: &EditorStyle,
8923 edits: &Vec<(Range<Anchor>, String)>,
8924 edit_preview: &Option<language::EditPreview>,
8925 snapshot: &language::BufferSnapshot,
8926 window: &mut Window,
8927 cx: &mut App,
8928 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8929 let edit_start = edits
8930 .first()
8931 .unwrap()
8932 .0
8933 .start
8934 .to_display_point(editor_snapshot);
8935 let edit_end = edits
8936 .last()
8937 .unwrap()
8938 .0
8939 .end
8940 .to_display_point(editor_snapshot);
8941
8942 let is_visible = visible_row_range.contains(&edit_start.row())
8943 || visible_row_range.contains(&edit_end.row());
8944 if !is_visible {
8945 return None;
8946 }
8947
8948 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8949 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8950 } else {
8951 // Fallback for providers without edit_preview
8952 crate::edit_prediction_fallback_text(edits, cx)
8953 };
8954
8955 let styled_text = highlighted_edits.to_styled_text(&style.text);
8956 let line_count = highlighted_edits.text.lines().count();
8957
8958 const BORDER_WIDTH: Pixels = px(1.);
8959
8960 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8961 let has_keybind = keybind.is_some();
8962
8963 let mut element = h_flex()
8964 .items_start()
8965 .child(
8966 h_flex()
8967 .bg(cx.theme().colors().editor_background)
8968 .border(BORDER_WIDTH)
8969 .shadow_xs()
8970 .border_color(cx.theme().colors().border)
8971 .rounded_l_lg()
8972 .when(line_count > 1, |el| el.rounded_br_lg())
8973 .pr_1()
8974 .child(styled_text),
8975 )
8976 .child(
8977 h_flex()
8978 .h(line_height + BORDER_WIDTH * 2.)
8979 .px_1p5()
8980 .gap_1()
8981 // Workaround: For some reason, there's a gap if we don't do this
8982 .ml(-BORDER_WIDTH)
8983 .shadow(vec![gpui::BoxShadow {
8984 color: gpui::black().opacity(0.05),
8985 offset: point(px(1.), px(1.)),
8986 blur_radius: px(2.),
8987 spread_radius: px(0.),
8988 }])
8989 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8990 .border(BORDER_WIDTH)
8991 .border_color(cx.theme().colors().border)
8992 .rounded_r_lg()
8993 .id("edit_prediction_diff_popover_keybind")
8994 .when(!has_keybind, |el| {
8995 let status_colors = cx.theme().status();
8996
8997 el.bg(status_colors.error_background)
8998 .border_color(status_colors.error.opacity(0.6))
8999 .child(Icon::new(IconName::Info).color(Color::Error))
9000 .cursor_default()
9001 .hoverable_tooltip(move |_window, cx| {
9002 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9003 })
9004 })
9005 .children(keybind),
9006 )
9007 .into_any();
9008
9009 let longest_row =
9010 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9011 let longest_line_width = if visible_row_range.contains(&longest_row) {
9012 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9013 } else {
9014 layout_line(
9015 longest_row,
9016 editor_snapshot,
9017 style,
9018 editor_width,
9019 |_| false,
9020 window,
9021 cx,
9022 )
9023 .width
9024 };
9025
9026 let viewport_bounds =
9027 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9028 right: -right_margin,
9029 ..Default::default()
9030 });
9031
9032 let x_after_longest =
9033 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9034 - scroll_pixel_position.x;
9035
9036 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9037
9038 // Fully visible if it can be displayed within the window (allow overlapping other
9039 // panes). However, this is only allowed if the popover starts within text_bounds.
9040 let can_position_to_the_right = x_after_longest < text_bounds.right()
9041 && x_after_longest + element_bounds.width < viewport_bounds.right();
9042
9043 let mut origin = if can_position_to_the_right {
9044 point(
9045 x_after_longest,
9046 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9047 - scroll_pixel_position.y,
9048 )
9049 } else {
9050 let cursor_row = newest_selection_head.map(|head| head.row());
9051 let above_edit = edit_start
9052 .row()
9053 .0
9054 .checked_sub(line_count as u32)
9055 .map(DisplayRow);
9056 let below_edit = Some(edit_end.row() + 1);
9057 let above_cursor =
9058 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9059 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9060
9061 // Place the edit popover adjacent to the edit if there is a location
9062 // available that is onscreen and does not obscure the cursor. Otherwise,
9063 // place it adjacent to the cursor.
9064 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9065 .into_iter()
9066 .flatten()
9067 .find(|&start_row| {
9068 let end_row = start_row + line_count as u32;
9069 visible_row_range.contains(&start_row)
9070 && visible_row_range.contains(&end_row)
9071 && cursor_row
9072 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9073 })?;
9074
9075 content_origin
9076 + point(
9077 -scroll_pixel_position.x,
9078 row_target.as_f32() * line_height - scroll_pixel_position.y,
9079 )
9080 };
9081
9082 origin.x -= BORDER_WIDTH;
9083
9084 window.defer_draw(element, origin, 1);
9085
9086 // Do not return an element, since it will already be drawn due to defer_draw.
9087 None
9088 }
9089
9090 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9091 px(30.)
9092 }
9093
9094 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9095 if self.read_only(cx) {
9096 cx.theme().players().read_only()
9097 } else {
9098 self.style.as_ref().unwrap().local_player
9099 }
9100 }
9101
9102 fn render_edit_prediction_accept_keybind(
9103 &self,
9104 window: &mut Window,
9105 cx: &App,
9106 ) -> Option<AnyElement> {
9107 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9108 let accept_keystroke = accept_binding.keystroke()?;
9109
9110 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9111
9112 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9113 Color::Accent
9114 } else {
9115 Color::Muted
9116 };
9117
9118 h_flex()
9119 .px_0p5()
9120 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9121 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9122 .text_size(TextSize::XSmall.rems(cx))
9123 .child(h_flex().children(ui::render_modifiers(
9124 accept_keystroke.modifiers(),
9125 PlatformStyle::platform(),
9126 Some(modifiers_color),
9127 Some(IconSize::XSmall.rems().into()),
9128 true,
9129 )))
9130 .when(is_platform_style_mac, |parent| {
9131 parent.child(accept_keystroke.key().to_string())
9132 })
9133 .when(!is_platform_style_mac, |parent| {
9134 parent.child(
9135 Key::new(
9136 util::capitalize(accept_keystroke.key()),
9137 Some(Color::Default),
9138 )
9139 .size(Some(IconSize::XSmall.rems().into())),
9140 )
9141 })
9142 .into_any()
9143 .into()
9144 }
9145
9146 fn render_edit_prediction_line_popover(
9147 &self,
9148 label: impl Into<SharedString>,
9149 icon: Option<IconName>,
9150 window: &mut Window,
9151 cx: &App,
9152 ) -> Option<Stateful<Div>> {
9153 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9154
9155 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9156 let has_keybind = keybind.is_some();
9157
9158 let result = h_flex()
9159 .id("ep-line-popover")
9160 .py_0p5()
9161 .pl_1()
9162 .pr(padding_right)
9163 .gap_1()
9164 .rounded_md()
9165 .border_1()
9166 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9167 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9168 .shadow_xs()
9169 .when(!has_keybind, |el| {
9170 let status_colors = cx.theme().status();
9171
9172 el.bg(status_colors.error_background)
9173 .border_color(status_colors.error.opacity(0.6))
9174 .pl_2()
9175 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9176 .cursor_default()
9177 .hoverable_tooltip(move |_window, cx| {
9178 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9179 })
9180 })
9181 .children(keybind)
9182 .child(
9183 Label::new(label)
9184 .size(LabelSize::Small)
9185 .when(!has_keybind, |el| {
9186 el.color(cx.theme().status().error.into()).strikethrough()
9187 }),
9188 )
9189 .when(!has_keybind, |el| {
9190 el.child(
9191 h_flex().ml_1().child(
9192 Icon::new(IconName::Info)
9193 .size(IconSize::Small)
9194 .color(cx.theme().status().error.into()),
9195 ),
9196 )
9197 })
9198 .when_some(icon, |element, icon| {
9199 element.child(
9200 div()
9201 .mt(px(1.5))
9202 .child(Icon::new(icon).size(IconSize::Small)),
9203 )
9204 });
9205
9206 Some(result)
9207 }
9208
9209 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9210 let accent_color = cx.theme().colors().text_accent;
9211 let editor_bg_color = cx.theme().colors().editor_background;
9212 editor_bg_color.blend(accent_color.opacity(0.1))
9213 }
9214
9215 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9216 let accent_color = cx.theme().colors().text_accent;
9217 let editor_bg_color = cx.theme().colors().editor_background;
9218 editor_bg_color.blend(accent_color.opacity(0.6))
9219 }
9220 fn get_prediction_provider_icon_name(
9221 provider: &Option<RegisteredEditPredictionProvider>,
9222 ) -> IconName {
9223 match provider {
9224 Some(provider) => match provider.provider.name() {
9225 "copilot" => IconName::Copilot,
9226 "supermaven" => IconName::Supermaven,
9227 _ => IconName::ZedPredict,
9228 },
9229 None => IconName::ZedPredict,
9230 }
9231 }
9232
9233 fn render_edit_prediction_cursor_popover(
9234 &self,
9235 min_width: Pixels,
9236 max_width: Pixels,
9237 cursor_point: Point,
9238 style: &EditorStyle,
9239 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9240 _window: &Window,
9241 cx: &mut Context<Editor>,
9242 ) -> Option<AnyElement> {
9243 let provider = self.edit_prediction_provider.as_ref()?;
9244 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9245
9246 let is_refreshing = provider.provider.is_refreshing(cx);
9247
9248 fn pending_completion_container(icon: IconName) -> Div {
9249 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9250 }
9251
9252 let completion = match &self.active_edit_prediction {
9253 Some(prediction) => {
9254 if !self.has_visible_completions_menu() {
9255 const RADIUS: Pixels = px(6.);
9256 const BORDER_WIDTH: Pixels = px(1.);
9257
9258 return Some(
9259 h_flex()
9260 .elevation_2(cx)
9261 .border(BORDER_WIDTH)
9262 .border_color(cx.theme().colors().border)
9263 .when(accept_keystroke.is_none(), |el| {
9264 el.border_color(cx.theme().status().error)
9265 })
9266 .rounded(RADIUS)
9267 .rounded_tl(px(0.))
9268 .overflow_hidden()
9269 .child(div().px_1p5().child(match &prediction.completion {
9270 EditPrediction::Move { target, snapshot } => {
9271 use text::ToPoint as _;
9272 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9273 {
9274 Icon::new(IconName::ZedPredictDown)
9275 } else {
9276 Icon::new(IconName::ZedPredictUp)
9277 }
9278 }
9279 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9280 }))
9281 .child(
9282 h_flex()
9283 .gap_1()
9284 .py_1()
9285 .px_2()
9286 .rounded_r(RADIUS - BORDER_WIDTH)
9287 .border_l_1()
9288 .border_color(cx.theme().colors().border)
9289 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9290 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9291 el.child(
9292 Label::new("Hold")
9293 .size(LabelSize::Small)
9294 .when(accept_keystroke.is_none(), |el| {
9295 el.strikethrough()
9296 })
9297 .line_height_style(LineHeightStyle::UiLabel),
9298 )
9299 })
9300 .id("edit_prediction_cursor_popover_keybind")
9301 .when(accept_keystroke.is_none(), |el| {
9302 let status_colors = cx.theme().status();
9303
9304 el.bg(status_colors.error_background)
9305 .border_color(status_colors.error.opacity(0.6))
9306 .child(Icon::new(IconName::Info).color(Color::Error))
9307 .cursor_default()
9308 .hoverable_tooltip(move |_window, cx| {
9309 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9310 .into()
9311 })
9312 })
9313 .when_some(
9314 accept_keystroke.as_ref(),
9315 |el, accept_keystroke| {
9316 el.child(h_flex().children(ui::render_modifiers(
9317 accept_keystroke.modifiers(),
9318 PlatformStyle::platform(),
9319 Some(Color::Default),
9320 Some(IconSize::XSmall.rems().into()),
9321 false,
9322 )))
9323 },
9324 ),
9325 )
9326 .into_any(),
9327 );
9328 }
9329
9330 self.render_edit_prediction_cursor_popover_preview(
9331 prediction,
9332 cursor_point,
9333 style,
9334 cx,
9335 )?
9336 }
9337
9338 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9339 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9340 stale_completion,
9341 cursor_point,
9342 style,
9343 cx,
9344 )?,
9345
9346 None => pending_completion_container(provider_icon)
9347 .child(Label::new("...").size(LabelSize::Small)),
9348 },
9349
9350 None => pending_completion_container(provider_icon)
9351 .child(Label::new("...").size(LabelSize::Small)),
9352 };
9353
9354 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9355 completion
9356 .with_animation(
9357 "loading-completion",
9358 Animation::new(Duration::from_secs(2))
9359 .repeat()
9360 .with_easing(pulsating_between(0.4, 0.8)),
9361 |label, delta| label.opacity(delta),
9362 )
9363 .into_any_element()
9364 } else {
9365 completion.into_any_element()
9366 };
9367
9368 let has_completion = self.active_edit_prediction.is_some();
9369
9370 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9371 Some(
9372 h_flex()
9373 .min_w(min_width)
9374 .max_w(max_width)
9375 .flex_1()
9376 .elevation_2(cx)
9377 .border_color(cx.theme().colors().border)
9378 .child(
9379 div()
9380 .flex_1()
9381 .py_1()
9382 .px_2()
9383 .overflow_hidden()
9384 .child(completion),
9385 )
9386 .when_some(accept_keystroke, |el, accept_keystroke| {
9387 if !accept_keystroke.modifiers().modified() {
9388 return el;
9389 }
9390
9391 el.child(
9392 h_flex()
9393 .h_full()
9394 .border_l_1()
9395 .rounded_r_lg()
9396 .border_color(cx.theme().colors().border)
9397 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9398 .gap_1()
9399 .py_1()
9400 .px_2()
9401 .child(
9402 h_flex()
9403 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9404 .when(is_platform_style_mac, |parent| parent.gap_1())
9405 .child(h_flex().children(ui::render_modifiers(
9406 accept_keystroke.modifiers(),
9407 PlatformStyle::platform(),
9408 Some(if !has_completion {
9409 Color::Muted
9410 } else {
9411 Color::Default
9412 }),
9413 None,
9414 false,
9415 ))),
9416 )
9417 .child(Label::new("Preview").into_any_element())
9418 .opacity(if has_completion { 1.0 } else { 0.4 }),
9419 )
9420 })
9421 .into_any(),
9422 )
9423 }
9424
9425 fn render_edit_prediction_cursor_popover_preview(
9426 &self,
9427 completion: &EditPredictionState,
9428 cursor_point: Point,
9429 style: &EditorStyle,
9430 cx: &mut Context<Editor>,
9431 ) -> Option<Div> {
9432 use text::ToPoint as _;
9433
9434 fn render_relative_row_jump(
9435 prefix: impl Into<String>,
9436 current_row: u32,
9437 target_row: u32,
9438 ) -> Div {
9439 let (row_diff, arrow) = if target_row < current_row {
9440 (current_row - target_row, IconName::ArrowUp)
9441 } else {
9442 (target_row - current_row, IconName::ArrowDown)
9443 };
9444
9445 h_flex()
9446 .child(
9447 Label::new(format!("{}{}", prefix.into(), row_diff))
9448 .color(Color::Muted)
9449 .size(LabelSize::Small),
9450 )
9451 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9452 }
9453
9454 let supports_jump = self
9455 .edit_prediction_provider
9456 .as_ref()
9457 .map(|provider| provider.provider.supports_jump_to_edit())
9458 .unwrap_or(true);
9459
9460 match &completion.completion {
9461 EditPrediction::Move {
9462 target, snapshot, ..
9463 } => {
9464 if !supports_jump {
9465 return None;
9466 }
9467
9468 Some(
9469 h_flex()
9470 .px_2()
9471 .gap_2()
9472 .flex_1()
9473 .child(
9474 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9475 Icon::new(IconName::ZedPredictDown)
9476 } else {
9477 Icon::new(IconName::ZedPredictUp)
9478 },
9479 )
9480 .child(Label::new("Jump to Edit")),
9481 )
9482 }
9483
9484 EditPrediction::Edit {
9485 edits,
9486 edit_preview,
9487 snapshot,
9488 display_mode: _,
9489 } => {
9490 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9491
9492 let (highlighted_edits, has_more_lines) =
9493 if let Some(edit_preview) = edit_preview.as_ref() {
9494 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9495 .first_line_preview()
9496 } else {
9497 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9498 };
9499
9500 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9501 .with_default_highlights(&style.text, highlighted_edits.highlights);
9502
9503 let preview = h_flex()
9504 .gap_1()
9505 .min_w_16()
9506 .child(styled_text)
9507 .when(has_more_lines, |parent| parent.child("…"));
9508
9509 let left = if supports_jump && first_edit_row != cursor_point.row {
9510 render_relative_row_jump("", cursor_point.row, first_edit_row)
9511 .into_any_element()
9512 } else {
9513 let icon_name =
9514 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9515 Icon::new(icon_name).into_any_element()
9516 };
9517
9518 Some(
9519 h_flex()
9520 .h_full()
9521 .flex_1()
9522 .gap_2()
9523 .pr_1()
9524 .overflow_x_hidden()
9525 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9526 .child(left)
9527 .child(preview),
9528 )
9529 }
9530 }
9531 }
9532
9533 pub fn render_context_menu(
9534 &self,
9535 style: &EditorStyle,
9536 max_height_in_lines: u32,
9537 window: &mut Window,
9538 cx: &mut Context<Editor>,
9539 ) -> Option<AnyElement> {
9540 let menu = self.context_menu.borrow();
9541 let menu = menu.as_ref()?;
9542 if !menu.visible() {
9543 return None;
9544 };
9545 Some(menu.render(style, max_height_in_lines, window, cx))
9546 }
9547
9548 fn render_context_menu_aside(
9549 &mut self,
9550 max_size: Size<Pixels>,
9551 window: &mut Window,
9552 cx: &mut Context<Editor>,
9553 ) -> Option<AnyElement> {
9554 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9555 if menu.visible() {
9556 menu.render_aside(max_size, window, cx)
9557 } else {
9558 None
9559 }
9560 })
9561 }
9562
9563 fn hide_context_menu(
9564 &mut self,
9565 window: &mut Window,
9566 cx: &mut Context<Self>,
9567 ) -> Option<CodeContextMenu> {
9568 cx.notify();
9569 self.completion_tasks.clear();
9570 let context_menu = self.context_menu.borrow_mut().take();
9571 self.stale_edit_prediction_in_menu.take();
9572 self.update_visible_edit_prediction(window, cx);
9573 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9574 && let Some(completion_provider) = &self.completion_provider
9575 {
9576 completion_provider.selection_changed(None, window, cx);
9577 }
9578 context_menu
9579 }
9580
9581 fn show_snippet_choices(
9582 &mut self,
9583 choices: &Vec<String>,
9584 selection: Range<Anchor>,
9585 cx: &mut Context<Self>,
9586 ) {
9587 let Some((_, buffer, _)) = self
9588 .buffer()
9589 .read(cx)
9590 .excerpt_containing(selection.start, cx)
9591 else {
9592 return;
9593 };
9594 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9595 else {
9596 return;
9597 };
9598 if buffer != end_buffer {
9599 log::error!("expected anchor range to have matching buffer IDs");
9600 return;
9601 }
9602
9603 let id = post_inc(&mut self.next_completion_id);
9604 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9605 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9606 CompletionsMenu::new_snippet_choices(
9607 id,
9608 true,
9609 choices,
9610 selection,
9611 buffer,
9612 snippet_sort_order,
9613 ),
9614 ));
9615 }
9616
9617 pub fn insert_snippet(
9618 &mut self,
9619 insertion_ranges: &[Range<usize>],
9620 snippet: Snippet,
9621 window: &mut Window,
9622 cx: &mut Context<Self>,
9623 ) -> Result<()> {
9624 struct Tabstop<T> {
9625 is_end_tabstop: bool,
9626 ranges: Vec<Range<T>>,
9627 choices: Option<Vec<String>>,
9628 }
9629
9630 let tabstops = self.buffer.update(cx, |buffer, cx| {
9631 let snippet_text: Arc<str> = snippet.text.clone().into();
9632 let edits = insertion_ranges
9633 .iter()
9634 .cloned()
9635 .map(|range| (range, snippet_text.clone()));
9636 let autoindent_mode = AutoindentMode::Block {
9637 original_indent_columns: Vec::new(),
9638 };
9639 buffer.edit(edits, Some(autoindent_mode), cx);
9640
9641 let snapshot = &*buffer.read(cx);
9642 let snippet = &snippet;
9643 snippet
9644 .tabstops
9645 .iter()
9646 .map(|tabstop| {
9647 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9648 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9649 });
9650 let mut tabstop_ranges = tabstop
9651 .ranges
9652 .iter()
9653 .flat_map(|tabstop_range| {
9654 let mut delta = 0_isize;
9655 insertion_ranges.iter().map(move |insertion_range| {
9656 let insertion_start = insertion_range.start as isize + delta;
9657 delta +=
9658 snippet.text.len() as isize - insertion_range.len() as isize;
9659
9660 let start = ((insertion_start + tabstop_range.start) as usize)
9661 .min(snapshot.len());
9662 let end = ((insertion_start + tabstop_range.end) as usize)
9663 .min(snapshot.len());
9664 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9665 })
9666 })
9667 .collect::<Vec<_>>();
9668 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9669
9670 Tabstop {
9671 is_end_tabstop,
9672 ranges: tabstop_ranges,
9673 choices: tabstop.choices.clone(),
9674 }
9675 })
9676 .collect::<Vec<_>>()
9677 });
9678 if let Some(tabstop) = tabstops.first() {
9679 self.change_selections(Default::default(), window, cx, |s| {
9680 // Reverse order so that the first range is the newest created selection.
9681 // Completions will use it and autoscroll will prioritize it.
9682 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9683 });
9684
9685 if let Some(choices) = &tabstop.choices
9686 && let Some(selection) = tabstop.ranges.first()
9687 {
9688 self.show_snippet_choices(choices, selection.clone(), cx)
9689 }
9690
9691 // If we're already at the last tabstop and it's at the end of the snippet,
9692 // we're done, we don't need to keep the state around.
9693 if !tabstop.is_end_tabstop {
9694 let choices = tabstops
9695 .iter()
9696 .map(|tabstop| tabstop.choices.clone())
9697 .collect();
9698
9699 let ranges = tabstops
9700 .into_iter()
9701 .map(|tabstop| tabstop.ranges)
9702 .collect::<Vec<_>>();
9703
9704 self.snippet_stack.push(SnippetState {
9705 active_index: 0,
9706 ranges,
9707 choices,
9708 });
9709 }
9710
9711 // Check whether the just-entered snippet ends with an auto-closable bracket.
9712 if self.autoclose_regions.is_empty() {
9713 let snapshot = self.buffer.read(cx).snapshot(cx);
9714 let mut all_selections = self.selections.all::<Point>(cx);
9715 for selection in &mut all_selections {
9716 let selection_head = selection.head();
9717 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9718 continue;
9719 };
9720
9721 let mut bracket_pair = None;
9722 let max_lookup_length = scope
9723 .brackets()
9724 .map(|(pair, _)| {
9725 pair.start
9726 .as_str()
9727 .chars()
9728 .count()
9729 .max(pair.end.as_str().chars().count())
9730 })
9731 .max();
9732 if let Some(max_lookup_length) = max_lookup_length {
9733 let next_text = snapshot
9734 .chars_at(selection_head)
9735 .take(max_lookup_length)
9736 .collect::<String>();
9737 let prev_text = snapshot
9738 .reversed_chars_at(selection_head)
9739 .take(max_lookup_length)
9740 .collect::<String>();
9741
9742 for (pair, enabled) in scope.brackets() {
9743 if enabled
9744 && pair.close
9745 && prev_text.starts_with(pair.start.as_str())
9746 && next_text.starts_with(pair.end.as_str())
9747 {
9748 bracket_pair = Some(pair.clone());
9749 break;
9750 }
9751 }
9752 }
9753
9754 if let Some(pair) = bracket_pair {
9755 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9756 let autoclose_enabled =
9757 self.use_autoclose && snapshot_settings.use_autoclose;
9758 if autoclose_enabled {
9759 let start = snapshot.anchor_after(selection_head);
9760 let end = snapshot.anchor_after(selection_head);
9761 self.autoclose_regions.push(AutocloseRegion {
9762 selection_id: selection.id,
9763 range: start..end,
9764 pair,
9765 });
9766 }
9767 }
9768 }
9769 }
9770 }
9771 Ok(())
9772 }
9773
9774 pub fn move_to_next_snippet_tabstop(
9775 &mut self,
9776 window: &mut Window,
9777 cx: &mut Context<Self>,
9778 ) -> bool {
9779 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9780 }
9781
9782 pub fn move_to_prev_snippet_tabstop(
9783 &mut self,
9784 window: &mut Window,
9785 cx: &mut Context<Self>,
9786 ) -> bool {
9787 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9788 }
9789
9790 pub fn move_to_snippet_tabstop(
9791 &mut self,
9792 bias: Bias,
9793 window: &mut Window,
9794 cx: &mut Context<Self>,
9795 ) -> bool {
9796 if let Some(mut snippet) = self.snippet_stack.pop() {
9797 match bias {
9798 Bias::Left => {
9799 if snippet.active_index > 0 {
9800 snippet.active_index -= 1;
9801 } else {
9802 self.snippet_stack.push(snippet);
9803 return false;
9804 }
9805 }
9806 Bias::Right => {
9807 if snippet.active_index + 1 < snippet.ranges.len() {
9808 snippet.active_index += 1;
9809 } else {
9810 self.snippet_stack.push(snippet);
9811 return false;
9812 }
9813 }
9814 }
9815 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9816 self.change_selections(Default::default(), window, cx, |s| {
9817 // Reverse order so that the first range is the newest created selection.
9818 // Completions will use it and autoscroll will prioritize it.
9819 s.select_ranges(current_ranges.iter().rev().cloned())
9820 });
9821
9822 if let Some(choices) = &snippet.choices[snippet.active_index]
9823 && let Some(selection) = current_ranges.first()
9824 {
9825 self.show_snippet_choices(choices, selection.clone(), cx);
9826 }
9827
9828 // If snippet state is not at the last tabstop, push it back on the stack
9829 if snippet.active_index + 1 < snippet.ranges.len() {
9830 self.snippet_stack.push(snippet);
9831 }
9832 return true;
9833 }
9834 }
9835
9836 false
9837 }
9838
9839 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9840 self.transact(window, cx, |this, window, cx| {
9841 this.select_all(&SelectAll, window, cx);
9842 this.insert("", window, cx);
9843 });
9844 }
9845
9846 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9847 if self.read_only(cx) {
9848 return;
9849 }
9850 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9851 self.transact(window, cx, |this, window, cx| {
9852 this.select_autoclose_pair(window, cx);
9853 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9854 if !this.linked_edit_ranges.is_empty() {
9855 let selections = this.selections.all::<MultiBufferPoint>(cx);
9856 let snapshot = this.buffer.read(cx).snapshot(cx);
9857
9858 for selection in selections.iter() {
9859 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9860 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9861 if selection_start.buffer_id != selection_end.buffer_id {
9862 continue;
9863 }
9864 if let Some(ranges) =
9865 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9866 {
9867 for (buffer, entries) in ranges {
9868 linked_ranges.entry(buffer).or_default().extend(entries);
9869 }
9870 }
9871 }
9872 }
9873
9874 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9875 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9876 for selection in &mut selections {
9877 if selection.is_empty() {
9878 let old_head = selection.head();
9879 let mut new_head =
9880 movement::left(&display_map, old_head.to_display_point(&display_map))
9881 .to_point(&display_map);
9882 if let Some((buffer, line_buffer_range)) = display_map
9883 .buffer_snapshot
9884 .buffer_line_for_row(MultiBufferRow(old_head.row))
9885 {
9886 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9887 let indent_len = match indent_size.kind {
9888 IndentKind::Space => {
9889 buffer.settings_at(line_buffer_range.start, cx).tab_size
9890 }
9891 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9892 };
9893 if old_head.column <= indent_size.len && old_head.column > 0 {
9894 let indent_len = indent_len.get();
9895 new_head = cmp::min(
9896 new_head,
9897 MultiBufferPoint::new(
9898 old_head.row,
9899 ((old_head.column - 1) / indent_len) * indent_len,
9900 ),
9901 );
9902 }
9903 }
9904
9905 selection.set_head(new_head, SelectionGoal::None);
9906 }
9907 }
9908
9909 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9910 this.insert("", window, cx);
9911 let empty_str: Arc<str> = Arc::from("");
9912 for (buffer, edits) in linked_ranges {
9913 let snapshot = buffer.read(cx).snapshot();
9914 use text::ToPoint as TP;
9915
9916 let edits = edits
9917 .into_iter()
9918 .map(|range| {
9919 let end_point = TP::to_point(&range.end, &snapshot);
9920 let mut start_point = TP::to_point(&range.start, &snapshot);
9921
9922 if end_point == start_point {
9923 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9924 .saturating_sub(1);
9925 start_point =
9926 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9927 };
9928
9929 (start_point..end_point, empty_str.clone())
9930 })
9931 .sorted_by_key(|(range, _)| range.start)
9932 .collect::<Vec<_>>();
9933 buffer.update(cx, |this, cx| {
9934 this.edit(edits, None, cx);
9935 })
9936 }
9937 this.refresh_edit_prediction(true, false, window, cx);
9938 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9939 });
9940 }
9941
9942 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9943 if self.read_only(cx) {
9944 return;
9945 }
9946 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9947 self.transact(window, cx, |this, window, cx| {
9948 this.change_selections(Default::default(), window, cx, |s| {
9949 s.move_with(|map, selection| {
9950 if selection.is_empty() {
9951 let cursor = movement::right(map, selection.head());
9952 selection.end = cursor;
9953 selection.reversed = true;
9954 selection.goal = SelectionGoal::None;
9955 }
9956 })
9957 });
9958 this.insert("", window, cx);
9959 this.refresh_edit_prediction(true, false, window, cx);
9960 });
9961 }
9962
9963 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9964 if self.mode.is_single_line() {
9965 cx.propagate();
9966 return;
9967 }
9968
9969 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9970 if self.move_to_prev_snippet_tabstop(window, cx) {
9971 return;
9972 }
9973 self.outdent(&Outdent, window, cx);
9974 }
9975
9976 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9977 if self.mode.is_single_line() {
9978 cx.propagate();
9979 return;
9980 }
9981
9982 if self.move_to_next_snippet_tabstop(window, cx) {
9983 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9984 return;
9985 }
9986 if self.read_only(cx) {
9987 return;
9988 }
9989 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9990 let mut selections = self.selections.all_adjusted(cx);
9991 let buffer = self.buffer.read(cx);
9992 let snapshot = buffer.snapshot(cx);
9993 let rows_iter = selections.iter().map(|s| s.head().row);
9994 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9995
9996 let has_some_cursor_in_whitespace = selections
9997 .iter()
9998 .filter(|selection| selection.is_empty())
9999 .any(|selection| {
10000 let cursor = selection.head();
10001 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10002 cursor.column < current_indent.len
10003 });
10004
10005 let mut edits = Vec::new();
10006 let mut prev_edited_row = 0;
10007 let mut row_delta = 0;
10008 for selection in &mut selections {
10009 if selection.start.row != prev_edited_row {
10010 row_delta = 0;
10011 }
10012 prev_edited_row = selection.end.row;
10013
10014 // If the selection is non-empty, then increase the indentation of the selected lines.
10015 if !selection.is_empty() {
10016 row_delta =
10017 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10018 continue;
10019 }
10020
10021 let cursor = selection.head();
10022 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10023 if let Some(suggested_indent) =
10024 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10025 {
10026 // Don't do anything if already at suggested indent
10027 // and there is any other cursor which is not
10028 if has_some_cursor_in_whitespace
10029 && cursor.column == current_indent.len
10030 && current_indent.len == suggested_indent.len
10031 {
10032 continue;
10033 }
10034
10035 // Adjust line and move cursor to suggested indent
10036 // if cursor is not at suggested indent
10037 if cursor.column < suggested_indent.len
10038 && cursor.column <= current_indent.len
10039 && current_indent.len <= suggested_indent.len
10040 {
10041 selection.start = Point::new(cursor.row, suggested_indent.len);
10042 selection.end = selection.start;
10043 if row_delta == 0 {
10044 edits.extend(Buffer::edit_for_indent_size_adjustment(
10045 cursor.row,
10046 current_indent,
10047 suggested_indent,
10048 ));
10049 row_delta = suggested_indent.len - current_indent.len;
10050 }
10051 continue;
10052 }
10053
10054 // If current indent is more than suggested indent
10055 // only move cursor to current indent and skip indent
10056 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10057 selection.start = Point::new(cursor.row, current_indent.len);
10058 selection.end = selection.start;
10059 continue;
10060 }
10061 }
10062
10063 // Otherwise, insert a hard or soft tab.
10064 let settings = buffer.language_settings_at(cursor, cx);
10065 let tab_size = if settings.hard_tabs {
10066 IndentSize::tab()
10067 } else {
10068 let tab_size = settings.tab_size.get();
10069 let indent_remainder = snapshot
10070 .text_for_range(Point::new(cursor.row, 0)..cursor)
10071 .flat_map(str::chars)
10072 .fold(row_delta % tab_size, |counter: u32, c| {
10073 if c == '\t' {
10074 0
10075 } else {
10076 (counter + 1) % tab_size
10077 }
10078 });
10079
10080 let chars_to_next_tab_stop = tab_size - indent_remainder;
10081 IndentSize::spaces(chars_to_next_tab_stop)
10082 };
10083 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10084 selection.end = selection.start;
10085 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10086 row_delta += tab_size.len;
10087 }
10088
10089 self.transact(window, cx, |this, window, cx| {
10090 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10091 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10092 this.refresh_edit_prediction(true, false, window, cx);
10093 });
10094 }
10095
10096 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10097 if self.read_only(cx) {
10098 return;
10099 }
10100 if self.mode.is_single_line() {
10101 cx.propagate();
10102 return;
10103 }
10104
10105 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10106 let mut selections = self.selections.all::<Point>(cx);
10107 let mut prev_edited_row = 0;
10108 let mut row_delta = 0;
10109 let mut edits = Vec::new();
10110 let buffer = self.buffer.read(cx);
10111 let snapshot = buffer.snapshot(cx);
10112 for selection in &mut selections {
10113 if selection.start.row != prev_edited_row {
10114 row_delta = 0;
10115 }
10116 prev_edited_row = selection.end.row;
10117
10118 row_delta =
10119 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10120 }
10121
10122 self.transact(window, cx, |this, window, cx| {
10123 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10124 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10125 });
10126 }
10127
10128 fn indent_selection(
10129 buffer: &MultiBuffer,
10130 snapshot: &MultiBufferSnapshot,
10131 selection: &mut Selection<Point>,
10132 edits: &mut Vec<(Range<Point>, String)>,
10133 delta_for_start_row: u32,
10134 cx: &App,
10135 ) -> u32 {
10136 let settings = buffer.language_settings_at(selection.start, cx);
10137 let tab_size = settings.tab_size.get();
10138 let indent_kind = if settings.hard_tabs {
10139 IndentKind::Tab
10140 } else {
10141 IndentKind::Space
10142 };
10143 let mut start_row = selection.start.row;
10144 let mut end_row = selection.end.row + 1;
10145
10146 // If a selection ends at the beginning of a line, don't indent
10147 // that last line.
10148 if selection.end.column == 0 && selection.end.row > selection.start.row {
10149 end_row -= 1;
10150 }
10151
10152 // Avoid re-indenting a row that has already been indented by a
10153 // previous selection, but still update this selection's column
10154 // to reflect that indentation.
10155 if delta_for_start_row > 0 {
10156 start_row += 1;
10157 selection.start.column += delta_for_start_row;
10158 if selection.end.row == selection.start.row {
10159 selection.end.column += delta_for_start_row;
10160 }
10161 }
10162
10163 let mut delta_for_end_row = 0;
10164 let has_multiple_rows = start_row + 1 != end_row;
10165 for row in start_row..end_row {
10166 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10167 let indent_delta = match (current_indent.kind, indent_kind) {
10168 (IndentKind::Space, IndentKind::Space) => {
10169 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10170 IndentSize::spaces(columns_to_next_tab_stop)
10171 }
10172 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10173 (_, IndentKind::Tab) => IndentSize::tab(),
10174 };
10175
10176 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10177 0
10178 } else {
10179 selection.start.column
10180 };
10181 let row_start = Point::new(row, start);
10182 edits.push((
10183 row_start..row_start,
10184 indent_delta.chars().collect::<String>(),
10185 ));
10186
10187 // Update this selection's endpoints to reflect the indentation.
10188 if row == selection.start.row {
10189 selection.start.column += indent_delta.len;
10190 }
10191 if row == selection.end.row {
10192 selection.end.column += indent_delta.len;
10193 delta_for_end_row = indent_delta.len;
10194 }
10195 }
10196
10197 if selection.start.row == selection.end.row {
10198 delta_for_start_row + delta_for_end_row
10199 } else {
10200 delta_for_end_row
10201 }
10202 }
10203
10204 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10205 if self.read_only(cx) {
10206 return;
10207 }
10208 if self.mode.is_single_line() {
10209 cx.propagate();
10210 return;
10211 }
10212
10213 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10214 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10215 let selections = self.selections.all::<Point>(cx);
10216 let mut deletion_ranges = Vec::new();
10217 let mut last_outdent = None;
10218 {
10219 let buffer = self.buffer.read(cx);
10220 let snapshot = buffer.snapshot(cx);
10221 for selection in &selections {
10222 let settings = buffer.language_settings_at(selection.start, cx);
10223 let tab_size = settings.tab_size.get();
10224 let mut rows = selection.spanned_rows(false, &display_map);
10225
10226 // Avoid re-outdenting a row that has already been outdented by a
10227 // previous selection.
10228 if let Some(last_row) = last_outdent
10229 && last_row == rows.start
10230 {
10231 rows.start = rows.start.next_row();
10232 }
10233 let has_multiple_rows = rows.len() > 1;
10234 for row in rows.iter_rows() {
10235 let indent_size = snapshot.indent_size_for_line(row);
10236 if indent_size.len > 0 {
10237 let deletion_len = match indent_size.kind {
10238 IndentKind::Space => {
10239 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10240 if columns_to_prev_tab_stop == 0 {
10241 tab_size
10242 } else {
10243 columns_to_prev_tab_stop
10244 }
10245 }
10246 IndentKind::Tab => 1,
10247 };
10248 let start = if has_multiple_rows
10249 || deletion_len > selection.start.column
10250 || indent_size.len < selection.start.column
10251 {
10252 0
10253 } else {
10254 selection.start.column - deletion_len
10255 };
10256 deletion_ranges.push(
10257 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10258 );
10259 last_outdent = Some(row);
10260 }
10261 }
10262 }
10263 }
10264
10265 self.transact(window, cx, |this, window, cx| {
10266 this.buffer.update(cx, |buffer, cx| {
10267 let empty_str: Arc<str> = Arc::default();
10268 buffer.edit(
10269 deletion_ranges
10270 .into_iter()
10271 .map(|range| (range, empty_str.clone())),
10272 None,
10273 cx,
10274 );
10275 });
10276 let selections = this.selections.all::<usize>(cx);
10277 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10278 });
10279 }
10280
10281 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10282 if self.read_only(cx) {
10283 return;
10284 }
10285 if self.mode.is_single_line() {
10286 cx.propagate();
10287 return;
10288 }
10289
10290 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10291 let selections = self
10292 .selections
10293 .all::<usize>(cx)
10294 .into_iter()
10295 .map(|s| s.range());
10296
10297 self.transact(window, cx, |this, window, cx| {
10298 this.buffer.update(cx, |buffer, cx| {
10299 buffer.autoindent_ranges(selections, cx);
10300 });
10301 let selections = this.selections.all::<usize>(cx);
10302 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10303 });
10304 }
10305
10306 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10307 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10308 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10309 let selections = self.selections.all::<Point>(cx);
10310
10311 let mut new_cursors = Vec::new();
10312 let mut edit_ranges = Vec::new();
10313 let mut selections = selections.iter().peekable();
10314 while let Some(selection) = selections.next() {
10315 let mut rows = selection.spanned_rows(false, &display_map);
10316 let goal_display_column = selection.head().to_display_point(&display_map).column();
10317
10318 // Accumulate contiguous regions of rows that we want to delete.
10319 while let Some(next_selection) = selections.peek() {
10320 let next_rows = next_selection.spanned_rows(false, &display_map);
10321 if next_rows.start <= rows.end {
10322 rows.end = next_rows.end;
10323 selections.next().unwrap();
10324 } else {
10325 break;
10326 }
10327 }
10328
10329 let buffer = &display_map.buffer_snapshot;
10330 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10331 let edit_end;
10332 let cursor_buffer_row;
10333 if buffer.max_point().row >= rows.end.0 {
10334 // If there's a line after the range, delete the \n from the end of the row range
10335 // and position the cursor on the next line.
10336 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10337 cursor_buffer_row = rows.end;
10338 } else {
10339 // If there isn't a line after the range, delete the \n from the line before the
10340 // start of the row range and position the cursor there.
10341 edit_start = edit_start.saturating_sub(1);
10342 edit_end = buffer.len();
10343 cursor_buffer_row = rows.start.previous_row();
10344 }
10345
10346 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10347 *cursor.column_mut() =
10348 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10349
10350 new_cursors.push((
10351 selection.id,
10352 buffer.anchor_after(cursor.to_point(&display_map)),
10353 ));
10354 edit_ranges.push(edit_start..edit_end);
10355 }
10356
10357 self.transact(window, cx, |this, window, cx| {
10358 let buffer = this.buffer.update(cx, |buffer, cx| {
10359 let empty_str: Arc<str> = Arc::default();
10360 buffer.edit(
10361 edit_ranges
10362 .into_iter()
10363 .map(|range| (range, empty_str.clone())),
10364 None,
10365 cx,
10366 );
10367 buffer.snapshot(cx)
10368 });
10369 let new_selections = new_cursors
10370 .into_iter()
10371 .map(|(id, cursor)| {
10372 let cursor = cursor.to_point(&buffer);
10373 Selection {
10374 id,
10375 start: cursor,
10376 end: cursor,
10377 reversed: false,
10378 goal: SelectionGoal::None,
10379 }
10380 })
10381 .collect();
10382
10383 this.change_selections(Default::default(), window, cx, |s| {
10384 s.select(new_selections);
10385 });
10386 });
10387 }
10388
10389 pub fn join_lines_impl(
10390 &mut self,
10391 insert_whitespace: bool,
10392 window: &mut Window,
10393 cx: &mut Context<Self>,
10394 ) {
10395 if self.read_only(cx) {
10396 return;
10397 }
10398 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10399 for selection in self.selections.all::<Point>(cx) {
10400 let start = MultiBufferRow(selection.start.row);
10401 // Treat single line selections as if they include the next line. Otherwise this action
10402 // would do nothing for single line selections individual cursors.
10403 let end = if selection.start.row == selection.end.row {
10404 MultiBufferRow(selection.start.row + 1)
10405 } else {
10406 MultiBufferRow(selection.end.row)
10407 };
10408
10409 if let Some(last_row_range) = row_ranges.last_mut()
10410 && start <= last_row_range.end
10411 {
10412 last_row_range.end = end;
10413 continue;
10414 }
10415 row_ranges.push(start..end);
10416 }
10417
10418 let snapshot = self.buffer.read(cx).snapshot(cx);
10419 let mut cursor_positions = Vec::new();
10420 for row_range in &row_ranges {
10421 let anchor = snapshot.anchor_before(Point::new(
10422 row_range.end.previous_row().0,
10423 snapshot.line_len(row_range.end.previous_row()),
10424 ));
10425 cursor_positions.push(anchor..anchor);
10426 }
10427
10428 self.transact(window, cx, |this, window, cx| {
10429 for row_range in row_ranges.into_iter().rev() {
10430 for row in row_range.iter_rows().rev() {
10431 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10432 let next_line_row = row.next_row();
10433 let indent = snapshot.indent_size_for_line(next_line_row);
10434 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10435
10436 let replace =
10437 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10438 " "
10439 } else {
10440 ""
10441 };
10442
10443 this.buffer.update(cx, |buffer, cx| {
10444 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10445 });
10446 }
10447 }
10448
10449 this.change_selections(Default::default(), window, cx, |s| {
10450 s.select_anchor_ranges(cursor_positions)
10451 });
10452 });
10453 }
10454
10455 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10456 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10457 self.join_lines_impl(true, window, cx);
10458 }
10459
10460 pub fn sort_lines_case_sensitive(
10461 &mut self,
10462 _: &SortLinesCaseSensitive,
10463 window: &mut Window,
10464 cx: &mut Context<Self>,
10465 ) {
10466 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10467 }
10468
10469 pub fn sort_lines_by_length(
10470 &mut self,
10471 _: &SortLinesByLength,
10472 window: &mut Window,
10473 cx: &mut Context<Self>,
10474 ) {
10475 self.manipulate_immutable_lines(window, cx, |lines| {
10476 lines.sort_by_key(|&line| line.chars().count())
10477 })
10478 }
10479
10480 pub fn sort_lines_case_insensitive(
10481 &mut self,
10482 _: &SortLinesCaseInsensitive,
10483 window: &mut Window,
10484 cx: &mut Context<Self>,
10485 ) {
10486 self.manipulate_immutable_lines(window, cx, |lines| {
10487 lines.sort_by_key(|line| line.to_lowercase())
10488 })
10489 }
10490
10491 pub fn unique_lines_case_insensitive(
10492 &mut self,
10493 _: &UniqueLinesCaseInsensitive,
10494 window: &mut Window,
10495 cx: &mut Context<Self>,
10496 ) {
10497 self.manipulate_immutable_lines(window, cx, |lines| {
10498 let mut seen = HashSet::default();
10499 lines.retain(|line| seen.insert(line.to_lowercase()));
10500 })
10501 }
10502
10503 pub fn unique_lines_case_sensitive(
10504 &mut self,
10505 _: &UniqueLinesCaseSensitive,
10506 window: &mut Window,
10507 cx: &mut Context<Self>,
10508 ) {
10509 self.manipulate_immutable_lines(window, cx, |lines| {
10510 let mut seen = HashSet::default();
10511 lines.retain(|line| seen.insert(*line));
10512 })
10513 }
10514
10515 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10516 let snapshot = self.buffer.read(cx).snapshot(cx);
10517 for selection in self.selections.disjoint_anchors_arc().iter() {
10518 if snapshot
10519 .language_at(selection.start)
10520 .and_then(|lang| lang.config().wrap_characters.as_ref())
10521 .is_some()
10522 {
10523 return true;
10524 }
10525 }
10526 false
10527 }
10528
10529 fn wrap_selections_in_tag(
10530 &mut self,
10531 _: &WrapSelectionsInTag,
10532 window: &mut Window,
10533 cx: &mut Context<Self>,
10534 ) {
10535 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10536
10537 let snapshot = self.buffer.read(cx).snapshot(cx);
10538
10539 let mut edits = Vec::new();
10540 let mut boundaries = Vec::new();
10541
10542 for selection in self.selections.all::<Point>(cx).iter() {
10543 let Some(wrap_config) = snapshot
10544 .language_at(selection.start)
10545 .and_then(|lang| lang.config().wrap_characters.clone())
10546 else {
10547 continue;
10548 };
10549
10550 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10551 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10552
10553 let start_before = snapshot.anchor_before(selection.start);
10554 let end_after = snapshot.anchor_after(selection.end);
10555
10556 edits.push((start_before..start_before, open_tag));
10557 edits.push((end_after..end_after, close_tag));
10558
10559 boundaries.push((
10560 start_before,
10561 end_after,
10562 wrap_config.start_prefix.len(),
10563 wrap_config.end_suffix.len(),
10564 ));
10565 }
10566
10567 if edits.is_empty() {
10568 return;
10569 }
10570
10571 self.transact(window, cx, |this, window, cx| {
10572 let buffer = this.buffer.update(cx, |buffer, cx| {
10573 buffer.edit(edits, None, cx);
10574 buffer.snapshot(cx)
10575 });
10576
10577 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10578 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10579 boundaries.into_iter()
10580 {
10581 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10582 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10583 new_selections.push(open_offset..open_offset);
10584 new_selections.push(close_offset..close_offset);
10585 }
10586
10587 this.change_selections(Default::default(), window, cx, |s| {
10588 s.select_ranges(new_selections);
10589 });
10590
10591 this.request_autoscroll(Autoscroll::fit(), cx);
10592 });
10593 }
10594
10595 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10596 let Some(project) = self.project.clone() else {
10597 return;
10598 };
10599 self.reload(project, window, cx)
10600 .detach_and_notify_err(window, cx);
10601 }
10602
10603 pub fn restore_file(
10604 &mut self,
10605 _: &::git::RestoreFile,
10606 window: &mut Window,
10607 cx: &mut Context<Self>,
10608 ) {
10609 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10610 let mut buffer_ids = HashSet::default();
10611 let snapshot = self.buffer().read(cx).snapshot(cx);
10612 for selection in self.selections.all::<usize>(cx) {
10613 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10614 }
10615
10616 let buffer = self.buffer().read(cx);
10617 let ranges = buffer_ids
10618 .into_iter()
10619 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10620 .collect::<Vec<_>>();
10621
10622 self.restore_hunks_in_ranges(ranges, window, cx);
10623 }
10624
10625 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10626 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10627 let selections = self
10628 .selections
10629 .all(cx)
10630 .into_iter()
10631 .map(|s| s.range())
10632 .collect();
10633 self.restore_hunks_in_ranges(selections, window, cx);
10634 }
10635
10636 pub fn restore_hunks_in_ranges(
10637 &mut self,
10638 ranges: Vec<Range<Point>>,
10639 window: &mut Window,
10640 cx: &mut Context<Editor>,
10641 ) {
10642 let mut revert_changes = HashMap::default();
10643 let chunk_by = self
10644 .snapshot(window, cx)
10645 .hunks_for_ranges(ranges)
10646 .into_iter()
10647 .chunk_by(|hunk| hunk.buffer_id);
10648 for (buffer_id, hunks) in &chunk_by {
10649 let hunks = hunks.collect::<Vec<_>>();
10650 for hunk in &hunks {
10651 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10652 }
10653 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10654 }
10655 drop(chunk_by);
10656 if !revert_changes.is_empty() {
10657 self.transact(window, cx, |editor, window, cx| {
10658 editor.restore(revert_changes, window, cx);
10659 });
10660 }
10661 }
10662
10663 pub fn open_active_item_in_terminal(
10664 &mut self,
10665 _: &OpenInTerminal,
10666 window: &mut Window,
10667 cx: &mut Context<Self>,
10668 ) {
10669 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10670 let project_path = buffer.read(cx).project_path(cx)?;
10671 let project = self.project()?.read(cx);
10672 let entry = project.entry_for_path(&project_path, cx)?;
10673 let parent = match &entry.canonical_path {
10674 Some(canonical_path) => canonical_path.to_path_buf(),
10675 None => project.absolute_path(&project_path, cx)?,
10676 }
10677 .parent()?
10678 .to_path_buf();
10679 Some(parent)
10680 }) {
10681 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10682 }
10683 }
10684
10685 fn set_breakpoint_context_menu(
10686 &mut self,
10687 display_row: DisplayRow,
10688 position: Option<Anchor>,
10689 clicked_point: gpui::Point<Pixels>,
10690 window: &mut Window,
10691 cx: &mut Context<Self>,
10692 ) {
10693 let source = self
10694 .buffer
10695 .read(cx)
10696 .snapshot(cx)
10697 .anchor_before(Point::new(display_row.0, 0u32));
10698
10699 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10700
10701 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10702 self,
10703 source,
10704 clicked_point,
10705 context_menu,
10706 window,
10707 cx,
10708 );
10709 }
10710
10711 fn add_edit_breakpoint_block(
10712 &mut self,
10713 anchor: Anchor,
10714 breakpoint: &Breakpoint,
10715 edit_action: BreakpointPromptEditAction,
10716 window: &mut Window,
10717 cx: &mut Context<Self>,
10718 ) {
10719 let weak_editor = cx.weak_entity();
10720 let bp_prompt = cx.new(|cx| {
10721 BreakpointPromptEditor::new(
10722 weak_editor,
10723 anchor,
10724 breakpoint.clone(),
10725 edit_action,
10726 window,
10727 cx,
10728 )
10729 });
10730
10731 let height = bp_prompt.update(cx, |this, cx| {
10732 this.prompt
10733 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10734 });
10735 let cloned_prompt = bp_prompt.clone();
10736 let blocks = vec![BlockProperties {
10737 style: BlockStyle::Sticky,
10738 placement: BlockPlacement::Above(anchor),
10739 height: Some(height),
10740 render: Arc::new(move |cx| {
10741 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10742 cloned_prompt.clone().into_any_element()
10743 }),
10744 priority: 0,
10745 }];
10746
10747 let focus_handle = bp_prompt.focus_handle(cx);
10748 window.focus(&focus_handle);
10749
10750 let block_ids = self.insert_blocks(blocks, None, cx);
10751 bp_prompt.update(cx, |prompt, _| {
10752 prompt.add_block_ids(block_ids);
10753 });
10754 }
10755
10756 pub(crate) fn breakpoint_at_row(
10757 &self,
10758 row: u32,
10759 window: &mut Window,
10760 cx: &mut Context<Self>,
10761 ) -> Option<(Anchor, Breakpoint)> {
10762 let snapshot = self.snapshot(window, cx);
10763 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10764
10765 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10766 }
10767
10768 pub(crate) fn breakpoint_at_anchor(
10769 &self,
10770 breakpoint_position: Anchor,
10771 snapshot: &EditorSnapshot,
10772 cx: &mut Context<Self>,
10773 ) -> Option<(Anchor, Breakpoint)> {
10774 let buffer = self
10775 .buffer
10776 .read(cx)
10777 .buffer_for_anchor(breakpoint_position, cx)?;
10778
10779 let enclosing_excerpt = breakpoint_position.excerpt_id;
10780 let buffer_snapshot = buffer.read(cx).snapshot();
10781
10782 let row = buffer_snapshot
10783 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10784 .row;
10785
10786 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10787 let anchor_end = snapshot
10788 .buffer_snapshot
10789 .anchor_after(Point::new(row, line_len));
10790
10791 self.breakpoint_store
10792 .as_ref()?
10793 .read_with(cx, |breakpoint_store, cx| {
10794 breakpoint_store
10795 .breakpoints(
10796 &buffer,
10797 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10798 &buffer_snapshot,
10799 cx,
10800 )
10801 .next()
10802 .and_then(|(bp, _)| {
10803 let breakpoint_row = buffer_snapshot
10804 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10805 .row;
10806
10807 if breakpoint_row == row {
10808 snapshot
10809 .buffer_snapshot
10810 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10811 .map(|position| (position, bp.bp.clone()))
10812 } else {
10813 None
10814 }
10815 })
10816 })
10817 }
10818
10819 pub fn edit_log_breakpoint(
10820 &mut self,
10821 _: &EditLogBreakpoint,
10822 window: &mut Window,
10823 cx: &mut Context<Self>,
10824 ) {
10825 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10826 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10827 message: None,
10828 state: BreakpointState::Enabled,
10829 condition: None,
10830 hit_condition: None,
10831 });
10832
10833 self.add_edit_breakpoint_block(
10834 anchor,
10835 &breakpoint,
10836 BreakpointPromptEditAction::Log,
10837 window,
10838 cx,
10839 );
10840 }
10841 }
10842
10843 fn breakpoints_at_cursors(
10844 &self,
10845 window: &mut Window,
10846 cx: &mut Context<Self>,
10847 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10848 let snapshot = self.snapshot(window, cx);
10849 let cursors = self
10850 .selections
10851 .disjoint_anchors_arc()
10852 .iter()
10853 .map(|selection| {
10854 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10855
10856 let breakpoint_position = self
10857 .breakpoint_at_row(cursor_position.row, window, cx)
10858 .map(|bp| bp.0)
10859 .unwrap_or_else(|| {
10860 snapshot
10861 .display_snapshot
10862 .buffer_snapshot
10863 .anchor_after(Point::new(cursor_position.row, 0))
10864 });
10865
10866 let breakpoint = self
10867 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10868 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10869
10870 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10871 })
10872 // 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.
10873 .collect::<HashMap<Anchor, _>>();
10874
10875 cursors.into_iter().collect()
10876 }
10877
10878 pub fn enable_breakpoint(
10879 &mut self,
10880 _: &crate::actions::EnableBreakpoint,
10881 window: &mut Window,
10882 cx: &mut Context<Self>,
10883 ) {
10884 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10885 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10886 continue;
10887 };
10888 self.edit_breakpoint_at_anchor(
10889 anchor,
10890 breakpoint,
10891 BreakpointEditAction::InvertState,
10892 cx,
10893 );
10894 }
10895 }
10896
10897 pub fn disable_breakpoint(
10898 &mut self,
10899 _: &crate::actions::DisableBreakpoint,
10900 window: &mut Window,
10901 cx: &mut Context<Self>,
10902 ) {
10903 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10904 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10905 continue;
10906 };
10907 self.edit_breakpoint_at_anchor(
10908 anchor,
10909 breakpoint,
10910 BreakpointEditAction::InvertState,
10911 cx,
10912 );
10913 }
10914 }
10915
10916 pub fn toggle_breakpoint(
10917 &mut self,
10918 _: &crate::actions::ToggleBreakpoint,
10919 window: &mut Window,
10920 cx: &mut Context<Self>,
10921 ) {
10922 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10923 if let Some(breakpoint) = breakpoint {
10924 self.edit_breakpoint_at_anchor(
10925 anchor,
10926 breakpoint,
10927 BreakpointEditAction::Toggle,
10928 cx,
10929 );
10930 } else {
10931 self.edit_breakpoint_at_anchor(
10932 anchor,
10933 Breakpoint::new_standard(),
10934 BreakpointEditAction::Toggle,
10935 cx,
10936 );
10937 }
10938 }
10939 }
10940
10941 pub fn edit_breakpoint_at_anchor(
10942 &mut self,
10943 breakpoint_position: Anchor,
10944 breakpoint: Breakpoint,
10945 edit_action: BreakpointEditAction,
10946 cx: &mut Context<Self>,
10947 ) {
10948 let Some(breakpoint_store) = &self.breakpoint_store else {
10949 return;
10950 };
10951
10952 let Some(buffer) = self
10953 .buffer
10954 .read(cx)
10955 .buffer_for_anchor(breakpoint_position, cx)
10956 else {
10957 return;
10958 };
10959
10960 breakpoint_store.update(cx, |breakpoint_store, cx| {
10961 breakpoint_store.toggle_breakpoint(
10962 buffer,
10963 BreakpointWithPosition {
10964 position: breakpoint_position.text_anchor,
10965 bp: breakpoint,
10966 },
10967 edit_action,
10968 cx,
10969 );
10970 });
10971
10972 cx.notify();
10973 }
10974
10975 #[cfg(any(test, feature = "test-support"))]
10976 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10977 self.breakpoint_store.clone()
10978 }
10979
10980 pub fn prepare_restore_change(
10981 &self,
10982 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10983 hunk: &MultiBufferDiffHunk,
10984 cx: &mut App,
10985 ) -> Option<()> {
10986 if hunk.is_created_file() {
10987 return None;
10988 }
10989 let buffer = self.buffer.read(cx);
10990 let diff = buffer.diff_for(hunk.buffer_id)?;
10991 let buffer = buffer.buffer(hunk.buffer_id)?;
10992 let buffer = buffer.read(cx);
10993 let original_text = diff
10994 .read(cx)
10995 .base_text()
10996 .as_rope()
10997 .slice(hunk.diff_base_byte_range.clone());
10998 let buffer_snapshot = buffer.snapshot();
10999 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11000 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11001 probe
11002 .0
11003 .start
11004 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11005 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11006 }) {
11007 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11008 Some(())
11009 } else {
11010 None
11011 }
11012 }
11013
11014 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11015 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11016 }
11017
11018 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11019 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11020 }
11021
11022 fn manipulate_lines<M>(
11023 &mut self,
11024 window: &mut Window,
11025 cx: &mut Context<Self>,
11026 mut manipulate: M,
11027 ) where
11028 M: FnMut(&str) -> LineManipulationResult,
11029 {
11030 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11031
11032 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11033 let buffer = self.buffer.read(cx).snapshot(cx);
11034
11035 let mut edits = Vec::new();
11036
11037 let selections = self.selections.all::<Point>(cx);
11038 let mut selections = selections.iter().peekable();
11039 let mut contiguous_row_selections = Vec::new();
11040 let mut new_selections = Vec::new();
11041 let mut added_lines = 0;
11042 let mut removed_lines = 0;
11043
11044 while let Some(selection) = selections.next() {
11045 let (start_row, end_row) = consume_contiguous_rows(
11046 &mut contiguous_row_selections,
11047 selection,
11048 &display_map,
11049 &mut selections,
11050 );
11051
11052 let start_point = Point::new(start_row.0, 0);
11053 let end_point = Point::new(
11054 end_row.previous_row().0,
11055 buffer.line_len(end_row.previous_row()),
11056 );
11057 let text = buffer
11058 .text_for_range(start_point..end_point)
11059 .collect::<String>();
11060
11061 let LineManipulationResult {
11062 new_text,
11063 line_count_before,
11064 line_count_after,
11065 } = manipulate(&text);
11066
11067 edits.push((start_point..end_point, new_text));
11068
11069 // Selections must change based on added and removed line count
11070 let start_row =
11071 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11072 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11073 new_selections.push(Selection {
11074 id: selection.id,
11075 start: start_row,
11076 end: end_row,
11077 goal: SelectionGoal::None,
11078 reversed: selection.reversed,
11079 });
11080
11081 if line_count_after > line_count_before {
11082 added_lines += line_count_after - line_count_before;
11083 } else if line_count_before > line_count_after {
11084 removed_lines += line_count_before - line_count_after;
11085 }
11086 }
11087
11088 self.transact(window, cx, |this, window, cx| {
11089 let buffer = this.buffer.update(cx, |buffer, cx| {
11090 buffer.edit(edits, None, cx);
11091 buffer.snapshot(cx)
11092 });
11093
11094 // Recalculate offsets on newly edited buffer
11095 let new_selections = new_selections
11096 .iter()
11097 .map(|s| {
11098 let start_point = Point::new(s.start.0, 0);
11099 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11100 Selection {
11101 id: s.id,
11102 start: buffer.point_to_offset(start_point),
11103 end: buffer.point_to_offset(end_point),
11104 goal: s.goal,
11105 reversed: s.reversed,
11106 }
11107 })
11108 .collect();
11109
11110 this.change_selections(Default::default(), window, cx, |s| {
11111 s.select(new_selections);
11112 });
11113
11114 this.request_autoscroll(Autoscroll::fit(), cx);
11115 });
11116 }
11117
11118 fn manipulate_immutable_lines<Fn>(
11119 &mut self,
11120 window: &mut Window,
11121 cx: &mut Context<Self>,
11122 mut callback: Fn,
11123 ) where
11124 Fn: FnMut(&mut Vec<&str>),
11125 {
11126 self.manipulate_lines(window, cx, |text| {
11127 let mut lines: Vec<&str> = text.split('\n').collect();
11128 let line_count_before = lines.len();
11129
11130 callback(&mut lines);
11131
11132 LineManipulationResult {
11133 new_text: lines.join("\n"),
11134 line_count_before,
11135 line_count_after: lines.len(),
11136 }
11137 });
11138 }
11139
11140 fn manipulate_mutable_lines<Fn>(
11141 &mut self,
11142 window: &mut Window,
11143 cx: &mut Context<Self>,
11144 mut callback: Fn,
11145 ) where
11146 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11147 {
11148 self.manipulate_lines(window, cx, |text| {
11149 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11150 let line_count_before = lines.len();
11151
11152 callback(&mut lines);
11153
11154 LineManipulationResult {
11155 new_text: lines.join("\n"),
11156 line_count_before,
11157 line_count_after: lines.len(),
11158 }
11159 });
11160 }
11161
11162 pub fn convert_indentation_to_spaces(
11163 &mut self,
11164 _: &ConvertIndentationToSpaces,
11165 window: &mut Window,
11166 cx: &mut Context<Self>,
11167 ) {
11168 let settings = self.buffer.read(cx).language_settings(cx);
11169 let tab_size = settings.tab_size.get() as usize;
11170
11171 self.manipulate_mutable_lines(window, cx, |lines| {
11172 // Allocates a reasonably sized scratch buffer once for the whole loop
11173 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11174 // Avoids recomputing spaces that could be inserted many times
11175 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11176 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11177 .collect();
11178
11179 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11180 let mut chars = line.as_ref().chars();
11181 let mut col = 0;
11182 let mut changed = false;
11183
11184 for ch in chars.by_ref() {
11185 match ch {
11186 ' ' => {
11187 reindented_line.push(' ');
11188 col += 1;
11189 }
11190 '\t' => {
11191 // \t are converted to spaces depending on the current column
11192 let spaces_len = tab_size - (col % tab_size);
11193 reindented_line.extend(&space_cache[spaces_len - 1]);
11194 col += spaces_len;
11195 changed = true;
11196 }
11197 _ => {
11198 // If we dont append before break, the character is consumed
11199 reindented_line.push(ch);
11200 break;
11201 }
11202 }
11203 }
11204
11205 if !changed {
11206 reindented_line.clear();
11207 continue;
11208 }
11209 // Append the rest of the line and replace old reference with new one
11210 reindented_line.extend(chars);
11211 *line = Cow::Owned(reindented_line.clone());
11212 reindented_line.clear();
11213 }
11214 });
11215 }
11216
11217 pub fn convert_indentation_to_tabs(
11218 &mut self,
11219 _: &ConvertIndentationToTabs,
11220 window: &mut Window,
11221 cx: &mut Context<Self>,
11222 ) {
11223 let settings = self.buffer.read(cx).language_settings(cx);
11224 let tab_size = settings.tab_size.get() as usize;
11225
11226 self.manipulate_mutable_lines(window, cx, |lines| {
11227 // Allocates a reasonably sized buffer once for the whole loop
11228 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11229 // Avoids recomputing spaces that could be inserted many times
11230 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11231 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11232 .collect();
11233
11234 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11235 let mut chars = line.chars();
11236 let mut spaces_count = 0;
11237 let mut first_non_indent_char = None;
11238 let mut changed = false;
11239
11240 for ch in chars.by_ref() {
11241 match ch {
11242 ' ' => {
11243 // Keep track of spaces. Append \t when we reach tab_size
11244 spaces_count += 1;
11245 changed = true;
11246 if spaces_count == tab_size {
11247 reindented_line.push('\t');
11248 spaces_count = 0;
11249 }
11250 }
11251 '\t' => {
11252 reindented_line.push('\t');
11253 spaces_count = 0;
11254 }
11255 _ => {
11256 // Dont append it yet, we might have remaining spaces
11257 first_non_indent_char = Some(ch);
11258 break;
11259 }
11260 }
11261 }
11262
11263 if !changed {
11264 reindented_line.clear();
11265 continue;
11266 }
11267 // Remaining spaces that didn't make a full tab stop
11268 if spaces_count > 0 {
11269 reindented_line.extend(&space_cache[spaces_count - 1]);
11270 }
11271 // If we consume an extra character that was not indentation, add it back
11272 if let Some(extra_char) = first_non_indent_char {
11273 reindented_line.push(extra_char);
11274 }
11275 // Append the rest of the line and replace old reference with new one
11276 reindented_line.extend(chars);
11277 *line = Cow::Owned(reindented_line.clone());
11278 reindented_line.clear();
11279 }
11280 });
11281 }
11282
11283 pub fn convert_to_upper_case(
11284 &mut self,
11285 _: &ConvertToUpperCase,
11286 window: &mut Window,
11287 cx: &mut Context<Self>,
11288 ) {
11289 self.manipulate_text(window, cx, |text| text.to_uppercase())
11290 }
11291
11292 pub fn convert_to_lower_case(
11293 &mut self,
11294 _: &ConvertToLowerCase,
11295 window: &mut Window,
11296 cx: &mut Context<Self>,
11297 ) {
11298 self.manipulate_text(window, cx, |text| text.to_lowercase())
11299 }
11300
11301 pub fn convert_to_title_case(
11302 &mut self,
11303 _: &ConvertToTitleCase,
11304 window: &mut Window,
11305 cx: &mut Context<Self>,
11306 ) {
11307 self.manipulate_text(window, cx, |text| {
11308 text.split('\n')
11309 .map(|line| line.to_case(Case::Title))
11310 .join("\n")
11311 })
11312 }
11313
11314 pub fn convert_to_snake_case(
11315 &mut self,
11316 _: &ConvertToSnakeCase,
11317 window: &mut Window,
11318 cx: &mut Context<Self>,
11319 ) {
11320 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11321 }
11322
11323 pub fn convert_to_kebab_case(
11324 &mut self,
11325 _: &ConvertToKebabCase,
11326 window: &mut Window,
11327 cx: &mut Context<Self>,
11328 ) {
11329 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11330 }
11331
11332 pub fn convert_to_upper_camel_case(
11333 &mut self,
11334 _: &ConvertToUpperCamelCase,
11335 window: &mut Window,
11336 cx: &mut Context<Self>,
11337 ) {
11338 self.manipulate_text(window, cx, |text| {
11339 text.split('\n')
11340 .map(|line| line.to_case(Case::UpperCamel))
11341 .join("\n")
11342 })
11343 }
11344
11345 pub fn convert_to_lower_camel_case(
11346 &mut self,
11347 _: &ConvertToLowerCamelCase,
11348 window: &mut Window,
11349 cx: &mut Context<Self>,
11350 ) {
11351 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11352 }
11353
11354 pub fn convert_to_opposite_case(
11355 &mut self,
11356 _: &ConvertToOppositeCase,
11357 window: &mut Window,
11358 cx: &mut Context<Self>,
11359 ) {
11360 self.manipulate_text(window, cx, |text| {
11361 text.chars()
11362 .fold(String::with_capacity(text.len()), |mut t, c| {
11363 if c.is_uppercase() {
11364 t.extend(c.to_lowercase());
11365 } else {
11366 t.extend(c.to_uppercase());
11367 }
11368 t
11369 })
11370 })
11371 }
11372
11373 pub fn convert_to_sentence_case(
11374 &mut self,
11375 _: &ConvertToSentenceCase,
11376 window: &mut Window,
11377 cx: &mut Context<Self>,
11378 ) {
11379 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11380 }
11381
11382 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11383 self.manipulate_text(window, cx, |text| {
11384 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11385 if has_upper_case_characters {
11386 text.to_lowercase()
11387 } else {
11388 text.to_uppercase()
11389 }
11390 })
11391 }
11392
11393 pub fn convert_to_rot13(
11394 &mut self,
11395 _: &ConvertToRot13,
11396 window: &mut Window,
11397 cx: &mut Context<Self>,
11398 ) {
11399 self.manipulate_text(window, cx, |text| {
11400 text.chars()
11401 .map(|c| match c {
11402 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11403 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11404 _ => c,
11405 })
11406 .collect()
11407 })
11408 }
11409
11410 pub fn convert_to_rot47(
11411 &mut self,
11412 _: &ConvertToRot47,
11413 window: &mut Window,
11414 cx: &mut Context<Self>,
11415 ) {
11416 self.manipulate_text(window, cx, |text| {
11417 text.chars()
11418 .map(|c| {
11419 let code_point = c as u32;
11420 if code_point >= 33 && code_point <= 126 {
11421 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11422 }
11423 c
11424 })
11425 .collect()
11426 })
11427 }
11428
11429 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11430 where
11431 Fn: FnMut(&str) -> String,
11432 {
11433 let buffer = self.buffer.read(cx).snapshot(cx);
11434
11435 let mut new_selections = Vec::new();
11436 let mut edits = Vec::new();
11437 let mut selection_adjustment = 0i32;
11438
11439 for selection in self.selections.all_adjusted(cx) {
11440 let selection_is_empty = selection.is_empty();
11441
11442 let (start, end) = if selection_is_empty {
11443 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11444 (word_range.start, word_range.end)
11445 } else {
11446 (
11447 buffer.point_to_offset(selection.start),
11448 buffer.point_to_offset(selection.end),
11449 )
11450 };
11451
11452 let text = buffer.text_for_range(start..end).collect::<String>();
11453 let old_length = text.len() as i32;
11454 let text = callback(&text);
11455
11456 new_selections.push(Selection {
11457 start: (start as i32 - selection_adjustment) as usize,
11458 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11459 goal: SelectionGoal::None,
11460 id: selection.id,
11461 reversed: selection.reversed,
11462 });
11463
11464 selection_adjustment += old_length - text.len() as i32;
11465
11466 edits.push((start..end, text));
11467 }
11468
11469 self.transact(window, cx, |this, window, cx| {
11470 this.buffer.update(cx, |buffer, cx| {
11471 buffer.edit(edits, None, cx);
11472 });
11473
11474 this.change_selections(Default::default(), window, cx, |s| {
11475 s.select(new_selections);
11476 });
11477
11478 this.request_autoscroll(Autoscroll::fit(), cx);
11479 });
11480 }
11481
11482 pub fn move_selection_on_drop(
11483 &mut self,
11484 selection: &Selection<Anchor>,
11485 target: DisplayPoint,
11486 is_cut: bool,
11487 window: &mut Window,
11488 cx: &mut Context<Self>,
11489 ) {
11490 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11491 let buffer = &display_map.buffer_snapshot;
11492 let mut edits = Vec::new();
11493 let insert_point = display_map
11494 .clip_point(target, Bias::Left)
11495 .to_point(&display_map);
11496 let text = buffer
11497 .text_for_range(selection.start..selection.end)
11498 .collect::<String>();
11499 if is_cut {
11500 edits.push(((selection.start..selection.end), String::new()));
11501 }
11502 let insert_anchor = buffer.anchor_before(insert_point);
11503 edits.push(((insert_anchor..insert_anchor), text));
11504 let last_edit_start = insert_anchor.bias_left(buffer);
11505 let last_edit_end = insert_anchor.bias_right(buffer);
11506 self.transact(window, cx, |this, window, cx| {
11507 this.buffer.update(cx, |buffer, cx| {
11508 buffer.edit(edits, None, cx);
11509 });
11510 this.change_selections(Default::default(), window, cx, |s| {
11511 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11512 });
11513 });
11514 }
11515
11516 pub fn clear_selection_drag_state(&mut self) {
11517 self.selection_drag_state = SelectionDragState::None;
11518 }
11519
11520 pub fn duplicate(
11521 &mut self,
11522 upwards: bool,
11523 whole_lines: bool,
11524 window: &mut Window,
11525 cx: &mut Context<Self>,
11526 ) {
11527 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11528
11529 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11530 let buffer = &display_map.buffer_snapshot;
11531 let selections = self.selections.all::<Point>(cx);
11532
11533 let mut edits = Vec::new();
11534 let mut selections_iter = selections.iter().peekable();
11535 while let Some(selection) = selections_iter.next() {
11536 let mut rows = selection.spanned_rows(false, &display_map);
11537 // duplicate line-wise
11538 if whole_lines || selection.start == selection.end {
11539 // Avoid duplicating the same lines twice.
11540 while let Some(next_selection) = selections_iter.peek() {
11541 let next_rows = next_selection.spanned_rows(false, &display_map);
11542 if next_rows.start < rows.end {
11543 rows.end = next_rows.end;
11544 selections_iter.next().unwrap();
11545 } else {
11546 break;
11547 }
11548 }
11549
11550 // Copy the text from the selected row region and splice it either at the start
11551 // or end of the region.
11552 let start = Point::new(rows.start.0, 0);
11553 let end = Point::new(
11554 rows.end.previous_row().0,
11555 buffer.line_len(rows.end.previous_row()),
11556 );
11557 let text = buffer
11558 .text_for_range(start..end)
11559 .chain(Some("\n"))
11560 .collect::<String>();
11561 let insert_location = if upwards {
11562 Point::new(rows.end.0, 0)
11563 } else {
11564 start
11565 };
11566 edits.push((insert_location..insert_location, text));
11567 } else {
11568 // duplicate character-wise
11569 let start = selection.start;
11570 let end = selection.end;
11571 let text = buffer.text_for_range(start..end).collect::<String>();
11572 edits.push((selection.end..selection.end, text));
11573 }
11574 }
11575
11576 self.transact(window, cx, |this, _, cx| {
11577 this.buffer.update(cx, |buffer, cx| {
11578 buffer.edit(edits, None, cx);
11579 });
11580
11581 this.request_autoscroll(Autoscroll::fit(), cx);
11582 });
11583 }
11584
11585 pub fn duplicate_line_up(
11586 &mut self,
11587 _: &DuplicateLineUp,
11588 window: &mut Window,
11589 cx: &mut Context<Self>,
11590 ) {
11591 self.duplicate(true, true, window, cx);
11592 }
11593
11594 pub fn duplicate_line_down(
11595 &mut self,
11596 _: &DuplicateLineDown,
11597 window: &mut Window,
11598 cx: &mut Context<Self>,
11599 ) {
11600 self.duplicate(false, true, window, cx);
11601 }
11602
11603 pub fn duplicate_selection(
11604 &mut self,
11605 _: &DuplicateSelection,
11606 window: &mut Window,
11607 cx: &mut Context<Self>,
11608 ) {
11609 self.duplicate(false, false, window, cx);
11610 }
11611
11612 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11613 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11614 if self.mode.is_single_line() {
11615 cx.propagate();
11616 return;
11617 }
11618
11619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11620 let buffer = self.buffer.read(cx).snapshot(cx);
11621
11622 let mut edits = Vec::new();
11623 let mut unfold_ranges = Vec::new();
11624 let mut refold_creases = Vec::new();
11625
11626 let selections = self.selections.all::<Point>(cx);
11627 let mut selections = selections.iter().peekable();
11628 let mut contiguous_row_selections = Vec::new();
11629 let mut new_selections = Vec::new();
11630
11631 while let Some(selection) = selections.next() {
11632 // Find all the selections that span a contiguous row range
11633 let (start_row, end_row) = consume_contiguous_rows(
11634 &mut contiguous_row_selections,
11635 selection,
11636 &display_map,
11637 &mut selections,
11638 );
11639
11640 // Move the text spanned by the row range to be before the line preceding the row range
11641 if start_row.0 > 0 {
11642 let range_to_move = Point::new(
11643 start_row.previous_row().0,
11644 buffer.line_len(start_row.previous_row()),
11645 )
11646 ..Point::new(
11647 end_row.previous_row().0,
11648 buffer.line_len(end_row.previous_row()),
11649 );
11650 let insertion_point = display_map
11651 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11652 .0;
11653
11654 // Don't move lines across excerpts
11655 if buffer
11656 .excerpt_containing(insertion_point..range_to_move.end)
11657 .is_some()
11658 {
11659 let text = buffer
11660 .text_for_range(range_to_move.clone())
11661 .flat_map(|s| s.chars())
11662 .skip(1)
11663 .chain(['\n'])
11664 .collect::<String>();
11665
11666 edits.push((
11667 buffer.anchor_after(range_to_move.start)
11668 ..buffer.anchor_before(range_to_move.end),
11669 String::new(),
11670 ));
11671 let insertion_anchor = buffer.anchor_after(insertion_point);
11672 edits.push((insertion_anchor..insertion_anchor, text));
11673
11674 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11675
11676 // Move selections up
11677 new_selections.extend(contiguous_row_selections.drain(..).map(
11678 |mut selection| {
11679 selection.start.row -= row_delta;
11680 selection.end.row -= row_delta;
11681 selection
11682 },
11683 ));
11684
11685 // Move folds up
11686 unfold_ranges.push(range_to_move.clone());
11687 for fold in display_map.folds_in_range(
11688 buffer.anchor_before(range_to_move.start)
11689 ..buffer.anchor_after(range_to_move.end),
11690 ) {
11691 let mut start = fold.range.start.to_point(&buffer);
11692 let mut end = fold.range.end.to_point(&buffer);
11693 start.row -= row_delta;
11694 end.row -= row_delta;
11695 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11696 }
11697 }
11698 }
11699
11700 // If we didn't move line(s), preserve the existing selections
11701 new_selections.append(&mut contiguous_row_selections);
11702 }
11703
11704 self.transact(window, cx, |this, window, cx| {
11705 this.unfold_ranges(&unfold_ranges, true, true, cx);
11706 this.buffer.update(cx, |buffer, cx| {
11707 for (range, text) in edits {
11708 buffer.edit([(range, text)], None, cx);
11709 }
11710 });
11711 this.fold_creases(refold_creases, true, window, cx);
11712 this.change_selections(Default::default(), window, cx, |s| {
11713 s.select(new_selections);
11714 })
11715 });
11716 }
11717
11718 pub fn move_line_down(
11719 &mut self,
11720 _: &MoveLineDown,
11721 window: &mut Window,
11722 cx: &mut Context<Self>,
11723 ) {
11724 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11725 if self.mode.is_single_line() {
11726 cx.propagate();
11727 return;
11728 }
11729
11730 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11731 let buffer = self.buffer.read(cx).snapshot(cx);
11732
11733 let mut edits = Vec::new();
11734 let mut unfold_ranges = Vec::new();
11735 let mut refold_creases = Vec::new();
11736
11737 let selections = self.selections.all::<Point>(cx);
11738 let mut selections = selections.iter().peekable();
11739 let mut contiguous_row_selections = Vec::new();
11740 let mut new_selections = Vec::new();
11741
11742 while let Some(selection) = selections.next() {
11743 // Find all the selections that span a contiguous row range
11744 let (start_row, end_row) = consume_contiguous_rows(
11745 &mut contiguous_row_selections,
11746 selection,
11747 &display_map,
11748 &mut selections,
11749 );
11750
11751 // Move the text spanned by the row range to be after the last line of the row range
11752 if end_row.0 <= buffer.max_point().row {
11753 let range_to_move =
11754 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11755 let insertion_point = display_map
11756 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11757 .0;
11758
11759 // Don't move lines across excerpt boundaries
11760 if buffer
11761 .excerpt_containing(range_to_move.start..insertion_point)
11762 .is_some()
11763 {
11764 let mut text = String::from("\n");
11765 text.extend(buffer.text_for_range(range_to_move.clone()));
11766 text.pop(); // Drop trailing newline
11767 edits.push((
11768 buffer.anchor_after(range_to_move.start)
11769 ..buffer.anchor_before(range_to_move.end),
11770 String::new(),
11771 ));
11772 let insertion_anchor = buffer.anchor_after(insertion_point);
11773 edits.push((insertion_anchor..insertion_anchor, text));
11774
11775 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11776
11777 // Move selections down
11778 new_selections.extend(contiguous_row_selections.drain(..).map(
11779 |mut selection| {
11780 selection.start.row += row_delta;
11781 selection.end.row += row_delta;
11782 selection
11783 },
11784 ));
11785
11786 // Move folds down
11787 unfold_ranges.push(range_to_move.clone());
11788 for fold in display_map.folds_in_range(
11789 buffer.anchor_before(range_to_move.start)
11790 ..buffer.anchor_after(range_to_move.end),
11791 ) {
11792 let mut start = fold.range.start.to_point(&buffer);
11793 let mut end = fold.range.end.to_point(&buffer);
11794 start.row += row_delta;
11795 end.row += row_delta;
11796 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11797 }
11798 }
11799 }
11800
11801 // If we didn't move line(s), preserve the existing selections
11802 new_selections.append(&mut contiguous_row_selections);
11803 }
11804
11805 self.transact(window, cx, |this, window, cx| {
11806 this.unfold_ranges(&unfold_ranges, true, true, cx);
11807 this.buffer.update(cx, |buffer, cx| {
11808 for (range, text) in edits {
11809 buffer.edit([(range, text)], None, cx);
11810 }
11811 });
11812 this.fold_creases(refold_creases, true, window, cx);
11813 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11814 });
11815 }
11816
11817 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11818 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11819 let text_layout_details = &self.text_layout_details(window);
11820 self.transact(window, cx, |this, window, cx| {
11821 let edits = this.change_selections(Default::default(), window, cx, |s| {
11822 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11823 s.move_with(|display_map, selection| {
11824 if !selection.is_empty() {
11825 return;
11826 }
11827
11828 let mut head = selection.head();
11829 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11830 if head.column() == display_map.line_len(head.row()) {
11831 transpose_offset = display_map
11832 .buffer_snapshot
11833 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11834 }
11835
11836 if transpose_offset == 0 {
11837 return;
11838 }
11839
11840 *head.column_mut() += 1;
11841 head = display_map.clip_point(head, Bias::Right);
11842 let goal = SelectionGoal::HorizontalPosition(
11843 display_map
11844 .x_for_display_point(head, text_layout_details)
11845 .into(),
11846 );
11847 selection.collapse_to(head, goal);
11848
11849 let transpose_start = display_map
11850 .buffer_snapshot
11851 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11852 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11853 let transpose_end = display_map
11854 .buffer_snapshot
11855 .clip_offset(transpose_offset + 1, Bias::Right);
11856 if let Some(ch) =
11857 display_map.buffer_snapshot.chars_at(transpose_start).next()
11858 {
11859 edits.push((transpose_start..transpose_offset, String::new()));
11860 edits.push((transpose_end..transpose_end, ch.to_string()));
11861 }
11862 }
11863 });
11864 edits
11865 });
11866 this.buffer
11867 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11868 let selections = this.selections.all::<usize>(cx);
11869 this.change_selections(Default::default(), window, cx, |s| {
11870 s.select(selections);
11871 });
11872 });
11873 }
11874
11875 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11876 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11877 if self.mode.is_single_line() {
11878 cx.propagate();
11879 return;
11880 }
11881
11882 self.rewrap_impl(RewrapOptions::default(), cx)
11883 }
11884
11885 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11886 let buffer = self.buffer.read(cx).snapshot(cx);
11887 let selections = self.selections.all::<Point>(cx);
11888
11889 #[derive(Clone, Debug, PartialEq)]
11890 enum CommentFormat {
11891 /// single line comment, with prefix for line
11892 Line(String),
11893 /// single line within a block comment, with prefix for line
11894 BlockLine(String),
11895 /// a single line of a block comment that includes the initial delimiter
11896 BlockCommentWithStart(BlockCommentConfig),
11897 /// a single line of a block comment that includes the ending delimiter
11898 BlockCommentWithEnd(BlockCommentConfig),
11899 }
11900
11901 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11902 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11903 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11904 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11905 .peekable();
11906
11907 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11908 row
11909 } else {
11910 return Vec::new();
11911 };
11912
11913 let language_settings = buffer.language_settings_at(selection.head(), cx);
11914 let language_scope = buffer.language_scope_at(selection.head());
11915
11916 let indent_and_prefix_for_row =
11917 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11918 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11919 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11920 &language_scope
11921 {
11922 let indent_end = Point::new(row, indent.len);
11923 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11924 let line_text_after_indent = buffer
11925 .text_for_range(indent_end..line_end)
11926 .collect::<String>();
11927
11928 let is_within_comment_override = buffer
11929 .language_scope_at(indent_end)
11930 .is_some_and(|scope| scope.override_name() == Some("comment"));
11931 let comment_delimiters = if is_within_comment_override {
11932 // we are within a comment syntax node, but we don't
11933 // yet know what kind of comment: block, doc or line
11934 match (
11935 language_scope.documentation_comment(),
11936 language_scope.block_comment(),
11937 ) {
11938 (Some(config), _) | (_, Some(config))
11939 if buffer.contains_str_at(indent_end, &config.start) =>
11940 {
11941 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11942 }
11943 (Some(config), _) | (_, Some(config))
11944 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11945 {
11946 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11947 }
11948 (Some(config), _) | (_, Some(config))
11949 if buffer.contains_str_at(indent_end, &config.prefix) =>
11950 {
11951 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11952 }
11953 (_, _) => language_scope
11954 .line_comment_prefixes()
11955 .iter()
11956 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11957 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11958 }
11959 } else {
11960 // we not in an overridden comment node, but we may
11961 // be within a non-overridden line comment node
11962 language_scope
11963 .line_comment_prefixes()
11964 .iter()
11965 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11966 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11967 };
11968
11969 let rewrap_prefix = language_scope
11970 .rewrap_prefixes()
11971 .iter()
11972 .find_map(|prefix_regex| {
11973 prefix_regex.find(&line_text_after_indent).map(|mat| {
11974 if mat.start() == 0 {
11975 Some(mat.as_str().to_string())
11976 } else {
11977 None
11978 }
11979 })
11980 })
11981 .flatten();
11982 (comment_delimiters, rewrap_prefix)
11983 } else {
11984 (None, None)
11985 };
11986 (indent, comment_prefix, rewrap_prefix)
11987 };
11988
11989 let mut ranges = Vec::new();
11990 let from_empty_selection = selection.is_empty();
11991
11992 let mut current_range_start = first_row;
11993 let mut prev_row = first_row;
11994 let (
11995 mut current_range_indent,
11996 mut current_range_comment_delimiters,
11997 mut current_range_rewrap_prefix,
11998 ) = indent_and_prefix_for_row(first_row);
11999
12000 for row in non_blank_rows_iter.skip(1) {
12001 let has_paragraph_break = row > prev_row + 1;
12002
12003 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12004 indent_and_prefix_for_row(row);
12005
12006 let has_indent_change = row_indent != current_range_indent;
12007 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12008
12009 let has_boundary_change = has_comment_change
12010 || row_rewrap_prefix.is_some()
12011 || (has_indent_change && current_range_comment_delimiters.is_some());
12012
12013 if has_paragraph_break || has_boundary_change {
12014 ranges.push((
12015 language_settings.clone(),
12016 Point::new(current_range_start, 0)
12017 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12018 current_range_indent,
12019 current_range_comment_delimiters.clone(),
12020 current_range_rewrap_prefix.clone(),
12021 from_empty_selection,
12022 ));
12023 current_range_start = row;
12024 current_range_indent = row_indent;
12025 current_range_comment_delimiters = row_comment_delimiters;
12026 current_range_rewrap_prefix = row_rewrap_prefix;
12027 }
12028 prev_row = row;
12029 }
12030
12031 ranges.push((
12032 language_settings.clone(),
12033 Point::new(current_range_start, 0)
12034 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12035 current_range_indent,
12036 current_range_comment_delimiters,
12037 current_range_rewrap_prefix,
12038 from_empty_selection,
12039 ));
12040
12041 ranges
12042 });
12043
12044 let mut edits = Vec::new();
12045 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12046
12047 for (
12048 language_settings,
12049 wrap_range,
12050 mut indent_size,
12051 comment_prefix,
12052 rewrap_prefix,
12053 from_empty_selection,
12054 ) in wrap_ranges
12055 {
12056 let mut start_row = wrap_range.start.row;
12057 let mut end_row = wrap_range.end.row;
12058
12059 // Skip selections that overlap with a range that has already been rewrapped.
12060 let selection_range = start_row..end_row;
12061 if rewrapped_row_ranges
12062 .iter()
12063 .any(|range| range.overlaps(&selection_range))
12064 {
12065 continue;
12066 }
12067
12068 let tab_size = language_settings.tab_size;
12069
12070 let (line_prefix, inside_comment) = match &comment_prefix {
12071 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12072 (Some(prefix.as_str()), true)
12073 }
12074 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12075 (Some(prefix.as_ref()), true)
12076 }
12077 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12078 start: _,
12079 end: _,
12080 prefix,
12081 tab_size,
12082 })) => {
12083 indent_size.len += tab_size;
12084 (Some(prefix.as_ref()), true)
12085 }
12086 None => (None, false),
12087 };
12088 let indent_prefix = indent_size.chars().collect::<String>();
12089 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12090
12091 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12092 RewrapBehavior::InComments => inside_comment,
12093 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12094 RewrapBehavior::Anywhere => true,
12095 };
12096
12097 let should_rewrap = options.override_language_settings
12098 || allow_rewrap_based_on_language
12099 || self.hard_wrap.is_some();
12100 if !should_rewrap {
12101 continue;
12102 }
12103
12104 if from_empty_selection {
12105 'expand_upwards: while start_row > 0 {
12106 let prev_row = start_row - 1;
12107 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12108 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12109 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12110 {
12111 start_row = prev_row;
12112 } else {
12113 break 'expand_upwards;
12114 }
12115 }
12116
12117 'expand_downwards: while end_row < buffer.max_point().row {
12118 let next_row = end_row + 1;
12119 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12120 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12121 && !buffer.is_line_blank(MultiBufferRow(next_row))
12122 {
12123 end_row = next_row;
12124 } else {
12125 break 'expand_downwards;
12126 }
12127 }
12128 }
12129
12130 let start = Point::new(start_row, 0);
12131 let start_offset = start.to_offset(&buffer);
12132 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12133 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12134 let mut first_line_delimiter = None;
12135 let mut last_line_delimiter = None;
12136 let Some(lines_without_prefixes) = selection_text
12137 .lines()
12138 .enumerate()
12139 .map(|(ix, line)| {
12140 let line_trimmed = line.trim_start();
12141 if rewrap_prefix.is_some() && ix > 0 {
12142 Ok(line_trimmed)
12143 } else if let Some(
12144 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12145 start,
12146 prefix,
12147 end,
12148 tab_size,
12149 })
12150 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12151 start,
12152 prefix,
12153 end,
12154 tab_size,
12155 }),
12156 ) = &comment_prefix
12157 {
12158 let line_trimmed = line_trimmed
12159 .strip_prefix(start.as_ref())
12160 .map(|s| {
12161 let mut indent_size = indent_size;
12162 indent_size.len -= tab_size;
12163 let indent_prefix: String = indent_size.chars().collect();
12164 first_line_delimiter = Some((indent_prefix, start));
12165 s.trim_start()
12166 })
12167 .unwrap_or(line_trimmed);
12168 let line_trimmed = line_trimmed
12169 .strip_suffix(end.as_ref())
12170 .map(|s| {
12171 last_line_delimiter = Some(end);
12172 s.trim_end()
12173 })
12174 .unwrap_or(line_trimmed);
12175 let line_trimmed = line_trimmed
12176 .strip_prefix(prefix.as_ref())
12177 .unwrap_or(line_trimmed);
12178 Ok(line_trimmed)
12179 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12180 line_trimmed.strip_prefix(prefix).with_context(|| {
12181 format!("line did not start with prefix {prefix:?}: {line:?}")
12182 })
12183 } else {
12184 line_trimmed
12185 .strip_prefix(&line_prefix.trim_start())
12186 .with_context(|| {
12187 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12188 })
12189 }
12190 })
12191 .collect::<Result<Vec<_>, _>>()
12192 .log_err()
12193 else {
12194 continue;
12195 };
12196
12197 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12198 buffer
12199 .language_settings_at(Point::new(start_row, 0), cx)
12200 .preferred_line_length as usize
12201 });
12202
12203 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12204 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12205 } else {
12206 line_prefix.clone()
12207 };
12208
12209 let wrapped_text = {
12210 let mut wrapped_text = wrap_with_prefix(
12211 line_prefix,
12212 subsequent_lines_prefix,
12213 lines_without_prefixes.join("\n"),
12214 wrap_column,
12215 tab_size,
12216 options.preserve_existing_whitespace,
12217 );
12218
12219 if let Some((indent, delimiter)) = first_line_delimiter {
12220 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12221 }
12222 if let Some(last_line) = last_line_delimiter {
12223 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12224 }
12225
12226 wrapped_text
12227 };
12228
12229 // TODO: should always use char-based diff while still supporting cursor behavior that
12230 // matches vim.
12231 let mut diff_options = DiffOptions::default();
12232 if options.override_language_settings {
12233 diff_options.max_word_diff_len = 0;
12234 diff_options.max_word_diff_line_count = 0;
12235 } else {
12236 diff_options.max_word_diff_len = usize::MAX;
12237 diff_options.max_word_diff_line_count = usize::MAX;
12238 }
12239
12240 for (old_range, new_text) in
12241 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12242 {
12243 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12244 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12245 edits.push((edit_start..edit_end, new_text));
12246 }
12247
12248 rewrapped_row_ranges.push(start_row..=end_row);
12249 }
12250
12251 self.buffer
12252 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12253 }
12254
12255 pub fn cut_common(
12256 &mut self,
12257 cut_no_selection_line: bool,
12258 window: &mut Window,
12259 cx: &mut Context<Self>,
12260 ) -> ClipboardItem {
12261 let mut text = String::new();
12262 let buffer = self.buffer.read(cx).snapshot(cx);
12263 let mut selections = self.selections.all::<Point>(cx);
12264 let mut clipboard_selections = Vec::with_capacity(selections.len());
12265 {
12266 let max_point = buffer.max_point();
12267 let mut is_first = true;
12268 for selection in &mut selections {
12269 let is_entire_line =
12270 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode;
12271 if is_entire_line {
12272 selection.start = Point::new(selection.start.row, 0);
12273 if !selection.is_empty() && selection.end.column == 0 {
12274 selection.end = cmp::min(max_point, selection.end);
12275 } else {
12276 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12277 }
12278 selection.goal = SelectionGoal::None;
12279 }
12280 if is_first {
12281 is_first = false;
12282 } else {
12283 text += "\n";
12284 }
12285 let mut len = 0;
12286 for chunk in buffer.text_for_range(selection.start..selection.end) {
12287 text.push_str(chunk);
12288 len += chunk.len();
12289 }
12290 clipboard_selections.push(ClipboardSelection {
12291 len,
12292 is_entire_line,
12293 first_line_indent: buffer
12294 .indent_size_for_line(MultiBufferRow(selection.start.row))
12295 .len,
12296 });
12297 }
12298 }
12299
12300 self.transact(window, cx, |this, window, cx| {
12301 this.change_selections(Default::default(), window, cx, |s| {
12302 s.select(selections);
12303 });
12304 this.insert("", window, cx);
12305 });
12306 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12307 }
12308
12309 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12310 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12311 let item = self.cut_common(true, window, cx);
12312 cx.write_to_clipboard(item);
12313 }
12314
12315 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12316 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12317 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12318 s.move_with(|snapshot, sel| {
12319 if sel.is_empty() {
12320 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12321 }
12322 if sel.is_empty() {
12323 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12324 }
12325 });
12326 });
12327 let item = self.cut_common(true, window, cx);
12328 cx.set_global(KillRing(item))
12329 }
12330
12331 pub fn kill_ring_yank(
12332 &mut self,
12333 _: &KillRingYank,
12334 window: &mut Window,
12335 cx: &mut Context<Self>,
12336 ) {
12337 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12338 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12339 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12340 (kill_ring.text().to_string(), kill_ring.metadata_json())
12341 } else {
12342 return;
12343 }
12344 } else {
12345 return;
12346 };
12347 self.do_paste(&text, metadata, false, window, cx);
12348 }
12349
12350 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12351 self.do_copy(true, cx);
12352 }
12353
12354 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12355 self.do_copy(false, cx);
12356 }
12357
12358 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12359 let selections = self.selections.all::<Point>(cx);
12360 let buffer = self.buffer.read(cx).read(cx);
12361 let mut text = String::new();
12362
12363 let mut clipboard_selections = Vec::with_capacity(selections.len());
12364 {
12365 let max_point = buffer.max_point();
12366 let mut is_first = true;
12367 for selection in &selections {
12368 let mut start = selection.start;
12369 let mut end = selection.end;
12370 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12371 if is_entire_line {
12372 start = Point::new(start.row, 0);
12373 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12374 }
12375
12376 let mut trimmed_selections = Vec::new();
12377 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12378 let row = MultiBufferRow(start.row);
12379 let first_indent = buffer.indent_size_for_line(row);
12380 if first_indent.len == 0 || start.column > first_indent.len {
12381 trimmed_selections.push(start..end);
12382 } else {
12383 trimmed_selections.push(
12384 Point::new(row.0, first_indent.len)
12385 ..Point::new(row.0, buffer.line_len(row)),
12386 );
12387 for row in start.row + 1..=end.row {
12388 let mut line_len = buffer.line_len(MultiBufferRow(row));
12389 if row == end.row {
12390 line_len = end.column;
12391 }
12392 if line_len == 0 {
12393 trimmed_selections
12394 .push(Point::new(row, 0)..Point::new(row, line_len));
12395 continue;
12396 }
12397 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12398 if row_indent_size.len >= first_indent.len {
12399 trimmed_selections.push(
12400 Point::new(row, first_indent.len)..Point::new(row, line_len),
12401 );
12402 } else {
12403 trimmed_selections.clear();
12404 trimmed_selections.push(start..end);
12405 break;
12406 }
12407 }
12408 }
12409 } else {
12410 trimmed_selections.push(start..end);
12411 }
12412
12413 for trimmed_range in trimmed_selections {
12414 if is_first {
12415 is_first = false;
12416 } else {
12417 text += "\n";
12418 }
12419 let mut len = 0;
12420 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12421 text.push_str(chunk);
12422 len += chunk.len();
12423 }
12424 clipboard_selections.push(ClipboardSelection {
12425 len,
12426 is_entire_line,
12427 first_line_indent: buffer
12428 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12429 .len,
12430 });
12431 }
12432 }
12433 }
12434
12435 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12436 text,
12437 clipboard_selections,
12438 ));
12439 }
12440
12441 pub fn do_paste(
12442 &mut self,
12443 text: &String,
12444 clipboard_selections: Option<Vec<ClipboardSelection>>,
12445 handle_entire_lines: bool,
12446 window: &mut Window,
12447 cx: &mut Context<Self>,
12448 ) {
12449 if self.read_only(cx) {
12450 return;
12451 }
12452
12453 let clipboard_text = Cow::Borrowed(text);
12454
12455 self.transact(window, cx, |this, window, cx| {
12456 let had_active_edit_prediction = this.has_active_edit_prediction();
12457
12458 if let Some(mut clipboard_selections) = clipboard_selections {
12459 let old_selections = this.selections.all::<usize>(cx);
12460 let all_selections_were_entire_line =
12461 clipboard_selections.iter().all(|s| s.is_entire_line);
12462 let first_selection_indent_column =
12463 clipboard_selections.first().map(|s| s.first_line_indent);
12464 if clipboard_selections.len() != old_selections.len() {
12465 clipboard_selections.drain(..);
12466 }
12467 let cursor_offset = this.selections.last::<usize>(cx).head();
12468 let mut auto_indent_on_paste = true;
12469
12470 this.buffer.update(cx, |buffer, cx| {
12471 let snapshot = buffer.read(cx);
12472 auto_indent_on_paste = snapshot
12473 .language_settings_at(cursor_offset, cx)
12474 .auto_indent_on_paste;
12475
12476 let mut start_offset = 0;
12477 let mut edits = Vec::new();
12478 let mut original_indent_columns = Vec::new();
12479 for (ix, selection) in old_selections.iter().enumerate() {
12480 let to_insert;
12481 let entire_line;
12482 let original_indent_column;
12483 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12484 let end_offset = start_offset + clipboard_selection.len;
12485 to_insert = &clipboard_text[start_offset..end_offset];
12486 entire_line = clipboard_selection.is_entire_line;
12487 start_offset = end_offset + 1;
12488 original_indent_column = Some(clipboard_selection.first_line_indent);
12489 } else {
12490 to_insert = clipboard_text.as_str();
12491 entire_line = all_selections_were_entire_line;
12492 original_indent_column = first_selection_indent_column
12493 }
12494
12495 // If the corresponding selection was empty when this slice of the
12496 // clipboard text was written, then the entire line containing the
12497 // selection was copied. If this selection is also currently empty,
12498 // then paste the line before the current line of the buffer.
12499 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12500 let column = selection.start.to_point(&snapshot).column as usize;
12501 let line_start = selection.start - column;
12502 line_start..line_start
12503 } else {
12504 selection.range()
12505 };
12506
12507 edits.push((range, to_insert));
12508 original_indent_columns.push(original_indent_column);
12509 }
12510 drop(snapshot);
12511
12512 buffer.edit(
12513 edits,
12514 if auto_indent_on_paste {
12515 Some(AutoindentMode::Block {
12516 original_indent_columns,
12517 })
12518 } else {
12519 None
12520 },
12521 cx,
12522 );
12523 });
12524
12525 let selections = this.selections.all::<usize>(cx);
12526 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12527 } else {
12528 this.insert(&clipboard_text, window, cx);
12529 }
12530
12531 let trigger_in_words =
12532 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12533
12534 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12535 });
12536 }
12537
12538 pub fn diff_clipboard_with_selection(
12539 &mut self,
12540 _: &DiffClipboardWithSelection,
12541 window: &mut Window,
12542 cx: &mut Context<Self>,
12543 ) {
12544 let selections = self.selections.all::<usize>(cx);
12545
12546 if selections.is_empty() {
12547 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12548 return;
12549 };
12550
12551 let clipboard_text = match cx.read_from_clipboard() {
12552 Some(item) => match item.entries().first() {
12553 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12554 _ => None,
12555 },
12556 None => None,
12557 };
12558
12559 let Some(clipboard_text) = clipboard_text else {
12560 log::warn!("Clipboard doesn't contain text.");
12561 return;
12562 };
12563
12564 window.dispatch_action(
12565 Box::new(DiffClipboardWithSelectionData {
12566 clipboard_text,
12567 editor: cx.entity(),
12568 }),
12569 cx,
12570 );
12571 }
12572
12573 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12574 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12575 if let Some(item) = cx.read_from_clipboard() {
12576 let entries = item.entries();
12577
12578 match entries.first() {
12579 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12580 // of all the pasted entries.
12581 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12582 .do_paste(
12583 clipboard_string.text(),
12584 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12585 true,
12586 window,
12587 cx,
12588 ),
12589 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12590 }
12591 }
12592 }
12593
12594 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12595 if self.read_only(cx) {
12596 return;
12597 }
12598
12599 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12600
12601 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12602 if let Some((selections, _)) =
12603 self.selection_history.transaction(transaction_id).cloned()
12604 {
12605 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12606 s.select_anchors(selections.to_vec());
12607 });
12608 } else {
12609 log::error!(
12610 "No entry in selection_history found for undo. \
12611 This may correspond to a bug where undo does not update the selection. \
12612 If this is occurring, please add details to \
12613 https://github.com/zed-industries/zed/issues/22692"
12614 );
12615 }
12616 self.request_autoscroll(Autoscroll::fit(), cx);
12617 self.unmark_text(window, cx);
12618 self.refresh_edit_prediction(true, false, window, cx);
12619 cx.emit(EditorEvent::Edited { transaction_id });
12620 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12621 }
12622 }
12623
12624 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12625 if self.read_only(cx) {
12626 return;
12627 }
12628
12629 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12630
12631 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12632 if let Some((_, Some(selections))) =
12633 self.selection_history.transaction(transaction_id).cloned()
12634 {
12635 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12636 s.select_anchors(selections.to_vec());
12637 });
12638 } else {
12639 log::error!(
12640 "No entry in selection_history found for redo. \
12641 This may correspond to a bug where undo does not update the selection. \
12642 If this is occurring, please add details to \
12643 https://github.com/zed-industries/zed/issues/22692"
12644 );
12645 }
12646 self.request_autoscroll(Autoscroll::fit(), cx);
12647 self.unmark_text(window, cx);
12648 self.refresh_edit_prediction(true, false, window, cx);
12649 cx.emit(EditorEvent::Edited { transaction_id });
12650 }
12651 }
12652
12653 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12654 self.buffer
12655 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12656 }
12657
12658 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12659 self.buffer
12660 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12661 }
12662
12663 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12664 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12665 self.change_selections(Default::default(), window, cx, |s| {
12666 s.move_with(|map, selection| {
12667 let cursor = if selection.is_empty() {
12668 movement::left(map, selection.start)
12669 } else {
12670 selection.start
12671 };
12672 selection.collapse_to(cursor, SelectionGoal::None);
12673 });
12674 })
12675 }
12676
12677 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12679 self.change_selections(Default::default(), window, cx, |s| {
12680 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12681 })
12682 }
12683
12684 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12686 self.change_selections(Default::default(), window, cx, |s| {
12687 s.move_with(|map, selection| {
12688 let cursor = if selection.is_empty() {
12689 movement::right(map, selection.end)
12690 } else {
12691 selection.end
12692 };
12693 selection.collapse_to(cursor, SelectionGoal::None)
12694 });
12695 })
12696 }
12697
12698 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12699 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12700 self.change_selections(Default::default(), window, cx, |s| {
12701 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12702 })
12703 }
12704
12705 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12706 if self.take_rename(true, window, cx).is_some() {
12707 return;
12708 }
12709
12710 if self.mode.is_single_line() {
12711 cx.propagate();
12712 return;
12713 }
12714
12715 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12716
12717 let text_layout_details = &self.text_layout_details(window);
12718 let selection_count = self.selections.count();
12719 let first_selection = self.selections.first_anchor();
12720
12721 self.change_selections(Default::default(), window, cx, |s| {
12722 s.move_with(|map, selection| {
12723 if !selection.is_empty() {
12724 selection.goal = SelectionGoal::None;
12725 }
12726 let (cursor, goal) = movement::up(
12727 map,
12728 selection.start,
12729 selection.goal,
12730 false,
12731 text_layout_details,
12732 );
12733 selection.collapse_to(cursor, goal);
12734 });
12735 });
12736
12737 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12738 {
12739 cx.propagate();
12740 }
12741 }
12742
12743 pub fn move_up_by_lines(
12744 &mut self,
12745 action: &MoveUpByLines,
12746 window: &mut Window,
12747 cx: &mut Context<Self>,
12748 ) {
12749 if self.take_rename(true, window, cx).is_some() {
12750 return;
12751 }
12752
12753 if self.mode.is_single_line() {
12754 cx.propagate();
12755 return;
12756 }
12757
12758 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12759
12760 let text_layout_details = &self.text_layout_details(window);
12761
12762 self.change_selections(Default::default(), window, cx, |s| {
12763 s.move_with(|map, selection| {
12764 if !selection.is_empty() {
12765 selection.goal = SelectionGoal::None;
12766 }
12767 let (cursor, goal) = movement::up_by_rows(
12768 map,
12769 selection.start,
12770 action.lines,
12771 selection.goal,
12772 false,
12773 text_layout_details,
12774 );
12775 selection.collapse_to(cursor, goal);
12776 });
12777 })
12778 }
12779
12780 pub fn move_down_by_lines(
12781 &mut self,
12782 action: &MoveDownByLines,
12783 window: &mut Window,
12784 cx: &mut Context<Self>,
12785 ) {
12786 if self.take_rename(true, window, cx).is_some() {
12787 return;
12788 }
12789
12790 if self.mode.is_single_line() {
12791 cx.propagate();
12792 return;
12793 }
12794
12795 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12796
12797 let text_layout_details = &self.text_layout_details(window);
12798
12799 self.change_selections(Default::default(), window, cx, |s| {
12800 s.move_with(|map, selection| {
12801 if !selection.is_empty() {
12802 selection.goal = SelectionGoal::None;
12803 }
12804 let (cursor, goal) = movement::down_by_rows(
12805 map,
12806 selection.start,
12807 action.lines,
12808 selection.goal,
12809 false,
12810 text_layout_details,
12811 );
12812 selection.collapse_to(cursor, goal);
12813 });
12814 })
12815 }
12816
12817 pub fn select_down_by_lines(
12818 &mut self,
12819 action: &SelectDownByLines,
12820 window: &mut Window,
12821 cx: &mut Context<Self>,
12822 ) {
12823 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12824 let text_layout_details = &self.text_layout_details(window);
12825 self.change_selections(Default::default(), window, cx, |s| {
12826 s.move_heads_with(|map, head, goal| {
12827 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12828 })
12829 })
12830 }
12831
12832 pub fn select_up_by_lines(
12833 &mut self,
12834 action: &SelectUpByLines,
12835 window: &mut Window,
12836 cx: &mut Context<Self>,
12837 ) {
12838 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12839 let text_layout_details = &self.text_layout_details(window);
12840 self.change_selections(Default::default(), window, cx, |s| {
12841 s.move_heads_with(|map, head, goal| {
12842 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12843 })
12844 })
12845 }
12846
12847 pub fn select_page_up(
12848 &mut self,
12849 _: &SelectPageUp,
12850 window: &mut Window,
12851 cx: &mut Context<Self>,
12852 ) {
12853 let Some(row_count) = self.visible_row_count() else {
12854 return;
12855 };
12856
12857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12858
12859 let text_layout_details = &self.text_layout_details(window);
12860
12861 self.change_selections(Default::default(), window, cx, |s| {
12862 s.move_heads_with(|map, head, goal| {
12863 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12864 })
12865 })
12866 }
12867
12868 pub fn move_page_up(
12869 &mut self,
12870 action: &MovePageUp,
12871 window: &mut Window,
12872 cx: &mut Context<Self>,
12873 ) {
12874 if self.take_rename(true, window, cx).is_some() {
12875 return;
12876 }
12877
12878 if self
12879 .context_menu
12880 .borrow_mut()
12881 .as_mut()
12882 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12883 .unwrap_or(false)
12884 {
12885 return;
12886 }
12887
12888 if matches!(self.mode, EditorMode::SingleLine) {
12889 cx.propagate();
12890 return;
12891 }
12892
12893 let Some(row_count) = self.visible_row_count() else {
12894 return;
12895 };
12896
12897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12898
12899 let effects = if action.center_cursor {
12900 SelectionEffects::scroll(Autoscroll::center())
12901 } else {
12902 SelectionEffects::default()
12903 };
12904
12905 let text_layout_details = &self.text_layout_details(window);
12906
12907 self.change_selections(effects, window, cx, |s| {
12908 s.move_with(|map, selection| {
12909 if !selection.is_empty() {
12910 selection.goal = SelectionGoal::None;
12911 }
12912 let (cursor, goal) = movement::up_by_rows(
12913 map,
12914 selection.end,
12915 row_count,
12916 selection.goal,
12917 false,
12918 text_layout_details,
12919 );
12920 selection.collapse_to(cursor, goal);
12921 });
12922 });
12923 }
12924
12925 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12927 let text_layout_details = &self.text_layout_details(window);
12928 self.change_selections(Default::default(), window, cx, |s| {
12929 s.move_heads_with(|map, head, goal| {
12930 movement::up(map, head, goal, false, text_layout_details)
12931 })
12932 })
12933 }
12934
12935 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12936 self.take_rename(true, window, cx);
12937
12938 if self.mode.is_single_line() {
12939 cx.propagate();
12940 return;
12941 }
12942
12943 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12944
12945 let text_layout_details = &self.text_layout_details(window);
12946 let selection_count = self.selections.count();
12947 let first_selection = self.selections.first_anchor();
12948
12949 self.change_selections(Default::default(), window, cx, |s| {
12950 s.move_with(|map, selection| {
12951 if !selection.is_empty() {
12952 selection.goal = SelectionGoal::None;
12953 }
12954 let (cursor, goal) = movement::down(
12955 map,
12956 selection.end,
12957 selection.goal,
12958 false,
12959 text_layout_details,
12960 );
12961 selection.collapse_to(cursor, goal);
12962 });
12963 });
12964
12965 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12966 {
12967 cx.propagate();
12968 }
12969 }
12970
12971 pub fn select_page_down(
12972 &mut self,
12973 _: &SelectPageDown,
12974 window: &mut Window,
12975 cx: &mut Context<Self>,
12976 ) {
12977 let Some(row_count) = self.visible_row_count() else {
12978 return;
12979 };
12980
12981 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12982
12983 let text_layout_details = &self.text_layout_details(window);
12984
12985 self.change_selections(Default::default(), window, cx, |s| {
12986 s.move_heads_with(|map, head, goal| {
12987 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12988 })
12989 })
12990 }
12991
12992 pub fn move_page_down(
12993 &mut self,
12994 action: &MovePageDown,
12995 window: &mut Window,
12996 cx: &mut Context<Self>,
12997 ) {
12998 if self.take_rename(true, window, cx).is_some() {
12999 return;
13000 }
13001
13002 if self
13003 .context_menu
13004 .borrow_mut()
13005 .as_mut()
13006 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13007 .unwrap_or(false)
13008 {
13009 return;
13010 }
13011
13012 if matches!(self.mode, EditorMode::SingleLine) {
13013 cx.propagate();
13014 return;
13015 }
13016
13017 let Some(row_count) = self.visible_row_count() else {
13018 return;
13019 };
13020
13021 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13022
13023 let effects = if action.center_cursor {
13024 SelectionEffects::scroll(Autoscroll::center())
13025 } else {
13026 SelectionEffects::default()
13027 };
13028
13029 let text_layout_details = &self.text_layout_details(window);
13030 self.change_selections(effects, window, cx, |s| {
13031 s.move_with(|map, selection| {
13032 if !selection.is_empty() {
13033 selection.goal = SelectionGoal::None;
13034 }
13035 let (cursor, goal) = movement::down_by_rows(
13036 map,
13037 selection.end,
13038 row_count,
13039 selection.goal,
13040 false,
13041 text_layout_details,
13042 );
13043 selection.collapse_to(cursor, goal);
13044 });
13045 });
13046 }
13047
13048 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13049 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13050 let text_layout_details = &self.text_layout_details(window);
13051 self.change_selections(Default::default(), window, cx, |s| {
13052 s.move_heads_with(|map, head, goal| {
13053 movement::down(map, head, goal, false, text_layout_details)
13054 })
13055 });
13056 }
13057
13058 pub fn context_menu_first(
13059 &mut self,
13060 _: &ContextMenuFirst,
13061 window: &mut Window,
13062 cx: &mut Context<Self>,
13063 ) {
13064 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13065 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13066 }
13067 }
13068
13069 pub fn context_menu_prev(
13070 &mut self,
13071 _: &ContextMenuPrevious,
13072 window: &mut Window,
13073 cx: &mut Context<Self>,
13074 ) {
13075 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13076 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13077 }
13078 }
13079
13080 pub fn context_menu_next(
13081 &mut self,
13082 _: &ContextMenuNext,
13083 window: &mut Window,
13084 cx: &mut Context<Self>,
13085 ) {
13086 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13087 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13088 }
13089 }
13090
13091 pub fn context_menu_last(
13092 &mut self,
13093 _: &ContextMenuLast,
13094 window: &mut Window,
13095 cx: &mut Context<Self>,
13096 ) {
13097 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13098 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13099 }
13100 }
13101
13102 pub fn signature_help_prev(
13103 &mut self,
13104 _: &SignatureHelpPrevious,
13105 _: &mut Window,
13106 cx: &mut Context<Self>,
13107 ) {
13108 if let Some(popover) = self.signature_help_state.popover_mut() {
13109 if popover.current_signature == 0 {
13110 popover.current_signature = popover.signatures.len() - 1;
13111 } else {
13112 popover.current_signature -= 1;
13113 }
13114 cx.notify();
13115 }
13116 }
13117
13118 pub fn signature_help_next(
13119 &mut self,
13120 _: &SignatureHelpNext,
13121 _: &mut Window,
13122 cx: &mut Context<Self>,
13123 ) {
13124 if let Some(popover) = self.signature_help_state.popover_mut() {
13125 if popover.current_signature + 1 == popover.signatures.len() {
13126 popover.current_signature = 0;
13127 } else {
13128 popover.current_signature += 1;
13129 }
13130 cx.notify();
13131 }
13132 }
13133
13134 pub fn move_to_previous_word_start(
13135 &mut self,
13136 _: &MoveToPreviousWordStart,
13137 window: &mut Window,
13138 cx: &mut Context<Self>,
13139 ) {
13140 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13141 self.change_selections(Default::default(), window, cx, |s| {
13142 s.move_cursors_with(|map, head, _| {
13143 (
13144 movement::previous_word_start(map, head),
13145 SelectionGoal::None,
13146 )
13147 });
13148 })
13149 }
13150
13151 pub fn move_to_previous_subword_start(
13152 &mut self,
13153 _: &MoveToPreviousSubwordStart,
13154 window: &mut Window,
13155 cx: &mut Context<Self>,
13156 ) {
13157 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13158 self.change_selections(Default::default(), window, cx, |s| {
13159 s.move_cursors_with(|map, head, _| {
13160 (
13161 movement::previous_subword_start(map, head),
13162 SelectionGoal::None,
13163 )
13164 });
13165 })
13166 }
13167
13168 pub fn select_to_previous_word_start(
13169 &mut self,
13170 _: &SelectToPreviousWordStart,
13171 window: &mut Window,
13172 cx: &mut Context<Self>,
13173 ) {
13174 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13175 self.change_selections(Default::default(), window, cx, |s| {
13176 s.move_heads_with(|map, head, _| {
13177 (
13178 movement::previous_word_start(map, head),
13179 SelectionGoal::None,
13180 )
13181 });
13182 })
13183 }
13184
13185 pub fn select_to_previous_subword_start(
13186 &mut self,
13187 _: &SelectToPreviousSubwordStart,
13188 window: &mut Window,
13189 cx: &mut Context<Self>,
13190 ) {
13191 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13192 self.change_selections(Default::default(), window, cx, |s| {
13193 s.move_heads_with(|map, head, _| {
13194 (
13195 movement::previous_subword_start(map, head),
13196 SelectionGoal::None,
13197 )
13198 });
13199 })
13200 }
13201
13202 pub fn delete_to_previous_word_start(
13203 &mut self,
13204 action: &DeleteToPreviousWordStart,
13205 window: &mut Window,
13206 cx: &mut Context<Self>,
13207 ) {
13208 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13209 self.transact(window, cx, |this, window, cx| {
13210 this.select_autoclose_pair(window, cx);
13211 this.change_selections(Default::default(), window, cx, |s| {
13212 s.move_with(|map, selection| {
13213 if selection.is_empty() {
13214 let mut cursor = if action.ignore_newlines {
13215 movement::previous_word_start(map, selection.head())
13216 } else {
13217 movement::previous_word_start_or_newline(map, selection.head())
13218 };
13219 cursor = movement::adjust_greedy_deletion(
13220 map,
13221 selection.head(),
13222 cursor,
13223 action.ignore_brackets,
13224 );
13225 selection.set_head(cursor, SelectionGoal::None);
13226 }
13227 });
13228 });
13229 this.insert("", window, cx);
13230 });
13231 }
13232
13233 pub fn delete_to_previous_subword_start(
13234 &mut self,
13235 _: &DeleteToPreviousSubwordStart,
13236 window: &mut Window,
13237 cx: &mut Context<Self>,
13238 ) {
13239 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13240 self.transact(window, cx, |this, window, cx| {
13241 this.select_autoclose_pair(window, cx);
13242 this.change_selections(Default::default(), window, cx, |s| {
13243 s.move_with(|map, selection| {
13244 if selection.is_empty() {
13245 let mut cursor = movement::previous_subword_start(map, selection.head());
13246 cursor =
13247 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13248 selection.set_head(cursor, SelectionGoal::None);
13249 }
13250 });
13251 });
13252 this.insert("", window, cx);
13253 });
13254 }
13255
13256 pub fn move_to_next_word_end(
13257 &mut self,
13258 _: &MoveToNextWordEnd,
13259 window: &mut Window,
13260 cx: &mut Context<Self>,
13261 ) {
13262 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13263 self.change_selections(Default::default(), window, cx, |s| {
13264 s.move_cursors_with(|map, head, _| {
13265 (movement::next_word_end(map, head), SelectionGoal::None)
13266 });
13267 })
13268 }
13269
13270 pub fn move_to_next_subword_end(
13271 &mut self,
13272 _: &MoveToNextSubwordEnd,
13273 window: &mut Window,
13274 cx: &mut Context<Self>,
13275 ) {
13276 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13277 self.change_selections(Default::default(), window, cx, |s| {
13278 s.move_cursors_with(|map, head, _| {
13279 (movement::next_subword_end(map, head), SelectionGoal::None)
13280 });
13281 })
13282 }
13283
13284 pub fn select_to_next_word_end(
13285 &mut self,
13286 _: &SelectToNextWordEnd,
13287 window: &mut Window,
13288 cx: &mut Context<Self>,
13289 ) {
13290 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13291 self.change_selections(Default::default(), window, cx, |s| {
13292 s.move_heads_with(|map, head, _| {
13293 (movement::next_word_end(map, head), SelectionGoal::None)
13294 });
13295 })
13296 }
13297
13298 pub fn select_to_next_subword_end(
13299 &mut self,
13300 _: &SelectToNextSubwordEnd,
13301 window: &mut Window,
13302 cx: &mut Context<Self>,
13303 ) {
13304 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13305 self.change_selections(Default::default(), window, cx, |s| {
13306 s.move_heads_with(|map, head, _| {
13307 (movement::next_subword_end(map, head), SelectionGoal::None)
13308 });
13309 })
13310 }
13311
13312 pub fn delete_to_next_word_end(
13313 &mut self,
13314 action: &DeleteToNextWordEnd,
13315 window: &mut Window,
13316 cx: &mut Context<Self>,
13317 ) {
13318 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13319 self.transact(window, cx, |this, window, cx| {
13320 this.change_selections(Default::default(), window, cx, |s| {
13321 s.move_with(|map, selection| {
13322 if selection.is_empty() {
13323 let mut cursor = if action.ignore_newlines {
13324 movement::next_word_end(map, selection.head())
13325 } else {
13326 movement::next_word_end_or_newline(map, selection.head())
13327 };
13328 cursor = movement::adjust_greedy_deletion(
13329 map,
13330 selection.head(),
13331 cursor,
13332 action.ignore_brackets,
13333 );
13334 selection.set_head(cursor, SelectionGoal::None);
13335 }
13336 });
13337 });
13338 this.insert("", window, cx);
13339 });
13340 }
13341
13342 pub fn delete_to_next_subword_end(
13343 &mut self,
13344 _: &DeleteToNextSubwordEnd,
13345 window: &mut Window,
13346 cx: &mut Context<Self>,
13347 ) {
13348 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13349 self.transact(window, cx, |this, window, cx| {
13350 this.change_selections(Default::default(), window, cx, |s| {
13351 s.move_with(|map, selection| {
13352 if selection.is_empty() {
13353 let mut cursor = movement::next_subword_end(map, selection.head());
13354 cursor =
13355 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13356 selection.set_head(cursor, SelectionGoal::None);
13357 }
13358 });
13359 });
13360 this.insert("", window, cx);
13361 });
13362 }
13363
13364 pub fn move_to_beginning_of_line(
13365 &mut self,
13366 action: &MoveToBeginningOfLine,
13367 window: &mut Window,
13368 cx: &mut Context<Self>,
13369 ) {
13370 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13371 self.change_selections(Default::default(), window, cx, |s| {
13372 s.move_cursors_with(|map, head, _| {
13373 (
13374 movement::indented_line_beginning(
13375 map,
13376 head,
13377 action.stop_at_soft_wraps,
13378 action.stop_at_indent,
13379 ),
13380 SelectionGoal::None,
13381 )
13382 });
13383 })
13384 }
13385
13386 pub fn select_to_beginning_of_line(
13387 &mut self,
13388 action: &SelectToBeginningOfLine,
13389 window: &mut Window,
13390 cx: &mut Context<Self>,
13391 ) {
13392 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13393 self.change_selections(Default::default(), window, cx, |s| {
13394 s.move_heads_with(|map, head, _| {
13395 (
13396 movement::indented_line_beginning(
13397 map,
13398 head,
13399 action.stop_at_soft_wraps,
13400 action.stop_at_indent,
13401 ),
13402 SelectionGoal::None,
13403 )
13404 });
13405 });
13406 }
13407
13408 pub fn delete_to_beginning_of_line(
13409 &mut self,
13410 action: &DeleteToBeginningOfLine,
13411 window: &mut Window,
13412 cx: &mut Context<Self>,
13413 ) {
13414 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13415 self.transact(window, cx, |this, window, cx| {
13416 this.change_selections(Default::default(), window, cx, |s| {
13417 s.move_with(|_, selection| {
13418 selection.reversed = true;
13419 });
13420 });
13421
13422 this.select_to_beginning_of_line(
13423 &SelectToBeginningOfLine {
13424 stop_at_soft_wraps: false,
13425 stop_at_indent: action.stop_at_indent,
13426 },
13427 window,
13428 cx,
13429 );
13430 this.backspace(&Backspace, window, cx);
13431 });
13432 }
13433
13434 pub fn move_to_end_of_line(
13435 &mut self,
13436 action: &MoveToEndOfLine,
13437 window: &mut Window,
13438 cx: &mut Context<Self>,
13439 ) {
13440 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13441 self.change_selections(Default::default(), window, cx, |s| {
13442 s.move_cursors_with(|map, head, _| {
13443 (
13444 movement::line_end(map, head, action.stop_at_soft_wraps),
13445 SelectionGoal::None,
13446 )
13447 });
13448 })
13449 }
13450
13451 pub fn select_to_end_of_line(
13452 &mut self,
13453 action: &SelectToEndOfLine,
13454 window: &mut Window,
13455 cx: &mut Context<Self>,
13456 ) {
13457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13458 self.change_selections(Default::default(), window, cx, |s| {
13459 s.move_heads_with(|map, head, _| {
13460 (
13461 movement::line_end(map, head, action.stop_at_soft_wraps),
13462 SelectionGoal::None,
13463 )
13464 });
13465 })
13466 }
13467
13468 pub fn delete_to_end_of_line(
13469 &mut self,
13470 _: &DeleteToEndOfLine,
13471 window: &mut Window,
13472 cx: &mut Context<Self>,
13473 ) {
13474 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13475 self.transact(window, cx, |this, window, cx| {
13476 this.select_to_end_of_line(
13477 &SelectToEndOfLine {
13478 stop_at_soft_wraps: false,
13479 },
13480 window,
13481 cx,
13482 );
13483 this.delete(&Delete, window, cx);
13484 });
13485 }
13486
13487 pub fn cut_to_end_of_line(
13488 &mut self,
13489 action: &CutToEndOfLine,
13490 window: &mut Window,
13491 cx: &mut Context<Self>,
13492 ) {
13493 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13494 self.transact(window, cx, |this, window, cx| {
13495 this.select_to_end_of_line(
13496 &SelectToEndOfLine {
13497 stop_at_soft_wraps: false,
13498 },
13499 window,
13500 cx,
13501 );
13502 if !action.stop_at_newlines {
13503 this.change_selections(Default::default(), window, cx, |s| {
13504 s.move_with(|_, sel| {
13505 if sel.is_empty() {
13506 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13507 }
13508 });
13509 });
13510 }
13511 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13512 let item = this.cut_common(false, window, cx);
13513 cx.write_to_clipboard(item);
13514 });
13515 }
13516
13517 pub fn move_to_start_of_paragraph(
13518 &mut self,
13519 _: &MoveToStartOfParagraph,
13520 window: &mut Window,
13521 cx: &mut Context<Self>,
13522 ) {
13523 if matches!(self.mode, EditorMode::SingleLine) {
13524 cx.propagate();
13525 return;
13526 }
13527 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13528 self.change_selections(Default::default(), window, cx, |s| {
13529 s.move_with(|map, selection| {
13530 selection.collapse_to(
13531 movement::start_of_paragraph(map, selection.head(), 1),
13532 SelectionGoal::None,
13533 )
13534 });
13535 })
13536 }
13537
13538 pub fn move_to_end_of_paragraph(
13539 &mut self,
13540 _: &MoveToEndOfParagraph,
13541 window: &mut Window,
13542 cx: &mut Context<Self>,
13543 ) {
13544 if matches!(self.mode, EditorMode::SingleLine) {
13545 cx.propagate();
13546 return;
13547 }
13548 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13549 self.change_selections(Default::default(), window, cx, |s| {
13550 s.move_with(|map, selection| {
13551 selection.collapse_to(
13552 movement::end_of_paragraph(map, selection.head(), 1),
13553 SelectionGoal::None,
13554 )
13555 });
13556 })
13557 }
13558
13559 pub fn select_to_start_of_paragraph(
13560 &mut self,
13561 _: &SelectToStartOfParagraph,
13562 window: &mut Window,
13563 cx: &mut Context<Self>,
13564 ) {
13565 if matches!(self.mode, EditorMode::SingleLine) {
13566 cx.propagate();
13567 return;
13568 }
13569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13570 self.change_selections(Default::default(), window, cx, |s| {
13571 s.move_heads_with(|map, head, _| {
13572 (
13573 movement::start_of_paragraph(map, head, 1),
13574 SelectionGoal::None,
13575 )
13576 });
13577 })
13578 }
13579
13580 pub fn select_to_end_of_paragraph(
13581 &mut self,
13582 _: &SelectToEndOfParagraph,
13583 window: &mut Window,
13584 cx: &mut Context<Self>,
13585 ) {
13586 if matches!(self.mode, EditorMode::SingleLine) {
13587 cx.propagate();
13588 return;
13589 }
13590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13591 self.change_selections(Default::default(), window, cx, |s| {
13592 s.move_heads_with(|map, head, _| {
13593 (
13594 movement::end_of_paragraph(map, head, 1),
13595 SelectionGoal::None,
13596 )
13597 });
13598 })
13599 }
13600
13601 pub fn move_to_start_of_excerpt(
13602 &mut self,
13603 _: &MoveToStartOfExcerpt,
13604 window: &mut Window,
13605 cx: &mut Context<Self>,
13606 ) {
13607 if matches!(self.mode, EditorMode::SingleLine) {
13608 cx.propagate();
13609 return;
13610 }
13611 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13612 self.change_selections(Default::default(), window, cx, |s| {
13613 s.move_with(|map, selection| {
13614 selection.collapse_to(
13615 movement::start_of_excerpt(
13616 map,
13617 selection.head(),
13618 workspace::searchable::Direction::Prev,
13619 ),
13620 SelectionGoal::None,
13621 )
13622 });
13623 })
13624 }
13625
13626 pub fn move_to_start_of_next_excerpt(
13627 &mut self,
13628 _: &MoveToStartOfNextExcerpt,
13629 window: &mut Window,
13630 cx: &mut Context<Self>,
13631 ) {
13632 if matches!(self.mode, EditorMode::SingleLine) {
13633 cx.propagate();
13634 return;
13635 }
13636
13637 self.change_selections(Default::default(), window, cx, |s| {
13638 s.move_with(|map, selection| {
13639 selection.collapse_to(
13640 movement::start_of_excerpt(
13641 map,
13642 selection.head(),
13643 workspace::searchable::Direction::Next,
13644 ),
13645 SelectionGoal::None,
13646 )
13647 });
13648 })
13649 }
13650
13651 pub fn move_to_end_of_excerpt(
13652 &mut self,
13653 _: &MoveToEndOfExcerpt,
13654 window: &mut Window,
13655 cx: &mut Context<Self>,
13656 ) {
13657 if matches!(self.mode, EditorMode::SingleLine) {
13658 cx.propagate();
13659 return;
13660 }
13661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13662 self.change_selections(Default::default(), window, cx, |s| {
13663 s.move_with(|map, selection| {
13664 selection.collapse_to(
13665 movement::end_of_excerpt(
13666 map,
13667 selection.head(),
13668 workspace::searchable::Direction::Next,
13669 ),
13670 SelectionGoal::None,
13671 )
13672 });
13673 })
13674 }
13675
13676 pub fn move_to_end_of_previous_excerpt(
13677 &mut self,
13678 _: &MoveToEndOfPreviousExcerpt,
13679 window: &mut Window,
13680 cx: &mut Context<Self>,
13681 ) {
13682 if matches!(self.mode, EditorMode::SingleLine) {
13683 cx.propagate();
13684 return;
13685 }
13686 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13687 self.change_selections(Default::default(), window, cx, |s| {
13688 s.move_with(|map, selection| {
13689 selection.collapse_to(
13690 movement::end_of_excerpt(
13691 map,
13692 selection.head(),
13693 workspace::searchable::Direction::Prev,
13694 ),
13695 SelectionGoal::None,
13696 )
13697 });
13698 })
13699 }
13700
13701 pub fn select_to_start_of_excerpt(
13702 &mut self,
13703 _: &SelectToStartOfExcerpt,
13704 window: &mut Window,
13705 cx: &mut Context<Self>,
13706 ) {
13707 if matches!(self.mode, EditorMode::SingleLine) {
13708 cx.propagate();
13709 return;
13710 }
13711 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13712 self.change_selections(Default::default(), window, cx, |s| {
13713 s.move_heads_with(|map, head, _| {
13714 (
13715 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13716 SelectionGoal::None,
13717 )
13718 });
13719 })
13720 }
13721
13722 pub fn select_to_start_of_next_excerpt(
13723 &mut self,
13724 _: &SelectToStartOfNextExcerpt,
13725 window: &mut Window,
13726 cx: &mut Context<Self>,
13727 ) {
13728 if matches!(self.mode, EditorMode::SingleLine) {
13729 cx.propagate();
13730 return;
13731 }
13732 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13733 self.change_selections(Default::default(), window, cx, |s| {
13734 s.move_heads_with(|map, head, _| {
13735 (
13736 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13737 SelectionGoal::None,
13738 )
13739 });
13740 })
13741 }
13742
13743 pub fn select_to_end_of_excerpt(
13744 &mut self,
13745 _: &SelectToEndOfExcerpt,
13746 window: &mut Window,
13747 cx: &mut Context<Self>,
13748 ) {
13749 if matches!(self.mode, EditorMode::SingleLine) {
13750 cx.propagate();
13751 return;
13752 }
13753 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13754 self.change_selections(Default::default(), window, cx, |s| {
13755 s.move_heads_with(|map, head, _| {
13756 (
13757 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13758 SelectionGoal::None,
13759 )
13760 });
13761 })
13762 }
13763
13764 pub fn select_to_end_of_previous_excerpt(
13765 &mut self,
13766 _: &SelectToEndOfPreviousExcerpt,
13767 window: &mut Window,
13768 cx: &mut Context<Self>,
13769 ) {
13770 if matches!(self.mode, EditorMode::SingleLine) {
13771 cx.propagate();
13772 return;
13773 }
13774 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13775 self.change_selections(Default::default(), window, cx, |s| {
13776 s.move_heads_with(|map, head, _| {
13777 (
13778 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13779 SelectionGoal::None,
13780 )
13781 });
13782 })
13783 }
13784
13785 pub fn move_to_beginning(
13786 &mut self,
13787 _: &MoveToBeginning,
13788 window: &mut Window,
13789 cx: &mut Context<Self>,
13790 ) {
13791 if matches!(self.mode, EditorMode::SingleLine) {
13792 cx.propagate();
13793 return;
13794 }
13795 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13796 self.change_selections(Default::default(), window, cx, |s| {
13797 s.select_ranges(vec![0..0]);
13798 });
13799 }
13800
13801 pub fn select_to_beginning(
13802 &mut self,
13803 _: &SelectToBeginning,
13804 window: &mut Window,
13805 cx: &mut Context<Self>,
13806 ) {
13807 let mut selection = self.selections.last::<Point>(cx);
13808 selection.set_head(Point::zero(), SelectionGoal::None);
13809 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13810 self.change_selections(Default::default(), window, cx, |s| {
13811 s.select(vec![selection]);
13812 });
13813 }
13814
13815 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13816 if matches!(self.mode, EditorMode::SingleLine) {
13817 cx.propagate();
13818 return;
13819 }
13820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13821 let cursor = self.buffer.read(cx).read(cx).len();
13822 self.change_selections(Default::default(), window, cx, |s| {
13823 s.select_ranges(vec![cursor..cursor])
13824 });
13825 }
13826
13827 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13828 self.nav_history = nav_history;
13829 }
13830
13831 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13832 self.nav_history.as_ref()
13833 }
13834
13835 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13836 self.push_to_nav_history(
13837 self.selections.newest_anchor().head(),
13838 None,
13839 false,
13840 true,
13841 cx,
13842 );
13843 }
13844
13845 fn push_to_nav_history(
13846 &mut self,
13847 cursor_anchor: Anchor,
13848 new_position: Option<Point>,
13849 is_deactivate: bool,
13850 always: bool,
13851 cx: &mut Context<Self>,
13852 ) {
13853 if let Some(nav_history) = self.nav_history.as_mut() {
13854 let buffer = self.buffer.read(cx).read(cx);
13855 let cursor_position = cursor_anchor.to_point(&buffer);
13856 let scroll_state = self.scroll_manager.anchor();
13857 let scroll_top_row = scroll_state.top_row(&buffer);
13858 drop(buffer);
13859
13860 if let Some(new_position) = new_position {
13861 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13862 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13863 return;
13864 }
13865 }
13866
13867 nav_history.push(
13868 Some(NavigationData {
13869 cursor_anchor,
13870 cursor_position,
13871 scroll_anchor: scroll_state,
13872 scroll_top_row,
13873 }),
13874 cx,
13875 );
13876 cx.emit(EditorEvent::PushedToNavHistory {
13877 anchor: cursor_anchor,
13878 is_deactivate,
13879 })
13880 }
13881 }
13882
13883 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13885 let buffer = self.buffer.read(cx).snapshot(cx);
13886 let mut selection = self.selections.first::<usize>(cx);
13887 selection.set_head(buffer.len(), SelectionGoal::None);
13888 self.change_selections(Default::default(), window, cx, |s| {
13889 s.select(vec![selection]);
13890 });
13891 }
13892
13893 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13894 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13895 let end = self.buffer.read(cx).read(cx).len();
13896 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13897 s.select_ranges(vec![0..end]);
13898 });
13899 }
13900
13901 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13902 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13903 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13904 let mut selections = self.selections.all::<Point>(cx);
13905 let max_point = display_map.buffer_snapshot.max_point();
13906 for selection in &mut selections {
13907 let rows = selection.spanned_rows(true, &display_map);
13908 selection.start = Point::new(rows.start.0, 0);
13909 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13910 selection.reversed = false;
13911 }
13912 self.change_selections(Default::default(), window, cx, |s| {
13913 s.select(selections);
13914 });
13915 }
13916
13917 pub fn split_selection_into_lines(
13918 &mut self,
13919 action: &SplitSelectionIntoLines,
13920 window: &mut Window,
13921 cx: &mut Context<Self>,
13922 ) {
13923 let selections = self
13924 .selections
13925 .all::<Point>(cx)
13926 .into_iter()
13927 .map(|selection| selection.start..selection.end)
13928 .collect::<Vec<_>>();
13929 self.unfold_ranges(&selections, true, true, cx);
13930
13931 let mut new_selection_ranges = Vec::new();
13932 {
13933 let buffer = self.buffer.read(cx).read(cx);
13934 for selection in selections {
13935 for row in selection.start.row..selection.end.row {
13936 let line_start = Point::new(row, 0);
13937 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13938
13939 if action.keep_selections {
13940 // Keep the selection range for each line
13941 let selection_start = if row == selection.start.row {
13942 selection.start
13943 } else {
13944 line_start
13945 };
13946 new_selection_ranges.push(selection_start..line_end);
13947 } else {
13948 // Collapse to cursor at end of line
13949 new_selection_ranges.push(line_end..line_end);
13950 }
13951 }
13952
13953 let is_multiline_selection = selection.start.row != selection.end.row;
13954 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13955 // so this action feels more ergonomic when paired with other selection operations
13956 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13957 if !should_skip_last {
13958 if action.keep_selections {
13959 if is_multiline_selection {
13960 let line_start = Point::new(selection.end.row, 0);
13961 new_selection_ranges.push(line_start..selection.end);
13962 } else {
13963 new_selection_ranges.push(selection.start..selection.end);
13964 }
13965 } else {
13966 new_selection_ranges.push(selection.end..selection.end);
13967 }
13968 }
13969 }
13970 }
13971 self.change_selections(Default::default(), window, cx, |s| {
13972 s.select_ranges(new_selection_ranges);
13973 });
13974 }
13975
13976 pub fn add_selection_above(
13977 &mut self,
13978 _: &AddSelectionAbove,
13979 window: &mut Window,
13980 cx: &mut Context<Self>,
13981 ) {
13982 self.add_selection(true, window, cx);
13983 }
13984
13985 pub fn add_selection_below(
13986 &mut self,
13987 _: &AddSelectionBelow,
13988 window: &mut Window,
13989 cx: &mut Context<Self>,
13990 ) {
13991 self.add_selection(false, window, cx);
13992 }
13993
13994 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13996
13997 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13998 let all_selections = self.selections.all::<Point>(cx);
13999 let text_layout_details = self.text_layout_details(window);
14000
14001 let (mut columnar_selections, new_selections_to_columnarize) = {
14002 if let Some(state) = self.add_selections_state.as_ref() {
14003 let columnar_selection_ids: HashSet<_> = state
14004 .groups
14005 .iter()
14006 .flat_map(|group| group.stack.iter())
14007 .copied()
14008 .collect();
14009
14010 all_selections
14011 .into_iter()
14012 .partition(|s| columnar_selection_ids.contains(&s.id))
14013 } else {
14014 (Vec::new(), all_selections)
14015 }
14016 };
14017
14018 let mut state = self
14019 .add_selections_state
14020 .take()
14021 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14022
14023 for selection in new_selections_to_columnarize {
14024 let range = selection.display_range(&display_map).sorted();
14025 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14026 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14027 let positions = start_x.min(end_x)..start_x.max(end_x);
14028 let mut stack = Vec::new();
14029 for row in range.start.row().0..=range.end.row().0 {
14030 if let Some(selection) = self.selections.build_columnar_selection(
14031 &display_map,
14032 DisplayRow(row),
14033 &positions,
14034 selection.reversed,
14035 &text_layout_details,
14036 ) {
14037 stack.push(selection.id);
14038 columnar_selections.push(selection);
14039 }
14040 }
14041 if !stack.is_empty() {
14042 if above {
14043 stack.reverse();
14044 }
14045 state.groups.push(AddSelectionsGroup { above, stack });
14046 }
14047 }
14048
14049 let mut final_selections = Vec::new();
14050 let end_row = if above {
14051 DisplayRow(0)
14052 } else {
14053 display_map.max_point().row()
14054 };
14055
14056 let mut last_added_item_per_group = HashMap::default();
14057 for group in state.groups.iter_mut() {
14058 if let Some(last_id) = group.stack.last() {
14059 last_added_item_per_group.insert(*last_id, group);
14060 }
14061 }
14062
14063 for selection in columnar_selections {
14064 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14065 if above == group.above {
14066 let range = selection.display_range(&display_map).sorted();
14067 debug_assert_eq!(range.start.row(), range.end.row());
14068 let mut row = range.start.row();
14069 let positions =
14070 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14071 px(start)..px(end)
14072 } else {
14073 let start_x =
14074 display_map.x_for_display_point(range.start, &text_layout_details);
14075 let end_x =
14076 display_map.x_for_display_point(range.end, &text_layout_details);
14077 start_x.min(end_x)..start_x.max(end_x)
14078 };
14079
14080 let mut maybe_new_selection = None;
14081 while row != end_row {
14082 if above {
14083 row.0 -= 1;
14084 } else {
14085 row.0 += 1;
14086 }
14087 if let Some(new_selection) = self.selections.build_columnar_selection(
14088 &display_map,
14089 row,
14090 &positions,
14091 selection.reversed,
14092 &text_layout_details,
14093 ) {
14094 maybe_new_selection = Some(new_selection);
14095 break;
14096 }
14097 }
14098
14099 if let Some(new_selection) = maybe_new_selection {
14100 group.stack.push(new_selection.id);
14101 if above {
14102 final_selections.push(new_selection);
14103 final_selections.push(selection);
14104 } else {
14105 final_selections.push(selection);
14106 final_selections.push(new_selection);
14107 }
14108 } else {
14109 final_selections.push(selection);
14110 }
14111 } else {
14112 group.stack.pop();
14113 }
14114 } else {
14115 final_selections.push(selection);
14116 }
14117 }
14118
14119 self.change_selections(Default::default(), window, cx, |s| {
14120 s.select(final_selections);
14121 });
14122
14123 let final_selection_ids: HashSet<_> = self
14124 .selections
14125 .all::<Point>(cx)
14126 .iter()
14127 .map(|s| s.id)
14128 .collect();
14129 state.groups.retain_mut(|group| {
14130 // selections might get merged above so we remove invalid items from stacks
14131 group.stack.retain(|id| final_selection_ids.contains(id));
14132
14133 // single selection in stack can be treated as initial state
14134 group.stack.len() > 1
14135 });
14136
14137 if !state.groups.is_empty() {
14138 self.add_selections_state = Some(state);
14139 }
14140 }
14141
14142 fn select_match_ranges(
14143 &mut self,
14144 range: Range<usize>,
14145 reversed: bool,
14146 replace_newest: bool,
14147 auto_scroll: Option<Autoscroll>,
14148 window: &mut Window,
14149 cx: &mut Context<Editor>,
14150 ) {
14151 self.unfold_ranges(
14152 std::slice::from_ref(&range),
14153 false,
14154 auto_scroll.is_some(),
14155 cx,
14156 );
14157 let effects = if let Some(scroll) = auto_scroll {
14158 SelectionEffects::scroll(scroll)
14159 } else {
14160 SelectionEffects::no_scroll()
14161 };
14162 self.change_selections(effects, window, cx, |s| {
14163 if replace_newest {
14164 s.delete(s.newest_anchor().id);
14165 }
14166 if reversed {
14167 s.insert_range(range.end..range.start);
14168 } else {
14169 s.insert_range(range);
14170 }
14171 });
14172 }
14173
14174 pub fn select_next_match_internal(
14175 &mut self,
14176 display_map: &DisplaySnapshot,
14177 replace_newest: bool,
14178 autoscroll: Option<Autoscroll>,
14179 window: &mut Window,
14180 cx: &mut Context<Self>,
14181 ) -> Result<()> {
14182 let buffer = &display_map.buffer_snapshot;
14183 let mut selections = self.selections.all::<usize>(cx);
14184 if let Some(mut select_next_state) = self.select_next_state.take() {
14185 let query = &select_next_state.query;
14186 if !select_next_state.done {
14187 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14188 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14189 let mut next_selected_range = None;
14190
14191 let bytes_after_last_selection =
14192 buffer.bytes_in_range(last_selection.end..buffer.len());
14193 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14194 let query_matches = query
14195 .stream_find_iter(bytes_after_last_selection)
14196 .map(|result| (last_selection.end, result))
14197 .chain(
14198 query
14199 .stream_find_iter(bytes_before_first_selection)
14200 .map(|result| (0, result)),
14201 );
14202
14203 for (start_offset, query_match) in query_matches {
14204 let query_match = query_match.unwrap(); // can only fail due to I/O
14205 let offset_range =
14206 start_offset + query_match.start()..start_offset + query_match.end();
14207
14208 if !select_next_state.wordwise
14209 || (!buffer.is_inside_word(offset_range.start, false)
14210 && !buffer.is_inside_word(offset_range.end, false))
14211 {
14212 // TODO: This is n^2, because we might check all the selections
14213 if !selections
14214 .iter()
14215 .any(|selection| selection.range().overlaps(&offset_range))
14216 {
14217 next_selected_range = Some(offset_range);
14218 break;
14219 }
14220 }
14221 }
14222
14223 if let Some(next_selected_range) = next_selected_range {
14224 self.select_match_ranges(
14225 next_selected_range,
14226 last_selection.reversed,
14227 replace_newest,
14228 autoscroll,
14229 window,
14230 cx,
14231 );
14232 } else {
14233 select_next_state.done = true;
14234 }
14235 }
14236
14237 self.select_next_state = Some(select_next_state);
14238 } else {
14239 let mut only_carets = true;
14240 let mut same_text_selected = true;
14241 let mut selected_text = None;
14242
14243 let mut selections_iter = selections.iter().peekable();
14244 while let Some(selection) = selections_iter.next() {
14245 if selection.start != selection.end {
14246 only_carets = false;
14247 }
14248
14249 if same_text_selected {
14250 if selected_text.is_none() {
14251 selected_text =
14252 Some(buffer.text_for_range(selection.range()).collect::<String>());
14253 }
14254
14255 if let Some(next_selection) = selections_iter.peek() {
14256 if next_selection.range().len() == selection.range().len() {
14257 let next_selected_text = buffer
14258 .text_for_range(next_selection.range())
14259 .collect::<String>();
14260 if Some(next_selected_text) != selected_text {
14261 same_text_selected = false;
14262 selected_text = None;
14263 }
14264 } else {
14265 same_text_selected = false;
14266 selected_text = None;
14267 }
14268 }
14269 }
14270 }
14271
14272 if only_carets {
14273 for selection in &mut selections {
14274 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14275 selection.start = word_range.start;
14276 selection.end = word_range.end;
14277 selection.goal = SelectionGoal::None;
14278 selection.reversed = false;
14279 self.select_match_ranges(
14280 selection.start..selection.end,
14281 selection.reversed,
14282 replace_newest,
14283 autoscroll,
14284 window,
14285 cx,
14286 );
14287 }
14288
14289 if selections.len() == 1 {
14290 let selection = selections
14291 .last()
14292 .expect("ensured that there's only one selection");
14293 let query = buffer
14294 .text_for_range(selection.start..selection.end)
14295 .collect::<String>();
14296 let is_empty = query.is_empty();
14297 let select_state = SelectNextState {
14298 query: AhoCorasick::new(&[query])?,
14299 wordwise: true,
14300 done: is_empty,
14301 };
14302 self.select_next_state = Some(select_state);
14303 } else {
14304 self.select_next_state = None;
14305 }
14306 } else if let Some(selected_text) = selected_text {
14307 self.select_next_state = Some(SelectNextState {
14308 query: AhoCorasick::new(&[selected_text])?,
14309 wordwise: false,
14310 done: false,
14311 });
14312 self.select_next_match_internal(
14313 display_map,
14314 replace_newest,
14315 autoscroll,
14316 window,
14317 cx,
14318 )?;
14319 }
14320 }
14321 Ok(())
14322 }
14323
14324 pub fn select_all_matches(
14325 &mut self,
14326 _action: &SelectAllMatches,
14327 window: &mut Window,
14328 cx: &mut Context<Self>,
14329 ) -> Result<()> {
14330 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14331
14332 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14333
14334 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14335 let Some(select_next_state) = self.select_next_state.as_mut() else {
14336 return Ok(());
14337 };
14338 if select_next_state.done {
14339 return Ok(());
14340 }
14341
14342 let mut new_selections = Vec::new();
14343
14344 let reversed = self.selections.oldest::<usize>(cx).reversed;
14345 let buffer = &display_map.buffer_snapshot;
14346 let query_matches = select_next_state
14347 .query
14348 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14349
14350 for query_match in query_matches.into_iter() {
14351 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14352 let offset_range = if reversed {
14353 query_match.end()..query_match.start()
14354 } else {
14355 query_match.start()..query_match.end()
14356 };
14357
14358 if !select_next_state.wordwise
14359 || (!buffer.is_inside_word(offset_range.start, false)
14360 && !buffer.is_inside_word(offset_range.end, false))
14361 {
14362 new_selections.push(offset_range.start..offset_range.end);
14363 }
14364 }
14365
14366 select_next_state.done = true;
14367
14368 if new_selections.is_empty() {
14369 log::error!("bug: new_selections is empty in select_all_matches");
14370 return Ok(());
14371 }
14372
14373 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14374 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14375 selections.select_ranges(new_selections)
14376 });
14377
14378 Ok(())
14379 }
14380
14381 pub fn select_next(
14382 &mut self,
14383 action: &SelectNext,
14384 window: &mut Window,
14385 cx: &mut Context<Self>,
14386 ) -> Result<()> {
14387 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14388 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14389 self.select_next_match_internal(
14390 &display_map,
14391 action.replace_newest,
14392 Some(Autoscroll::newest()),
14393 window,
14394 cx,
14395 )?;
14396 Ok(())
14397 }
14398
14399 pub fn select_previous(
14400 &mut self,
14401 action: &SelectPrevious,
14402 window: &mut Window,
14403 cx: &mut Context<Self>,
14404 ) -> Result<()> {
14405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14406 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14407 let buffer = &display_map.buffer_snapshot;
14408 let mut selections = self.selections.all::<usize>(cx);
14409 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14410 let query = &select_prev_state.query;
14411 if !select_prev_state.done {
14412 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14413 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14414 let mut next_selected_range = None;
14415 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14416 let bytes_before_last_selection =
14417 buffer.reversed_bytes_in_range(0..last_selection.start);
14418 let bytes_after_first_selection =
14419 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14420 let query_matches = query
14421 .stream_find_iter(bytes_before_last_selection)
14422 .map(|result| (last_selection.start, result))
14423 .chain(
14424 query
14425 .stream_find_iter(bytes_after_first_selection)
14426 .map(|result| (buffer.len(), result)),
14427 );
14428 for (end_offset, query_match) in query_matches {
14429 let query_match = query_match.unwrap(); // can only fail due to I/O
14430 let offset_range =
14431 end_offset - query_match.end()..end_offset - query_match.start();
14432
14433 if !select_prev_state.wordwise
14434 || (!buffer.is_inside_word(offset_range.start, false)
14435 && !buffer.is_inside_word(offset_range.end, false))
14436 {
14437 next_selected_range = Some(offset_range);
14438 break;
14439 }
14440 }
14441
14442 if let Some(next_selected_range) = next_selected_range {
14443 self.select_match_ranges(
14444 next_selected_range,
14445 last_selection.reversed,
14446 action.replace_newest,
14447 Some(Autoscroll::newest()),
14448 window,
14449 cx,
14450 );
14451 } else {
14452 select_prev_state.done = true;
14453 }
14454 }
14455
14456 self.select_prev_state = Some(select_prev_state);
14457 } else {
14458 let mut only_carets = true;
14459 let mut same_text_selected = true;
14460 let mut selected_text = None;
14461
14462 let mut selections_iter = selections.iter().peekable();
14463 while let Some(selection) = selections_iter.next() {
14464 if selection.start != selection.end {
14465 only_carets = false;
14466 }
14467
14468 if same_text_selected {
14469 if selected_text.is_none() {
14470 selected_text =
14471 Some(buffer.text_for_range(selection.range()).collect::<String>());
14472 }
14473
14474 if let Some(next_selection) = selections_iter.peek() {
14475 if next_selection.range().len() == selection.range().len() {
14476 let next_selected_text = buffer
14477 .text_for_range(next_selection.range())
14478 .collect::<String>();
14479 if Some(next_selected_text) != selected_text {
14480 same_text_selected = false;
14481 selected_text = None;
14482 }
14483 } else {
14484 same_text_selected = false;
14485 selected_text = None;
14486 }
14487 }
14488 }
14489 }
14490
14491 if only_carets {
14492 for selection in &mut selections {
14493 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14494 selection.start = word_range.start;
14495 selection.end = word_range.end;
14496 selection.goal = SelectionGoal::None;
14497 selection.reversed = false;
14498 self.select_match_ranges(
14499 selection.start..selection.end,
14500 selection.reversed,
14501 action.replace_newest,
14502 Some(Autoscroll::newest()),
14503 window,
14504 cx,
14505 );
14506 }
14507 if selections.len() == 1 {
14508 let selection = selections
14509 .last()
14510 .expect("ensured that there's only one selection");
14511 let query = buffer
14512 .text_for_range(selection.start..selection.end)
14513 .collect::<String>();
14514 let is_empty = query.is_empty();
14515 let select_state = SelectNextState {
14516 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14517 wordwise: true,
14518 done: is_empty,
14519 };
14520 self.select_prev_state = Some(select_state);
14521 } else {
14522 self.select_prev_state = None;
14523 }
14524 } else if let Some(selected_text) = selected_text {
14525 self.select_prev_state = Some(SelectNextState {
14526 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14527 wordwise: false,
14528 done: false,
14529 });
14530 self.select_previous(action, window, cx)?;
14531 }
14532 }
14533 Ok(())
14534 }
14535
14536 pub fn find_next_match(
14537 &mut self,
14538 _: &FindNextMatch,
14539 window: &mut Window,
14540 cx: &mut Context<Self>,
14541 ) -> Result<()> {
14542 let selections = self.selections.disjoint_anchors_arc();
14543 match selections.first() {
14544 Some(first) if selections.len() >= 2 => {
14545 self.change_selections(Default::default(), window, cx, |s| {
14546 s.select_ranges([first.range()]);
14547 });
14548 }
14549 _ => self.select_next(
14550 &SelectNext {
14551 replace_newest: true,
14552 },
14553 window,
14554 cx,
14555 )?,
14556 }
14557 Ok(())
14558 }
14559
14560 pub fn find_previous_match(
14561 &mut self,
14562 _: &FindPreviousMatch,
14563 window: &mut Window,
14564 cx: &mut Context<Self>,
14565 ) -> Result<()> {
14566 let selections = self.selections.disjoint_anchors_arc();
14567 match selections.last() {
14568 Some(last) if selections.len() >= 2 => {
14569 self.change_selections(Default::default(), window, cx, |s| {
14570 s.select_ranges([last.range()]);
14571 });
14572 }
14573 _ => self.select_previous(
14574 &SelectPrevious {
14575 replace_newest: true,
14576 },
14577 window,
14578 cx,
14579 )?,
14580 }
14581 Ok(())
14582 }
14583
14584 pub fn toggle_comments(
14585 &mut self,
14586 action: &ToggleComments,
14587 window: &mut Window,
14588 cx: &mut Context<Self>,
14589 ) {
14590 if self.read_only(cx) {
14591 return;
14592 }
14593 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14594 let text_layout_details = &self.text_layout_details(window);
14595 self.transact(window, cx, |this, window, cx| {
14596 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14597 let mut edits = Vec::new();
14598 let mut selection_edit_ranges = Vec::new();
14599 let mut last_toggled_row = None;
14600 let snapshot = this.buffer.read(cx).read(cx);
14601 let empty_str: Arc<str> = Arc::default();
14602 let mut suffixes_inserted = Vec::new();
14603 let ignore_indent = action.ignore_indent;
14604
14605 fn comment_prefix_range(
14606 snapshot: &MultiBufferSnapshot,
14607 row: MultiBufferRow,
14608 comment_prefix: &str,
14609 comment_prefix_whitespace: &str,
14610 ignore_indent: bool,
14611 ) -> Range<Point> {
14612 let indent_size = if ignore_indent {
14613 0
14614 } else {
14615 snapshot.indent_size_for_line(row).len
14616 };
14617
14618 let start = Point::new(row.0, indent_size);
14619
14620 let mut line_bytes = snapshot
14621 .bytes_in_range(start..snapshot.max_point())
14622 .flatten()
14623 .copied();
14624
14625 // If this line currently begins with the line comment prefix, then record
14626 // the range containing the prefix.
14627 if line_bytes
14628 .by_ref()
14629 .take(comment_prefix.len())
14630 .eq(comment_prefix.bytes())
14631 {
14632 // Include any whitespace that matches the comment prefix.
14633 let matching_whitespace_len = line_bytes
14634 .zip(comment_prefix_whitespace.bytes())
14635 .take_while(|(a, b)| a == b)
14636 .count() as u32;
14637 let end = Point::new(
14638 start.row,
14639 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14640 );
14641 start..end
14642 } else {
14643 start..start
14644 }
14645 }
14646
14647 fn comment_suffix_range(
14648 snapshot: &MultiBufferSnapshot,
14649 row: MultiBufferRow,
14650 comment_suffix: &str,
14651 comment_suffix_has_leading_space: bool,
14652 ) -> Range<Point> {
14653 let end = Point::new(row.0, snapshot.line_len(row));
14654 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14655
14656 let mut line_end_bytes = snapshot
14657 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14658 .flatten()
14659 .copied();
14660
14661 let leading_space_len = if suffix_start_column > 0
14662 && line_end_bytes.next() == Some(b' ')
14663 && comment_suffix_has_leading_space
14664 {
14665 1
14666 } else {
14667 0
14668 };
14669
14670 // If this line currently begins with the line comment prefix, then record
14671 // the range containing the prefix.
14672 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14673 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14674 start..end
14675 } else {
14676 end..end
14677 }
14678 }
14679
14680 // TODO: Handle selections that cross excerpts
14681 for selection in &mut selections {
14682 let start_column = snapshot
14683 .indent_size_for_line(MultiBufferRow(selection.start.row))
14684 .len;
14685 let language = if let Some(language) =
14686 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14687 {
14688 language
14689 } else {
14690 continue;
14691 };
14692
14693 selection_edit_ranges.clear();
14694
14695 // If multiple selections contain a given row, avoid processing that
14696 // row more than once.
14697 let mut start_row = MultiBufferRow(selection.start.row);
14698 if last_toggled_row == Some(start_row) {
14699 start_row = start_row.next_row();
14700 }
14701 let end_row =
14702 if selection.end.row > selection.start.row && selection.end.column == 0 {
14703 MultiBufferRow(selection.end.row - 1)
14704 } else {
14705 MultiBufferRow(selection.end.row)
14706 };
14707 last_toggled_row = Some(end_row);
14708
14709 if start_row > end_row {
14710 continue;
14711 }
14712
14713 // If the language has line comments, toggle those.
14714 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14715
14716 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14717 if ignore_indent {
14718 full_comment_prefixes = full_comment_prefixes
14719 .into_iter()
14720 .map(|s| Arc::from(s.trim_end()))
14721 .collect();
14722 }
14723
14724 if !full_comment_prefixes.is_empty() {
14725 let first_prefix = full_comment_prefixes
14726 .first()
14727 .expect("prefixes is non-empty");
14728 let prefix_trimmed_lengths = full_comment_prefixes
14729 .iter()
14730 .map(|p| p.trim_end_matches(' ').len())
14731 .collect::<SmallVec<[usize; 4]>>();
14732
14733 let mut all_selection_lines_are_comments = true;
14734
14735 for row in start_row.0..=end_row.0 {
14736 let row = MultiBufferRow(row);
14737 if start_row < end_row && snapshot.is_line_blank(row) {
14738 continue;
14739 }
14740
14741 let prefix_range = full_comment_prefixes
14742 .iter()
14743 .zip(prefix_trimmed_lengths.iter().copied())
14744 .map(|(prefix, trimmed_prefix_len)| {
14745 comment_prefix_range(
14746 snapshot.deref(),
14747 row,
14748 &prefix[..trimmed_prefix_len],
14749 &prefix[trimmed_prefix_len..],
14750 ignore_indent,
14751 )
14752 })
14753 .max_by_key(|range| range.end.column - range.start.column)
14754 .expect("prefixes is non-empty");
14755
14756 if prefix_range.is_empty() {
14757 all_selection_lines_are_comments = false;
14758 }
14759
14760 selection_edit_ranges.push(prefix_range);
14761 }
14762
14763 if all_selection_lines_are_comments {
14764 edits.extend(
14765 selection_edit_ranges
14766 .iter()
14767 .cloned()
14768 .map(|range| (range, empty_str.clone())),
14769 );
14770 } else {
14771 let min_column = selection_edit_ranges
14772 .iter()
14773 .map(|range| range.start.column)
14774 .min()
14775 .unwrap_or(0);
14776 edits.extend(selection_edit_ranges.iter().map(|range| {
14777 let position = Point::new(range.start.row, min_column);
14778 (position..position, first_prefix.clone())
14779 }));
14780 }
14781 } else if let Some(BlockCommentConfig {
14782 start: full_comment_prefix,
14783 end: comment_suffix,
14784 ..
14785 }) = language.block_comment()
14786 {
14787 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14788 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14789 let prefix_range = comment_prefix_range(
14790 snapshot.deref(),
14791 start_row,
14792 comment_prefix,
14793 comment_prefix_whitespace,
14794 ignore_indent,
14795 );
14796 let suffix_range = comment_suffix_range(
14797 snapshot.deref(),
14798 end_row,
14799 comment_suffix.trim_start_matches(' '),
14800 comment_suffix.starts_with(' '),
14801 );
14802
14803 if prefix_range.is_empty() || suffix_range.is_empty() {
14804 edits.push((
14805 prefix_range.start..prefix_range.start,
14806 full_comment_prefix.clone(),
14807 ));
14808 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14809 suffixes_inserted.push((end_row, comment_suffix.len()));
14810 } else {
14811 edits.push((prefix_range, empty_str.clone()));
14812 edits.push((suffix_range, empty_str.clone()));
14813 }
14814 } else {
14815 continue;
14816 }
14817 }
14818
14819 drop(snapshot);
14820 this.buffer.update(cx, |buffer, cx| {
14821 buffer.edit(edits, None, cx);
14822 });
14823
14824 // Adjust selections so that they end before any comment suffixes that
14825 // were inserted.
14826 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14827 let mut selections = this.selections.all::<Point>(cx);
14828 let snapshot = this.buffer.read(cx).read(cx);
14829 for selection in &mut selections {
14830 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14831 match row.cmp(&MultiBufferRow(selection.end.row)) {
14832 Ordering::Less => {
14833 suffixes_inserted.next();
14834 continue;
14835 }
14836 Ordering::Greater => break,
14837 Ordering::Equal => {
14838 if selection.end.column == snapshot.line_len(row) {
14839 if selection.is_empty() {
14840 selection.start.column -= suffix_len as u32;
14841 }
14842 selection.end.column -= suffix_len as u32;
14843 }
14844 break;
14845 }
14846 }
14847 }
14848 }
14849
14850 drop(snapshot);
14851 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14852
14853 let selections = this.selections.all::<Point>(cx);
14854 let selections_on_single_row = selections.windows(2).all(|selections| {
14855 selections[0].start.row == selections[1].start.row
14856 && selections[0].end.row == selections[1].end.row
14857 && selections[0].start.row == selections[0].end.row
14858 });
14859 let selections_selecting = selections
14860 .iter()
14861 .any(|selection| selection.start != selection.end);
14862 let advance_downwards = action.advance_downwards
14863 && selections_on_single_row
14864 && !selections_selecting
14865 && !matches!(this.mode, EditorMode::SingleLine);
14866
14867 if advance_downwards {
14868 let snapshot = this.buffer.read(cx).snapshot(cx);
14869
14870 this.change_selections(Default::default(), window, cx, |s| {
14871 s.move_cursors_with(|display_snapshot, display_point, _| {
14872 let mut point = display_point.to_point(display_snapshot);
14873 point.row += 1;
14874 point = snapshot.clip_point(point, Bias::Left);
14875 let display_point = point.to_display_point(display_snapshot);
14876 let goal = SelectionGoal::HorizontalPosition(
14877 display_snapshot
14878 .x_for_display_point(display_point, text_layout_details)
14879 .into(),
14880 );
14881 (display_point, goal)
14882 })
14883 });
14884 }
14885 });
14886 }
14887
14888 pub fn select_enclosing_symbol(
14889 &mut self,
14890 _: &SelectEnclosingSymbol,
14891 window: &mut Window,
14892 cx: &mut Context<Self>,
14893 ) {
14894 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14895
14896 let buffer = self.buffer.read(cx).snapshot(cx);
14897 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14898
14899 fn update_selection(
14900 selection: &Selection<usize>,
14901 buffer_snap: &MultiBufferSnapshot,
14902 ) -> Option<Selection<usize>> {
14903 let cursor = selection.head();
14904 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14905 for symbol in symbols.iter().rev() {
14906 let start = symbol.range.start.to_offset(buffer_snap);
14907 let end = symbol.range.end.to_offset(buffer_snap);
14908 let new_range = start..end;
14909 if start < selection.start || end > selection.end {
14910 return Some(Selection {
14911 id: selection.id,
14912 start: new_range.start,
14913 end: new_range.end,
14914 goal: SelectionGoal::None,
14915 reversed: selection.reversed,
14916 });
14917 }
14918 }
14919 None
14920 }
14921
14922 let mut selected_larger_symbol = false;
14923 let new_selections = old_selections
14924 .iter()
14925 .map(|selection| match update_selection(selection, &buffer) {
14926 Some(new_selection) => {
14927 if new_selection.range() != selection.range() {
14928 selected_larger_symbol = true;
14929 }
14930 new_selection
14931 }
14932 None => selection.clone(),
14933 })
14934 .collect::<Vec<_>>();
14935
14936 if selected_larger_symbol {
14937 self.change_selections(Default::default(), window, cx, |s| {
14938 s.select(new_selections);
14939 });
14940 }
14941 }
14942
14943 pub fn select_larger_syntax_node(
14944 &mut self,
14945 _: &SelectLargerSyntaxNode,
14946 window: &mut Window,
14947 cx: &mut Context<Self>,
14948 ) {
14949 let Some(visible_row_count) = self.visible_row_count() else {
14950 return;
14951 };
14952 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14953 if old_selections.is_empty() {
14954 return;
14955 }
14956
14957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14958
14959 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14960 let buffer = self.buffer.read(cx).snapshot(cx);
14961
14962 let mut selected_larger_node = false;
14963 let mut new_selections = old_selections
14964 .iter()
14965 .map(|selection| {
14966 let old_range = selection.start..selection.end;
14967
14968 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14969 // manually select word at selection
14970 if ["string_content", "inline"].contains(&node.kind()) {
14971 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14972 // ignore if word is already selected
14973 if !word_range.is_empty() && old_range != word_range {
14974 let (last_word_range, _) =
14975 buffer.surrounding_word(old_range.end, false);
14976 // only select word if start and end point belongs to same word
14977 if word_range == last_word_range {
14978 selected_larger_node = true;
14979 return Selection {
14980 id: selection.id,
14981 start: word_range.start,
14982 end: word_range.end,
14983 goal: SelectionGoal::None,
14984 reversed: selection.reversed,
14985 };
14986 }
14987 }
14988 }
14989 }
14990
14991 let mut new_range = old_range.clone();
14992 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
14993 {
14994 new_range = match containing_range {
14995 MultiOrSingleBufferOffsetRange::Single(_) => break,
14996 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14997 };
14998 if !node.is_named() {
14999 continue;
15000 }
15001 if !display_map.intersects_fold(new_range.start)
15002 && !display_map.intersects_fold(new_range.end)
15003 {
15004 break;
15005 }
15006 }
15007
15008 selected_larger_node |= new_range != old_range;
15009 Selection {
15010 id: selection.id,
15011 start: new_range.start,
15012 end: new_range.end,
15013 goal: SelectionGoal::None,
15014 reversed: selection.reversed,
15015 }
15016 })
15017 .collect::<Vec<_>>();
15018
15019 if !selected_larger_node {
15020 return; // don't put this call in the history
15021 }
15022
15023 // scroll based on transformation done to the last selection created by the user
15024 let (last_old, last_new) = old_selections
15025 .last()
15026 .zip(new_selections.last().cloned())
15027 .expect("old_selections isn't empty");
15028
15029 // revert selection
15030 let is_selection_reversed = {
15031 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15032 new_selections.last_mut().expect("checked above").reversed =
15033 should_newest_selection_be_reversed;
15034 should_newest_selection_be_reversed
15035 };
15036
15037 if selected_larger_node {
15038 self.select_syntax_node_history.disable_clearing = true;
15039 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15040 s.select(new_selections.clone());
15041 });
15042 self.select_syntax_node_history.disable_clearing = false;
15043 }
15044
15045 let start_row = last_new.start.to_display_point(&display_map).row().0;
15046 let end_row = last_new.end.to_display_point(&display_map).row().0;
15047 let selection_height = end_row - start_row + 1;
15048 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15049
15050 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15051 let scroll_behavior = if fits_on_the_screen {
15052 self.request_autoscroll(Autoscroll::fit(), cx);
15053 SelectSyntaxNodeScrollBehavior::FitSelection
15054 } else if is_selection_reversed {
15055 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15056 SelectSyntaxNodeScrollBehavior::CursorTop
15057 } else {
15058 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15059 SelectSyntaxNodeScrollBehavior::CursorBottom
15060 };
15061
15062 self.select_syntax_node_history.push((
15063 old_selections,
15064 scroll_behavior,
15065 is_selection_reversed,
15066 ));
15067 }
15068
15069 pub fn select_smaller_syntax_node(
15070 &mut self,
15071 _: &SelectSmallerSyntaxNode,
15072 window: &mut Window,
15073 cx: &mut Context<Self>,
15074 ) {
15075 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15076
15077 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15078 self.select_syntax_node_history.pop()
15079 {
15080 if let Some(selection) = selections.last_mut() {
15081 selection.reversed = is_selection_reversed;
15082 }
15083
15084 self.select_syntax_node_history.disable_clearing = true;
15085 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15086 s.select(selections.to_vec());
15087 });
15088 self.select_syntax_node_history.disable_clearing = false;
15089
15090 match scroll_behavior {
15091 SelectSyntaxNodeScrollBehavior::CursorTop => {
15092 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15093 }
15094 SelectSyntaxNodeScrollBehavior::FitSelection => {
15095 self.request_autoscroll(Autoscroll::fit(), cx);
15096 }
15097 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15098 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15099 }
15100 }
15101 }
15102 }
15103
15104 pub fn unwrap_syntax_node(
15105 &mut self,
15106 _: &UnwrapSyntaxNode,
15107 window: &mut Window,
15108 cx: &mut Context<Self>,
15109 ) {
15110 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15111
15112 let buffer = self.buffer.read(cx).snapshot(cx);
15113 let selections = self
15114 .selections
15115 .all::<usize>(cx)
15116 .into_iter()
15117 // subtracting the offset requires sorting
15118 .sorted_by_key(|i| i.start);
15119
15120 let full_edits = selections
15121 .into_iter()
15122 .filter_map(|selection| {
15123 let child = if selection.is_empty()
15124 && let Some((_, ancestor_range)) =
15125 buffer.syntax_ancestor(selection.start..selection.end)
15126 {
15127 match ancestor_range {
15128 MultiOrSingleBufferOffsetRange::Single(range) => range,
15129 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15130 }
15131 } else {
15132 selection.range()
15133 };
15134
15135 let mut parent = child.clone();
15136 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15137 parent = match ancestor_range {
15138 MultiOrSingleBufferOffsetRange::Single(range) => range,
15139 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15140 };
15141 if parent.start < child.start || parent.end > child.end {
15142 break;
15143 }
15144 }
15145
15146 if parent == child {
15147 return None;
15148 }
15149 let text = buffer.text_for_range(child).collect::<String>();
15150 Some((selection.id, parent, text))
15151 })
15152 .collect::<Vec<_>>();
15153 if full_edits.is_empty() {
15154 return;
15155 }
15156
15157 self.transact(window, cx, |this, window, cx| {
15158 this.buffer.update(cx, |buffer, cx| {
15159 buffer.edit(
15160 full_edits
15161 .iter()
15162 .map(|(_, p, t)| (p.clone(), t.clone()))
15163 .collect::<Vec<_>>(),
15164 None,
15165 cx,
15166 );
15167 });
15168 this.change_selections(Default::default(), window, cx, |s| {
15169 let mut offset = 0;
15170 let mut selections = vec![];
15171 for (id, parent, text) in full_edits {
15172 let start = parent.start - offset;
15173 offset += parent.len() - text.len();
15174 selections.push(Selection {
15175 id,
15176 start,
15177 end: start + text.len(),
15178 reversed: false,
15179 goal: Default::default(),
15180 });
15181 }
15182 s.select(selections);
15183 });
15184 });
15185 }
15186
15187 pub fn select_next_syntax_node(
15188 &mut self,
15189 _: &SelectNextSyntaxNode,
15190 window: &mut Window,
15191 cx: &mut Context<Self>,
15192 ) {
15193 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15194 if old_selections.is_empty() {
15195 return;
15196 }
15197
15198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15199
15200 let buffer = self.buffer.read(cx).snapshot(cx);
15201 let mut selected_sibling = false;
15202
15203 let new_selections = old_selections
15204 .iter()
15205 .map(|selection| {
15206 let old_range = selection.start..selection.end;
15207
15208 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15209 let new_range = node.byte_range();
15210 selected_sibling = true;
15211 Selection {
15212 id: selection.id,
15213 start: new_range.start,
15214 end: new_range.end,
15215 goal: SelectionGoal::None,
15216 reversed: selection.reversed,
15217 }
15218 } else {
15219 selection.clone()
15220 }
15221 })
15222 .collect::<Vec<_>>();
15223
15224 if selected_sibling {
15225 self.change_selections(
15226 SelectionEffects::scroll(Autoscroll::fit()),
15227 window,
15228 cx,
15229 |s| {
15230 s.select(new_selections);
15231 },
15232 );
15233 }
15234 }
15235
15236 pub fn select_prev_syntax_node(
15237 &mut self,
15238 _: &SelectPreviousSyntaxNode,
15239 window: &mut Window,
15240 cx: &mut Context<Self>,
15241 ) {
15242 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15243 if old_selections.is_empty() {
15244 return;
15245 }
15246
15247 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15248
15249 let buffer = self.buffer.read(cx).snapshot(cx);
15250 let mut selected_sibling = false;
15251
15252 let new_selections = old_selections
15253 .iter()
15254 .map(|selection| {
15255 let old_range = selection.start..selection.end;
15256
15257 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15258 let new_range = node.byte_range();
15259 selected_sibling = true;
15260 Selection {
15261 id: selection.id,
15262 start: new_range.start,
15263 end: new_range.end,
15264 goal: SelectionGoal::None,
15265 reversed: selection.reversed,
15266 }
15267 } else {
15268 selection.clone()
15269 }
15270 })
15271 .collect::<Vec<_>>();
15272
15273 if selected_sibling {
15274 self.change_selections(
15275 SelectionEffects::scroll(Autoscroll::fit()),
15276 window,
15277 cx,
15278 |s| {
15279 s.select(new_selections);
15280 },
15281 );
15282 }
15283 }
15284
15285 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15286 if !EditorSettings::get_global(cx).gutter.runnables {
15287 self.clear_tasks();
15288 return Task::ready(());
15289 }
15290 let project = self.project().map(Entity::downgrade);
15291 let task_sources = self.lsp_task_sources(cx);
15292 let multi_buffer = self.buffer.downgrade();
15293 cx.spawn_in(window, async move |editor, cx| {
15294 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15295 let Some(project) = project.and_then(|p| p.upgrade()) else {
15296 return;
15297 };
15298 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15299 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15300 }) else {
15301 return;
15302 };
15303
15304 let hide_runnables = project
15305 .update(cx, |project, _| project.is_via_collab())
15306 .unwrap_or(true);
15307 if hide_runnables {
15308 return;
15309 }
15310 let new_rows =
15311 cx.background_spawn({
15312 let snapshot = display_snapshot.clone();
15313 async move {
15314 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15315 }
15316 })
15317 .await;
15318 let Ok(lsp_tasks) =
15319 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15320 else {
15321 return;
15322 };
15323 let lsp_tasks = lsp_tasks.await;
15324
15325 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15326 lsp_tasks
15327 .into_iter()
15328 .flat_map(|(kind, tasks)| {
15329 tasks.into_iter().filter_map(move |(location, task)| {
15330 Some((kind.clone(), location?, task))
15331 })
15332 })
15333 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15334 let buffer = location.target.buffer;
15335 let buffer_snapshot = buffer.read(cx).snapshot();
15336 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15337 |(excerpt_id, snapshot, _)| {
15338 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15339 display_snapshot
15340 .buffer_snapshot
15341 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15342 } else {
15343 None
15344 }
15345 },
15346 );
15347 if let Some(offset) = offset {
15348 let task_buffer_range =
15349 location.target.range.to_point(&buffer_snapshot);
15350 let context_buffer_range =
15351 task_buffer_range.to_offset(&buffer_snapshot);
15352 let context_range = BufferOffset(context_buffer_range.start)
15353 ..BufferOffset(context_buffer_range.end);
15354
15355 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15356 .or_insert_with(|| RunnableTasks {
15357 templates: Vec::new(),
15358 offset,
15359 column: task_buffer_range.start.column,
15360 extra_variables: HashMap::default(),
15361 context_range,
15362 })
15363 .templates
15364 .push((kind, task.original_task().clone()));
15365 }
15366
15367 acc
15368 })
15369 }) else {
15370 return;
15371 };
15372
15373 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15374 buffer.language_settings(cx).tasks.prefer_lsp
15375 }) else {
15376 return;
15377 };
15378
15379 let rows = Self::runnable_rows(
15380 project,
15381 display_snapshot,
15382 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15383 new_rows,
15384 cx.clone(),
15385 )
15386 .await;
15387 editor
15388 .update(cx, |editor, _| {
15389 editor.clear_tasks();
15390 for (key, mut value) in rows {
15391 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15392 value.templates.extend(lsp_tasks.templates);
15393 }
15394
15395 editor.insert_tasks(key, value);
15396 }
15397 for (key, value) in lsp_tasks_by_rows {
15398 editor.insert_tasks(key, value);
15399 }
15400 })
15401 .ok();
15402 })
15403 }
15404 fn fetch_runnable_ranges(
15405 snapshot: &DisplaySnapshot,
15406 range: Range<Anchor>,
15407 ) -> Vec<language::RunnableRange> {
15408 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15409 }
15410
15411 fn runnable_rows(
15412 project: Entity<Project>,
15413 snapshot: DisplaySnapshot,
15414 prefer_lsp: bool,
15415 runnable_ranges: Vec<RunnableRange>,
15416 cx: AsyncWindowContext,
15417 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15418 cx.spawn(async move |cx| {
15419 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15420 for mut runnable in runnable_ranges {
15421 let Some(tasks) = cx
15422 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15423 .ok()
15424 else {
15425 continue;
15426 };
15427 let mut tasks = tasks.await;
15428
15429 if prefer_lsp {
15430 tasks.retain(|(task_kind, _)| {
15431 !matches!(task_kind, TaskSourceKind::Language { .. })
15432 });
15433 }
15434 if tasks.is_empty() {
15435 continue;
15436 }
15437
15438 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15439 let Some(row) = snapshot
15440 .buffer_snapshot
15441 .buffer_line_for_row(MultiBufferRow(point.row))
15442 .map(|(_, range)| range.start.row)
15443 else {
15444 continue;
15445 };
15446
15447 let context_range =
15448 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15449 runnable_rows.push((
15450 (runnable.buffer_id, row),
15451 RunnableTasks {
15452 templates: tasks,
15453 offset: snapshot
15454 .buffer_snapshot
15455 .anchor_before(runnable.run_range.start),
15456 context_range,
15457 column: point.column,
15458 extra_variables: runnable.extra_captures,
15459 },
15460 ));
15461 }
15462 runnable_rows
15463 })
15464 }
15465
15466 fn templates_with_tags(
15467 project: &Entity<Project>,
15468 runnable: &mut Runnable,
15469 cx: &mut App,
15470 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15471 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15472 let (worktree_id, file) = project
15473 .buffer_for_id(runnable.buffer, cx)
15474 .and_then(|buffer| buffer.read(cx).file())
15475 .map(|file| (file.worktree_id(cx), file.clone()))
15476 .unzip();
15477
15478 (
15479 project.task_store().read(cx).task_inventory().cloned(),
15480 worktree_id,
15481 file,
15482 )
15483 });
15484
15485 let tags = mem::take(&mut runnable.tags);
15486 let language = runnable.language.clone();
15487 cx.spawn(async move |cx| {
15488 let mut templates_with_tags = Vec::new();
15489 if let Some(inventory) = inventory {
15490 for RunnableTag(tag) in tags {
15491 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15492 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15493 }) else {
15494 return templates_with_tags;
15495 };
15496 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15497 move |(_, template)| {
15498 template.tags.iter().any(|source_tag| source_tag == &tag)
15499 },
15500 ));
15501 }
15502 }
15503 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15504
15505 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15506 // Strongest source wins; if we have worktree tag binding, prefer that to
15507 // global and language bindings;
15508 // if we have a global binding, prefer that to language binding.
15509 let first_mismatch = templates_with_tags
15510 .iter()
15511 .position(|(tag_source, _)| tag_source != leading_tag_source);
15512 if let Some(index) = first_mismatch {
15513 templates_with_tags.truncate(index);
15514 }
15515 }
15516
15517 templates_with_tags
15518 })
15519 }
15520
15521 pub fn move_to_enclosing_bracket(
15522 &mut self,
15523 _: &MoveToEnclosingBracket,
15524 window: &mut Window,
15525 cx: &mut Context<Self>,
15526 ) {
15527 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15528 self.change_selections(Default::default(), window, cx, |s| {
15529 s.move_offsets_with(|snapshot, selection| {
15530 let Some(enclosing_bracket_ranges) =
15531 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15532 else {
15533 return;
15534 };
15535
15536 let mut best_length = usize::MAX;
15537 let mut best_inside = false;
15538 let mut best_in_bracket_range = false;
15539 let mut best_destination = None;
15540 for (open, close) in enclosing_bracket_ranges {
15541 let close = close.to_inclusive();
15542 let length = close.end() - open.start;
15543 let inside = selection.start >= open.end && selection.end <= *close.start();
15544 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15545 || close.contains(&selection.head());
15546
15547 // If best is next to a bracket and current isn't, skip
15548 if !in_bracket_range && best_in_bracket_range {
15549 continue;
15550 }
15551
15552 // Prefer smaller lengths unless best is inside and current isn't
15553 if length > best_length && (best_inside || !inside) {
15554 continue;
15555 }
15556
15557 best_length = length;
15558 best_inside = inside;
15559 best_in_bracket_range = in_bracket_range;
15560 best_destination = Some(
15561 if close.contains(&selection.start) && close.contains(&selection.end) {
15562 if inside { open.end } else { open.start }
15563 } else if inside {
15564 *close.start()
15565 } else {
15566 *close.end()
15567 },
15568 );
15569 }
15570
15571 if let Some(destination) = best_destination {
15572 selection.collapse_to(destination, SelectionGoal::None);
15573 }
15574 })
15575 });
15576 }
15577
15578 pub fn undo_selection(
15579 &mut self,
15580 _: &UndoSelection,
15581 window: &mut Window,
15582 cx: &mut Context<Self>,
15583 ) {
15584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15585 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15586 self.selection_history.mode = SelectionHistoryMode::Undoing;
15587 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15588 this.end_selection(window, cx);
15589 this.change_selections(
15590 SelectionEffects::scroll(Autoscroll::newest()),
15591 window,
15592 cx,
15593 |s| s.select_anchors(entry.selections.to_vec()),
15594 );
15595 });
15596 self.selection_history.mode = SelectionHistoryMode::Normal;
15597
15598 self.select_next_state = entry.select_next_state;
15599 self.select_prev_state = entry.select_prev_state;
15600 self.add_selections_state = entry.add_selections_state;
15601 }
15602 }
15603
15604 pub fn redo_selection(
15605 &mut self,
15606 _: &RedoSelection,
15607 window: &mut Window,
15608 cx: &mut Context<Self>,
15609 ) {
15610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15611 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15612 self.selection_history.mode = SelectionHistoryMode::Redoing;
15613 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15614 this.end_selection(window, cx);
15615 this.change_selections(
15616 SelectionEffects::scroll(Autoscroll::newest()),
15617 window,
15618 cx,
15619 |s| s.select_anchors(entry.selections.to_vec()),
15620 );
15621 });
15622 self.selection_history.mode = SelectionHistoryMode::Normal;
15623
15624 self.select_next_state = entry.select_next_state;
15625 self.select_prev_state = entry.select_prev_state;
15626 self.add_selections_state = entry.add_selections_state;
15627 }
15628 }
15629
15630 pub fn expand_excerpts(
15631 &mut self,
15632 action: &ExpandExcerpts,
15633 _: &mut Window,
15634 cx: &mut Context<Self>,
15635 ) {
15636 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15637 }
15638
15639 pub fn expand_excerpts_down(
15640 &mut self,
15641 action: &ExpandExcerptsDown,
15642 _: &mut Window,
15643 cx: &mut Context<Self>,
15644 ) {
15645 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15646 }
15647
15648 pub fn expand_excerpts_up(
15649 &mut self,
15650 action: &ExpandExcerptsUp,
15651 _: &mut Window,
15652 cx: &mut Context<Self>,
15653 ) {
15654 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15655 }
15656
15657 pub fn expand_excerpts_for_direction(
15658 &mut self,
15659 lines: u32,
15660 direction: ExpandExcerptDirection,
15661
15662 cx: &mut Context<Self>,
15663 ) {
15664 let selections = self.selections.disjoint_anchors_arc();
15665
15666 let lines = if lines == 0 {
15667 EditorSettings::get_global(cx).expand_excerpt_lines
15668 } else {
15669 lines
15670 };
15671
15672 self.buffer.update(cx, |buffer, cx| {
15673 let snapshot = buffer.snapshot(cx);
15674 let mut excerpt_ids = selections
15675 .iter()
15676 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15677 .collect::<Vec<_>>();
15678 excerpt_ids.sort();
15679 excerpt_ids.dedup();
15680 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15681 })
15682 }
15683
15684 pub fn expand_excerpt(
15685 &mut self,
15686 excerpt: ExcerptId,
15687 direction: ExpandExcerptDirection,
15688 window: &mut Window,
15689 cx: &mut Context<Self>,
15690 ) {
15691 let current_scroll_position = self.scroll_position(cx);
15692 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15693 let mut should_scroll_up = false;
15694
15695 if direction == ExpandExcerptDirection::Down {
15696 let multi_buffer = self.buffer.read(cx);
15697 let snapshot = multi_buffer.snapshot(cx);
15698 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15699 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15700 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15701 {
15702 let buffer_snapshot = buffer.read(cx).snapshot();
15703 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15704 let last_row = buffer_snapshot.max_point().row;
15705 let lines_below = last_row.saturating_sub(excerpt_end_row);
15706 should_scroll_up = lines_below >= lines_to_expand;
15707 }
15708 }
15709
15710 self.buffer.update(cx, |buffer, cx| {
15711 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15712 });
15713
15714 if should_scroll_up {
15715 let new_scroll_position =
15716 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15717 self.set_scroll_position(new_scroll_position, window, cx);
15718 }
15719 }
15720
15721 pub fn go_to_singleton_buffer_point(
15722 &mut self,
15723 point: Point,
15724 window: &mut Window,
15725 cx: &mut Context<Self>,
15726 ) {
15727 self.go_to_singleton_buffer_range(point..point, window, cx);
15728 }
15729
15730 pub fn go_to_singleton_buffer_range(
15731 &mut self,
15732 range: Range<Point>,
15733 window: &mut Window,
15734 cx: &mut Context<Self>,
15735 ) {
15736 let multibuffer = self.buffer().read(cx);
15737 let Some(buffer) = multibuffer.as_singleton() else {
15738 return;
15739 };
15740 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15741 return;
15742 };
15743 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15744 return;
15745 };
15746 self.change_selections(
15747 SelectionEffects::default().nav_history(true),
15748 window,
15749 cx,
15750 |s| s.select_anchor_ranges([start..end]),
15751 );
15752 }
15753
15754 pub fn go_to_diagnostic(
15755 &mut self,
15756 action: &GoToDiagnostic,
15757 window: &mut Window,
15758 cx: &mut Context<Self>,
15759 ) {
15760 if !self.diagnostics_enabled() {
15761 return;
15762 }
15763 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15764 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15765 }
15766
15767 pub fn go_to_prev_diagnostic(
15768 &mut self,
15769 action: &GoToPreviousDiagnostic,
15770 window: &mut Window,
15771 cx: &mut Context<Self>,
15772 ) {
15773 if !self.diagnostics_enabled() {
15774 return;
15775 }
15776 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15777 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15778 }
15779
15780 pub fn go_to_diagnostic_impl(
15781 &mut self,
15782 direction: Direction,
15783 severity: GoToDiagnosticSeverityFilter,
15784 window: &mut Window,
15785 cx: &mut Context<Self>,
15786 ) {
15787 let buffer = self.buffer.read(cx).snapshot(cx);
15788 let selection = self.selections.newest::<usize>(cx);
15789
15790 let mut active_group_id = None;
15791 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15792 && active_group.active_range.start.to_offset(&buffer) == selection.start
15793 {
15794 active_group_id = Some(active_group.group_id);
15795 }
15796
15797 fn filtered(
15798 snapshot: EditorSnapshot,
15799 severity: GoToDiagnosticSeverityFilter,
15800 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15801 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15802 diagnostics
15803 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15804 .filter(|entry| entry.range.start != entry.range.end)
15805 .filter(|entry| !entry.diagnostic.is_unnecessary)
15806 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15807 }
15808
15809 let snapshot = self.snapshot(window, cx);
15810 let before = filtered(
15811 snapshot.clone(),
15812 severity,
15813 buffer
15814 .diagnostics_in_range(0..selection.start)
15815 .filter(|entry| entry.range.start <= selection.start),
15816 );
15817 let after = filtered(
15818 snapshot,
15819 severity,
15820 buffer
15821 .diagnostics_in_range(selection.start..buffer.len())
15822 .filter(|entry| entry.range.start >= selection.start),
15823 );
15824
15825 let mut found: Option<DiagnosticEntry<usize>> = None;
15826 if direction == Direction::Prev {
15827 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15828 {
15829 for diagnostic in prev_diagnostics.into_iter().rev() {
15830 if diagnostic.range.start != selection.start
15831 || active_group_id
15832 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15833 {
15834 found = Some(diagnostic);
15835 break 'outer;
15836 }
15837 }
15838 }
15839 } else {
15840 for diagnostic in after.chain(before) {
15841 if diagnostic.range.start != selection.start
15842 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15843 {
15844 found = Some(diagnostic);
15845 break;
15846 }
15847 }
15848 }
15849 let Some(next_diagnostic) = found else {
15850 return;
15851 };
15852
15853 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15854 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15855 return;
15856 };
15857 self.change_selections(Default::default(), window, cx, |s| {
15858 s.select_ranges(vec![
15859 next_diagnostic.range.start..next_diagnostic.range.start,
15860 ])
15861 });
15862 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15863 self.refresh_edit_prediction(false, true, window, cx);
15864 }
15865
15866 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15868 let snapshot = self.snapshot(window, cx);
15869 let selection = self.selections.newest::<Point>(cx);
15870 self.go_to_hunk_before_or_after_position(
15871 &snapshot,
15872 selection.head(),
15873 Direction::Next,
15874 window,
15875 cx,
15876 );
15877 }
15878
15879 pub fn go_to_hunk_before_or_after_position(
15880 &mut self,
15881 snapshot: &EditorSnapshot,
15882 position: Point,
15883 direction: Direction,
15884 window: &mut Window,
15885 cx: &mut Context<Editor>,
15886 ) {
15887 let row = if direction == Direction::Next {
15888 self.hunk_after_position(snapshot, position)
15889 .map(|hunk| hunk.row_range.start)
15890 } else {
15891 self.hunk_before_position(snapshot, position)
15892 };
15893
15894 if let Some(row) = row {
15895 let destination = Point::new(row.0, 0);
15896 let autoscroll = Autoscroll::center();
15897
15898 self.unfold_ranges(&[destination..destination], false, false, cx);
15899 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15900 s.select_ranges([destination..destination]);
15901 });
15902 }
15903 }
15904
15905 fn hunk_after_position(
15906 &mut self,
15907 snapshot: &EditorSnapshot,
15908 position: Point,
15909 ) -> Option<MultiBufferDiffHunk> {
15910 snapshot
15911 .buffer_snapshot
15912 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15913 .find(|hunk| hunk.row_range.start.0 > position.row)
15914 .or_else(|| {
15915 snapshot
15916 .buffer_snapshot
15917 .diff_hunks_in_range(Point::zero()..position)
15918 .find(|hunk| hunk.row_range.end.0 < position.row)
15919 })
15920 }
15921
15922 fn go_to_prev_hunk(
15923 &mut self,
15924 _: &GoToPreviousHunk,
15925 window: &mut Window,
15926 cx: &mut Context<Self>,
15927 ) {
15928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15929 let snapshot = self.snapshot(window, cx);
15930 let selection = self.selections.newest::<Point>(cx);
15931 self.go_to_hunk_before_or_after_position(
15932 &snapshot,
15933 selection.head(),
15934 Direction::Prev,
15935 window,
15936 cx,
15937 );
15938 }
15939
15940 fn hunk_before_position(
15941 &mut self,
15942 snapshot: &EditorSnapshot,
15943 position: Point,
15944 ) -> Option<MultiBufferRow> {
15945 snapshot
15946 .buffer_snapshot
15947 .diff_hunk_before(position)
15948 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15949 }
15950
15951 fn go_to_next_change(
15952 &mut self,
15953 _: &GoToNextChange,
15954 window: &mut Window,
15955 cx: &mut Context<Self>,
15956 ) {
15957 if let Some(selections) = self
15958 .change_list
15959 .next_change(1, Direction::Next)
15960 .map(|s| s.to_vec())
15961 {
15962 self.change_selections(Default::default(), window, cx, |s| {
15963 let map = s.display_map();
15964 s.select_display_ranges(selections.iter().map(|a| {
15965 let point = a.to_display_point(&map);
15966 point..point
15967 }))
15968 })
15969 }
15970 }
15971
15972 fn go_to_previous_change(
15973 &mut self,
15974 _: &GoToPreviousChange,
15975 window: &mut Window,
15976 cx: &mut Context<Self>,
15977 ) {
15978 if let Some(selections) = self
15979 .change_list
15980 .next_change(1, Direction::Prev)
15981 .map(|s| s.to_vec())
15982 {
15983 self.change_selections(Default::default(), window, cx, |s| {
15984 let map = s.display_map();
15985 s.select_display_ranges(selections.iter().map(|a| {
15986 let point = a.to_display_point(&map);
15987 point..point
15988 }))
15989 })
15990 }
15991 }
15992
15993 pub fn go_to_next_document_highlight(
15994 &mut self,
15995 _: &GoToNextDocumentHighlight,
15996 window: &mut Window,
15997 cx: &mut Context<Self>,
15998 ) {
15999 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16000 }
16001
16002 pub fn go_to_prev_document_highlight(
16003 &mut self,
16004 _: &GoToPreviousDocumentHighlight,
16005 window: &mut Window,
16006 cx: &mut Context<Self>,
16007 ) {
16008 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16009 }
16010
16011 pub fn go_to_document_highlight_before_or_after_position(
16012 &mut self,
16013 direction: Direction,
16014 window: &mut Window,
16015 cx: &mut Context<Editor>,
16016 ) {
16017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16018 let snapshot = self.snapshot(window, cx);
16019 let buffer = &snapshot.buffer_snapshot;
16020 let position = self.selections.newest::<Point>(cx).head();
16021 let anchor_position = buffer.anchor_after(position);
16022
16023 // Get all document highlights (both read and write)
16024 let mut all_highlights = Vec::new();
16025
16026 if let Some((_, read_highlights)) = self
16027 .background_highlights
16028 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16029 {
16030 all_highlights.extend(read_highlights.iter());
16031 }
16032
16033 if let Some((_, write_highlights)) = self
16034 .background_highlights
16035 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16036 {
16037 all_highlights.extend(write_highlights.iter());
16038 }
16039
16040 if all_highlights.is_empty() {
16041 return;
16042 }
16043
16044 // Sort highlights by position
16045 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16046
16047 let target_highlight = match direction {
16048 Direction::Next => {
16049 // Find the first highlight after the current position
16050 all_highlights
16051 .iter()
16052 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16053 }
16054 Direction::Prev => {
16055 // Find the last highlight before the current position
16056 all_highlights
16057 .iter()
16058 .rev()
16059 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16060 }
16061 };
16062
16063 if let Some(highlight) = target_highlight {
16064 let destination = highlight.start.to_point(buffer);
16065 let autoscroll = Autoscroll::center();
16066
16067 self.unfold_ranges(&[destination..destination], false, false, cx);
16068 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16069 s.select_ranges([destination..destination]);
16070 });
16071 }
16072 }
16073
16074 fn go_to_line<T: 'static>(
16075 &mut self,
16076 position: Anchor,
16077 highlight_color: Option<Hsla>,
16078 window: &mut Window,
16079 cx: &mut Context<Self>,
16080 ) {
16081 let snapshot = self.snapshot(window, cx).display_snapshot;
16082 let position = position.to_point(&snapshot.buffer_snapshot);
16083 let start = snapshot
16084 .buffer_snapshot
16085 .clip_point(Point::new(position.row, 0), Bias::Left);
16086 let end = start + Point::new(1, 0);
16087 let start = snapshot.buffer_snapshot.anchor_before(start);
16088 let end = snapshot.buffer_snapshot.anchor_before(end);
16089
16090 self.highlight_rows::<T>(
16091 start..end,
16092 highlight_color
16093 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16094 Default::default(),
16095 cx,
16096 );
16097
16098 if self.buffer.read(cx).is_singleton() {
16099 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16100 }
16101 }
16102
16103 pub fn go_to_definition(
16104 &mut self,
16105 _: &GoToDefinition,
16106 window: &mut Window,
16107 cx: &mut Context<Self>,
16108 ) -> Task<Result<Navigated>> {
16109 let definition =
16110 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16111 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16112 cx.spawn_in(window, async move |editor, cx| {
16113 if definition.await? == Navigated::Yes {
16114 return Ok(Navigated::Yes);
16115 }
16116 match fallback_strategy {
16117 GoToDefinitionFallback::None => Ok(Navigated::No),
16118 GoToDefinitionFallback::FindAllReferences => {
16119 match editor.update_in(cx, |editor, window, cx| {
16120 editor.find_all_references(&FindAllReferences, window, cx)
16121 })? {
16122 Some(references) => references.await,
16123 None => Ok(Navigated::No),
16124 }
16125 }
16126 }
16127 })
16128 }
16129
16130 pub fn go_to_declaration(
16131 &mut self,
16132 _: &GoToDeclaration,
16133 window: &mut Window,
16134 cx: &mut Context<Self>,
16135 ) -> Task<Result<Navigated>> {
16136 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16137 }
16138
16139 pub fn go_to_declaration_split(
16140 &mut self,
16141 _: &GoToDeclaration,
16142 window: &mut Window,
16143 cx: &mut Context<Self>,
16144 ) -> Task<Result<Navigated>> {
16145 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16146 }
16147
16148 pub fn go_to_implementation(
16149 &mut self,
16150 _: &GoToImplementation,
16151 window: &mut Window,
16152 cx: &mut Context<Self>,
16153 ) -> Task<Result<Navigated>> {
16154 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16155 }
16156
16157 pub fn go_to_implementation_split(
16158 &mut self,
16159 _: &GoToImplementationSplit,
16160 window: &mut Window,
16161 cx: &mut Context<Self>,
16162 ) -> Task<Result<Navigated>> {
16163 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16164 }
16165
16166 pub fn go_to_type_definition(
16167 &mut self,
16168 _: &GoToTypeDefinition,
16169 window: &mut Window,
16170 cx: &mut Context<Self>,
16171 ) -> Task<Result<Navigated>> {
16172 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16173 }
16174
16175 pub fn go_to_definition_split(
16176 &mut self,
16177 _: &GoToDefinitionSplit,
16178 window: &mut Window,
16179 cx: &mut Context<Self>,
16180 ) -> Task<Result<Navigated>> {
16181 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16182 }
16183
16184 pub fn go_to_type_definition_split(
16185 &mut self,
16186 _: &GoToTypeDefinitionSplit,
16187 window: &mut Window,
16188 cx: &mut Context<Self>,
16189 ) -> Task<Result<Navigated>> {
16190 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16191 }
16192
16193 fn go_to_definition_of_kind(
16194 &mut self,
16195 kind: GotoDefinitionKind,
16196 split: bool,
16197 window: &mut Window,
16198 cx: &mut Context<Self>,
16199 ) -> Task<Result<Navigated>> {
16200 let Some(provider) = self.semantics_provider.clone() else {
16201 return Task::ready(Ok(Navigated::No));
16202 };
16203 let head = self.selections.newest::<usize>(cx).head();
16204 let buffer = self.buffer.read(cx);
16205 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16206 return Task::ready(Ok(Navigated::No));
16207 };
16208 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16209 return Task::ready(Ok(Navigated::No));
16210 };
16211
16212 cx.spawn_in(window, async move |editor, cx| {
16213 let Some(definitions) = definitions.await? else {
16214 return Ok(Navigated::No);
16215 };
16216 let navigated = editor
16217 .update_in(cx, |editor, window, cx| {
16218 editor.navigate_to_hover_links(
16219 Some(kind),
16220 definitions
16221 .into_iter()
16222 .filter(|location| {
16223 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16224 })
16225 .map(HoverLink::Text)
16226 .collect::<Vec<_>>(),
16227 split,
16228 window,
16229 cx,
16230 )
16231 })?
16232 .await?;
16233 anyhow::Ok(navigated)
16234 })
16235 }
16236
16237 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16238 let selection = self.selections.newest_anchor();
16239 let head = selection.head();
16240 let tail = selection.tail();
16241
16242 let Some((buffer, start_position)) =
16243 self.buffer.read(cx).text_anchor_for_position(head, cx)
16244 else {
16245 return;
16246 };
16247
16248 let end_position = if head != tail {
16249 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16250 return;
16251 };
16252 Some(pos)
16253 } else {
16254 None
16255 };
16256
16257 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16258 let url = if let Some(end_pos) = end_position {
16259 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16260 } else {
16261 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16262 };
16263
16264 if let Some(url) = url {
16265 editor.update(cx, |_, cx| {
16266 cx.open_url(&url);
16267 })
16268 } else {
16269 Ok(())
16270 }
16271 });
16272
16273 url_finder.detach();
16274 }
16275
16276 pub fn open_selected_filename(
16277 &mut self,
16278 _: &OpenSelectedFilename,
16279 window: &mut Window,
16280 cx: &mut Context<Self>,
16281 ) {
16282 let Some(workspace) = self.workspace() else {
16283 return;
16284 };
16285
16286 let position = self.selections.newest_anchor().head();
16287
16288 let Some((buffer, buffer_position)) =
16289 self.buffer.read(cx).text_anchor_for_position(position, cx)
16290 else {
16291 return;
16292 };
16293
16294 let project = self.project.clone();
16295
16296 cx.spawn_in(window, async move |_, cx| {
16297 let result = find_file(&buffer, project, buffer_position, cx).await;
16298
16299 if let Some((_, path)) = result {
16300 workspace
16301 .update_in(cx, |workspace, window, cx| {
16302 workspace.open_resolved_path(path, window, cx)
16303 })?
16304 .await?;
16305 }
16306 anyhow::Ok(())
16307 })
16308 .detach();
16309 }
16310
16311 pub(crate) fn navigate_to_hover_links(
16312 &mut self,
16313 kind: Option<GotoDefinitionKind>,
16314 definitions: Vec<HoverLink>,
16315 split: bool,
16316 window: &mut Window,
16317 cx: &mut Context<Editor>,
16318 ) -> Task<Result<Navigated>> {
16319 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16320 let mut first_url_or_file = None;
16321 let definitions: Vec<_> = definitions
16322 .into_iter()
16323 .filter_map(|def| match def {
16324 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16325 HoverLink::InlayHint(lsp_location, server_id) => {
16326 let computation =
16327 self.compute_target_location(lsp_location, server_id, window, cx);
16328 Some(cx.background_spawn(computation))
16329 }
16330 HoverLink::Url(url) => {
16331 first_url_or_file = Some(Either::Left(url));
16332 None
16333 }
16334 HoverLink::File(path) => {
16335 first_url_or_file = Some(Either::Right(path));
16336 None
16337 }
16338 })
16339 .collect();
16340
16341 let workspace = self.workspace();
16342
16343 cx.spawn_in(window, async move |editor, acx| {
16344 let mut locations: Vec<Location> = future::join_all(definitions)
16345 .await
16346 .into_iter()
16347 .filter_map(|location| location.transpose())
16348 .collect::<Result<_>>()
16349 .context("location tasks")?;
16350
16351 if locations.len() > 1 {
16352 let Some(workspace) = workspace else {
16353 return Ok(Navigated::No);
16354 };
16355
16356 let tab_kind = match kind {
16357 Some(GotoDefinitionKind::Implementation) => "Implementations",
16358 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16359 Some(GotoDefinitionKind::Declaration) => "Declarations",
16360 Some(GotoDefinitionKind::Type) => "Types",
16361 };
16362 let title = editor
16363 .update_in(acx, |_, _, cx| {
16364 let target = locations
16365 .iter()
16366 .map(|location| {
16367 location
16368 .buffer
16369 .read(cx)
16370 .text_for_range(location.range.clone())
16371 .collect::<String>()
16372 })
16373 .filter(|text| !text.contains('\n'))
16374 .unique()
16375 .take(3)
16376 .join(", ");
16377 if target.is_empty() {
16378 tab_kind.to_owned()
16379 } else {
16380 format!("{tab_kind} for {target}")
16381 }
16382 })
16383 .context("buffer title")?;
16384
16385 let opened = workspace
16386 .update_in(acx, |workspace, window, cx| {
16387 Self::open_locations_in_multibuffer(
16388 workspace,
16389 locations,
16390 title,
16391 split,
16392 MultibufferSelectionMode::First,
16393 window,
16394 cx,
16395 )
16396 })
16397 .is_ok();
16398
16399 anyhow::Ok(Navigated::from_bool(opened))
16400 } else if locations.is_empty() {
16401 // If there is one url or file, open it directly
16402 match first_url_or_file {
16403 Some(Either::Left(url)) => {
16404 acx.update(|_, cx| cx.open_url(&url))?;
16405 Ok(Navigated::Yes)
16406 }
16407 Some(Either::Right(path)) => {
16408 let Some(workspace) = workspace else {
16409 return Ok(Navigated::No);
16410 };
16411
16412 workspace
16413 .update_in(acx, |workspace, window, cx| {
16414 workspace.open_resolved_path(path, window, cx)
16415 })?
16416 .await?;
16417 Ok(Navigated::Yes)
16418 }
16419 None => Ok(Navigated::No),
16420 }
16421 } else {
16422 let Some(workspace) = workspace else {
16423 return Ok(Navigated::No);
16424 };
16425
16426 let target = locations.pop().unwrap();
16427 editor.update_in(acx, |editor, window, cx| {
16428 let range = target.range.to_point(target.buffer.read(cx));
16429 let range = editor.range_for_match(&range);
16430 let range = collapse_multiline_range(range);
16431
16432 if !split
16433 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16434 {
16435 editor.go_to_singleton_buffer_range(range, window, cx);
16436 } else {
16437 let pane = workspace.read(cx).active_pane().clone();
16438 window.defer(cx, move |window, cx| {
16439 let target_editor: Entity<Self> =
16440 workspace.update(cx, |workspace, cx| {
16441 let pane = if split {
16442 workspace.adjacent_pane(window, cx)
16443 } else {
16444 workspace.active_pane().clone()
16445 };
16446
16447 workspace.open_project_item(
16448 pane,
16449 target.buffer.clone(),
16450 true,
16451 true,
16452 window,
16453 cx,
16454 )
16455 });
16456 target_editor.update(cx, |target_editor, cx| {
16457 // When selecting a definition in a different buffer, disable the nav history
16458 // to avoid creating a history entry at the previous cursor location.
16459 pane.update(cx, |pane, _| pane.disable_history());
16460 target_editor.go_to_singleton_buffer_range(range, window, cx);
16461 pane.update(cx, |pane, _| pane.enable_history());
16462 });
16463 });
16464 }
16465 Navigated::Yes
16466 })
16467 }
16468 })
16469 }
16470
16471 fn compute_target_location(
16472 &self,
16473 lsp_location: lsp::Location,
16474 server_id: LanguageServerId,
16475 window: &mut Window,
16476 cx: &mut Context<Self>,
16477 ) -> Task<anyhow::Result<Option<Location>>> {
16478 let Some(project) = self.project.clone() else {
16479 return Task::ready(Ok(None));
16480 };
16481
16482 cx.spawn_in(window, async move |editor, cx| {
16483 let location_task = editor.update(cx, |_, cx| {
16484 project.update(cx, |project, cx| {
16485 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16486 })
16487 })?;
16488 let location = Some({
16489 let target_buffer_handle = location_task.await.context("open local buffer")?;
16490 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16491 let target_start = target_buffer
16492 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16493 let target_end = target_buffer
16494 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16495 target_buffer.anchor_after(target_start)
16496 ..target_buffer.anchor_before(target_end)
16497 })?;
16498 Location {
16499 buffer: target_buffer_handle,
16500 range,
16501 }
16502 });
16503 Ok(location)
16504 })
16505 }
16506
16507 pub fn find_all_references(
16508 &mut self,
16509 _: &FindAllReferences,
16510 window: &mut Window,
16511 cx: &mut Context<Self>,
16512 ) -> Option<Task<Result<Navigated>>> {
16513 let selection = self.selections.newest::<usize>(cx);
16514 let multi_buffer = self.buffer.read(cx);
16515 let head = selection.head();
16516
16517 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16518 let head_anchor = multi_buffer_snapshot.anchor_at(
16519 head,
16520 if head < selection.tail() {
16521 Bias::Right
16522 } else {
16523 Bias::Left
16524 },
16525 );
16526
16527 match self
16528 .find_all_references_task_sources
16529 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16530 {
16531 Ok(_) => {
16532 log::info!(
16533 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16534 );
16535 return None;
16536 }
16537 Err(i) => {
16538 self.find_all_references_task_sources.insert(i, head_anchor);
16539 }
16540 }
16541
16542 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16543 let workspace = self.workspace()?;
16544 let project = workspace.read(cx).project().clone();
16545 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16546 Some(cx.spawn_in(window, async move |editor, cx| {
16547 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16548 if let Ok(i) = editor
16549 .find_all_references_task_sources
16550 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16551 {
16552 editor.find_all_references_task_sources.remove(i);
16553 }
16554 });
16555
16556 let Some(locations) = references.await? else {
16557 return anyhow::Ok(Navigated::No);
16558 };
16559 if locations.is_empty() {
16560 return anyhow::Ok(Navigated::No);
16561 }
16562
16563 workspace.update_in(cx, |workspace, window, cx| {
16564 let target = locations
16565 .iter()
16566 .map(|location| {
16567 location
16568 .buffer
16569 .read(cx)
16570 .text_for_range(location.range.clone())
16571 .collect::<String>()
16572 })
16573 .filter(|text| !text.contains('\n'))
16574 .unique()
16575 .take(3)
16576 .join(", ");
16577 let title = if target.is_empty() {
16578 "References".to_owned()
16579 } else {
16580 format!("References to {target}")
16581 };
16582 Self::open_locations_in_multibuffer(
16583 workspace,
16584 locations,
16585 title,
16586 false,
16587 MultibufferSelectionMode::First,
16588 window,
16589 cx,
16590 );
16591 Navigated::Yes
16592 })
16593 }))
16594 }
16595
16596 /// Opens a multibuffer with the given project locations in it
16597 pub fn open_locations_in_multibuffer(
16598 workspace: &mut Workspace,
16599 mut locations: Vec<Location>,
16600 title: String,
16601 split: bool,
16602 multibuffer_selection_mode: MultibufferSelectionMode,
16603 window: &mut Window,
16604 cx: &mut Context<Workspace>,
16605 ) {
16606 if locations.is_empty() {
16607 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16608 return;
16609 }
16610
16611 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16612
16613 let mut locations = locations.into_iter().peekable();
16614 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16615 let capability = workspace.project().read(cx).capability();
16616
16617 // a key to find existing multibuffer editors with the same set of locations
16618 // to prevent us from opening more and more multibuffer tabs for searches and the like
16619 let mut key = (title.clone(), vec![]);
16620 let excerpt_buffer = cx.new(|cx| {
16621 let key = &mut key.1;
16622 let mut multibuffer = MultiBuffer::new(capability);
16623 while let Some(location) = locations.next() {
16624 let buffer = location.buffer.read(cx);
16625 let mut ranges_for_buffer = Vec::new();
16626 let range = location.range.to_point(buffer);
16627 ranges_for_buffer.push(range.clone());
16628
16629 while let Some(next_location) =
16630 locations.next_if(|next_location| next_location.buffer == location.buffer)
16631 {
16632 ranges_for_buffer.push(next_location.range.to_point(buffer));
16633 }
16634
16635 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16636 key.push((
16637 location.buffer.read(cx).remote_id(),
16638 ranges_for_buffer.clone(),
16639 ));
16640 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16641 PathKey::for_buffer(&location.buffer, cx),
16642 location.buffer.clone(),
16643 ranges_for_buffer,
16644 multibuffer_context_lines(cx),
16645 cx,
16646 );
16647 ranges.extend(new_ranges)
16648 }
16649
16650 multibuffer.with_title(title)
16651 });
16652 let existing = workspace.active_pane().update(cx, |pane, cx| {
16653 pane.items()
16654 .filter_map(|item| item.downcast::<Editor>())
16655 .find(|editor| {
16656 editor
16657 .read(cx)
16658 .lookup_key
16659 .as_ref()
16660 .and_then(|it| {
16661 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16662 })
16663 .is_some_and(|it| *it == key)
16664 })
16665 });
16666 let editor = existing.unwrap_or_else(|| {
16667 cx.new(|cx| {
16668 let mut editor = Editor::for_multibuffer(
16669 excerpt_buffer,
16670 Some(workspace.project().clone()),
16671 window,
16672 cx,
16673 );
16674 editor.lookup_key = Some(Box::new(key));
16675 editor
16676 })
16677 });
16678 editor.update(cx, |editor, cx| {
16679 match multibuffer_selection_mode {
16680 MultibufferSelectionMode::First => {
16681 if let Some(first_range) = ranges.first() {
16682 editor.change_selections(
16683 SelectionEffects::no_scroll(),
16684 window,
16685 cx,
16686 |selections| {
16687 selections.clear_disjoint();
16688 selections
16689 .select_anchor_ranges(std::iter::once(first_range.clone()));
16690 },
16691 );
16692 }
16693 editor.highlight_background::<Self>(
16694 &ranges,
16695 |theme| theme.colors().editor_highlighted_line_background,
16696 cx,
16697 );
16698 }
16699 MultibufferSelectionMode::All => {
16700 editor.change_selections(
16701 SelectionEffects::no_scroll(),
16702 window,
16703 cx,
16704 |selections| {
16705 selections.clear_disjoint();
16706 selections.select_anchor_ranges(ranges);
16707 },
16708 );
16709 }
16710 }
16711 editor.register_buffers_with_language_servers(cx);
16712 });
16713
16714 let item = Box::new(editor);
16715 let item_id = item.item_id();
16716
16717 if split {
16718 workspace.split_item(SplitDirection::Right, item, window, cx);
16719 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16720 let (preview_item_id, preview_item_idx) =
16721 workspace.active_pane().read_with(cx, |pane, _| {
16722 (pane.preview_item_id(), pane.preview_item_idx())
16723 });
16724
16725 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16726
16727 if let Some(preview_item_id) = preview_item_id {
16728 workspace.active_pane().update(cx, |pane, cx| {
16729 pane.remove_item(preview_item_id, false, false, window, cx);
16730 });
16731 }
16732 } else {
16733 workspace.add_item_to_active_pane(item, None, true, window, cx);
16734 }
16735 workspace.active_pane().update(cx, |pane, cx| {
16736 pane.set_preview_item_id(Some(item_id), cx);
16737 });
16738 }
16739
16740 pub fn rename(
16741 &mut self,
16742 _: &Rename,
16743 window: &mut Window,
16744 cx: &mut Context<Self>,
16745 ) -> Option<Task<Result<()>>> {
16746 use language::ToOffset as _;
16747
16748 let provider = self.semantics_provider.clone()?;
16749 let selection = self.selections.newest_anchor().clone();
16750 let (cursor_buffer, cursor_buffer_position) = self
16751 .buffer
16752 .read(cx)
16753 .text_anchor_for_position(selection.head(), cx)?;
16754 let (tail_buffer, cursor_buffer_position_end) = self
16755 .buffer
16756 .read(cx)
16757 .text_anchor_for_position(selection.tail(), cx)?;
16758 if tail_buffer != cursor_buffer {
16759 return None;
16760 }
16761
16762 let snapshot = cursor_buffer.read(cx).snapshot();
16763 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16764 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16765 let prepare_rename = provider
16766 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16767 .unwrap_or_else(|| Task::ready(Ok(None)));
16768 drop(snapshot);
16769
16770 Some(cx.spawn_in(window, async move |this, cx| {
16771 let rename_range = if let Some(range) = prepare_rename.await? {
16772 Some(range)
16773 } else {
16774 this.update(cx, |this, cx| {
16775 let buffer = this.buffer.read(cx).snapshot(cx);
16776 let mut buffer_highlights = this
16777 .document_highlights_for_position(selection.head(), &buffer)
16778 .filter(|highlight| {
16779 highlight.start.excerpt_id == selection.head().excerpt_id
16780 && highlight.end.excerpt_id == selection.head().excerpt_id
16781 });
16782 buffer_highlights
16783 .next()
16784 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16785 })?
16786 };
16787 if let Some(rename_range) = rename_range {
16788 this.update_in(cx, |this, window, cx| {
16789 let snapshot = cursor_buffer.read(cx).snapshot();
16790 let rename_buffer_range = rename_range.to_offset(&snapshot);
16791 let cursor_offset_in_rename_range =
16792 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16793 let cursor_offset_in_rename_range_end =
16794 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16795
16796 this.take_rename(false, window, cx);
16797 let buffer = this.buffer.read(cx).read(cx);
16798 let cursor_offset = selection.head().to_offset(&buffer);
16799 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16800 let rename_end = rename_start + rename_buffer_range.len();
16801 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16802 let mut old_highlight_id = None;
16803 let old_name: Arc<str> = buffer
16804 .chunks(rename_start..rename_end, true)
16805 .map(|chunk| {
16806 if old_highlight_id.is_none() {
16807 old_highlight_id = chunk.syntax_highlight_id;
16808 }
16809 chunk.text
16810 })
16811 .collect::<String>()
16812 .into();
16813
16814 drop(buffer);
16815
16816 // Position the selection in the rename editor so that it matches the current selection.
16817 this.show_local_selections = false;
16818 let rename_editor = cx.new(|cx| {
16819 let mut editor = Editor::single_line(window, cx);
16820 editor.buffer.update(cx, |buffer, cx| {
16821 buffer.edit([(0..0, old_name.clone())], None, cx)
16822 });
16823 let rename_selection_range = match cursor_offset_in_rename_range
16824 .cmp(&cursor_offset_in_rename_range_end)
16825 {
16826 Ordering::Equal => {
16827 editor.select_all(&SelectAll, window, cx);
16828 return editor;
16829 }
16830 Ordering::Less => {
16831 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16832 }
16833 Ordering::Greater => {
16834 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16835 }
16836 };
16837 if rename_selection_range.end > old_name.len() {
16838 editor.select_all(&SelectAll, window, cx);
16839 } else {
16840 editor.change_selections(Default::default(), window, cx, |s| {
16841 s.select_ranges([rename_selection_range]);
16842 });
16843 }
16844 editor
16845 });
16846 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16847 if e == &EditorEvent::Focused {
16848 cx.emit(EditorEvent::FocusedIn)
16849 }
16850 })
16851 .detach();
16852
16853 let write_highlights =
16854 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16855 let read_highlights =
16856 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16857 let ranges = write_highlights
16858 .iter()
16859 .flat_map(|(_, ranges)| ranges.iter())
16860 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16861 .cloned()
16862 .collect();
16863
16864 this.highlight_text::<Rename>(
16865 ranges,
16866 HighlightStyle {
16867 fade_out: Some(0.6),
16868 ..Default::default()
16869 },
16870 cx,
16871 );
16872 let rename_focus_handle = rename_editor.focus_handle(cx);
16873 window.focus(&rename_focus_handle);
16874 let block_id = this.insert_blocks(
16875 [BlockProperties {
16876 style: BlockStyle::Flex,
16877 placement: BlockPlacement::Below(range.start),
16878 height: Some(1),
16879 render: Arc::new({
16880 let rename_editor = rename_editor.clone();
16881 move |cx: &mut BlockContext| {
16882 let mut text_style = cx.editor_style.text.clone();
16883 if let Some(highlight_style) = old_highlight_id
16884 .and_then(|h| h.style(&cx.editor_style.syntax))
16885 {
16886 text_style = text_style.highlight(highlight_style);
16887 }
16888 div()
16889 .block_mouse_except_scroll()
16890 .pl(cx.anchor_x)
16891 .child(EditorElement::new(
16892 &rename_editor,
16893 EditorStyle {
16894 background: cx.theme().system().transparent,
16895 local_player: cx.editor_style.local_player,
16896 text: text_style,
16897 scrollbar_width: cx.editor_style.scrollbar_width,
16898 syntax: cx.editor_style.syntax.clone(),
16899 status: cx.editor_style.status.clone(),
16900 inlay_hints_style: HighlightStyle {
16901 font_weight: Some(FontWeight::BOLD),
16902 ..make_inlay_hints_style(cx.app)
16903 },
16904 edit_prediction_styles: make_suggestion_styles(
16905 cx.app,
16906 ),
16907 ..EditorStyle::default()
16908 },
16909 ))
16910 .into_any_element()
16911 }
16912 }),
16913 priority: 0,
16914 }],
16915 Some(Autoscroll::fit()),
16916 cx,
16917 )[0];
16918 this.pending_rename = Some(RenameState {
16919 range,
16920 old_name,
16921 editor: rename_editor,
16922 block_id,
16923 });
16924 })?;
16925 }
16926
16927 Ok(())
16928 }))
16929 }
16930
16931 pub fn confirm_rename(
16932 &mut self,
16933 _: &ConfirmRename,
16934 window: &mut Window,
16935 cx: &mut Context<Self>,
16936 ) -> Option<Task<Result<()>>> {
16937 let rename = self.take_rename(false, window, cx)?;
16938 let workspace = self.workspace()?.downgrade();
16939 let (buffer, start) = self
16940 .buffer
16941 .read(cx)
16942 .text_anchor_for_position(rename.range.start, cx)?;
16943 let (end_buffer, _) = self
16944 .buffer
16945 .read(cx)
16946 .text_anchor_for_position(rename.range.end, cx)?;
16947 if buffer != end_buffer {
16948 return None;
16949 }
16950
16951 let old_name = rename.old_name;
16952 let new_name = rename.editor.read(cx).text(cx);
16953
16954 let rename = self.semantics_provider.as_ref()?.perform_rename(
16955 &buffer,
16956 start,
16957 new_name.clone(),
16958 cx,
16959 )?;
16960
16961 Some(cx.spawn_in(window, async move |editor, cx| {
16962 let project_transaction = rename.await?;
16963 Self::open_project_transaction(
16964 &editor,
16965 workspace,
16966 project_transaction,
16967 format!("Rename: {} → {}", old_name, new_name),
16968 cx,
16969 )
16970 .await?;
16971
16972 editor.update(cx, |editor, cx| {
16973 editor.refresh_document_highlights(cx);
16974 })?;
16975 Ok(())
16976 }))
16977 }
16978
16979 fn take_rename(
16980 &mut self,
16981 moving_cursor: bool,
16982 window: &mut Window,
16983 cx: &mut Context<Self>,
16984 ) -> Option<RenameState> {
16985 let rename = self.pending_rename.take()?;
16986 if rename.editor.focus_handle(cx).is_focused(window) {
16987 window.focus(&self.focus_handle);
16988 }
16989
16990 self.remove_blocks(
16991 [rename.block_id].into_iter().collect(),
16992 Some(Autoscroll::fit()),
16993 cx,
16994 );
16995 self.clear_highlights::<Rename>(cx);
16996 self.show_local_selections = true;
16997
16998 if moving_cursor {
16999 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17000 editor.selections.newest::<usize>(cx).head()
17001 });
17002
17003 // Update the selection to match the position of the selection inside
17004 // the rename editor.
17005 let snapshot = self.buffer.read(cx).read(cx);
17006 let rename_range = rename.range.to_offset(&snapshot);
17007 let cursor_in_editor = snapshot
17008 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17009 .min(rename_range.end);
17010 drop(snapshot);
17011
17012 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17013 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17014 });
17015 } else {
17016 self.refresh_document_highlights(cx);
17017 }
17018
17019 Some(rename)
17020 }
17021
17022 pub fn pending_rename(&self) -> Option<&RenameState> {
17023 self.pending_rename.as_ref()
17024 }
17025
17026 fn format(
17027 &mut self,
17028 _: &Format,
17029 window: &mut Window,
17030 cx: &mut Context<Self>,
17031 ) -> Option<Task<Result<()>>> {
17032 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17033
17034 let project = match &self.project {
17035 Some(project) => project.clone(),
17036 None => return None,
17037 };
17038
17039 Some(self.perform_format(
17040 project,
17041 FormatTrigger::Manual,
17042 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17043 window,
17044 cx,
17045 ))
17046 }
17047
17048 fn format_selections(
17049 &mut self,
17050 _: &FormatSelections,
17051 window: &mut Window,
17052 cx: &mut Context<Self>,
17053 ) -> Option<Task<Result<()>>> {
17054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17055
17056 let project = match &self.project {
17057 Some(project) => project.clone(),
17058 None => return None,
17059 };
17060
17061 let ranges = self
17062 .selections
17063 .all_adjusted(cx)
17064 .into_iter()
17065 .map(|selection| selection.range())
17066 .collect_vec();
17067
17068 Some(self.perform_format(
17069 project,
17070 FormatTrigger::Manual,
17071 FormatTarget::Ranges(ranges),
17072 window,
17073 cx,
17074 ))
17075 }
17076
17077 fn perform_format(
17078 &mut self,
17079 project: Entity<Project>,
17080 trigger: FormatTrigger,
17081 target: FormatTarget,
17082 window: &mut Window,
17083 cx: &mut Context<Self>,
17084 ) -> Task<Result<()>> {
17085 let buffer = self.buffer.clone();
17086 let (buffers, target) = match target {
17087 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17088 FormatTarget::Ranges(selection_ranges) => {
17089 let multi_buffer = buffer.read(cx);
17090 let snapshot = multi_buffer.read(cx);
17091 let mut buffers = HashSet::default();
17092 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17093 BTreeMap::new();
17094 for selection_range in selection_ranges {
17095 for (buffer, buffer_range, _) in
17096 snapshot.range_to_buffer_ranges(selection_range)
17097 {
17098 let buffer_id = buffer.remote_id();
17099 let start = buffer.anchor_before(buffer_range.start);
17100 let end = buffer.anchor_after(buffer_range.end);
17101 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17102 buffer_id_to_ranges
17103 .entry(buffer_id)
17104 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17105 .or_insert_with(|| vec![start..end]);
17106 }
17107 }
17108 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17109 }
17110 };
17111
17112 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17113 let selections_prev = transaction_id_prev
17114 .and_then(|transaction_id_prev| {
17115 // default to selections as they were after the last edit, if we have them,
17116 // instead of how they are now.
17117 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17118 // will take you back to where you made the last edit, instead of staying where you scrolled
17119 self.selection_history
17120 .transaction(transaction_id_prev)
17121 .map(|t| t.0.clone())
17122 })
17123 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17124
17125 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17126 let format = project.update(cx, |project, cx| {
17127 project.format(buffers, target, true, trigger, cx)
17128 });
17129
17130 cx.spawn_in(window, async move |editor, cx| {
17131 let transaction = futures::select_biased! {
17132 transaction = format.log_err().fuse() => transaction,
17133 () = timeout => {
17134 log::warn!("timed out waiting for formatting");
17135 None
17136 }
17137 };
17138
17139 buffer
17140 .update(cx, |buffer, cx| {
17141 if let Some(transaction) = transaction
17142 && !buffer.is_singleton()
17143 {
17144 buffer.push_transaction(&transaction.0, cx);
17145 }
17146 cx.notify();
17147 })
17148 .ok();
17149
17150 if let Some(transaction_id_now) =
17151 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17152 {
17153 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17154 if has_new_transaction {
17155 _ = editor.update(cx, |editor, _| {
17156 editor
17157 .selection_history
17158 .insert_transaction(transaction_id_now, selections_prev);
17159 });
17160 }
17161 }
17162
17163 Ok(())
17164 })
17165 }
17166
17167 fn organize_imports(
17168 &mut self,
17169 _: &OrganizeImports,
17170 window: &mut Window,
17171 cx: &mut Context<Self>,
17172 ) -> Option<Task<Result<()>>> {
17173 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17174 let project = match &self.project {
17175 Some(project) => project.clone(),
17176 None => return None,
17177 };
17178 Some(self.perform_code_action_kind(
17179 project,
17180 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17181 window,
17182 cx,
17183 ))
17184 }
17185
17186 fn perform_code_action_kind(
17187 &mut self,
17188 project: Entity<Project>,
17189 kind: CodeActionKind,
17190 window: &mut Window,
17191 cx: &mut Context<Self>,
17192 ) -> Task<Result<()>> {
17193 let buffer = self.buffer.clone();
17194 let buffers = buffer.read(cx).all_buffers();
17195 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17196 let apply_action = project.update(cx, |project, cx| {
17197 project.apply_code_action_kind(buffers, kind, true, cx)
17198 });
17199 cx.spawn_in(window, async move |_, cx| {
17200 let transaction = futures::select_biased! {
17201 () = timeout => {
17202 log::warn!("timed out waiting for executing code action");
17203 None
17204 }
17205 transaction = apply_action.log_err().fuse() => transaction,
17206 };
17207 buffer
17208 .update(cx, |buffer, cx| {
17209 // check if we need this
17210 if let Some(transaction) = transaction
17211 && !buffer.is_singleton()
17212 {
17213 buffer.push_transaction(&transaction.0, cx);
17214 }
17215 cx.notify();
17216 })
17217 .ok();
17218 Ok(())
17219 })
17220 }
17221
17222 pub fn restart_language_server(
17223 &mut self,
17224 _: &RestartLanguageServer,
17225 _: &mut Window,
17226 cx: &mut Context<Self>,
17227 ) {
17228 if let Some(project) = self.project.clone() {
17229 self.buffer.update(cx, |multi_buffer, cx| {
17230 project.update(cx, |project, cx| {
17231 project.restart_language_servers_for_buffers(
17232 multi_buffer.all_buffers().into_iter().collect(),
17233 HashSet::default(),
17234 cx,
17235 );
17236 });
17237 })
17238 }
17239 }
17240
17241 pub fn stop_language_server(
17242 &mut self,
17243 _: &StopLanguageServer,
17244 _: &mut Window,
17245 cx: &mut Context<Self>,
17246 ) {
17247 if let Some(project) = self.project.clone() {
17248 self.buffer.update(cx, |multi_buffer, cx| {
17249 project.update(cx, |project, cx| {
17250 project.stop_language_servers_for_buffers(
17251 multi_buffer.all_buffers().into_iter().collect(),
17252 HashSet::default(),
17253 cx,
17254 );
17255 cx.emit(project::Event::RefreshInlayHints);
17256 });
17257 });
17258 }
17259 }
17260
17261 fn cancel_language_server_work(
17262 workspace: &mut Workspace,
17263 _: &actions::CancelLanguageServerWork,
17264 _: &mut Window,
17265 cx: &mut Context<Workspace>,
17266 ) {
17267 let project = workspace.project();
17268 let buffers = workspace
17269 .active_item(cx)
17270 .and_then(|item| item.act_as::<Editor>(cx))
17271 .map_or(HashSet::default(), |editor| {
17272 editor.read(cx).buffer.read(cx).all_buffers()
17273 });
17274 project.update(cx, |project, cx| {
17275 project.cancel_language_server_work_for_buffers(buffers, cx);
17276 });
17277 }
17278
17279 fn show_character_palette(
17280 &mut self,
17281 _: &ShowCharacterPalette,
17282 window: &mut Window,
17283 _: &mut Context<Self>,
17284 ) {
17285 window.show_character_palette();
17286 }
17287
17288 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17289 if !self.diagnostics_enabled() {
17290 return;
17291 }
17292
17293 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17294 let buffer = self.buffer.read(cx).snapshot(cx);
17295 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17296 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17297 let is_valid = buffer
17298 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17299 .any(|entry| {
17300 entry.diagnostic.is_primary
17301 && !entry.range.is_empty()
17302 && entry.range.start == primary_range_start
17303 && entry.diagnostic.message == active_diagnostics.active_message
17304 });
17305
17306 if !is_valid {
17307 self.dismiss_diagnostics(cx);
17308 }
17309 }
17310 }
17311
17312 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17313 match &self.active_diagnostics {
17314 ActiveDiagnostic::Group(group) => Some(group),
17315 _ => None,
17316 }
17317 }
17318
17319 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17320 if !self.diagnostics_enabled() {
17321 return;
17322 }
17323 self.dismiss_diagnostics(cx);
17324 self.active_diagnostics = ActiveDiagnostic::All;
17325 }
17326
17327 fn activate_diagnostics(
17328 &mut self,
17329 buffer_id: BufferId,
17330 diagnostic: DiagnosticEntry<usize>,
17331 window: &mut Window,
17332 cx: &mut Context<Self>,
17333 ) {
17334 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17335 return;
17336 }
17337 self.dismiss_diagnostics(cx);
17338 let snapshot = self.snapshot(window, cx);
17339 let buffer = self.buffer.read(cx).snapshot(cx);
17340 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17341 return;
17342 };
17343
17344 let diagnostic_group = buffer
17345 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17346 .collect::<Vec<_>>();
17347
17348 let blocks =
17349 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17350
17351 let blocks = self.display_map.update(cx, |display_map, cx| {
17352 display_map.insert_blocks(blocks, cx).into_iter().collect()
17353 });
17354 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17355 active_range: buffer.anchor_before(diagnostic.range.start)
17356 ..buffer.anchor_after(diagnostic.range.end),
17357 active_message: diagnostic.diagnostic.message.clone(),
17358 group_id: diagnostic.diagnostic.group_id,
17359 blocks,
17360 });
17361 cx.notify();
17362 }
17363
17364 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17365 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17366 return;
17367 };
17368
17369 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17370 if let ActiveDiagnostic::Group(group) = prev {
17371 self.display_map.update(cx, |display_map, cx| {
17372 display_map.remove_blocks(group.blocks, cx);
17373 });
17374 cx.notify();
17375 }
17376 }
17377
17378 /// Disable inline diagnostics rendering for this editor.
17379 pub fn disable_inline_diagnostics(&mut self) {
17380 self.inline_diagnostics_enabled = false;
17381 self.inline_diagnostics_update = Task::ready(());
17382 self.inline_diagnostics.clear();
17383 }
17384
17385 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17386 self.diagnostics_enabled = false;
17387 self.dismiss_diagnostics(cx);
17388 self.inline_diagnostics_update = Task::ready(());
17389 self.inline_diagnostics.clear();
17390 }
17391
17392 pub fn disable_word_completions(&mut self) {
17393 self.word_completions_enabled = false;
17394 }
17395
17396 pub fn diagnostics_enabled(&self) -> bool {
17397 self.diagnostics_enabled && self.mode.is_full()
17398 }
17399
17400 pub fn inline_diagnostics_enabled(&self) -> bool {
17401 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17402 }
17403
17404 pub fn show_inline_diagnostics(&self) -> bool {
17405 self.show_inline_diagnostics
17406 }
17407
17408 pub fn toggle_inline_diagnostics(
17409 &mut self,
17410 _: &ToggleInlineDiagnostics,
17411 window: &mut Window,
17412 cx: &mut Context<Editor>,
17413 ) {
17414 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17415 self.refresh_inline_diagnostics(false, window, cx);
17416 }
17417
17418 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17419 self.diagnostics_max_severity = severity;
17420 self.display_map.update(cx, |display_map, _| {
17421 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17422 });
17423 }
17424
17425 pub fn toggle_diagnostics(
17426 &mut self,
17427 _: &ToggleDiagnostics,
17428 window: &mut Window,
17429 cx: &mut Context<Editor>,
17430 ) {
17431 if !self.diagnostics_enabled() {
17432 return;
17433 }
17434
17435 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17436 EditorSettings::get_global(cx)
17437 .diagnostics_max_severity
17438 .filter(|severity| severity != &DiagnosticSeverity::Off)
17439 .unwrap_or(DiagnosticSeverity::Hint)
17440 } else {
17441 DiagnosticSeverity::Off
17442 };
17443 self.set_max_diagnostics_severity(new_severity, cx);
17444 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17445 self.active_diagnostics = ActiveDiagnostic::None;
17446 self.inline_diagnostics_update = Task::ready(());
17447 self.inline_diagnostics.clear();
17448 } else {
17449 self.refresh_inline_diagnostics(false, window, cx);
17450 }
17451
17452 cx.notify();
17453 }
17454
17455 pub fn toggle_minimap(
17456 &mut self,
17457 _: &ToggleMinimap,
17458 window: &mut Window,
17459 cx: &mut Context<Editor>,
17460 ) {
17461 if self.supports_minimap(cx) {
17462 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17463 }
17464 }
17465
17466 fn refresh_inline_diagnostics(
17467 &mut self,
17468 debounce: bool,
17469 window: &mut Window,
17470 cx: &mut Context<Self>,
17471 ) {
17472 let max_severity = ProjectSettings::get_global(cx)
17473 .diagnostics
17474 .inline
17475 .max_severity
17476 .unwrap_or(self.diagnostics_max_severity);
17477
17478 if !self.inline_diagnostics_enabled()
17479 || !self.show_inline_diagnostics
17480 || max_severity == DiagnosticSeverity::Off
17481 {
17482 self.inline_diagnostics_update = Task::ready(());
17483 self.inline_diagnostics.clear();
17484 return;
17485 }
17486
17487 let debounce_ms = ProjectSettings::get_global(cx)
17488 .diagnostics
17489 .inline
17490 .update_debounce_ms;
17491 let debounce = if debounce && debounce_ms > 0 {
17492 Some(Duration::from_millis(debounce_ms))
17493 } else {
17494 None
17495 };
17496 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17497 if let Some(debounce) = debounce {
17498 cx.background_executor().timer(debounce).await;
17499 }
17500 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17501 editor
17502 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17503 .ok()
17504 }) else {
17505 return;
17506 };
17507
17508 let new_inline_diagnostics = cx
17509 .background_spawn(async move {
17510 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17511 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17512 let message = diagnostic_entry
17513 .diagnostic
17514 .message
17515 .split_once('\n')
17516 .map(|(line, _)| line)
17517 .map(SharedString::new)
17518 .unwrap_or_else(|| {
17519 SharedString::from(diagnostic_entry.diagnostic.message)
17520 });
17521 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17522 let (Ok(i) | Err(i)) = inline_diagnostics
17523 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17524 inline_diagnostics.insert(
17525 i,
17526 (
17527 start_anchor,
17528 InlineDiagnostic {
17529 message,
17530 group_id: diagnostic_entry.diagnostic.group_id,
17531 start: diagnostic_entry.range.start.to_point(&snapshot),
17532 is_primary: diagnostic_entry.diagnostic.is_primary,
17533 severity: diagnostic_entry.diagnostic.severity,
17534 },
17535 ),
17536 );
17537 }
17538 inline_diagnostics
17539 })
17540 .await;
17541
17542 editor
17543 .update(cx, |editor, cx| {
17544 editor.inline_diagnostics = new_inline_diagnostics;
17545 cx.notify();
17546 })
17547 .ok();
17548 });
17549 }
17550
17551 fn pull_diagnostics(
17552 &mut self,
17553 buffer_id: Option<BufferId>,
17554 window: &Window,
17555 cx: &mut Context<Self>,
17556 ) -> Option<()> {
17557 if !self.mode().is_full() {
17558 return None;
17559 }
17560 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17561 .diagnostics
17562 .lsp_pull_diagnostics;
17563 if !pull_diagnostics_settings.enabled {
17564 return None;
17565 }
17566 let project = self.project()?.downgrade();
17567 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17568 let mut buffers = self.buffer.read(cx).all_buffers();
17569 if let Some(buffer_id) = buffer_id {
17570 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17571 }
17572
17573 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17574 cx.background_executor().timer(debounce).await;
17575
17576 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17577 buffers
17578 .into_iter()
17579 .filter_map(|buffer| {
17580 project
17581 .update(cx, |project, cx| {
17582 project.lsp_store().update(cx, |lsp_store, cx| {
17583 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17584 })
17585 })
17586 .ok()
17587 })
17588 .collect::<FuturesUnordered<_>>()
17589 }) else {
17590 return;
17591 };
17592
17593 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17594 match pull_task {
17595 Ok(()) => {
17596 if editor
17597 .update_in(cx, |editor, window, cx| {
17598 editor.update_diagnostics_state(window, cx);
17599 })
17600 .is_err()
17601 {
17602 return;
17603 }
17604 }
17605 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17606 }
17607 }
17608 });
17609
17610 Some(())
17611 }
17612
17613 pub fn set_selections_from_remote(
17614 &mut self,
17615 selections: Vec<Selection<Anchor>>,
17616 pending_selection: Option<Selection<Anchor>>,
17617 window: &mut Window,
17618 cx: &mut Context<Self>,
17619 ) {
17620 let old_cursor_position = self.selections.newest_anchor().head();
17621 self.selections.change_with(cx, |s| {
17622 s.select_anchors(selections);
17623 if let Some(pending_selection) = pending_selection {
17624 s.set_pending(pending_selection, SelectMode::Character);
17625 } else {
17626 s.clear_pending();
17627 }
17628 });
17629 self.selections_did_change(
17630 false,
17631 &old_cursor_position,
17632 SelectionEffects::default(),
17633 window,
17634 cx,
17635 );
17636 }
17637
17638 pub fn transact(
17639 &mut self,
17640 window: &mut Window,
17641 cx: &mut Context<Self>,
17642 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17643 ) -> Option<TransactionId> {
17644 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17645 this.start_transaction_at(Instant::now(), window, cx);
17646 update(this, window, cx);
17647 this.end_transaction_at(Instant::now(), cx)
17648 })
17649 }
17650
17651 pub fn start_transaction_at(
17652 &mut self,
17653 now: Instant,
17654 window: &mut Window,
17655 cx: &mut Context<Self>,
17656 ) -> Option<TransactionId> {
17657 self.end_selection(window, cx);
17658 if let Some(tx_id) = self
17659 .buffer
17660 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17661 {
17662 self.selection_history
17663 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17664 cx.emit(EditorEvent::TransactionBegun {
17665 transaction_id: tx_id,
17666 });
17667 Some(tx_id)
17668 } else {
17669 None
17670 }
17671 }
17672
17673 pub fn end_transaction_at(
17674 &mut self,
17675 now: Instant,
17676 cx: &mut Context<Self>,
17677 ) -> Option<TransactionId> {
17678 if let Some(transaction_id) = self
17679 .buffer
17680 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17681 {
17682 if let Some((_, end_selections)) =
17683 self.selection_history.transaction_mut(transaction_id)
17684 {
17685 *end_selections = Some(self.selections.disjoint_anchors_arc());
17686 } else {
17687 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17688 }
17689
17690 cx.emit(EditorEvent::Edited { transaction_id });
17691 Some(transaction_id)
17692 } else {
17693 None
17694 }
17695 }
17696
17697 pub fn modify_transaction_selection_history(
17698 &mut self,
17699 transaction_id: TransactionId,
17700 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17701 ) -> bool {
17702 self.selection_history
17703 .transaction_mut(transaction_id)
17704 .map(modify)
17705 .is_some()
17706 }
17707
17708 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17709 if self.selection_mark_mode {
17710 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17711 s.move_with(|_, sel| {
17712 sel.collapse_to(sel.head(), SelectionGoal::None);
17713 });
17714 })
17715 }
17716 self.selection_mark_mode = true;
17717 cx.notify();
17718 }
17719
17720 pub fn swap_selection_ends(
17721 &mut self,
17722 _: &actions::SwapSelectionEnds,
17723 window: &mut Window,
17724 cx: &mut Context<Self>,
17725 ) {
17726 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17727 s.move_with(|_, sel| {
17728 if sel.start != sel.end {
17729 sel.reversed = !sel.reversed
17730 }
17731 });
17732 });
17733 self.request_autoscroll(Autoscroll::newest(), cx);
17734 cx.notify();
17735 }
17736
17737 pub fn toggle_focus(
17738 workspace: &mut Workspace,
17739 _: &actions::ToggleFocus,
17740 window: &mut Window,
17741 cx: &mut Context<Workspace>,
17742 ) {
17743 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17744 return;
17745 };
17746 workspace.activate_item(&item, true, true, window, cx);
17747 }
17748
17749 pub fn toggle_fold(
17750 &mut self,
17751 _: &actions::ToggleFold,
17752 window: &mut Window,
17753 cx: &mut Context<Self>,
17754 ) {
17755 if self.is_singleton(cx) {
17756 let selection = self.selections.newest::<Point>(cx);
17757
17758 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17759 let range = if selection.is_empty() {
17760 let point = selection.head().to_display_point(&display_map);
17761 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17762 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17763 .to_point(&display_map);
17764 start..end
17765 } else {
17766 selection.range()
17767 };
17768 if display_map.folds_in_range(range).next().is_some() {
17769 self.unfold_lines(&Default::default(), window, cx)
17770 } else {
17771 self.fold(&Default::default(), window, cx)
17772 }
17773 } else {
17774 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17775 let buffer_ids: HashSet<_> = self
17776 .selections
17777 .disjoint_anchor_ranges()
17778 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17779 .collect();
17780
17781 let should_unfold = buffer_ids
17782 .iter()
17783 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17784
17785 for buffer_id in buffer_ids {
17786 if should_unfold {
17787 self.unfold_buffer(buffer_id, cx);
17788 } else {
17789 self.fold_buffer(buffer_id, cx);
17790 }
17791 }
17792 }
17793 }
17794
17795 pub fn toggle_fold_recursive(
17796 &mut self,
17797 _: &actions::ToggleFoldRecursive,
17798 window: &mut Window,
17799 cx: &mut Context<Self>,
17800 ) {
17801 let selection = self.selections.newest::<Point>(cx);
17802
17803 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17804 let range = if selection.is_empty() {
17805 let point = selection.head().to_display_point(&display_map);
17806 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17807 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17808 .to_point(&display_map);
17809 start..end
17810 } else {
17811 selection.range()
17812 };
17813 if display_map.folds_in_range(range).next().is_some() {
17814 self.unfold_recursive(&Default::default(), window, cx)
17815 } else {
17816 self.fold_recursive(&Default::default(), window, cx)
17817 }
17818 }
17819
17820 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17821 if self.is_singleton(cx) {
17822 let mut to_fold = Vec::new();
17823 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17824 let selections = self.selections.all_adjusted(cx);
17825
17826 for selection in selections {
17827 let range = selection.range().sorted();
17828 let buffer_start_row = range.start.row;
17829
17830 if range.start.row != range.end.row {
17831 let mut found = false;
17832 let mut row = range.start.row;
17833 while row <= range.end.row {
17834 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17835 {
17836 found = true;
17837 row = crease.range().end.row + 1;
17838 to_fold.push(crease);
17839 } else {
17840 row += 1
17841 }
17842 }
17843 if found {
17844 continue;
17845 }
17846 }
17847
17848 for row in (0..=range.start.row).rev() {
17849 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17850 && crease.range().end.row >= buffer_start_row
17851 {
17852 to_fold.push(crease);
17853 if row <= range.start.row {
17854 break;
17855 }
17856 }
17857 }
17858 }
17859
17860 self.fold_creases(to_fold, true, window, cx);
17861 } else {
17862 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17863 let buffer_ids = self
17864 .selections
17865 .disjoint_anchor_ranges()
17866 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17867 .collect::<HashSet<_>>();
17868 for buffer_id in buffer_ids {
17869 self.fold_buffer(buffer_id, cx);
17870 }
17871 }
17872 }
17873
17874 pub fn toggle_fold_all(
17875 &mut self,
17876 _: &actions::ToggleFoldAll,
17877 window: &mut Window,
17878 cx: &mut Context<Self>,
17879 ) {
17880 if self.buffer.read(cx).is_singleton() {
17881 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17882 let has_folds = display_map
17883 .folds_in_range(0..display_map.buffer_snapshot.len())
17884 .next()
17885 .is_some();
17886
17887 if has_folds {
17888 self.unfold_all(&actions::UnfoldAll, window, cx);
17889 } else {
17890 self.fold_all(&actions::FoldAll, window, cx);
17891 }
17892 } else {
17893 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17894 let should_unfold = buffer_ids
17895 .iter()
17896 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17897
17898 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17899 editor
17900 .update_in(cx, |editor, _, cx| {
17901 for buffer_id in buffer_ids {
17902 if should_unfold {
17903 editor.unfold_buffer(buffer_id, cx);
17904 } else {
17905 editor.fold_buffer(buffer_id, cx);
17906 }
17907 }
17908 })
17909 .ok();
17910 });
17911 }
17912 }
17913
17914 fn fold_at_level(
17915 &mut self,
17916 fold_at: &FoldAtLevel,
17917 window: &mut Window,
17918 cx: &mut Context<Self>,
17919 ) {
17920 if !self.buffer.read(cx).is_singleton() {
17921 return;
17922 }
17923
17924 let fold_at_level = fold_at.0;
17925 let snapshot = self.buffer.read(cx).snapshot(cx);
17926 let mut to_fold = Vec::new();
17927 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17928
17929 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17930 while start_row < end_row {
17931 match self
17932 .snapshot(window, cx)
17933 .crease_for_buffer_row(MultiBufferRow(start_row))
17934 {
17935 Some(crease) => {
17936 let nested_start_row = crease.range().start.row + 1;
17937 let nested_end_row = crease.range().end.row;
17938
17939 if current_level < fold_at_level {
17940 stack.push((nested_start_row, nested_end_row, current_level + 1));
17941 } else if current_level == fold_at_level {
17942 to_fold.push(crease);
17943 }
17944
17945 start_row = nested_end_row + 1;
17946 }
17947 None => start_row += 1,
17948 }
17949 }
17950 }
17951
17952 self.fold_creases(to_fold, true, window, cx);
17953 }
17954
17955 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17956 if self.buffer.read(cx).is_singleton() {
17957 let mut fold_ranges = Vec::new();
17958 let snapshot = self.buffer.read(cx).snapshot(cx);
17959
17960 for row in 0..snapshot.max_row().0 {
17961 if let Some(foldable_range) = self
17962 .snapshot(window, cx)
17963 .crease_for_buffer_row(MultiBufferRow(row))
17964 {
17965 fold_ranges.push(foldable_range);
17966 }
17967 }
17968
17969 self.fold_creases(fold_ranges, true, window, cx);
17970 } else {
17971 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17972 editor
17973 .update_in(cx, |editor, _, cx| {
17974 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17975 editor.fold_buffer(buffer_id, cx);
17976 }
17977 })
17978 .ok();
17979 });
17980 }
17981 }
17982
17983 pub fn fold_function_bodies(
17984 &mut self,
17985 _: &actions::FoldFunctionBodies,
17986 window: &mut Window,
17987 cx: &mut Context<Self>,
17988 ) {
17989 let snapshot = self.buffer.read(cx).snapshot(cx);
17990
17991 let ranges = snapshot
17992 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17993 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17994 .collect::<Vec<_>>();
17995
17996 let creases = ranges
17997 .into_iter()
17998 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17999 .collect();
18000
18001 self.fold_creases(creases, true, window, cx);
18002 }
18003
18004 pub fn fold_recursive(
18005 &mut self,
18006 _: &actions::FoldRecursive,
18007 window: &mut Window,
18008 cx: &mut Context<Self>,
18009 ) {
18010 let mut to_fold = Vec::new();
18011 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18012 let selections = self.selections.all_adjusted(cx);
18013
18014 for selection in selections {
18015 let range = selection.range().sorted();
18016 let buffer_start_row = range.start.row;
18017
18018 if range.start.row != range.end.row {
18019 let mut found = false;
18020 for row in range.start.row..=range.end.row {
18021 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18022 found = true;
18023 to_fold.push(crease);
18024 }
18025 }
18026 if found {
18027 continue;
18028 }
18029 }
18030
18031 for row in (0..=range.start.row).rev() {
18032 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18033 if crease.range().end.row >= buffer_start_row {
18034 to_fold.push(crease);
18035 } else {
18036 break;
18037 }
18038 }
18039 }
18040 }
18041
18042 self.fold_creases(to_fold, true, window, cx);
18043 }
18044
18045 pub fn fold_at(
18046 &mut self,
18047 buffer_row: MultiBufferRow,
18048 window: &mut Window,
18049 cx: &mut Context<Self>,
18050 ) {
18051 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18052
18053 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18054 let autoscroll = self
18055 .selections
18056 .all::<Point>(cx)
18057 .iter()
18058 .any(|selection| crease.range().overlaps(&selection.range()));
18059
18060 self.fold_creases(vec![crease], autoscroll, window, cx);
18061 }
18062 }
18063
18064 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18065 if self.is_singleton(cx) {
18066 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18067 let buffer = &display_map.buffer_snapshot;
18068 let selections = self.selections.all::<Point>(cx);
18069 let ranges = selections
18070 .iter()
18071 .map(|s| {
18072 let range = s.display_range(&display_map).sorted();
18073 let mut start = range.start.to_point(&display_map);
18074 let mut end = range.end.to_point(&display_map);
18075 start.column = 0;
18076 end.column = buffer.line_len(MultiBufferRow(end.row));
18077 start..end
18078 })
18079 .collect::<Vec<_>>();
18080
18081 self.unfold_ranges(&ranges, true, true, cx);
18082 } else {
18083 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18084 let buffer_ids = self
18085 .selections
18086 .disjoint_anchor_ranges()
18087 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18088 .collect::<HashSet<_>>();
18089 for buffer_id in buffer_ids {
18090 self.unfold_buffer(buffer_id, cx);
18091 }
18092 }
18093 }
18094
18095 pub fn unfold_recursive(
18096 &mut self,
18097 _: &UnfoldRecursive,
18098 _window: &mut Window,
18099 cx: &mut Context<Self>,
18100 ) {
18101 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18102 let selections = self.selections.all::<Point>(cx);
18103 let ranges = selections
18104 .iter()
18105 .map(|s| {
18106 let mut range = s.display_range(&display_map).sorted();
18107 *range.start.column_mut() = 0;
18108 *range.end.column_mut() = display_map.line_len(range.end.row());
18109 let start = range.start.to_point(&display_map);
18110 let end = range.end.to_point(&display_map);
18111 start..end
18112 })
18113 .collect::<Vec<_>>();
18114
18115 self.unfold_ranges(&ranges, true, true, cx);
18116 }
18117
18118 pub fn unfold_at(
18119 &mut self,
18120 buffer_row: MultiBufferRow,
18121 _window: &mut Window,
18122 cx: &mut Context<Self>,
18123 ) {
18124 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18125
18126 let intersection_range = Point::new(buffer_row.0, 0)
18127 ..Point::new(
18128 buffer_row.0,
18129 display_map.buffer_snapshot.line_len(buffer_row),
18130 );
18131
18132 let autoscroll = self
18133 .selections
18134 .all::<Point>(cx)
18135 .iter()
18136 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18137
18138 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18139 }
18140
18141 pub fn unfold_all(
18142 &mut self,
18143 _: &actions::UnfoldAll,
18144 _window: &mut Window,
18145 cx: &mut Context<Self>,
18146 ) {
18147 if self.buffer.read(cx).is_singleton() {
18148 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18149 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18150 } else {
18151 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18152 editor
18153 .update(cx, |editor, cx| {
18154 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18155 editor.unfold_buffer(buffer_id, cx);
18156 }
18157 })
18158 .ok();
18159 });
18160 }
18161 }
18162
18163 pub fn fold_selected_ranges(
18164 &mut self,
18165 _: &FoldSelectedRanges,
18166 window: &mut Window,
18167 cx: &mut Context<Self>,
18168 ) {
18169 let selections = self.selections.all_adjusted(cx);
18170 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18171 let ranges = selections
18172 .into_iter()
18173 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18174 .collect::<Vec<_>>();
18175 self.fold_creases(ranges, true, window, cx);
18176 }
18177
18178 pub fn fold_ranges<T: ToOffset + Clone>(
18179 &mut self,
18180 ranges: Vec<Range<T>>,
18181 auto_scroll: bool,
18182 window: &mut Window,
18183 cx: &mut Context<Self>,
18184 ) {
18185 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18186 let ranges = ranges
18187 .into_iter()
18188 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18189 .collect::<Vec<_>>();
18190 self.fold_creases(ranges, auto_scroll, window, cx);
18191 }
18192
18193 pub fn fold_creases<T: ToOffset + Clone>(
18194 &mut self,
18195 creases: Vec<Crease<T>>,
18196 auto_scroll: bool,
18197 _window: &mut Window,
18198 cx: &mut Context<Self>,
18199 ) {
18200 if creases.is_empty() {
18201 return;
18202 }
18203
18204 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18205
18206 if auto_scroll {
18207 self.request_autoscroll(Autoscroll::fit(), cx);
18208 }
18209
18210 cx.notify();
18211
18212 self.scrollbar_marker_state.dirty = true;
18213 self.folds_did_change(cx);
18214 }
18215
18216 /// Removes any folds whose ranges intersect any of the given ranges.
18217 pub fn unfold_ranges<T: ToOffset + Clone>(
18218 &mut self,
18219 ranges: &[Range<T>],
18220 inclusive: bool,
18221 auto_scroll: bool,
18222 cx: &mut Context<Self>,
18223 ) {
18224 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18225 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18226 });
18227 self.folds_did_change(cx);
18228 }
18229
18230 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18231 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18232 return;
18233 }
18234 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18235 self.display_map.update(cx, |display_map, cx| {
18236 display_map.fold_buffers([buffer_id], cx)
18237 });
18238 cx.emit(EditorEvent::BufferFoldToggled {
18239 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18240 folded: true,
18241 });
18242 cx.notify();
18243 }
18244
18245 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18246 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18247 return;
18248 }
18249 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18250 self.display_map.update(cx, |display_map, cx| {
18251 display_map.unfold_buffers([buffer_id], cx);
18252 });
18253 cx.emit(EditorEvent::BufferFoldToggled {
18254 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18255 folded: false,
18256 });
18257 cx.notify();
18258 }
18259
18260 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18261 self.display_map.read(cx).is_buffer_folded(buffer)
18262 }
18263
18264 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18265 self.display_map.read(cx).folded_buffers()
18266 }
18267
18268 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18269 self.display_map.update(cx, |display_map, cx| {
18270 display_map.disable_header_for_buffer(buffer_id, cx);
18271 });
18272 cx.notify();
18273 }
18274
18275 /// Removes any folds with the given ranges.
18276 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18277 &mut self,
18278 ranges: &[Range<T>],
18279 type_id: TypeId,
18280 auto_scroll: bool,
18281 cx: &mut Context<Self>,
18282 ) {
18283 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18284 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18285 });
18286 self.folds_did_change(cx);
18287 }
18288
18289 fn remove_folds_with<T: ToOffset + Clone>(
18290 &mut self,
18291 ranges: &[Range<T>],
18292 auto_scroll: bool,
18293 cx: &mut Context<Self>,
18294 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18295 ) {
18296 if ranges.is_empty() {
18297 return;
18298 }
18299
18300 let mut buffers_affected = HashSet::default();
18301 let multi_buffer = self.buffer().read(cx);
18302 for range in ranges {
18303 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18304 buffers_affected.insert(buffer.read(cx).remote_id());
18305 };
18306 }
18307
18308 self.display_map.update(cx, update);
18309
18310 if auto_scroll {
18311 self.request_autoscroll(Autoscroll::fit(), cx);
18312 }
18313
18314 cx.notify();
18315 self.scrollbar_marker_state.dirty = true;
18316 self.active_indent_guides_state.dirty = true;
18317 }
18318
18319 pub fn update_renderer_widths(
18320 &mut self,
18321 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18322 cx: &mut Context<Self>,
18323 ) -> bool {
18324 self.display_map
18325 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18326 }
18327
18328 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18329 self.display_map.read(cx).fold_placeholder.clone()
18330 }
18331
18332 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18333 self.buffer.update(cx, |buffer, cx| {
18334 buffer.set_all_diff_hunks_expanded(cx);
18335 });
18336 }
18337
18338 pub fn expand_all_diff_hunks(
18339 &mut self,
18340 _: &ExpandAllDiffHunks,
18341 _window: &mut Window,
18342 cx: &mut Context<Self>,
18343 ) {
18344 self.buffer.update(cx, |buffer, cx| {
18345 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18346 });
18347 }
18348
18349 pub fn toggle_selected_diff_hunks(
18350 &mut self,
18351 _: &ToggleSelectedDiffHunks,
18352 _window: &mut Window,
18353 cx: &mut Context<Self>,
18354 ) {
18355 let ranges: Vec<_> = self
18356 .selections
18357 .disjoint_anchors()
18358 .iter()
18359 .map(|s| s.range())
18360 .collect();
18361 self.toggle_diff_hunks_in_ranges(ranges, cx);
18362 }
18363
18364 pub fn diff_hunks_in_ranges<'a>(
18365 &'a self,
18366 ranges: &'a [Range<Anchor>],
18367 buffer: &'a MultiBufferSnapshot,
18368 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18369 ranges.iter().flat_map(move |range| {
18370 let end_excerpt_id = range.end.excerpt_id;
18371 let range = range.to_point(buffer);
18372 let mut peek_end = range.end;
18373 if range.end.row < buffer.max_row().0 {
18374 peek_end = Point::new(range.end.row + 1, 0);
18375 }
18376 buffer
18377 .diff_hunks_in_range(range.start..peek_end)
18378 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18379 })
18380 }
18381
18382 pub fn has_stageable_diff_hunks_in_ranges(
18383 &self,
18384 ranges: &[Range<Anchor>],
18385 snapshot: &MultiBufferSnapshot,
18386 ) -> bool {
18387 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18388 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18389 }
18390
18391 pub fn toggle_staged_selected_diff_hunks(
18392 &mut self,
18393 _: &::git::ToggleStaged,
18394 _: &mut Window,
18395 cx: &mut Context<Self>,
18396 ) {
18397 let snapshot = self.buffer.read(cx).snapshot(cx);
18398 let ranges: Vec<_> = self
18399 .selections
18400 .disjoint_anchors()
18401 .iter()
18402 .map(|s| s.range())
18403 .collect();
18404 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18405 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18406 }
18407
18408 pub fn set_render_diff_hunk_controls(
18409 &mut self,
18410 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18411 cx: &mut Context<Self>,
18412 ) {
18413 self.render_diff_hunk_controls = render_diff_hunk_controls;
18414 cx.notify();
18415 }
18416
18417 pub fn stage_and_next(
18418 &mut self,
18419 _: &::git::StageAndNext,
18420 window: &mut Window,
18421 cx: &mut Context<Self>,
18422 ) {
18423 self.do_stage_or_unstage_and_next(true, window, cx);
18424 }
18425
18426 pub fn unstage_and_next(
18427 &mut self,
18428 _: &::git::UnstageAndNext,
18429 window: &mut Window,
18430 cx: &mut Context<Self>,
18431 ) {
18432 self.do_stage_or_unstage_and_next(false, window, cx);
18433 }
18434
18435 pub fn stage_or_unstage_diff_hunks(
18436 &mut self,
18437 stage: bool,
18438 ranges: Vec<Range<Anchor>>,
18439 cx: &mut Context<Self>,
18440 ) {
18441 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18442 cx.spawn(async move |this, cx| {
18443 task.await?;
18444 this.update(cx, |this, cx| {
18445 let snapshot = this.buffer.read(cx).snapshot(cx);
18446 let chunk_by = this
18447 .diff_hunks_in_ranges(&ranges, &snapshot)
18448 .chunk_by(|hunk| hunk.buffer_id);
18449 for (buffer_id, hunks) in &chunk_by {
18450 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18451 }
18452 })
18453 })
18454 .detach_and_log_err(cx);
18455 }
18456
18457 fn save_buffers_for_ranges_if_needed(
18458 &mut self,
18459 ranges: &[Range<Anchor>],
18460 cx: &mut Context<Editor>,
18461 ) -> Task<Result<()>> {
18462 let multibuffer = self.buffer.read(cx);
18463 let snapshot = multibuffer.read(cx);
18464 let buffer_ids: HashSet<_> = ranges
18465 .iter()
18466 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18467 .collect();
18468 drop(snapshot);
18469
18470 let mut buffers = HashSet::default();
18471 for buffer_id in buffer_ids {
18472 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18473 let buffer = buffer_entity.read(cx);
18474 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18475 {
18476 buffers.insert(buffer_entity);
18477 }
18478 }
18479 }
18480
18481 if let Some(project) = &self.project {
18482 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18483 } else {
18484 Task::ready(Ok(()))
18485 }
18486 }
18487
18488 fn do_stage_or_unstage_and_next(
18489 &mut self,
18490 stage: bool,
18491 window: &mut Window,
18492 cx: &mut Context<Self>,
18493 ) {
18494 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18495
18496 if ranges.iter().any(|range| range.start != range.end) {
18497 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18498 return;
18499 }
18500
18501 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18502 let snapshot = self.snapshot(window, cx);
18503 let position = self.selections.newest::<Point>(cx).head();
18504 let mut row = snapshot
18505 .buffer_snapshot
18506 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18507 .find(|hunk| hunk.row_range.start.0 > position.row)
18508 .map(|hunk| hunk.row_range.start);
18509
18510 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18511 // Outside of the project diff editor, wrap around to the beginning.
18512 if !all_diff_hunks_expanded {
18513 row = row.or_else(|| {
18514 snapshot
18515 .buffer_snapshot
18516 .diff_hunks_in_range(Point::zero()..position)
18517 .find(|hunk| hunk.row_range.end.0 < position.row)
18518 .map(|hunk| hunk.row_range.start)
18519 });
18520 }
18521
18522 if let Some(row) = row {
18523 let destination = Point::new(row.0, 0);
18524 let autoscroll = Autoscroll::center();
18525
18526 self.unfold_ranges(&[destination..destination], false, false, cx);
18527 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18528 s.select_ranges([destination..destination]);
18529 });
18530 }
18531 }
18532
18533 fn do_stage_or_unstage(
18534 &self,
18535 stage: bool,
18536 buffer_id: BufferId,
18537 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18538 cx: &mut App,
18539 ) -> Option<()> {
18540 let project = self.project()?;
18541 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18542 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18543 let buffer_snapshot = buffer.read(cx).snapshot();
18544 let file_exists = buffer_snapshot
18545 .file()
18546 .is_some_and(|file| file.disk_state().exists());
18547 diff.update(cx, |diff, cx| {
18548 diff.stage_or_unstage_hunks(
18549 stage,
18550 &hunks
18551 .map(|hunk| buffer_diff::DiffHunk {
18552 buffer_range: hunk.buffer_range,
18553 diff_base_byte_range: hunk.diff_base_byte_range,
18554 secondary_status: hunk.secondary_status,
18555 range: Point::zero()..Point::zero(), // unused
18556 })
18557 .collect::<Vec<_>>(),
18558 &buffer_snapshot,
18559 file_exists,
18560 cx,
18561 )
18562 });
18563 None
18564 }
18565
18566 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18567 let ranges: Vec<_> = self
18568 .selections
18569 .disjoint_anchors()
18570 .iter()
18571 .map(|s| s.range())
18572 .collect();
18573 self.buffer
18574 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18575 }
18576
18577 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18578 self.buffer.update(cx, |buffer, cx| {
18579 let ranges = vec![Anchor::min()..Anchor::max()];
18580 if !buffer.all_diff_hunks_expanded()
18581 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18582 {
18583 buffer.collapse_diff_hunks(ranges, cx);
18584 true
18585 } else {
18586 false
18587 }
18588 })
18589 }
18590
18591 fn toggle_diff_hunks_in_ranges(
18592 &mut self,
18593 ranges: Vec<Range<Anchor>>,
18594 cx: &mut Context<Editor>,
18595 ) {
18596 self.buffer.update(cx, |buffer, cx| {
18597 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18598 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18599 })
18600 }
18601
18602 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18603 self.buffer.update(cx, |buffer, cx| {
18604 let snapshot = buffer.snapshot(cx);
18605 let excerpt_id = range.end.excerpt_id;
18606 let point_range = range.to_point(&snapshot);
18607 let expand = !buffer.single_hunk_is_expanded(range, cx);
18608 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18609 })
18610 }
18611
18612 pub(crate) fn apply_all_diff_hunks(
18613 &mut self,
18614 _: &ApplyAllDiffHunks,
18615 window: &mut Window,
18616 cx: &mut Context<Self>,
18617 ) {
18618 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18619
18620 let buffers = self.buffer.read(cx).all_buffers();
18621 for branch_buffer in buffers {
18622 branch_buffer.update(cx, |branch_buffer, cx| {
18623 branch_buffer.merge_into_base(Vec::new(), cx);
18624 });
18625 }
18626
18627 if let Some(project) = self.project.clone() {
18628 self.save(
18629 SaveOptions {
18630 format: true,
18631 autosave: false,
18632 },
18633 project,
18634 window,
18635 cx,
18636 )
18637 .detach_and_log_err(cx);
18638 }
18639 }
18640
18641 pub(crate) fn apply_selected_diff_hunks(
18642 &mut self,
18643 _: &ApplyDiffHunk,
18644 window: &mut Window,
18645 cx: &mut Context<Self>,
18646 ) {
18647 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18648 let snapshot = self.snapshot(window, cx);
18649 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18650 let mut ranges_by_buffer = HashMap::default();
18651 self.transact(window, cx, |editor, _window, cx| {
18652 for hunk in hunks {
18653 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18654 ranges_by_buffer
18655 .entry(buffer.clone())
18656 .or_insert_with(Vec::new)
18657 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18658 }
18659 }
18660
18661 for (buffer, ranges) in ranges_by_buffer {
18662 buffer.update(cx, |buffer, cx| {
18663 buffer.merge_into_base(ranges, cx);
18664 });
18665 }
18666 });
18667
18668 if let Some(project) = self.project.clone() {
18669 self.save(
18670 SaveOptions {
18671 format: true,
18672 autosave: false,
18673 },
18674 project,
18675 window,
18676 cx,
18677 )
18678 .detach_and_log_err(cx);
18679 }
18680 }
18681
18682 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18683 if hovered != self.gutter_hovered {
18684 self.gutter_hovered = hovered;
18685 cx.notify();
18686 }
18687 }
18688
18689 pub fn insert_blocks(
18690 &mut self,
18691 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18692 autoscroll: Option<Autoscroll>,
18693 cx: &mut Context<Self>,
18694 ) -> Vec<CustomBlockId> {
18695 let blocks = self
18696 .display_map
18697 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18698 if let Some(autoscroll) = autoscroll {
18699 self.request_autoscroll(autoscroll, cx);
18700 }
18701 cx.notify();
18702 blocks
18703 }
18704
18705 pub fn resize_blocks(
18706 &mut self,
18707 heights: HashMap<CustomBlockId, u32>,
18708 autoscroll: Option<Autoscroll>,
18709 cx: &mut Context<Self>,
18710 ) {
18711 self.display_map
18712 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18713 if let Some(autoscroll) = autoscroll {
18714 self.request_autoscroll(autoscroll, cx);
18715 }
18716 cx.notify();
18717 }
18718
18719 pub fn replace_blocks(
18720 &mut self,
18721 renderers: HashMap<CustomBlockId, RenderBlock>,
18722 autoscroll: Option<Autoscroll>,
18723 cx: &mut Context<Self>,
18724 ) {
18725 self.display_map
18726 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18727 if let Some(autoscroll) = autoscroll {
18728 self.request_autoscroll(autoscroll, cx);
18729 }
18730 cx.notify();
18731 }
18732
18733 pub fn remove_blocks(
18734 &mut self,
18735 block_ids: HashSet<CustomBlockId>,
18736 autoscroll: Option<Autoscroll>,
18737 cx: &mut Context<Self>,
18738 ) {
18739 self.display_map.update(cx, |display_map, cx| {
18740 display_map.remove_blocks(block_ids, cx)
18741 });
18742 if let Some(autoscroll) = autoscroll {
18743 self.request_autoscroll(autoscroll, cx);
18744 }
18745 cx.notify();
18746 }
18747
18748 pub fn row_for_block(
18749 &self,
18750 block_id: CustomBlockId,
18751 cx: &mut Context<Self>,
18752 ) -> Option<DisplayRow> {
18753 self.display_map
18754 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18755 }
18756
18757 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18758 self.focused_block = Some(focused_block);
18759 }
18760
18761 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18762 self.focused_block.take()
18763 }
18764
18765 pub fn insert_creases(
18766 &mut self,
18767 creases: impl IntoIterator<Item = Crease<Anchor>>,
18768 cx: &mut Context<Self>,
18769 ) -> Vec<CreaseId> {
18770 self.display_map
18771 .update(cx, |map, cx| map.insert_creases(creases, cx))
18772 }
18773
18774 pub fn remove_creases(
18775 &mut self,
18776 ids: impl IntoIterator<Item = CreaseId>,
18777 cx: &mut Context<Self>,
18778 ) -> Vec<(CreaseId, Range<Anchor>)> {
18779 self.display_map
18780 .update(cx, |map, cx| map.remove_creases(ids, cx))
18781 }
18782
18783 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18784 self.display_map
18785 .update(cx, |map, cx| map.snapshot(cx))
18786 .longest_row()
18787 }
18788
18789 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18790 self.display_map
18791 .update(cx, |map, cx| map.snapshot(cx))
18792 .max_point()
18793 }
18794
18795 pub fn text(&self, cx: &App) -> String {
18796 self.buffer.read(cx).read(cx).text()
18797 }
18798
18799 pub fn is_empty(&self, cx: &App) -> bool {
18800 self.buffer.read(cx).read(cx).is_empty()
18801 }
18802
18803 pub fn text_option(&self, cx: &App) -> Option<String> {
18804 let text = self.text(cx);
18805 let text = text.trim();
18806
18807 if text.is_empty() {
18808 return None;
18809 }
18810
18811 Some(text.to_string())
18812 }
18813
18814 pub fn set_text(
18815 &mut self,
18816 text: impl Into<Arc<str>>,
18817 window: &mut Window,
18818 cx: &mut Context<Self>,
18819 ) {
18820 self.transact(window, cx, |this, _, cx| {
18821 this.buffer
18822 .read(cx)
18823 .as_singleton()
18824 .expect("you can only call set_text on editors for singleton buffers")
18825 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18826 });
18827 }
18828
18829 pub fn display_text(&self, cx: &mut App) -> String {
18830 self.display_map
18831 .update(cx, |map, cx| map.snapshot(cx))
18832 .text()
18833 }
18834
18835 fn create_minimap(
18836 &self,
18837 minimap_settings: MinimapSettings,
18838 window: &mut Window,
18839 cx: &mut Context<Self>,
18840 ) -> Option<Entity<Self>> {
18841 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18842 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18843 }
18844
18845 fn initialize_new_minimap(
18846 &self,
18847 minimap_settings: MinimapSettings,
18848 window: &mut Window,
18849 cx: &mut Context<Self>,
18850 ) -> Entity<Self> {
18851 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18852
18853 let mut minimap = Editor::new_internal(
18854 EditorMode::Minimap {
18855 parent: cx.weak_entity(),
18856 },
18857 self.buffer.clone(),
18858 None,
18859 Some(self.display_map.clone()),
18860 window,
18861 cx,
18862 );
18863 minimap.scroll_manager.clone_state(&self.scroll_manager);
18864 minimap.set_text_style_refinement(TextStyleRefinement {
18865 font_size: Some(MINIMAP_FONT_SIZE),
18866 font_weight: Some(MINIMAP_FONT_WEIGHT),
18867 ..Default::default()
18868 });
18869 minimap.update_minimap_configuration(minimap_settings, cx);
18870 cx.new(|_| minimap)
18871 }
18872
18873 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18874 let current_line_highlight = minimap_settings
18875 .current_line_highlight
18876 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18877 self.set_current_line_highlight(Some(current_line_highlight));
18878 }
18879
18880 pub fn minimap(&self) -> Option<&Entity<Self>> {
18881 self.minimap
18882 .as_ref()
18883 .filter(|_| self.minimap_visibility.visible())
18884 }
18885
18886 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18887 let mut wrap_guides = smallvec![];
18888
18889 if self.show_wrap_guides == Some(false) {
18890 return wrap_guides;
18891 }
18892
18893 let settings = self.buffer.read(cx).language_settings(cx);
18894 if settings.show_wrap_guides {
18895 match self.soft_wrap_mode(cx) {
18896 SoftWrap::Column(soft_wrap) => {
18897 wrap_guides.push((soft_wrap as usize, true));
18898 }
18899 SoftWrap::Bounded(soft_wrap) => {
18900 wrap_guides.push((soft_wrap as usize, true));
18901 }
18902 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18903 }
18904 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18905 }
18906
18907 wrap_guides
18908 }
18909
18910 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18911 let settings = self.buffer.read(cx).language_settings(cx);
18912 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18913 match mode {
18914 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18915 SoftWrap::None
18916 }
18917 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18918 language_settings::SoftWrap::PreferredLineLength => {
18919 SoftWrap::Column(settings.preferred_line_length)
18920 }
18921 language_settings::SoftWrap::Bounded => {
18922 SoftWrap::Bounded(settings.preferred_line_length)
18923 }
18924 }
18925 }
18926
18927 pub fn set_soft_wrap_mode(
18928 &mut self,
18929 mode: language_settings::SoftWrap,
18930
18931 cx: &mut Context<Self>,
18932 ) {
18933 self.soft_wrap_mode_override = Some(mode);
18934 cx.notify();
18935 }
18936
18937 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18938 self.hard_wrap = hard_wrap;
18939 cx.notify();
18940 }
18941
18942 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18943 self.text_style_refinement = Some(style);
18944 }
18945
18946 /// called by the Element so we know what style we were most recently rendered with.
18947 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
18948 // We intentionally do not inform the display map about the minimap style
18949 // so that wrapping is not recalculated and stays consistent for the editor
18950 // and its linked minimap.
18951 if !self.mode.is_minimap() {
18952 let font = style.text.font();
18953 let font_size = style.text.font_size.to_pixels(window.rem_size());
18954 let display_map = self
18955 .placeholder_display_map
18956 .as_ref()
18957 .filter(|_| self.is_empty(cx))
18958 .unwrap_or(&self.display_map);
18959
18960 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
18961 }
18962 self.style = Some(style);
18963 }
18964
18965 pub fn style(&self) -> Option<&EditorStyle> {
18966 self.style.as_ref()
18967 }
18968
18969 // Called by the element. This method is not designed to be called outside of the editor
18970 // element's layout code because it does not notify when rewrapping is computed synchronously.
18971 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18972 if self.is_empty(cx) {
18973 self.placeholder_display_map
18974 .as_ref()
18975 .map_or(false, |display_map| {
18976 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
18977 })
18978 } else {
18979 self.display_map
18980 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18981 }
18982 }
18983
18984 pub fn set_soft_wrap(&mut self) {
18985 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18986 }
18987
18988 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18989 if self.soft_wrap_mode_override.is_some() {
18990 self.soft_wrap_mode_override.take();
18991 } else {
18992 let soft_wrap = match self.soft_wrap_mode(cx) {
18993 SoftWrap::GitDiff => return,
18994 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18995 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18996 language_settings::SoftWrap::None
18997 }
18998 };
18999 self.soft_wrap_mode_override = Some(soft_wrap);
19000 }
19001 cx.notify();
19002 }
19003
19004 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19005 let Some(workspace) = self.workspace() else {
19006 return;
19007 };
19008 let fs = workspace.read(cx).app_state().fs.clone();
19009 let current_show = TabBarSettings::get_global(cx).show;
19010 update_settings_file(fs, cx, move |setting, _| {
19011 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19012 });
19013 }
19014
19015 pub fn toggle_indent_guides(
19016 &mut self,
19017 _: &ToggleIndentGuides,
19018 _: &mut Window,
19019 cx: &mut Context<Self>,
19020 ) {
19021 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19022 self.buffer
19023 .read(cx)
19024 .language_settings(cx)
19025 .indent_guides
19026 .enabled
19027 });
19028 self.show_indent_guides = Some(!currently_enabled);
19029 cx.notify();
19030 }
19031
19032 fn should_show_indent_guides(&self) -> Option<bool> {
19033 self.show_indent_guides
19034 }
19035
19036 pub fn toggle_line_numbers(
19037 &mut self,
19038 _: &ToggleLineNumbers,
19039 _: &mut Window,
19040 cx: &mut Context<Self>,
19041 ) {
19042 let mut editor_settings = EditorSettings::get_global(cx).clone();
19043 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19044 EditorSettings::override_global(editor_settings, cx);
19045 }
19046
19047 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19048 if let Some(show_line_numbers) = self.show_line_numbers {
19049 return show_line_numbers;
19050 }
19051 EditorSettings::get_global(cx).gutter.line_numbers
19052 }
19053
19054 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19055 self.use_relative_line_numbers
19056 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19057 }
19058
19059 pub fn toggle_relative_line_numbers(
19060 &mut self,
19061 _: &ToggleRelativeLineNumbers,
19062 _: &mut Window,
19063 cx: &mut Context<Self>,
19064 ) {
19065 let is_relative = self.should_use_relative_line_numbers(cx);
19066 self.set_relative_line_number(Some(!is_relative), cx)
19067 }
19068
19069 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19070 self.use_relative_line_numbers = is_relative;
19071 cx.notify();
19072 }
19073
19074 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19075 self.show_gutter = show_gutter;
19076 cx.notify();
19077 }
19078
19079 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19080 self.show_scrollbars = ScrollbarAxes {
19081 horizontal: show,
19082 vertical: show,
19083 };
19084 cx.notify();
19085 }
19086
19087 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19088 self.show_scrollbars.vertical = show;
19089 cx.notify();
19090 }
19091
19092 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19093 self.show_scrollbars.horizontal = show;
19094 cx.notify();
19095 }
19096
19097 pub fn set_minimap_visibility(
19098 &mut self,
19099 minimap_visibility: MinimapVisibility,
19100 window: &mut Window,
19101 cx: &mut Context<Self>,
19102 ) {
19103 if self.minimap_visibility != minimap_visibility {
19104 if minimap_visibility.visible() && self.minimap.is_none() {
19105 let minimap_settings = EditorSettings::get_global(cx).minimap;
19106 self.minimap =
19107 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19108 }
19109 self.minimap_visibility = minimap_visibility;
19110 cx.notify();
19111 }
19112 }
19113
19114 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19115 self.set_show_scrollbars(false, cx);
19116 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19117 }
19118
19119 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19120 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19121 }
19122
19123 /// Normally the text in full mode and auto height editors is padded on the
19124 /// left side by roughly half a character width for improved hit testing.
19125 ///
19126 /// Use this method to disable this for cases where this is not wanted (e.g.
19127 /// if you want to align the editor text with some other text above or below)
19128 /// or if you want to add this padding to single-line editors.
19129 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19130 self.offset_content = offset_content;
19131 cx.notify();
19132 }
19133
19134 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19135 self.show_line_numbers = Some(show_line_numbers);
19136 cx.notify();
19137 }
19138
19139 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19140 self.disable_expand_excerpt_buttons = true;
19141 cx.notify();
19142 }
19143
19144 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19145 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19146 cx.notify();
19147 }
19148
19149 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19150 self.show_code_actions = Some(show_code_actions);
19151 cx.notify();
19152 }
19153
19154 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19155 self.show_runnables = Some(show_runnables);
19156 cx.notify();
19157 }
19158
19159 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19160 self.show_breakpoints = Some(show_breakpoints);
19161 cx.notify();
19162 }
19163
19164 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19165 if self.display_map.read(cx).masked != masked {
19166 self.display_map.update(cx, |map, _| map.masked = masked);
19167 }
19168 cx.notify()
19169 }
19170
19171 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19172 self.show_wrap_guides = Some(show_wrap_guides);
19173 cx.notify();
19174 }
19175
19176 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19177 self.show_indent_guides = Some(show_indent_guides);
19178 cx.notify();
19179 }
19180
19181 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19182 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19183 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19184 && let Some(dir) = file.abs_path(cx).parent()
19185 {
19186 return Some(dir.to_owned());
19187 }
19188
19189 if let Some(project_path) = buffer.read(cx).project_path(cx) {
19190 return Some(project_path.path.to_path_buf());
19191 }
19192 }
19193
19194 None
19195 }
19196
19197 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19198 self.active_excerpt(cx)?
19199 .1
19200 .read(cx)
19201 .file()
19202 .and_then(|f| f.as_local())
19203 }
19204
19205 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19206 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19207 let buffer = buffer.read(cx);
19208 if let Some(project_path) = buffer.project_path(cx) {
19209 let project = self.project()?.read(cx);
19210 project.absolute_path(&project_path, cx)
19211 } else {
19212 buffer
19213 .file()
19214 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19215 }
19216 })
19217 }
19218
19219 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19220 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19221 let project_path = buffer.read(cx).project_path(cx)?;
19222 let project = self.project()?.read(cx);
19223 let entry = project.entry_for_path(&project_path, cx)?;
19224 let path = entry.path.to_path_buf();
19225 Some(path)
19226 })
19227 }
19228
19229 pub fn reveal_in_finder(
19230 &mut self,
19231 _: &RevealInFileManager,
19232 _window: &mut Window,
19233 cx: &mut Context<Self>,
19234 ) {
19235 if let Some(target) = self.target_file(cx) {
19236 cx.reveal_path(&target.abs_path(cx));
19237 }
19238 }
19239
19240 pub fn copy_path(
19241 &mut self,
19242 _: &zed_actions::workspace::CopyPath,
19243 _window: &mut Window,
19244 cx: &mut Context<Self>,
19245 ) {
19246 if let Some(path) = self.target_file_abs_path(cx)
19247 && let Some(path) = path.to_str()
19248 {
19249 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19250 }
19251 }
19252
19253 pub fn copy_relative_path(
19254 &mut self,
19255 _: &zed_actions::workspace::CopyRelativePath,
19256 _window: &mut Window,
19257 cx: &mut Context<Self>,
19258 ) {
19259 if let Some(path) = self.target_file_path(cx)
19260 && let Some(path) = path.to_str()
19261 {
19262 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19263 }
19264 }
19265
19266 /// Returns the project path for the editor's buffer, if any buffer is
19267 /// opened in the editor.
19268 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19269 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19270 buffer.read(cx).project_path(cx)
19271 } else {
19272 None
19273 }
19274 }
19275
19276 // Returns true if the editor handled a go-to-line request
19277 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19278 maybe!({
19279 let breakpoint_store = self.breakpoint_store.as_ref()?;
19280
19281 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19282 else {
19283 self.clear_row_highlights::<ActiveDebugLine>();
19284 return None;
19285 };
19286
19287 let position = active_stack_frame.position;
19288 let buffer_id = position.buffer_id?;
19289 let snapshot = self
19290 .project
19291 .as_ref()?
19292 .read(cx)
19293 .buffer_for_id(buffer_id, cx)?
19294 .read(cx)
19295 .snapshot();
19296
19297 let mut handled = false;
19298 for (id, ExcerptRange { context, .. }) in
19299 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19300 {
19301 if context.start.cmp(&position, &snapshot).is_ge()
19302 || context.end.cmp(&position, &snapshot).is_lt()
19303 {
19304 continue;
19305 }
19306 let snapshot = self.buffer.read(cx).snapshot(cx);
19307 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19308
19309 handled = true;
19310 self.clear_row_highlights::<ActiveDebugLine>();
19311
19312 self.go_to_line::<ActiveDebugLine>(
19313 multibuffer_anchor,
19314 Some(cx.theme().colors().editor_debugger_active_line_background),
19315 window,
19316 cx,
19317 );
19318
19319 cx.notify();
19320 }
19321
19322 handled.then_some(())
19323 })
19324 .is_some()
19325 }
19326
19327 pub fn copy_file_name_without_extension(
19328 &mut self,
19329 _: &CopyFileNameWithoutExtension,
19330 _: &mut Window,
19331 cx: &mut Context<Self>,
19332 ) {
19333 if let Some(file) = self.target_file(cx)
19334 && let Some(file_stem) = file.path().file_stem()
19335 && let Some(name) = file_stem.to_str()
19336 {
19337 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19338 }
19339 }
19340
19341 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19342 if let Some(file) = self.target_file(cx)
19343 && let Some(file_name) = file.path().file_name()
19344 && let Some(name) = file_name.to_str()
19345 {
19346 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19347 }
19348 }
19349
19350 pub fn toggle_git_blame(
19351 &mut self,
19352 _: &::git::Blame,
19353 window: &mut Window,
19354 cx: &mut Context<Self>,
19355 ) {
19356 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19357
19358 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19359 self.start_git_blame(true, window, cx);
19360 }
19361
19362 cx.notify();
19363 }
19364
19365 pub fn toggle_git_blame_inline(
19366 &mut self,
19367 _: &ToggleGitBlameInline,
19368 window: &mut Window,
19369 cx: &mut Context<Self>,
19370 ) {
19371 self.toggle_git_blame_inline_internal(true, window, cx);
19372 cx.notify();
19373 }
19374
19375 pub fn open_git_blame_commit(
19376 &mut self,
19377 _: &OpenGitBlameCommit,
19378 window: &mut Window,
19379 cx: &mut Context<Self>,
19380 ) {
19381 self.open_git_blame_commit_internal(window, cx);
19382 }
19383
19384 fn open_git_blame_commit_internal(
19385 &mut self,
19386 window: &mut Window,
19387 cx: &mut Context<Self>,
19388 ) -> Option<()> {
19389 let blame = self.blame.as_ref()?;
19390 let snapshot = self.snapshot(window, cx);
19391 let cursor = self.selections.newest::<Point>(cx).head();
19392 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19393 let (_, blame_entry) = blame
19394 .update(cx, |blame, cx| {
19395 blame
19396 .blame_for_rows(
19397 &[RowInfo {
19398 buffer_id: Some(buffer.remote_id()),
19399 buffer_row: Some(point.row),
19400 ..Default::default()
19401 }],
19402 cx,
19403 )
19404 .next()
19405 })
19406 .flatten()?;
19407 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19408 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19409 let workspace = self.workspace()?.downgrade();
19410 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19411 None
19412 }
19413
19414 pub fn git_blame_inline_enabled(&self) -> bool {
19415 self.git_blame_inline_enabled
19416 }
19417
19418 pub fn toggle_selection_menu(
19419 &mut self,
19420 _: &ToggleSelectionMenu,
19421 _: &mut Window,
19422 cx: &mut Context<Self>,
19423 ) {
19424 self.show_selection_menu = self
19425 .show_selection_menu
19426 .map(|show_selections_menu| !show_selections_menu)
19427 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19428
19429 cx.notify();
19430 }
19431
19432 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19433 self.show_selection_menu
19434 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19435 }
19436
19437 fn start_git_blame(
19438 &mut self,
19439 user_triggered: bool,
19440 window: &mut Window,
19441 cx: &mut Context<Self>,
19442 ) {
19443 if let Some(project) = self.project() {
19444 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19445 && buffer.read(cx).file().is_none()
19446 {
19447 return;
19448 }
19449
19450 let focused = self.focus_handle(cx).contains_focused(window, cx);
19451
19452 let project = project.clone();
19453 let blame = cx
19454 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19455 self.blame_subscription =
19456 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19457 self.blame = Some(blame);
19458 }
19459 }
19460
19461 fn toggle_git_blame_inline_internal(
19462 &mut self,
19463 user_triggered: bool,
19464 window: &mut Window,
19465 cx: &mut Context<Self>,
19466 ) {
19467 if self.git_blame_inline_enabled {
19468 self.git_blame_inline_enabled = false;
19469 self.show_git_blame_inline = false;
19470 self.show_git_blame_inline_delay_task.take();
19471 } else {
19472 self.git_blame_inline_enabled = true;
19473 self.start_git_blame_inline(user_triggered, window, cx);
19474 }
19475
19476 cx.notify();
19477 }
19478
19479 fn start_git_blame_inline(
19480 &mut self,
19481 user_triggered: bool,
19482 window: &mut Window,
19483 cx: &mut Context<Self>,
19484 ) {
19485 self.start_git_blame(user_triggered, window, cx);
19486
19487 if ProjectSettings::get_global(cx)
19488 .git
19489 .inline_blame_delay()
19490 .is_some()
19491 {
19492 self.start_inline_blame_timer(window, cx);
19493 } else {
19494 self.show_git_blame_inline = true
19495 }
19496 }
19497
19498 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19499 self.blame.as_ref()
19500 }
19501
19502 pub fn show_git_blame_gutter(&self) -> bool {
19503 self.show_git_blame_gutter
19504 }
19505
19506 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19507 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19508 }
19509
19510 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19511 self.show_git_blame_inline
19512 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19513 && !self.newest_selection_head_on_empty_line(cx)
19514 && self.has_blame_entries(cx)
19515 }
19516
19517 fn has_blame_entries(&self, cx: &App) -> bool {
19518 self.blame()
19519 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19520 }
19521
19522 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19523 let cursor_anchor = self.selections.newest_anchor().head();
19524
19525 let snapshot = self.buffer.read(cx).snapshot(cx);
19526 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19527
19528 snapshot.line_len(buffer_row) == 0
19529 }
19530
19531 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19532 let buffer_and_selection = maybe!({
19533 let selection = self.selections.newest::<Point>(cx);
19534 let selection_range = selection.range();
19535
19536 let multi_buffer = self.buffer().read(cx);
19537 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19538 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19539
19540 let (buffer, range, _) = if selection.reversed {
19541 buffer_ranges.first()
19542 } else {
19543 buffer_ranges.last()
19544 }?;
19545
19546 let selection = text::ToPoint::to_point(&range.start, buffer).row
19547 ..text::ToPoint::to_point(&range.end, buffer).row;
19548 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19549 });
19550
19551 let Some((buffer, selection)) = buffer_and_selection else {
19552 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19553 };
19554
19555 let Some(project) = self.project() else {
19556 return Task::ready(Err(anyhow!("editor does not have project")));
19557 };
19558
19559 project.update(cx, |project, cx| {
19560 project.get_permalink_to_line(&buffer, selection, cx)
19561 })
19562 }
19563
19564 pub fn copy_permalink_to_line(
19565 &mut self,
19566 _: &CopyPermalinkToLine,
19567 window: &mut Window,
19568 cx: &mut Context<Self>,
19569 ) {
19570 let permalink_task = self.get_permalink_to_line(cx);
19571 let workspace = self.workspace();
19572
19573 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19574 Ok(permalink) => {
19575 cx.update(|_, cx| {
19576 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19577 })
19578 .ok();
19579 }
19580 Err(err) => {
19581 let message = format!("Failed to copy permalink: {err}");
19582
19583 anyhow::Result::<()>::Err(err).log_err();
19584
19585 if let Some(workspace) = workspace {
19586 workspace
19587 .update_in(cx, |workspace, _, cx| {
19588 struct CopyPermalinkToLine;
19589
19590 workspace.show_toast(
19591 Toast::new(
19592 NotificationId::unique::<CopyPermalinkToLine>(),
19593 message,
19594 ),
19595 cx,
19596 )
19597 })
19598 .ok();
19599 }
19600 }
19601 })
19602 .detach();
19603 }
19604
19605 pub fn copy_file_location(
19606 &mut self,
19607 _: &CopyFileLocation,
19608 _: &mut Window,
19609 cx: &mut Context<Self>,
19610 ) {
19611 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19612 if let Some(file) = self.target_file(cx)
19613 && let Some(path) = file.path().to_str()
19614 {
19615 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19616 }
19617 }
19618
19619 pub fn open_permalink_to_line(
19620 &mut self,
19621 _: &OpenPermalinkToLine,
19622 window: &mut Window,
19623 cx: &mut Context<Self>,
19624 ) {
19625 let permalink_task = self.get_permalink_to_line(cx);
19626 let workspace = self.workspace();
19627
19628 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19629 Ok(permalink) => {
19630 cx.update(|_, cx| {
19631 cx.open_url(permalink.as_ref());
19632 })
19633 .ok();
19634 }
19635 Err(err) => {
19636 let message = format!("Failed to open permalink: {err}");
19637
19638 anyhow::Result::<()>::Err(err).log_err();
19639
19640 if let Some(workspace) = workspace {
19641 workspace
19642 .update(cx, |workspace, cx| {
19643 struct OpenPermalinkToLine;
19644
19645 workspace.show_toast(
19646 Toast::new(
19647 NotificationId::unique::<OpenPermalinkToLine>(),
19648 message,
19649 ),
19650 cx,
19651 )
19652 })
19653 .ok();
19654 }
19655 }
19656 })
19657 .detach();
19658 }
19659
19660 pub fn insert_uuid_v4(
19661 &mut self,
19662 _: &InsertUuidV4,
19663 window: &mut Window,
19664 cx: &mut Context<Self>,
19665 ) {
19666 self.insert_uuid(UuidVersion::V4, window, cx);
19667 }
19668
19669 pub fn insert_uuid_v7(
19670 &mut self,
19671 _: &InsertUuidV7,
19672 window: &mut Window,
19673 cx: &mut Context<Self>,
19674 ) {
19675 self.insert_uuid(UuidVersion::V7, window, cx);
19676 }
19677
19678 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19679 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19680 self.transact(window, cx, |this, window, cx| {
19681 let edits = this
19682 .selections
19683 .all::<Point>(cx)
19684 .into_iter()
19685 .map(|selection| {
19686 let uuid = match version {
19687 UuidVersion::V4 => uuid::Uuid::new_v4(),
19688 UuidVersion::V7 => uuid::Uuid::now_v7(),
19689 };
19690
19691 (selection.range(), uuid.to_string())
19692 });
19693 this.edit(edits, cx);
19694 this.refresh_edit_prediction(true, false, window, cx);
19695 });
19696 }
19697
19698 pub fn open_selections_in_multibuffer(
19699 &mut self,
19700 _: &OpenSelectionsInMultibuffer,
19701 window: &mut Window,
19702 cx: &mut Context<Self>,
19703 ) {
19704 let multibuffer = self.buffer.read(cx);
19705
19706 let Some(buffer) = multibuffer.as_singleton() else {
19707 return;
19708 };
19709
19710 let Some(workspace) = self.workspace() else {
19711 return;
19712 };
19713
19714 let title = multibuffer.title(cx).to_string();
19715
19716 let locations = self
19717 .selections
19718 .all_anchors(cx)
19719 .iter()
19720 .map(|selection| Location {
19721 buffer: buffer.clone(),
19722 range: selection.start.text_anchor..selection.end.text_anchor,
19723 })
19724 .collect::<Vec<_>>();
19725
19726 cx.spawn_in(window, async move |_, cx| {
19727 workspace.update_in(cx, |workspace, window, cx| {
19728 Self::open_locations_in_multibuffer(
19729 workspace,
19730 locations,
19731 format!("Selections for '{title}'"),
19732 false,
19733 MultibufferSelectionMode::All,
19734 window,
19735 cx,
19736 );
19737 })
19738 })
19739 .detach();
19740 }
19741
19742 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19743 /// last highlight added will be used.
19744 ///
19745 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19746 pub fn highlight_rows<T: 'static>(
19747 &mut self,
19748 range: Range<Anchor>,
19749 color: Hsla,
19750 options: RowHighlightOptions,
19751 cx: &mut Context<Self>,
19752 ) {
19753 let snapshot = self.buffer().read(cx).snapshot(cx);
19754 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19755 let ix = row_highlights.binary_search_by(|highlight| {
19756 Ordering::Equal
19757 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19758 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19759 });
19760
19761 if let Err(mut ix) = ix {
19762 let index = post_inc(&mut self.highlight_order);
19763
19764 // If this range intersects with the preceding highlight, then merge it with
19765 // the preceding highlight. Otherwise insert a new highlight.
19766 let mut merged = false;
19767 if ix > 0 {
19768 let prev_highlight = &mut row_highlights[ix - 1];
19769 if prev_highlight
19770 .range
19771 .end
19772 .cmp(&range.start, &snapshot)
19773 .is_ge()
19774 {
19775 ix -= 1;
19776 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19777 prev_highlight.range.end = range.end;
19778 }
19779 merged = true;
19780 prev_highlight.index = index;
19781 prev_highlight.color = color;
19782 prev_highlight.options = options;
19783 }
19784 }
19785
19786 if !merged {
19787 row_highlights.insert(
19788 ix,
19789 RowHighlight {
19790 range,
19791 index,
19792 color,
19793 options,
19794 type_id: TypeId::of::<T>(),
19795 },
19796 );
19797 }
19798
19799 // If any of the following highlights intersect with this one, merge them.
19800 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19801 let highlight = &row_highlights[ix];
19802 if next_highlight
19803 .range
19804 .start
19805 .cmp(&highlight.range.end, &snapshot)
19806 .is_le()
19807 {
19808 if next_highlight
19809 .range
19810 .end
19811 .cmp(&highlight.range.end, &snapshot)
19812 .is_gt()
19813 {
19814 row_highlights[ix].range.end = next_highlight.range.end;
19815 }
19816 row_highlights.remove(ix + 1);
19817 } else {
19818 break;
19819 }
19820 }
19821 }
19822 }
19823
19824 /// Remove any highlighted row ranges of the given type that intersect the
19825 /// given ranges.
19826 pub fn remove_highlighted_rows<T: 'static>(
19827 &mut self,
19828 ranges_to_remove: Vec<Range<Anchor>>,
19829 cx: &mut Context<Self>,
19830 ) {
19831 let snapshot = self.buffer().read(cx).snapshot(cx);
19832 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19833 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19834 row_highlights.retain(|highlight| {
19835 while let Some(range_to_remove) = ranges_to_remove.peek() {
19836 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19837 Ordering::Less | Ordering::Equal => {
19838 ranges_to_remove.next();
19839 }
19840 Ordering::Greater => {
19841 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19842 Ordering::Less | Ordering::Equal => {
19843 return false;
19844 }
19845 Ordering::Greater => break,
19846 }
19847 }
19848 }
19849 }
19850
19851 true
19852 })
19853 }
19854
19855 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19856 pub fn clear_row_highlights<T: 'static>(&mut self) {
19857 self.highlighted_rows.remove(&TypeId::of::<T>());
19858 }
19859
19860 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19861 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19862 self.highlighted_rows
19863 .get(&TypeId::of::<T>())
19864 .map_or(&[] as &[_], |vec| vec.as_slice())
19865 .iter()
19866 .map(|highlight| (highlight.range.clone(), highlight.color))
19867 }
19868
19869 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19870 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19871 /// Allows to ignore certain kinds of highlights.
19872 pub fn highlighted_display_rows(
19873 &self,
19874 window: &mut Window,
19875 cx: &mut App,
19876 ) -> BTreeMap<DisplayRow, LineHighlight> {
19877 let snapshot = self.snapshot(window, cx);
19878 let mut used_highlight_orders = HashMap::default();
19879 self.highlighted_rows
19880 .iter()
19881 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19882 .fold(
19883 BTreeMap::<DisplayRow, LineHighlight>::new(),
19884 |mut unique_rows, highlight| {
19885 let start = highlight.range.start.to_display_point(&snapshot);
19886 let end = highlight.range.end.to_display_point(&snapshot);
19887 let start_row = start.row().0;
19888 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19889 && end.column() == 0
19890 {
19891 end.row().0.saturating_sub(1)
19892 } else {
19893 end.row().0
19894 };
19895 for row in start_row..=end_row {
19896 let used_index =
19897 used_highlight_orders.entry(row).or_insert(highlight.index);
19898 if highlight.index >= *used_index {
19899 *used_index = highlight.index;
19900 unique_rows.insert(
19901 DisplayRow(row),
19902 LineHighlight {
19903 include_gutter: highlight.options.include_gutter,
19904 border: None,
19905 background: highlight.color.into(),
19906 type_id: Some(highlight.type_id),
19907 },
19908 );
19909 }
19910 }
19911 unique_rows
19912 },
19913 )
19914 }
19915
19916 pub fn highlighted_display_row_for_autoscroll(
19917 &self,
19918 snapshot: &DisplaySnapshot,
19919 ) -> Option<DisplayRow> {
19920 self.highlighted_rows
19921 .values()
19922 .flat_map(|highlighted_rows| highlighted_rows.iter())
19923 .filter_map(|highlight| {
19924 if highlight.options.autoscroll {
19925 Some(highlight.range.start.to_display_point(snapshot).row())
19926 } else {
19927 None
19928 }
19929 })
19930 .min()
19931 }
19932
19933 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19934 self.highlight_background::<SearchWithinRange>(
19935 ranges,
19936 |colors| colors.colors().editor_document_highlight_read_background,
19937 cx,
19938 )
19939 }
19940
19941 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19942 self.breadcrumb_header = Some(new_header);
19943 }
19944
19945 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19946 self.clear_background_highlights::<SearchWithinRange>(cx);
19947 }
19948
19949 pub fn highlight_background<T: 'static>(
19950 &mut self,
19951 ranges: &[Range<Anchor>],
19952 color_fetcher: fn(&Theme) -> Hsla,
19953 cx: &mut Context<Self>,
19954 ) {
19955 self.background_highlights.insert(
19956 HighlightKey::Type(TypeId::of::<T>()),
19957 (color_fetcher, Arc::from(ranges)),
19958 );
19959 self.scrollbar_marker_state.dirty = true;
19960 cx.notify();
19961 }
19962
19963 pub fn highlight_background_key<T: 'static>(
19964 &mut self,
19965 key: usize,
19966 ranges: &[Range<Anchor>],
19967 color_fetcher: fn(&Theme) -> Hsla,
19968 cx: &mut Context<Self>,
19969 ) {
19970 self.background_highlights.insert(
19971 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19972 (color_fetcher, Arc::from(ranges)),
19973 );
19974 self.scrollbar_marker_state.dirty = true;
19975 cx.notify();
19976 }
19977
19978 pub fn clear_background_highlights<T: 'static>(
19979 &mut self,
19980 cx: &mut Context<Self>,
19981 ) -> Option<BackgroundHighlight> {
19982 let text_highlights = self
19983 .background_highlights
19984 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19985 if !text_highlights.1.is_empty() {
19986 self.scrollbar_marker_state.dirty = true;
19987 cx.notify();
19988 }
19989 Some(text_highlights)
19990 }
19991
19992 pub fn highlight_gutter<T: 'static>(
19993 &mut self,
19994 ranges: impl Into<Vec<Range<Anchor>>>,
19995 color_fetcher: fn(&App) -> Hsla,
19996 cx: &mut Context<Self>,
19997 ) {
19998 self.gutter_highlights
19999 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20000 cx.notify();
20001 }
20002
20003 pub fn clear_gutter_highlights<T: 'static>(
20004 &mut self,
20005 cx: &mut Context<Self>,
20006 ) -> Option<GutterHighlight> {
20007 cx.notify();
20008 self.gutter_highlights.remove(&TypeId::of::<T>())
20009 }
20010
20011 pub fn insert_gutter_highlight<T: 'static>(
20012 &mut self,
20013 range: Range<Anchor>,
20014 color_fetcher: fn(&App) -> Hsla,
20015 cx: &mut Context<Self>,
20016 ) {
20017 let snapshot = self.buffer().read(cx).snapshot(cx);
20018 let mut highlights = self
20019 .gutter_highlights
20020 .remove(&TypeId::of::<T>())
20021 .map(|(_, highlights)| highlights)
20022 .unwrap_or_default();
20023 let ix = highlights.binary_search_by(|highlight| {
20024 Ordering::Equal
20025 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20026 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20027 });
20028 if let Err(ix) = ix {
20029 highlights.insert(ix, range);
20030 }
20031 self.gutter_highlights
20032 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20033 }
20034
20035 pub fn remove_gutter_highlights<T: 'static>(
20036 &mut self,
20037 ranges_to_remove: Vec<Range<Anchor>>,
20038 cx: &mut Context<Self>,
20039 ) {
20040 let snapshot = self.buffer().read(cx).snapshot(cx);
20041 let Some((color_fetcher, mut gutter_highlights)) =
20042 self.gutter_highlights.remove(&TypeId::of::<T>())
20043 else {
20044 return;
20045 };
20046 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20047 gutter_highlights.retain(|highlight| {
20048 while let Some(range_to_remove) = ranges_to_remove.peek() {
20049 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20050 Ordering::Less | Ordering::Equal => {
20051 ranges_to_remove.next();
20052 }
20053 Ordering::Greater => {
20054 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20055 Ordering::Less | Ordering::Equal => {
20056 return false;
20057 }
20058 Ordering::Greater => break,
20059 }
20060 }
20061 }
20062 }
20063
20064 true
20065 });
20066 self.gutter_highlights
20067 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20068 }
20069
20070 #[cfg(feature = "test-support")]
20071 pub fn all_text_highlights(
20072 &self,
20073 window: &mut Window,
20074 cx: &mut Context<Self>,
20075 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20076 let snapshot = self.snapshot(window, cx);
20077 self.display_map.update(cx, |display_map, _| {
20078 display_map
20079 .all_text_highlights()
20080 .map(|highlight| {
20081 let (style, ranges) = highlight.as_ref();
20082 (
20083 *style,
20084 ranges
20085 .iter()
20086 .map(|range| range.clone().to_display_points(&snapshot))
20087 .collect(),
20088 )
20089 })
20090 .collect()
20091 })
20092 }
20093
20094 #[cfg(feature = "test-support")]
20095 pub fn all_text_background_highlights(
20096 &self,
20097 window: &mut Window,
20098 cx: &mut Context<Self>,
20099 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20100 let snapshot = self.snapshot(window, cx);
20101 let buffer = &snapshot.buffer_snapshot;
20102 let start = buffer.anchor_before(0);
20103 let end = buffer.anchor_after(buffer.len());
20104 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20105 }
20106
20107 #[cfg(any(test, feature = "test-support"))]
20108 pub fn sorted_background_highlights_in_range(
20109 &self,
20110 search_range: Range<Anchor>,
20111 display_snapshot: &DisplaySnapshot,
20112 theme: &Theme,
20113 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20114 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20115 res.sort_by(|a, b| {
20116 a.0.start
20117 .cmp(&b.0.start)
20118 .then_with(|| a.0.end.cmp(&b.0.end))
20119 .then_with(|| a.1.cmp(&b.1))
20120 });
20121 res
20122 }
20123
20124 #[cfg(feature = "test-support")]
20125 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20126 let snapshot = self.buffer().read(cx).snapshot(cx);
20127
20128 let highlights = self
20129 .background_highlights
20130 .get(&HighlightKey::Type(TypeId::of::<
20131 items::BufferSearchHighlights,
20132 >()));
20133
20134 if let Some((_color, ranges)) = highlights {
20135 ranges
20136 .iter()
20137 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20138 .collect_vec()
20139 } else {
20140 vec![]
20141 }
20142 }
20143
20144 fn document_highlights_for_position<'a>(
20145 &'a self,
20146 position: Anchor,
20147 buffer: &'a MultiBufferSnapshot,
20148 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20149 let read_highlights = self
20150 .background_highlights
20151 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20152 .map(|h| &h.1);
20153 let write_highlights = self
20154 .background_highlights
20155 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20156 .map(|h| &h.1);
20157 let left_position = position.bias_left(buffer);
20158 let right_position = position.bias_right(buffer);
20159 read_highlights
20160 .into_iter()
20161 .chain(write_highlights)
20162 .flat_map(move |ranges| {
20163 let start_ix = match ranges.binary_search_by(|probe| {
20164 let cmp = probe.end.cmp(&left_position, buffer);
20165 if cmp.is_ge() {
20166 Ordering::Greater
20167 } else {
20168 Ordering::Less
20169 }
20170 }) {
20171 Ok(i) | Err(i) => i,
20172 };
20173
20174 ranges[start_ix..]
20175 .iter()
20176 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20177 })
20178 }
20179
20180 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20181 self.background_highlights
20182 .get(&HighlightKey::Type(TypeId::of::<T>()))
20183 .is_some_and(|(_, highlights)| !highlights.is_empty())
20184 }
20185
20186 /// Returns all background highlights for a given range.
20187 ///
20188 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20189 pub fn background_highlights_in_range(
20190 &self,
20191 search_range: Range<Anchor>,
20192 display_snapshot: &DisplaySnapshot,
20193 theme: &Theme,
20194 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20195 let mut results = Vec::new();
20196 for (color_fetcher, ranges) in self.background_highlights.values() {
20197 let color = color_fetcher(theme);
20198 let start_ix = match ranges.binary_search_by(|probe| {
20199 let cmp = probe
20200 .end
20201 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20202 if cmp.is_gt() {
20203 Ordering::Greater
20204 } else {
20205 Ordering::Less
20206 }
20207 }) {
20208 Ok(i) | Err(i) => i,
20209 };
20210 for range in &ranges[start_ix..] {
20211 if range
20212 .start
20213 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20214 .is_ge()
20215 {
20216 break;
20217 }
20218
20219 let start = range.start.to_display_point(display_snapshot);
20220 let end = range.end.to_display_point(display_snapshot);
20221 results.push((start..end, color))
20222 }
20223 }
20224 results
20225 }
20226
20227 pub fn gutter_highlights_in_range(
20228 &self,
20229 search_range: Range<Anchor>,
20230 display_snapshot: &DisplaySnapshot,
20231 cx: &App,
20232 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20233 let mut results = Vec::new();
20234 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20235 let color = color_fetcher(cx);
20236 let start_ix = match ranges.binary_search_by(|probe| {
20237 let cmp = probe
20238 .end
20239 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20240 if cmp.is_gt() {
20241 Ordering::Greater
20242 } else {
20243 Ordering::Less
20244 }
20245 }) {
20246 Ok(i) | Err(i) => i,
20247 };
20248 for range in &ranges[start_ix..] {
20249 if range
20250 .start
20251 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20252 .is_ge()
20253 {
20254 break;
20255 }
20256
20257 let start = range.start.to_display_point(display_snapshot);
20258 let end = range.end.to_display_point(display_snapshot);
20259 results.push((start..end, color))
20260 }
20261 }
20262 results
20263 }
20264
20265 /// Get the text ranges corresponding to the redaction query
20266 pub fn redacted_ranges(
20267 &self,
20268 search_range: Range<Anchor>,
20269 display_snapshot: &DisplaySnapshot,
20270 cx: &App,
20271 ) -> Vec<Range<DisplayPoint>> {
20272 display_snapshot
20273 .buffer_snapshot
20274 .redacted_ranges(search_range, |file| {
20275 if let Some(file) = file {
20276 file.is_private()
20277 && EditorSettings::get(
20278 Some(SettingsLocation {
20279 worktree_id: file.worktree_id(cx),
20280 path: file.path().as_ref(),
20281 }),
20282 cx,
20283 )
20284 .redact_private_values
20285 } else {
20286 false
20287 }
20288 })
20289 .map(|range| {
20290 range.start.to_display_point(display_snapshot)
20291 ..range.end.to_display_point(display_snapshot)
20292 })
20293 .collect()
20294 }
20295
20296 pub fn highlight_text_key<T: 'static>(
20297 &mut self,
20298 key: usize,
20299 ranges: Vec<Range<Anchor>>,
20300 style: HighlightStyle,
20301 cx: &mut Context<Self>,
20302 ) {
20303 self.display_map.update(cx, |map, _| {
20304 map.highlight_text(
20305 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20306 ranges,
20307 style,
20308 );
20309 });
20310 cx.notify();
20311 }
20312
20313 pub fn highlight_text<T: 'static>(
20314 &mut self,
20315 ranges: Vec<Range<Anchor>>,
20316 style: HighlightStyle,
20317 cx: &mut Context<Self>,
20318 ) {
20319 self.display_map.update(cx, |map, _| {
20320 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20321 });
20322 cx.notify();
20323 }
20324
20325 pub(crate) fn highlight_inlays<T: 'static>(
20326 &mut self,
20327 highlights: Vec<InlayHighlight>,
20328 style: HighlightStyle,
20329 cx: &mut Context<Self>,
20330 ) {
20331 self.display_map.update(cx, |map, _| {
20332 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20333 });
20334 cx.notify();
20335 }
20336
20337 pub fn text_highlights<'a, T: 'static>(
20338 &'a self,
20339 cx: &'a App,
20340 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20341 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20342 }
20343
20344 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20345 let cleared = self
20346 .display_map
20347 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20348 if cleared {
20349 cx.notify();
20350 }
20351 }
20352
20353 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20354 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20355 && self.focus_handle.is_focused(window)
20356 }
20357
20358 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20359 self.show_cursor_when_unfocused = is_enabled;
20360 cx.notify();
20361 }
20362
20363 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20364 cx.notify();
20365 }
20366
20367 fn on_debug_session_event(
20368 &mut self,
20369 _session: Entity<Session>,
20370 event: &SessionEvent,
20371 cx: &mut Context<Self>,
20372 ) {
20373 if let SessionEvent::InvalidateInlineValue = event {
20374 self.refresh_inline_values(cx);
20375 }
20376 }
20377
20378 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20379 let Some(project) = self.project.clone() else {
20380 return;
20381 };
20382
20383 if !self.inline_value_cache.enabled {
20384 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20385 self.splice_inlays(&inlays, Vec::new(), cx);
20386 return;
20387 }
20388
20389 let current_execution_position = self
20390 .highlighted_rows
20391 .get(&TypeId::of::<ActiveDebugLine>())
20392 .and_then(|lines| lines.last().map(|line| line.range.end));
20393
20394 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20395 let inline_values = editor
20396 .update(cx, |editor, cx| {
20397 let Some(current_execution_position) = current_execution_position else {
20398 return Some(Task::ready(Ok(Vec::new())));
20399 };
20400
20401 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20402 let snapshot = buffer.snapshot(cx);
20403
20404 let excerpt = snapshot.excerpt_containing(
20405 current_execution_position..current_execution_position,
20406 )?;
20407
20408 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20409 })?;
20410
20411 let range =
20412 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20413
20414 project.inline_values(buffer, range, cx)
20415 })
20416 .ok()
20417 .flatten()?
20418 .await
20419 .context("refreshing debugger inlays")
20420 .log_err()?;
20421
20422 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20423
20424 for (buffer_id, inline_value) in inline_values
20425 .into_iter()
20426 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20427 {
20428 buffer_inline_values
20429 .entry(buffer_id)
20430 .or_default()
20431 .push(inline_value);
20432 }
20433
20434 editor
20435 .update(cx, |editor, cx| {
20436 let snapshot = editor.buffer.read(cx).snapshot(cx);
20437 let mut new_inlays = Vec::default();
20438
20439 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20440 let buffer_id = buffer_snapshot.remote_id();
20441 buffer_inline_values
20442 .get(&buffer_id)
20443 .into_iter()
20444 .flatten()
20445 .for_each(|hint| {
20446 let inlay = Inlay::debugger(
20447 post_inc(&mut editor.next_inlay_id),
20448 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20449 hint.text(),
20450 );
20451 if !inlay.text.chars().contains(&'\n') {
20452 new_inlays.push(inlay);
20453 }
20454 });
20455 }
20456
20457 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20458 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20459
20460 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20461 })
20462 .ok()?;
20463 Some(())
20464 });
20465 }
20466
20467 fn on_buffer_event(
20468 &mut self,
20469 multibuffer: &Entity<MultiBuffer>,
20470 event: &multi_buffer::Event,
20471 window: &mut Window,
20472 cx: &mut Context<Self>,
20473 ) {
20474 match event {
20475 multi_buffer::Event::Edited {
20476 singleton_buffer_edited,
20477 edited_buffer,
20478 } => {
20479 self.scrollbar_marker_state.dirty = true;
20480 self.active_indent_guides_state.dirty = true;
20481 self.refresh_active_diagnostics(cx);
20482 self.refresh_code_actions(window, cx);
20483 self.refresh_selected_text_highlights(true, window, cx);
20484 self.refresh_single_line_folds(window, cx);
20485 refresh_matching_bracket_highlights(self, window, cx);
20486 if self.has_active_edit_prediction() {
20487 self.update_visible_edit_prediction(window, cx);
20488 }
20489 if let Some(project) = self.project.as_ref()
20490 && let Some(edited_buffer) = edited_buffer
20491 {
20492 project.update(cx, |project, cx| {
20493 self.registered_buffers
20494 .entry(edited_buffer.read(cx).remote_id())
20495 .or_insert_with(|| {
20496 project.register_buffer_with_language_servers(edited_buffer, cx)
20497 });
20498 });
20499 }
20500 cx.emit(EditorEvent::BufferEdited);
20501 cx.emit(SearchEvent::MatchesInvalidated);
20502
20503 if let Some(buffer) = edited_buffer {
20504 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20505 }
20506
20507 if *singleton_buffer_edited {
20508 if let Some(buffer) = edited_buffer
20509 && buffer.read(cx).file().is_none()
20510 {
20511 cx.emit(EditorEvent::TitleChanged);
20512 }
20513 if let Some(project) = &self.project {
20514 #[allow(clippy::mutable_key_type)]
20515 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20516 multibuffer
20517 .all_buffers()
20518 .into_iter()
20519 .filter_map(|buffer| {
20520 buffer.update(cx, |buffer, cx| {
20521 let language = buffer.language()?;
20522 let should_discard = project.update(cx, |project, cx| {
20523 project.is_local()
20524 && !project.has_language_servers_for(buffer, cx)
20525 });
20526 should_discard.not().then_some(language.clone())
20527 })
20528 })
20529 .collect::<HashSet<_>>()
20530 });
20531 if !languages_affected.is_empty() {
20532 self.refresh_inlay_hints(
20533 InlayHintRefreshReason::BufferEdited(languages_affected),
20534 cx,
20535 );
20536 }
20537 }
20538 }
20539
20540 let Some(project) = &self.project else { return };
20541 let (telemetry, is_via_ssh) = {
20542 let project = project.read(cx);
20543 let telemetry = project.client().telemetry().clone();
20544 let is_via_ssh = project.is_via_remote_server();
20545 (telemetry, is_via_ssh)
20546 };
20547 refresh_linked_ranges(self, window, cx);
20548 telemetry.log_edit_event("editor", is_via_ssh);
20549 }
20550 multi_buffer::Event::ExcerptsAdded {
20551 buffer,
20552 predecessor,
20553 excerpts,
20554 } => {
20555 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20556 let buffer_id = buffer.read(cx).remote_id();
20557 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20558 && let Some(project) = &self.project
20559 {
20560 update_uncommitted_diff_for_buffer(
20561 cx.entity(),
20562 project,
20563 [buffer.clone()],
20564 self.buffer.clone(),
20565 cx,
20566 )
20567 .detach();
20568 }
20569 if self.active_diagnostics != ActiveDiagnostic::All {
20570 self.update_lsp_data(false, Some(buffer_id), window, cx);
20571 }
20572 cx.emit(EditorEvent::ExcerptsAdded {
20573 buffer: buffer.clone(),
20574 predecessor: *predecessor,
20575 excerpts: excerpts.clone(),
20576 });
20577 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20578 }
20579 multi_buffer::Event::ExcerptsRemoved {
20580 ids,
20581 removed_buffer_ids,
20582 } => {
20583 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20584 let buffer = self.buffer.read(cx);
20585 self.registered_buffers
20586 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20587 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20588 cx.emit(EditorEvent::ExcerptsRemoved {
20589 ids: ids.clone(),
20590 removed_buffer_ids: removed_buffer_ids.clone(),
20591 });
20592 }
20593 multi_buffer::Event::ExcerptsEdited {
20594 excerpt_ids,
20595 buffer_ids,
20596 } => {
20597 self.display_map.update(cx, |map, cx| {
20598 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20599 });
20600 cx.emit(EditorEvent::ExcerptsEdited {
20601 ids: excerpt_ids.clone(),
20602 });
20603 }
20604 multi_buffer::Event::ExcerptsExpanded { ids } => {
20605 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20606 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20607 }
20608 multi_buffer::Event::Reparsed(buffer_id) => {
20609 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20610 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20611
20612 cx.emit(EditorEvent::Reparsed(*buffer_id));
20613 }
20614 multi_buffer::Event::DiffHunksToggled => {
20615 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20616 }
20617 multi_buffer::Event::LanguageChanged(buffer_id) => {
20618 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20619 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20620 cx.emit(EditorEvent::Reparsed(*buffer_id));
20621 cx.notify();
20622 }
20623 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20624 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20625 multi_buffer::Event::FileHandleChanged
20626 | multi_buffer::Event::Reloaded
20627 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20628 multi_buffer::Event::DiagnosticsUpdated => {
20629 self.update_diagnostics_state(window, cx);
20630 }
20631 _ => {}
20632 };
20633 }
20634
20635 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20636 if !self.diagnostics_enabled() {
20637 return;
20638 }
20639 self.refresh_active_diagnostics(cx);
20640 self.refresh_inline_diagnostics(true, window, cx);
20641 self.scrollbar_marker_state.dirty = true;
20642 cx.notify();
20643 }
20644
20645 pub fn start_temporary_diff_override(&mut self) {
20646 self.load_diff_task.take();
20647 self.temporary_diff_override = true;
20648 }
20649
20650 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20651 self.temporary_diff_override = false;
20652 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20653 self.buffer.update(cx, |buffer, cx| {
20654 buffer.set_all_diff_hunks_collapsed(cx);
20655 });
20656
20657 if let Some(project) = self.project.clone() {
20658 self.load_diff_task = Some(
20659 update_uncommitted_diff_for_buffer(
20660 cx.entity(),
20661 &project,
20662 self.buffer.read(cx).all_buffers(),
20663 self.buffer.clone(),
20664 cx,
20665 )
20666 .shared(),
20667 );
20668 }
20669 }
20670
20671 fn on_display_map_changed(
20672 &mut self,
20673 _: Entity<DisplayMap>,
20674 _: &mut Window,
20675 cx: &mut Context<Self>,
20676 ) {
20677 cx.notify();
20678 }
20679
20680 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20681 if self.diagnostics_enabled() {
20682 let new_severity = EditorSettings::get_global(cx)
20683 .diagnostics_max_severity
20684 .unwrap_or(DiagnosticSeverity::Hint);
20685 self.set_max_diagnostics_severity(new_severity, cx);
20686 }
20687 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20688 self.update_edit_prediction_settings(cx);
20689 self.refresh_edit_prediction(true, false, window, cx);
20690 self.refresh_inline_values(cx);
20691 self.refresh_inlay_hints(
20692 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20693 self.selections.newest_anchor().head(),
20694 &self.buffer.read(cx).snapshot(cx),
20695 cx,
20696 )),
20697 cx,
20698 );
20699
20700 let old_cursor_shape = self.cursor_shape;
20701 let old_show_breadcrumbs = self.show_breadcrumbs;
20702
20703 {
20704 let editor_settings = EditorSettings::get_global(cx);
20705 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20706 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20707 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20708 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20709 }
20710
20711 if old_cursor_shape != self.cursor_shape {
20712 cx.emit(EditorEvent::CursorShapeChanged);
20713 }
20714
20715 if old_show_breadcrumbs != self.show_breadcrumbs {
20716 cx.emit(EditorEvent::BreadcrumbsChanged);
20717 }
20718
20719 let project_settings = ProjectSettings::get_global(cx);
20720 self.serialize_dirty_buffers =
20721 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20722
20723 if self.mode.is_full() {
20724 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20725 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20726 if self.show_inline_diagnostics != show_inline_diagnostics {
20727 self.show_inline_diagnostics = show_inline_diagnostics;
20728 self.refresh_inline_diagnostics(false, window, cx);
20729 }
20730
20731 if self.git_blame_inline_enabled != inline_blame_enabled {
20732 self.toggle_git_blame_inline_internal(false, window, cx);
20733 }
20734
20735 let minimap_settings = EditorSettings::get_global(cx).minimap;
20736 if self.minimap_visibility != MinimapVisibility::Disabled {
20737 if self.minimap_visibility.settings_visibility()
20738 != minimap_settings.minimap_enabled()
20739 {
20740 self.set_minimap_visibility(
20741 MinimapVisibility::for_mode(self.mode(), cx),
20742 window,
20743 cx,
20744 );
20745 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20746 minimap_entity.update(cx, |minimap_editor, cx| {
20747 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20748 })
20749 }
20750 }
20751 }
20752
20753 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20754 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20755 }) {
20756 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20757 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20758 }
20759 self.refresh_colors(false, None, window, cx);
20760 }
20761
20762 cx.notify();
20763 }
20764
20765 pub fn set_searchable(&mut self, searchable: bool) {
20766 self.searchable = searchable;
20767 }
20768
20769 pub fn searchable(&self) -> bool {
20770 self.searchable
20771 }
20772
20773 fn open_proposed_changes_editor(
20774 &mut self,
20775 _: &OpenProposedChangesEditor,
20776 window: &mut Window,
20777 cx: &mut Context<Self>,
20778 ) {
20779 let Some(workspace) = self.workspace() else {
20780 cx.propagate();
20781 return;
20782 };
20783
20784 let selections = self.selections.all::<usize>(cx);
20785 let multi_buffer = self.buffer.read(cx);
20786 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20787 let mut new_selections_by_buffer = HashMap::default();
20788 for selection in selections {
20789 for (buffer, range, _) in
20790 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20791 {
20792 let mut range = range.to_point(buffer);
20793 range.start.column = 0;
20794 range.end.column = buffer.line_len(range.end.row);
20795 new_selections_by_buffer
20796 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20797 .or_insert(Vec::new())
20798 .push(range)
20799 }
20800 }
20801
20802 let proposed_changes_buffers = new_selections_by_buffer
20803 .into_iter()
20804 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20805 .collect::<Vec<_>>();
20806 let proposed_changes_editor = cx.new(|cx| {
20807 ProposedChangesEditor::new(
20808 "Proposed changes",
20809 proposed_changes_buffers,
20810 self.project.clone(),
20811 window,
20812 cx,
20813 )
20814 });
20815
20816 window.defer(cx, move |window, cx| {
20817 workspace.update(cx, |workspace, cx| {
20818 workspace.active_pane().update(cx, |pane, cx| {
20819 pane.add_item(
20820 Box::new(proposed_changes_editor),
20821 true,
20822 true,
20823 None,
20824 window,
20825 cx,
20826 );
20827 });
20828 });
20829 });
20830 }
20831
20832 pub fn open_excerpts_in_split(
20833 &mut self,
20834 _: &OpenExcerptsSplit,
20835 window: &mut Window,
20836 cx: &mut Context<Self>,
20837 ) {
20838 self.open_excerpts_common(None, true, window, cx)
20839 }
20840
20841 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20842 self.open_excerpts_common(None, false, window, cx)
20843 }
20844
20845 fn open_excerpts_common(
20846 &mut self,
20847 jump_data: Option<JumpData>,
20848 split: bool,
20849 window: &mut Window,
20850 cx: &mut Context<Self>,
20851 ) {
20852 let Some(workspace) = self.workspace() else {
20853 cx.propagate();
20854 return;
20855 };
20856
20857 if self.buffer.read(cx).is_singleton() {
20858 cx.propagate();
20859 return;
20860 }
20861
20862 let mut new_selections_by_buffer = HashMap::default();
20863 match &jump_data {
20864 Some(JumpData::MultiBufferPoint {
20865 excerpt_id,
20866 position,
20867 anchor,
20868 line_offset_from_top,
20869 }) => {
20870 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20871 if let Some(buffer) = multi_buffer_snapshot
20872 .buffer_id_for_excerpt(*excerpt_id)
20873 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20874 {
20875 let buffer_snapshot = buffer.read(cx).snapshot();
20876 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20877 language::ToPoint::to_point(anchor, &buffer_snapshot)
20878 } else {
20879 buffer_snapshot.clip_point(*position, Bias::Left)
20880 };
20881 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20882 new_selections_by_buffer.insert(
20883 buffer,
20884 (
20885 vec![jump_to_offset..jump_to_offset],
20886 Some(*line_offset_from_top),
20887 ),
20888 );
20889 }
20890 }
20891 Some(JumpData::MultiBufferRow {
20892 row,
20893 line_offset_from_top,
20894 }) => {
20895 let point = MultiBufferPoint::new(row.0, 0);
20896 if let Some((buffer, buffer_point, _)) =
20897 self.buffer.read(cx).point_to_buffer_point(point, cx)
20898 {
20899 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20900 new_selections_by_buffer
20901 .entry(buffer)
20902 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20903 .0
20904 .push(buffer_offset..buffer_offset)
20905 }
20906 }
20907 None => {
20908 let selections = self.selections.all::<usize>(cx);
20909 let multi_buffer = self.buffer.read(cx);
20910 for selection in selections {
20911 for (snapshot, range, _, anchor) in multi_buffer
20912 .snapshot(cx)
20913 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20914 {
20915 if let Some(anchor) = anchor {
20916 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20917 else {
20918 continue;
20919 };
20920 let offset = text::ToOffset::to_offset(
20921 &anchor.text_anchor,
20922 &buffer_handle.read(cx).snapshot(),
20923 );
20924 let range = offset..offset;
20925 new_selections_by_buffer
20926 .entry(buffer_handle)
20927 .or_insert((Vec::new(), None))
20928 .0
20929 .push(range)
20930 } else {
20931 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20932 else {
20933 continue;
20934 };
20935 new_selections_by_buffer
20936 .entry(buffer_handle)
20937 .or_insert((Vec::new(), None))
20938 .0
20939 .push(range)
20940 }
20941 }
20942 }
20943 }
20944 }
20945
20946 new_selections_by_buffer
20947 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20948
20949 if new_selections_by_buffer.is_empty() {
20950 return;
20951 }
20952
20953 // We defer the pane interaction because we ourselves are a workspace item
20954 // and activating a new item causes the pane to call a method on us reentrantly,
20955 // which panics if we're on the stack.
20956 window.defer(cx, move |window, cx| {
20957 workspace.update(cx, |workspace, cx| {
20958 let pane = if split {
20959 workspace.adjacent_pane(window, cx)
20960 } else {
20961 workspace.active_pane().clone()
20962 };
20963
20964 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20965 let editor = buffer
20966 .read(cx)
20967 .file()
20968 .is_none()
20969 .then(|| {
20970 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20971 // so `workspace.open_project_item` will never find them, always opening a new editor.
20972 // Instead, we try to activate the existing editor in the pane first.
20973 let (editor, pane_item_index) =
20974 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20975 let editor = item.downcast::<Editor>()?;
20976 let singleton_buffer =
20977 editor.read(cx).buffer().read(cx).as_singleton()?;
20978 if singleton_buffer == buffer {
20979 Some((editor, i))
20980 } else {
20981 None
20982 }
20983 })?;
20984 pane.update(cx, |pane, cx| {
20985 pane.activate_item(pane_item_index, true, true, window, cx)
20986 });
20987 Some(editor)
20988 })
20989 .flatten()
20990 .unwrap_or_else(|| {
20991 workspace.open_project_item::<Self>(
20992 pane.clone(),
20993 buffer,
20994 true,
20995 true,
20996 window,
20997 cx,
20998 )
20999 });
21000
21001 editor.update(cx, |editor, cx| {
21002 let autoscroll = match scroll_offset {
21003 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21004 None => Autoscroll::newest(),
21005 };
21006 let nav_history = editor.nav_history.take();
21007 editor.change_selections(
21008 SelectionEffects::scroll(autoscroll),
21009 window,
21010 cx,
21011 |s| {
21012 s.select_ranges(ranges);
21013 },
21014 );
21015 editor.nav_history = nav_history;
21016 });
21017 }
21018 })
21019 });
21020 }
21021
21022 // For now, don't allow opening excerpts in buffers that aren't backed by
21023 // regular project files.
21024 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21025 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21026 }
21027
21028 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21029 let snapshot = self.buffer.read(cx).read(cx);
21030 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21031 Some(
21032 ranges
21033 .iter()
21034 .map(move |range| {
21035 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21036 })
21037 .collect(),
21038 )
21039 }
21040
21041 fn selection_replacement_ranges(
21042 &self,
21043 range: Range<OffsetUtf16>,
21044 cx: &mut App,
21045 ) -> Vec<Range<OffsetUtf16>> {
21046 let selections = self.selections.all::<OffsetUtf16>(cx);
21047 let newest_selection = selections
21048 .iter()
21049 .max_by_key(|selection| selection.id)
21050 .unwrap();
21051 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21052 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21053 let snapshot = self.buffer.read(cx).read(cx);
21054 selections
21055 .into_iter()
21056 .map(|mut selection| {
21057 selection.start.0 =
21058 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21059 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21060 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21061 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21062 })
21063 .collect()
21064 }
21065
21066 fn report_editor_event(
21067 &self,
21068 reported_event: ReportEditorEvent,
21069 file_extension: Option<String>,
21070 cx: &App,
21071 ) {
21072 if cfg!(any(test, feature = "test-support")) {
21073 return;
21074 }
21075
21076 let Some(project) = &self.project else { return };
21077
21078 // If None, we are in a file without an extension
21079 let file = self
21080 .buffer
21081 .read(cx)
21082 .as_singleton()
21083 .and_then(|b| b.read(cx).file());
21084 let file_extension = file_extension.or(file
21085 .as_ref()
21086 .and_then(|file| Path::new(file.file_name(cx)).extension())
21087 .and_then(|e| e.to_str())
21088 .map(|a| a.to_string()));
21089
21090 let vim_mode = vim_enabled(cx);
21091
21092 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21093 let copilot_enabled = edit_predictions_provider
21094 == language::language_settings::EditPredictionProvider::Copilot;
21095 let copilot_enabled_for_language = self
21096 .buffer
21097 .read(cx)
21098 .language_settings(cx)
21099 .show_edit_predictions;
21100
21101 let project = project.read(cx);
21102 let event_type = reported_event.event_type();
21103
21104 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21105 telemetry::event!(
21106 event_type,
21107 type = if auto_saved {"autosave"} else {"manual"},
21108 file_extension,
21109 vim_mode,
21110 copilot_enabled,
21111 copilot_enabled_for_language,
21112 edit_predictions_provider,
21113 is_via_ssh = project.is_via_remote_server(),
21114 );
21115 } else {
21116 telemetry::event!(
21117 event_type,
21118 file_extension,
21119 vim_mode,
21120 copilot_enabled,
21121 copilot_enabled_for_language,
21122 edit_predictions_provider,
21123 is_via_ssh = project.is_via_remote_server(),
21124 );
21125 };
21126 }
21127
21128 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21129 /// with each line being an array of {text, highlight} objects.
21130 fn copy_highlight_json(
21131 &mut self,
21132 _: &CopyHighlightJson,
21133 window: &mut Window,
21134 cx: &mut Context<Self>,
21135 ) {
21136 #[derive(Serialize)]
21137 struct Chunk<'a> {
21138 text: String,
21139 highlight: Option<&'a str>,
21140 }
21141
21142 let snapshot = self.buffer.read(cx).snapshot(cx);
21143 let range = self
21144 .selected_text_range(false, window, cx)
21145 .and_then(|selection| {
21146 if selection.range.is_empty() {
21147 None
21148 } else {
21149 Some(selection.range)
21150 }
21151 })
21152 .unwrap_or_else(|| 0..snapshot.len());
21153
21154 let chunks = snapshot.chunks(range, true);
21155 let mut lines = Vec::new();
21156 let mut line: VecDeque<Chunk> = VecDeque::new();
21157
21158 let Some(style) = self.style.as_ref() else {
21159 return;
21160 };
21161
21162 for chunk in chunks {
21163 let highlight = chunk
21164 .syntax_highlight_id
21165 .and_then(|id| id.name(&style.syntax));
21166 let mut chunk_lines = chunk.text.split('\n').peekable();
21167 while let Some(text) = chunk_lines.next() {
21168 let mut merged_with_last_token = false;
21169 if let Some(last_token) = line.back_mut()
21170 && last_token.highlight == highlight
21171 {
21172 last_token.text.push_str(text);
21173 merged_with_last_token = true;
21174 }
21175
21176 if !merged_with_last_token {
21177 line.push_back(Chunk {
21178 text: text.into(),
21179 highlight,
21180 });
21181 }
21182
21183 if chunk_lines.peek().is_some() {
21184 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21185 line.pop_front();
21186 }
21187 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21188 line.pop_back();
21189 }
21190
21191 lines.push(mem::take(&mut line));
21192 }
21193 }
21194 }
21195
21196 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21197 return;
21198 };
21199 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21200 }
21201
21202 pub fn open_context_menu(
21203 &mut self,
21204 _: &OpenContextMenu,
21205 window: &mut Window,
21206 cx: &mut Context<Self>,
21207 ) {
21208 self.request_autoscroll(Autoscroll::newest(), cx);
21209 let position = self.selections.newest_display(cx).start;
21210 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21211 }
21212
21213 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21214 &self.inlay_hint_cache
21215 }
21216
21217 pub fn replay_insert_event(
21218 &mut self,
21219 text: &str,
21220 relative_utf16_range: Option<Range<isize>>,
21221 window: &mut Window,
21222 cx: &mut Context<Self>,
21223 ) {
21224 if !self.input_enabled {
21225 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21226 return;
21227 }
21228 if let Some(relative_utf16_range) = relative_utf16_range {
21229 let selections = self.selections.all::<OffsetUtf16>(cx);
21230 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21231 let new_ranges = selections.into_iter().map(|range| {
21232 let start = OffsetUtf16(
21233 range
21234 .head()
21235 .0
21236 .saturating_add_signed(relative_utf16_range.start),
21237 );
21238 let end = OffsetUtf16(
21239 range
21240 .head()
21241 .0
21242 .saturating_add_signed(relative_utf16_range.end),
21243 );
21244 start..end
21245 });
21246 s.select_ranges(new_ranges);
21247 });
21248 }
21249
21250 self.handle_input(text, window, cx);
21251 }
21252
21253 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21254 let Some(provider) = self.semantics_provider.as_ref() else {
21255 return false;
21256 };
21257
21258 let mut supports = false;
21259 self.buffer().update(cx, |this, cx| {
21260 this.for_each_buffer(|buffer| {
21261 supports |= provider.supports_inlay_hints(buffer, cx);
21262 });
21263 });
21264
21265 supports
21266 }
21267
21268 pub fn is_focused(&self, window: &Window) -> bool {
21269 self.focus_handle.is_focused(window)
21270 }
21271
21272 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21273 cx.emit(EditorEvent::Focused);
21274
21275 if let Some(descendant) = self
21276 .last_focused_descendant
21277 .take()
21278 .and_then(|descendant| descendant.upgrade())
21279 {
21280 window.focus(&descendant);
21281 } else {
21282 if let Some(blame) = self.blame.as_ref() {
21283 blame.update(cx, GitBlame::focus)
21284 }
21285
21286 self.blink_manager.update(cx, BlinkManager::enable);
21287 self.show_cursor_names(window, cx);
21288 self.buffer.update(cx, |buffer, cx| {
21289 buffer.finalize_last_transaction(cx);
21290 if self.leader_id.is_none() {
21291 buffer.set_active_selections(
21292 &self.selections.disjoint_anchors_arc(),
21293 self.selections.line_mode,
21294 self.cursor_shape,
21295 cx,
21296 );
21297 }
21298 });
21299 }
21300 }
21301
21302 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21303 cx.emit(EditorEvent::FocusedIn)
21304 }
21305
21306 fn handle_focus_out(
21307 &mut self,
21308 event: FocusOutEvent,
21309 _window: &mut Window,
21310 cx: &mut Context<Self>,
21311 ) {
21312 if event.blurred != self.focus_handle {
21313 self.last_focused_descendant = Some(event.blurred);
21314 }
21315 self.selection_drag_state = SelectionDragState::None;
21316 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21317 }
21318
21319 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21320 self.blink_manager.update(cx, BlinkManager::disable);
21321 self.buffer
21322 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21323
21324 if let Some(blame) = self.blame.as_ref() {
21325 blame.update(cx, GitBlame::blur)
21326 }
21327 if !self.hover_state.focused(window, cx) {
21328 hide_hover(self, cx);
21329 }
21330 if !self
21331 .context_menu
21332 .borrow()
21333 .as_ref()
21334 .is_some_and(|context_menu| context_menu.focused(window, cx))
21335 {
21336 self.hide_context_menu(window, cx);
21337 }
21338 self.discard_edit_prediction(false, cx);
21339 cx.emit(EditorEvent::Blurred);
21340 cx.notify();
21341 }
21342
21343 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21344 let mut pending: String = window
21345 .pending_input_keystrokes()
21346 .into_iter()
21347 .flatten()
21348 .filter_map(|keystroke| {
21349 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21350 keystroke.key_char.clone()
21351 } else {
21352 None
21353 }
21354 })
21355 .collect();
21356
21357 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21358 pending = "".to_string();
21359 }
21360
21361 let existing_pending = self
21362 .text_highlights::<PendingInput>(cx)
21363 .map(|(_, ranges)| ranges.to_vec());
21364 if existing_pending.is_none() && pending.is_empty() {
21365 return;
21366 }
21367 let transaction =
21368 self.transact(window, cx, |this, window, cx| {
21369 let selections = this.selections.all::<usize>(cx);
21370 let edits = selections
21371 .iter()
21372 .map(|selection| (selection.end..selection.end, pending.clone()));
21373 this.edit(edits, cx);
21374 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21375 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21376 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21377 }));
21378 });
21379 if let Some(existing_ranges) = existing_pending {
21380 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21381 this.edit(edits, cx);
21382 }
21383 });
21384
21385 let snapshot = self.snapshot(window, cx);
21386 let ranges = self
21387 .selections
21388 .all::<usize>(cx)
21389 .into_iter()
21390 .map(|selection| {
21391 snapshot.buffer_snapshot.anchor_after(selection.end)
21392 ..snapshot
21393 .buffer_snapshot
21394 .anchor_before(selection.end + pending.len())
21395 })
21396 .collect();
21397
21398 if pending.is_empty() {
21399 self.clear_highlights::<PendingInput>(cx);
21400 } else {
21401 self.highlight_text::<PendingInput>(
21402 ranges,
21403 HighlightStyle {
21404 underline: Some(UnderlineStyle {
21405 thickness: px(1.),
21406 color: None,
21407 wavy: false,
21408 }),
21409 ..Default::default()
21410 },
21411 cx,
21412 );
21413 }
21414
21415 self.ime_transaction = self.ime_transaction.or(transaction);
21416 if let Some(transaction) = self.ime_transaction {
21417 self.buffer.update(cx, |buffer, cx| {
21418 buffer.group_until_transaction(transaction, cx);
21419 });
21420 }
21421
21422 if self.text_highlights::<PendingInput>(cx).is_none() {
21423 self.ime_transaction.take();
21424 }
21425 }
21426
21427 pub fn register_action_renderer(
21428 &mut self,
21429 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21430 ) -> Subscription {
21431 let id = self.next_editor_action_id.post_inc();
21432 self.editor_actions
21433 .borrow_mut()
21434 .insert(id, Box::new(listener));
21435
21436 let editor_actions = self.editor_actions.clone();
21437 Subscription::new(move || {
21438 editor_actions.borrow_mut().remove(&id);
21439 })
21440 }
21441
21442 pub fn register_action<A: Action>(
21443 &mut self,
21444 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21445 ) -> Subscription {
21446 let id = self.next_editor_action_id.post_inc();
21447 let listener = Arc::new(listener);
21448 self.editor_actions.borrow_mut().insert(
21449 id,
21450 Box::new(move |_, window, _| {
21451 let listener = listener.clone();
21452 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21453 let action = action.downcast_ref().unwrap();
21454 if phase == DispatchPhase::Bubble {
21455 listener(action, window, cx)
21456 }
21457 })
21458 }),
21459 );
21460
21461 let editor_actions = self.editor_actions.clone();
21462 Subscription::new(move || {
21463 editor_actions.borrow_mut().remove(&id);
21464 })
21465 }
21466
21467 pub fn file_header_size(&self) -> u32 {
21468 FILE_HEADER_HEIGHT
21469 }
21470
21471 pub fn restore(
21472 &mut self,
21473 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21474 window: &mut Window,
21475 cx: &mut Context<Self>,
21476 ) {
21477 let workspace = self.workspace();
21478 let project = self.project();
21479 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21480 let mut tasks = Vec::new();
21481 for (buffer_id, changes) in revert_changes {
21482 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21483 buffer.update(cx, |buffer, cx| {
21484 buffer.edit(
21485 changes
21486 .into_iter()
21487 .map(|(range, text)| (range, text.to_string())),
21488 None,
21489 cx,
21490 );
21491 });
21492
21493 if let Some(project) =
21494 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21495 {
21496 project.update(cx, |project, cx| {
21497 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21498 })
21499 }
21500 }
21501 }
21502 tasks
21503 });
21504 cx.spawn_in(window, async move |_, cx| {
21505 for (buffer, task) in save_tasks {
21506 let result = task.await;
21507 if result.is_err() {
21508 let Some(path) = buffer
21509 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21510 .ok()
21511 else {
21512 continue;
21513 };
21514 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21515 let Some(task) = cx
21516 .update_window_entity(workspace, |workspace, window, cx| {
21517 workspace
21518 .open_path_preview(path, None, false, false, false, window, cx)
21519 })
21520 .ok()
21521 else {
21522 continue;
21523 };
21524 task.await.log_err();
21525 }
21526 }
21527 }
21528 })
21529 .detach();
21530 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21531 selections.refresh()
21532 });
21533 }
21534
21535 pub fn to_pixel_point(
21536 &self,
21537 source: multi_buffer::Anchor,
21538 editor_snapshot: &EditorSnapshot,
21539 window: &mut Window,
21540 ) -> Option<gpui::Point<Pixels>> {
21541 let source_point = source.to_display_point(editor_snapshot);
21542 self.display_to_pixel_point(source_point, editor_snapshot, window)
21543 }
21544
21545 pub fn display_to_pixel_point(
21546 &self,
21547 source: DisplayPoint,
21548 editor_snapshot: &EditorSnapshot,
21549 window: &mut Window,
21550 ) -> Option<gpui::Point<Pixels>> {
21551 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21552 let text_layout_details = self.text_layout_details(window);
21553 let scroll_top = text_layout_details
21554 .scroll_anchor
21555 .scroll_position(editor_snapshot)
21556 .y;
21557
21558 if source.row().as_f32() < scroll_top.floor() {
21559 return None;
21560 }
21561 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21562 let source_y = line_height * (source.row().as_f32() - scroll_top);
21563 Some(gpui::Point::new(source_x, source_y))
21564 }
21565
21566 pub fn has_visible_completions_menu(&self) -> bool {
21567 !self.edit_prediction_preview_is_active()
21568 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21569 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21570 })
21571 }
21572
21573 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21574 if self.mode.is_minimap() {
21575 return;
21576 }
21577 self.addons
21578 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21579 }
21580
21581 pub fn unregister_addon<T: Addon>(&mut self) {
21582 self.addons.remove(&std::any::TypeId::of::<T>());
21583 }
21584
21585 pub fn addon<T: Addon>(&self) -> Option<&T> {
21586 let type_id = std::any::TypeId::of::<T>();
21587 self.addons
21588 .get(&type_id)
21589 .and_then(|item| item.to_any().downcast_ref::<T>())
21590 }
21591
21592 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21593 let type_id = std::any::TypeId::of::<T>();
21594 self.addons
21595 .get_mut(&type_id)
21596 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21597 }
21598
21599 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21600 let text_layout_details = self.text_layout_details(window);
21601 let style = &text_layout_details.editor_style;
21602 let font_id = window.text_system().resolve_font(&style.text.font());
21603 let font_size = style.text.font_size.to_pixels(window.rem_size());
21604 let line_height = style.text.line_height_in_pixels(window.rem_size());
21605 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21606 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21607
21608 CharacterDimensions {
21609 em_width,
21610 em_advance,
21611 line_height,
21612 }
21613 }
21614
21615 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21616 self.load_diff_task.clone()
21617 }
21618
21619 fn read_metadata_from_db(
21620 &mut self,
21621 item_id: u64,
21622 workspace_id: WorkspaceId,
21623 window: &mut Window,
21624 cx: &mut Context<Editor>,
21625 ) {
21626 if self.is_singleton(cx)
21627 && !self.mode.is_minimap()
21628 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21629 {
21630 let buffer_snapshot = OnceCell::new();
21631
21632 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21633 && !folds.is_empty()
21634 {
21635 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21636 self.fold_ranges(
21637 folds
21638 .into_iter()
21639 .map(|(start, end)| {
21640 snapshot.clip_offset(start, Bias::Left)
21641 ..snapshot.clip_offset(end, Bias::Right)
21642 })
21643 .collect(),
21644 false,
21645 window,
21646 cx,
21647 );
21648 }
21649
21650 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21651 && !selections.is_empty()
21652 {
21653 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21654 // skip adding the initial selection to selection history
21655 self.selection_history.mode = SelectionHistoryMode::Skipping;
21656 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21657 s.select_ranges(selections.into_iter().map(|(start, end)| {
21658 snapshot.clip_offset(start, Bias::Left)
21659 ..snapshot.clip_offset(end, Bias::Right)
21660 }));
21661 });
21662 self.selection_history.mode = SelectionHistoryMode::Normal;
21663 };
21664 }
21665
21666 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21667 }
21668
21669 fn update_lsp_data(
21670 &mut self,
21671 ignore_cache: bool,
21672 for_buffer: Option<BufferId>,
21673 window: &mut Window,
21674 cx: &mut Context<'_, Self>,
21675 ) {
21676 self.pull_diagnostics(for_buffer, window, cx);
21677 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21678 }
21679}
21680
21681// todo(settings_refactor) this should not be!
21682fn vim_enabled(cx: &App) -> bool {
21683 cx.global::<SettingsStore>()
21684 .raw_user_settings()
21685 .and_then(|settings| settings.content.vim_mode)
21686 == Some(true)
21687}
21688
21689fn process_completion_for_edit(
21690 completion: &Completion,
21691 intent: CompletionIntent,
21692 buffer: &Entity<Buffer>,
21693 cursor_position: &text::Anchor,
21694 cx: &mut Context<Editor>,
21695) -> CompletionEdit {
21696 let buffer = buffer.read(cx);
21697 let buffer_snapshot = buffer.snapshot();
21698 let (snippet, new_text) = if completion.is_snippet() {
21699 // Workaround for typescript language server issues so that methods don't expand within
21700 // strings and functions with type expressions. The previous point is used because the query
21701 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21702 let mut snippet_source = completion.new_text.clone();
21703 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21704 previous_point.column = previous_point.column.saturating_sub(1);
21705 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21706 && scope.prefers_label_for_snippet_in_completion()
21707 && let Some(label) = completion.label()
21708 && matches!(
21709 completion.kind(),
21710 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21711 )
21712 {
21713 snippet_source = label;
21714 }
21715 match Snippet::parse(&snippet_source).log_err() {
21716 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21717 None => (None, completion.new_text.clone()),
21718 }
21719 } else {
21720 (None, completion.new_text.clone())
21721 };
21722
21723 let mut range_to_replace = {
21724 let replace_range = &completion.replace_range;
21725 if let CompletionSource::Lsp {
21726 insert_range: Some(insert_range),
21727 ..
21728 } = &completion.source
21729 {
21730 debug_assert_eq!(
21731 insert_range.start, replace_range.start,
21732 "insert_range and replace_range should start at the same position"
21733 );
21734 debug_assert!(
21735 insert_range
21736 .start
21737 .cmp(cursor_position, &buffer_snapshot)
21738 .is_le(),
21739 "insert_range should start before or at cursor position"
21740 );
21741 debug_assert!(
21742 replace_range
21743 .start
21744 .cmp(cursor_position, &buffer_snapshot)
21745 .is_le(),
21746 "replace_range should start before or at cursor position"
21747 );
21748
21749 let should_replace = match intent {
21750 CompletionIntent::CompleteWithInsert => false,
21751 CompletionIntent::CompleteWithReplace => true,
21752 CompletionIntent::Complete | CompletionIntent::Compose => {
21753 let insert_mode =
21754 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21755 .completions
21756 .lsp_insert_mode;
21757 match insert_mode {
21758 LspInsertMode::Insert => false,
21759 LspInsertMode::Replace => true,
21760 LspInsertMode::ReplaceSubsequence => {
21761 let mut text_to_replace = buffer.chars_for_range(
21762 buffer.anchor_before(replace_range.start)
21763 ..buffer.anchor_after(replace_range.end),
21764 );
21765 let mut current_needle = text_to_replace.next();
21766 for haystack_ch in completion.label.text.chars() {
21767 if let Some(needle_ch) = current_needle
21768 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21769 {
21770 current_needle = text_to_replace.next();
21771 }
21772 }
21773 current_needle.is_none()
21774 }
21775 LspInsertMode::ReplaceSuffix => {
21776 if replace_range
21777 .end
21778 .cmp(cursor_position, &buffer_snapshot)
21779 .is_gt()
21780 {
21781 let range_after_cursor = *cursor_position..replace_range.end;
21782 let text_after_cursor = buffer
21783 .text_for_range(
21784 buffer.anchor_before(range_after_cursor.start)
21785 ..buffer.anchor_after(range_after_cursor.end),
21786 )
21787 .collect::<String>()
21788 .to_ascii_lowercase();
21789 completion
21790 .label
21791 .text
21792 .to_ascii_lowercase()
21793 .ends_with(&text_after_cursor)
21794 } else {
21795 true
21796 }
21797 }
21798 }
21799 }
21800 };
21801
21802 if should_replace {
21803 replace_range.clone()
21804 } else {
21805 insert_range.clone()
21806 }
21807 } else {
21808 replace_range.clone()
21809 }
21810 };
21811
21812 if range_to_replace
21813 .end
21814 .cmp(cursor_position, &buffer_snapshot)
21815 .is_lt()
21816 {
21817 range_to_replace.end = *cursor_position;
21818 }
21819
21820 CompletionEdit {
21821 new_text,
21822 replace_range: range_to_replace.to_offset(buffer),
21823 snippet,
21824 }
21825}
21826
21827struct CompletionEdit {
21828 new_text: String,
21829 replace_range: Range<usize>,
21830 snippet: Option<Snippet>,
21831}
21832
21833fn insert_extra_newline_brackets(
21834 buffer: &MultiBufferSnapshot,
21835 range: Range<usize>,
21836 language: &language::LanguageScope,
21837) -> bool {
21838 let leading_whitespace_len = buffer
21839 .reversed_chars_at(range.start)
21840 .take_while(|c| c.is_whitespace() && *c != '\n')
21841 .map(|c| c.len_utf8())
21842 .sum::<usize>();
21843 let trailing_whitespace_len = buffer
21844 .chars_at(range.end)
21845 .take_while(|c| c.is_whitespace() && *c != '\n')
21846 .map(|c| c.len_utf8())
21847 .sum::<usize>();
21848 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21849
21850 language.brackets().any(|(pair, enabled)| {
21851 let pair_start = pair.start.trim_end();
21852 let pair_end = pair.end.trim_start();
21853
21854 enabled
21855 && pair.newline
21856 && buffer.contains_str_at(range.end, pair_end)
21857 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21858 })
21859}
21860
21861fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21862 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21863 [(buffer, range, _)] => (*buffer, range.clone()),
21864 _ => return false,
21865 };
21866 let pair = {
21867 let mut result: Option<BracketMatch> = None;
21868
21869 for pair in buffer
21870 .all_bracket_ranges(range.clone())
21871 .filter(move |pair| {
21872 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21873 })
21874 {
21875 let len = pair.close_range.end - pair.open_range.start;
21876
21877 if let Some(existing) = &result {
21878 let existing_len = existing.close_range.end - existing.open_range.start;
21879 if len > existing_len {
21880 continue;
21881 }
21882 }
21883
21884 result = Some(pair);
21885 }
21886
21887 result
21888 };
21889 let Some(pair) = pair else {
21890 return false;
21891 };
21892 pair.newline_only
21893 && buffer
21894 .chars_for_range(pair.open_range.end..range.start)
21895 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21896 .all(|c| c.is_whitespace() && c != '\n')
21897}
21898
21899fn update_uncommitted_diff_for_buffer(
21900 editor: Entity<Editor>,
21901 project: &Entity<Project>,
21902 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21903 buffer: Entity<MultiBuffer>,
21904 cx: &mut App,
21905) -> Task<()> {
21906 let mut tasks = Vec::new();
21907 project.update(cx, |project, cx| {
21908 for buffer in buffers {
21909 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21910 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21911 }
21912 }
21913 });
21914 cx.spawn(async move |cx| {
21915 let diffs = future::join_all(tasks).await;
21916 if editor
21917 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21918 .unwrap_or(false)
21919 {
21920 return;
21921 }
21922
21923 buffer
21924 .update(cx, |buffer, cx| {
21925 for diff in diffs.into_iter().flatten() {
21926 buffer.add_diff(diff, cx);
21927 }
21928 })
21929 .ok();
21930 })
21931}
21932
21933fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21934 let tab_size = tab_size.get() as usize;
21935 let mut width = offset;
21936
21937 for ch in text.chars() {
21938 width += if ch == '\t' {
21939 tab_size - (width % tab_size)
21940 } else {
21941 1
21942 };
21943 }
21944
21945 width - offset
21946}
21947
21948#[cfg(test)]
21949mod tests {
21950 use super::*;
21951
21952 #[test]
21953 fn test_string_size_with_expanded_tabs() {
21954 let nz = |val| NonZeroU32::new(val).unwrap();
21955 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21956 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21957 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21958 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21959 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21960 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21961 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21962 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21963 }
21964}
21965
21966/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21967struct WordBreakingTokenizer<'a> {
21968 input: &'a str,
21969}
21970
21971impl<'a> WordBreakingTokenizer<'a> {
21972 fn new(input: &'a str) -> Self {
21973 Self { input }
21974 }
21975}
21976
21977fn is_char_ideographic(ch: char) -> bool {
21978 use unicode_script::Script::*;
21979 use unicode_script::UnicodeScript;
21980 matches!(ch.script(), Han | Tangut | Yi)
21981}
21982
21983fn is_grapheme_ideographic(text: &str) -> bool {
21984 text.chars().any(is_char_ideographic)
21985}
21986
21987fn is_grapheme_whitespace(text: &str) -> bool {
21988 text.chars().any(|x| x.is_whitespace())
21989}
21990
21991fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21992 text.chars()
21993 .next()
21994 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21995}
21996
21997#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21998enum WordBreakToken<'a> {
21999 Word { token: &'a str, grapheme_len: usize },
22000 InlineWhitespace { token: &'a str, grapheme_len: usize },
22001 Newline,
22002}
22003
22004impl<'a> Iterator for WordBreakingTokenizer<'a> {
22005 /// Yields a span, the count of graphemes in the token, and whether it was
22006 /// whitespace. Note that it also breaks at word boundaries.
22007 type Item = WordBreakToken<'a>;
22008
22009 fn next(&mut self) -> Option<Self::Item> {
22010 use unicode_segmentation::UnicodeSegmentation;
22011 if self.input.is_empty() {
22012 return None;
22013 }
22014
22015 let mut iter = self.input.graphemes(true).peekable();
22016 let mut offset = 0;
22017 let mut grapheme_len = 0;
22018 if let Some(first_grapheme) = iter.next() {
22019 let is_newline = first_grapheme == "\n";
22020 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22021 offset += first_grapheme.len();
22022 grapheme_len += 1;
22023 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22024 if let Some(grapheme) = iter.peek().copied()
22025 && should_stay_with_preceding_ideograph(grapheme)
22026 {
22027 offset += grapheme.len();
22028 grapheme_len += 1;
22029 }
22030 } else {
22031 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22032 let mut next_word_bound = words.peek().copied();
22033 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22034 next_word_bound = words.next();
22035 }
22036 while let Some(grapheme) = iter.peek().copied() {
22037 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22038 break;
22039 };
22040 if is_grapheme_whitespace(grapheme) != is_whitespace
22041 || (grapheme == "\n") != is_newline
22042 {
22043 break;
22044 };
22045 offset += grapheme.len();
22046 grapheme_len += 1;
22047 iter.next();
22048 }
22049 }
22050 let token = &self.input[..offset];
22051 self.input = &self.input[offset..];
22052 if token == "\n" {
22053 Some(WordBreakToken::Newline)
22054 } else if is_whitespace {
22055 Some(WordBreakToken::InlineWhitespace {
22056 token,
22057 grapheme_len,
22058 })
22059 } else {
22060 Some(WordBreakToken::Word {
22061 token,
22062 grapheme_len,
22063 })
22064 }
22065 } else {
22066 None
22067 }
22068 }
22069}
22070
22071#[test]
22072fn test_word_breaking_tokenizer() {
22073 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22074 ("", &[]),
22075 (" ", &[whitespace(" ", 2)]),
22076 ("Ʒ", &[word("Ʒ", 1)]),
22077 ("Ǽ", &[word("Ǽ", 1)]),
22078 ("⋑", &[word("⋑", 1)]),
22079 ("⋑⋑", &[word("⋑⋑", 2)]),
22080 (
22081 "原理,进而",
22082 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22083 ),
22084 (
22085 "hello world",
22086 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22087 ),
22088 (
22089 "hello, world",
22090 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22091 ),
22092 (
22093 " hello world",
22094 &[
22095 whitespace(" ", 2),
22096 word("hello", 5),
22097 whitespace(" ", 1),
22098 word("world", 5),
22099 ],
22100 ),
22101 (
22102 "这是什么 \n 钢笔",
22103 &[
22104 word("这", 1),
22105 word("是", 1),
22106 word("什", 1),
22107 word("么", 1),
22108 whitespace(" ", 1),
22109 newline(),
22110 whitespace(" ", 1),
22111 word("钢", 1),
22112 word("笔", 1),
22113 ],
22114 ),
22115 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22116 ];
22117
22118 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22119 WordBreakToken::Word {
22120 token,
22121 grapheme_len,
22122 }
22123 }
22124
22125 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22126 WordBreakToken::InlineWhitespace {
22127 token,
22128 grapheme_len,
22129 }
22130 }
22131
22132 fn newline() -> WordBreakToken<'static> {
22133 WordBreakToken::Newline
22134 }
22135
22136 for (input, result) in tests {
22137 assert_eq!(
22138 WordBreakingTokenizer::new(input)
22139 .collect::<Vec<_>>()
22140 .as_slice(),
22141 *result,
22142 );
22143 }
22144}
22145
22146fn wrap_with_prefix(
22147 first_line_prefix: String,
22148 subsequent_lines_prefix: String,
22149 unwrapped_text: String,
22150 wrap_column: usize,
22151 tab_size: NonZeroU32,
22152 preserve_existing_whitespace: bool,
22153) -> String {
22154 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22155 let subsequent_lines_prefix_len =
22156 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22157 let mut wrapped_text = String::new();
22158 let mut current_line = first_line_prefix;
22159 let mut is_first_line = true;
22160
22161 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22162 let mut current_line_len = first_line_prefix_len;
22163 let mut in_whitespace = false;
22164 for token in tokenizer {
22165 let have_preceding_whitespace = in_whitespace;
22166 match token {
22167 WordBreakToken::Word {
22168 token,
22169 grapheme_len,
22170 } => {
22171 in_whitespace = false;
22172 let current_prefix_len = if is_first_line {
22173 first_line_prefix_len
22174 } else {
22175 subsequent_lines_prefix_len
22176 };
22177 if current_line_len + grapheme_len > wrap_column
22178 && current_line_len != current_prefix_len
22179 {
22180 wrapped_text.push_str(current_line.trim_end());
22181 wrapped_text.push('\n');
22182 is_first_line = false;
22183 current_line = subsequent_lines_prefix.clone();
22184 current_line_len = subsequent_lines_prefix_len;
22185 }
22186 current_line.push_str(token);
22187 current_line_len += grapheme_len;
22188 }
22189 WordBreakToken::InlineWhitespace {
22190 mut token,
22191 mut grapheme_len,
22192 } => {
22193 in_whitespace = true;
22194 if have_preceding_whitespace && !preserve_existing_whitespace {
22195 continue;
22196 }
22197 if !preserve_existing_whitespace {
22198 token = " ";
22199 grapheme_len = 1;
22200 }
22201 let current_prefix_len = if is_first_line {
22202 first_line_prefix_len
22203 } else {
22204 subsequent_lines_prefix_len
22205 };
22206 if current_line_len + grapheme_len > wrap_column {
22207 wrapped_text.push_str(current_line.trim_end());
22208 wrapped_text.push('\n');
22209 is_first_line = false;
22210 current_line = subsequent_lines_prefix.clone();
22211 current_line_len = subsequent_lines_prefix_len;
22212 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22213 current_line.push_str(token);
22214 current_line_len += grapheme_len;
22215 }
22216 }
22217 WordBreakToken::Newline => {
22218 in_whitespace = true;
22219 let current_prefix_len = if is_first_line {
22220 first_line_prefix_len
22221 } else {
22222 subsequent_lines_prefix_len
22223 };
22224 if preserve_existing_whitespace {
22225 wrapped_text.push_str(current_line.trim_end());
22226 wrapped_text.push('\n');
22227 is_first_line = false;
22228 current_line = subsequent_lines_prefix.clone();
22229 current_line_len = subsequent_lines_prefix_len;
22230 } else if have_preceding_whitespace {
22231 continue;
22232 } else if current_line_len + 1 > wrap_column
22233 && current_line_len != current_prefix_len
22234 {
22235 wrapped_text.push_str(current_line.trim_end());
22236 wrapped_text.push('\n');
22237 is_first_line = false;
22238 current_line = subsequent_lines_prefix.clone();
22239 current_line_len = subsequent_lines_prefix_len;
22240 } else if current_line_len != current_prefix_len {
22241 current_line.push(' ');
22242 current_line_len += 1;
22243 }
22244 }
22245 }
22246 }
22247
22248 if !current_line.is_empty() {
22249 wrapped_text.push_str(¤t_line);
22250 }
22251 wrapped_text
22252}
22253
22254#[test]
22255fn test_wrap_with_prefix() {
22256 assert_eq!(
22257 wrap_with_prefix(
22258 "# ".to_string(),
22259 "# ".to_string(),
22260 "abcdefg".to_string(),
22261 4,
22262 NonZeroU32::new(4).unwrap(),
22263 false,
22264 ),
22265 "# abcdefg"
22266 );
22267 assert_eq!(
22268 wrap_with_prefix(
22269 "".to_string(),
22270 "".to_string(),
22271 "\thello world".to_string(),
22272 8,
22273 NonZeroU32::new(4).unwrap(),
22274 false,
22275 ),
22276 "hello\nworld"
22277 );
22278 assert_eq!(
22279 wrap_with_prefix(
22280 "// ".to_string(),
22281 "// ".to_string(),
22282 "xx \nyy zz aa bb cc".to_string(),
22283 12,
22284 NonZeroU32::new(4).unwrap(),
22285 false,
22286 ),
22287 "// xx yy zz\n// aa bb cc"
22288 );
22289 assert_eq!(
22290 wrap_with_prefix(
22291 String::new(),
22292 String::new(),
22293 "这是什么 \n 钢笔".to_string(),
22294 3,
22295 NonZeroU32::new(4).unwrap(),
22296 false,
22297 ),
22298 "这是什\n么 钢\n笔"
22299 );
22300}
22301
22302pub trait CollaborationHub {
22303 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22304 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22305 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22306}
22307
22308impl CollaborationHub for Entity<Project> {
22309 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22310 self.read(cx).collaborators()
22311 }
22312
22313 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22314 self.read(cx).user_store().read(cx).participant_indices()
22315 }
22316
22317 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22318 let this = self.read(cx);
22319 let user_ids = this.collaborators().values().map(|c| c.user_id);
22320 this.user_store().read(cx).participant_names(user_ids, cx)
22321 }
22322}
22323
22324pub trait SemanticsProvider {
22325 fn hover(
22326 &self,
22327 buffer: &Entity<Buffer>,
22328 position: text::Anchor,
22329 cx: &mut App,
22330 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22331
22332 fn inline_values(
22333 &self,
22334 buffer_handle: Entity<Buffer>,
22335 range: Range<text::Anchor>,
22336 cx: &mut App,
22337 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22338
22339 fn inlay_hints(
22340 &self,
22341 buffer_handle: Entity<Buffer>,
22342 range: Range<text::Anchor>,
22343 cx: &mut App,
22344 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22345
22346 fn resolve_inlay_hint(
22347 &self,
22348 hint: InlayHint,
22349 buffer_handle: Entity<Buffer>,
22350 server_id: LanguageServerId,
22351 cx: &mut App,
22352 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22353
22354 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22355
22356 fn document_highlights(
22357 &self,
22358 buffer: &Entity<Buffer>,
22359 position: text::Anchor,
22360 cx: &mut App,
22361 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22362
22363 fn definitions(
22364 &self,
22365 buffer: &Entity<Buffer>,
22366 position: text::Anchor,
22367 kind: GotoDefinitionKind,
22368 cx: &mut App,
22369 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22370
22371 fn range_for_rename(
22372 &self,
22373 buffer: &Entity<Buffer>,
22374 position: text::Anchor,
22375 cx: &mut App,
22376 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22377
22378 fn perform_rename(
22379 &self,
22380 buffer: &Entity<Buffer>,
22381 position: text::Anchor,
22382 new_name: String,
22383 cx: &mut App,
22384 ) -> Option<Task<Result<ProjectTransaction>>>;
22385}
22386
22387pub trait CompletionProvider {
22388 fn completions(
22389 &self,
22390 excerpt_id: ExcerptId,
22391 buffer: &Entity<Buffer>,
22392 buffer_position: text::Anchor,
22393 trigger: CompletionContext,
22394 window: &mut Window,
22395 cx: &mut Context<Editor>,
22396 ) -> Task<Result<Vec<CompletionResponse>>>;
22397
22398 fn resolve_completions(
22399 &self,
22400 _buffer: Entity<Buffer>,
22401 _completion_indices: Vec<usize>,
22402 _completions: Rc<RefCell<Box<[Completion]>>>,
22403 _cx: &mut Context<Editor>,
22404 ) -> Task<Result<bool>> {
22405 Task::ready(Ok(false))
22406 }
22407
22408 fn apply_additional_edits_for_completion(
22409 &self,
22410 _buffer: Entity<Buffer>,
22411 _completions: Rc<RefCell<Box<[Completion]>>>,
22412 _completion_index: usize,
22413 _push_to_history: bool,
22414 _cx: &mut Context<Editor>,
22415 ) -> Task<Result<Option<language::Transaction>>> {
22416 Task::ready(Ok(None))
22417 }
22418
22419 fn is_completion_trigger(
22420 &self,
22421 buffer: &Entity<Buffer>,
22422 position: language::Anchor,
22423 text: &str,
22424 trigger_in_words: bool,
22425 menu_is_open: bool,
22426 cx: &mut Context<Editor>,
22427 ) -> bool;
22428
22429 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22430
22431 fn sort_completions(&self) -> bool {
22432 true
22433 }
22434
22435 fn filter_completions(&self) -> bool {
22436 true
22437 }
22438}
22439
22440pub trait CodeActionProvider {
22441 fn id(&self) -> Arc<str>;
22442
22443 fn code_actions(
22444 &self,
22445 buffer: &Entity<Buffer>,
22446 range: Range<text::Anchor>,
22447 window: &mut Window,
22448 cx: &mut App,
22449 ) -> Task<Result<Vec<CodeAction>>>;
22450
22451 fn apply_code_action(
22452 &self,
22453 buffer_handle: Entity<Buffer>,
22454 action: CodeAction,
22455 excerpt_id: ExcerptId,
22456 push_to_history: bool,
22457 window: &mut Window,
22458 cx: &mut App,
22459 ) -> Task<Result<ProjectTransaction>>;
22460}
22461
22462impl CodeActionProvider for Entity<Project> {
22463 fn id(&self) -> Arc<str> {
22464 "project".into()
22465 }
22466
22467 fn code_actions(
22468 &self,
22469 buffer: &Entity<Buffer>,
22470 range: Range<text::Anchor>,
22471 _window: &mut Window,
22472 cx: &mut App,
22473 ) -> Task<Result<Vec<CodeAction>>> {
22474 self.update(cx, |project, cx| {
22475 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22476 let code_actions = project.code_actions(buffer, range, None, cx);
22477 cx.background_spawn(async move {
22478 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22479 Ok(code_lens_actions
22480 .context("code lens fetch")?
22481 .into_iter()
22482 .flatten()
22483 .chain(
22484 code_actions
22485 .context("code action fetch")?
22486 .into_iter()
22487 .flatten(),
22488 )
22489 .collect())
22490 })
22491 })
22492 }
22493
22494 fn apply_code_action(
22495 &self,
22496 buffer_handle: Entity<Buffer>,
22497 action: CodeAction,
22498 _excerpt_id: ExcerptId,
22499 push_to_history: bool,
22500 _window: &mut Window,
22501 cx: &mut App,
22502 ) -> Task<Result<ProjectTransaction>> {
22503 self.update(cx, |project, cx| {
22504 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22505 })
22506 }
22507}
22508
22509fn snippet_completions(
22510 project: &Project,
22511 buffer: &Entity<Buffer>,
22512 buffer_position: text::Anchor,
22513 cx: &mut App,
22514) -> Task<Result<CompletionResponse>> {
22515 let languages = buffer.read(cx).languages_at(buffer_position);
22516 let snippet_store = project.snippets().read(cx);
22517
22518 let scopes: Vec<_> = languages
22519 .iter()
22520 .filter_map(|language| {
22521 let language_name = language.lsp_id();
22522 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22523
22524 if snippets.is_empty() {
22525 None
22526 } else {
22527 Some((language.default_scope(), snippets))
22528 }
22529 })
22530 .collect();
22531
22532 if scopes.is_empty() {
22533 return Task::ready(Ok(CompletionResponse {
22534 completions: vec![],
22535 display_options: CompletionDisplayOptions::default(),
22536 is_incomplete: false,
22537 }));
22538 }
22539
22540 let snapshot = buffer.read(cx).text_snapshot();
22541 let chars: String = snapshot
22542 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22543 .collect();
22544 let executor = cx.background_executor().clone();
22545
22546 cx.background_spawn(async move {
22547 let mut is_incomplete = false;
22548 let mut completions: Vec<Completion> = Vec::new();
22549 for (scope, snippets) in scopes.into_iter() {
22550 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22551 let mut last_word = chars
22552 .chars()
22553 .take_while(|c| classifier.is_word(*c))
22554 .collect::<String>();
22555 last_word = last_word.chars().rev().collect();
22556
22557 if last_word.is_empty() {
22558 return Ok(CompletionResponse {
22559 completions: vec![],
22560 display_options: CompletionDisplayOptions::default(),
22561 is_incomplete: true,
22562 });
22563 }
22564
22565 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22566 let to_lsp = |point: &text::Anchor| {
22567 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22568 point_to_lsp(end)
22569 };
22570 let lsp_end = to_lsp(&buffer_position);
22571
22572 let candidates = snippets
22573 .iter()
22574 .enumerate()
22575 .flat_map(|(ix, snippet)| {
22576 snippet
22577 .prefix
22578 .iter()
22579 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22580 })
22581 .collect::<Vec<StringMatchCandidate>>();
22582
22583 const MAX_RESULTS: usize = 100;
22584 let mut matches = fuzzy::match_strings(
22585 &candidates,
22586 &last_word,
22587 last_word.chars().any(|c| c.is_uppercase()),
22588 true,
22589 MAX_RESULTS,
22590 &Default::default(),
22591 executor.clone(),
22592 )
22593 .await;
22594
22595 if matches.len() >= MAX_RESULTS {
22596 is_incomplete = true;
22597 }
22598
22599 // Remove all candidates where the query's start does not match the start of any word in the candidate
22600 if let Some(query_start) = last_word.chars().next() {
22601 matches.retain(|string_match| {
22602 split_words(&string_match.string).any(|word| {
22603 // Check that the first codepoint of the word as lowercase matches the first
22604 // codepoint of the query as lowercase
22605 word.chars()
22606 .flat_map(|codepoint| codepoint.to_lowercase())
22607 .zip(query_start.to_lowercase())
22608 .all(|(word_cp, query_cp)| word_cp == query_cp)
22609 })
22610 });
22611 }
22612
22613 let matched_strings = matches
22614 .into_iter()
22615 .map(|m| m.string)
22616 .collect::<HashSet<_>>();
22617
22618 completions.extend(snippets.iter().filter_map(|snippet| {
22619 let matching_prefix = snippet
22620 .prefix
22621 .iter()
22622 .find(|prefix| matched_strings.contains(*prefix))?;
22623 let start = as_offset - last_word.len();
22624 let start = snapshot.anchor_before(start);
22625 let range = start..buffer_position;
22626 let lsp_start = to_lsp(&start);
22627 let lsp_range = lsp::Range {
22628 start: lsp_start,
22629 end: lsp_end,
22630 };
22631 Some(Completion {
22632 replace_range: range,
22633 new_text: snippet.body.clone(),
22634 source: CompletionSource::Lsp {
22635 insert_range: None,
22636 server_id: LanguageServerId(usize::MAX),
22637 resolved: true,
22638 lsp_completion: Box::new(lsp::CompletionItem {
22639 label: snippet.prefix.first().unwrap().clone(),
22640 kind: Some(CompletionItemKind::SNIPPET),
22641 label_details: snippet.description.as_ref().map(|description| {
22642 lsp::CompletionItemLabelDetails {
22643 detail: Some(description.clone()),
22644 description: None,
22645 }
22646 }),
22647 insert_text_format: Some(InsertTextFormat::SNIPPET),
22648 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22649 lsp::InsertReplaceEdit {
22650 new_text: snippet.body.clone(),
22651 insert: lsp_range,
22652 replace: lsp_range,
22653 },
22654 )),
22655 filter_text: Some(snippet.body.clone()),
22656 sort_text: Some(char::MAX.to_string()),
22657 ..lsp::CompletionItem::default()
22658 }),
22659 lsp_defaults: None,
22660 },
22661 label: CodeLabel {
22662 text: matching_prefix.clone(),
22663 runs: Vec::new(),
22664 filter_range: 0..matching_prefix.len(),
22665 },
22666 icon_path: None,
22667 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22668 single_line: snippet.name.clone().into(),
22669 plain_text: snippet
22670 .description
22671 .clone()
22672 .map(|description| description.into()),
22673 }),
22674 insert_text_mode: None,
22675 confirm: None,
22676 })
22677 }))
22678 }
22679
22680 Ok(CompletionResponse {
22681 completions,
22682 display_options: CompletionDisplayOptions::default(),
22683 is_incomplete,
22684 })
22685 })
22686}
22687
22688impl CompletionProvider for Entity<Project> {
22689 fn completions(
22690 &self,
22691 _excerpt_id: ExcerptId,
22692 buffer: &Entity<Buffer>,
22693 buffer_position: text::Anchor,
22694 options: CompletionContext,
22695 _window: &mut Window,
22696 cx: &mut Context<Editor>,
22697 ) -> Task<Result<Vec<CompletionResponse>>> {
22698 self.update(cx, |project, cx| {
22699 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22700 let project_completions = project.completions(buffer, buffer_position, options, cx);
22701 cx.background_spawn(async move {
22702 let mut responses = project_completions.await?;
22703 let snippets = snippets.await?;
22704 if !snippets.completions.is_empty() {
22705 responses.push(snippets);
22706 }
22707 Ok(responses)
22708 })
22709 })
22710 }
22711
22712 fn resolve_completions(
22713 &self,
22714 buffer: Entity<Buffer>,
22715 completion_indices: Vec<usize>,
22716 completions: Rc<RefCell<Box<[Completion]>>>,
22717 cx: &mut Context<Editor>,
22718 ) -> Task<Result<bool>> {
22719 self.update(cx, |project, cx| {
22720 project.lsp_store().update(cx, |lsp_store, cx| {
22721 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22722 })
22723 })
22724 }
22725
22726 fn apply_additional_edits_for_completion(
22727 &self,
22728 buffer: Entity<Buffer>,
22729 completions: Rc<RefCell<Box<[Completion]>>>,
22730 completion_index: usize,
22731 push_to_history: bool,
22732 cx: &mut Context<Editor>,
22733 ) -> Task<Result<Option<language::Transaction>>> {
22734 self.update(cx, |project, cx| {
22735 project.lsp_store().update(cx, |lsp_store, cx| {
22736 lsp_store.apply_additional_edits_for_completion(
22737 buffer,
22738 completions,
22739 completion_index,
22740 push_to_history,
22741 cx,
22742 )
22743 })
22744 })
22745 }
22746
22747 fn is_completion_trigger(
22748 &self,
22749 buffer: &Entity<Buffer>,
22750 position: language::Anchor,
22751 text: &str,
22752 trigger_in_words: bool,
22753 menu_is_open: bool,
22754 cx: &mut Context<Editor>,
22755 ) -> bool {
22756 let mut chars = text.chars();
22757 let char = if let Some(char) = chars.next() {
22758 char
22759 } else {
22760 return false;
22761 };
22762 if chars.next().is_some() {
22763 return false;
22764 }
22765
22766 let buffer = buffer.read(cx);
22767 let snapshot = buffer.snapshot();
22768 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22769 return false;
22770 }
22771 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22772 if trigger_in_words && classifier.is_word(char) {
22773 return true;
22774 }
22775
22776 buffer.completion_triggers().contains(text)
22777 }
22778}
22779
22780impl SemanticsProvider for Entity<Project> {
22781 fn hover(
22782 &self,
22783 buffer: &Entity<Buffer>,
22784 position: text::Anchor,
22785 cx: &mut App,
22786 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22787 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22788 }
22789
22790 fn document_highlights(
22791 &self,
22792 buffer: &Entity<Buffer>,
22793 position: text::Anchor,
22794 cx: &mut App,
22795 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22796 Some(self.update(cx, |project, cx| {
22797 project.document_highlights(buffer, position, cx)
22798 }))
22799 }
22800
22801 fn definitions(
22802 &self,
22803 buffer: &Entity<Buffer>,
22804 position: text::Anchor,
22805 kind: GotoDefinitionKind,
22806 cx: &mut App,
22807 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22808 Some(self.update(cx, |project, cx| match kind {
22809 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22810 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22811 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22812 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22813 }))
22814 }
22815
22816 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22817 self.update(cx, |project, cx| {
22818 if project
22819 .active_debug_session(cx)
22820 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22821 {
22822 return true;
22823 }
22824
22825 buffer.update(cx, |buffer, cx| {
22826 project.any_language_server_supports_inlay_hints(buffer, cx)
22827 })
22828 })
22829 }
22830
22831 fn inline_values(
22832 &self,
22833 buffer_handle: Entity<Buffer>,
22834 range: Range<text::Anchor>,
22835 cx: &mut App,
22836 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22837 self.update(cx, |project, cx| {
22838 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22839
22840 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22841 })
22842 }
22843
22844 fn inlay_hints(
22845 &self,
22846 buffer_handle: Entity<Buffer>,
22847 range: Range<text::Anchor>,
22848 cx: &mut App,
22849 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22850 Some(self.update(cx, |project, cx| {
22851 project.inlay_hints(buffer_handle, range, cx)
22852 }))
22853 }
22854
22855 fn resolve_inlay_hint(
22856 &self,
22857 hint: InlayHint,
22858 buffer_handle: Entity<Buffer>,
22859 server_id: LanguageServerId,
22860 cx: &mut App,
22861 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22862 Some(self.update(cx, |project, cx| {
22863 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22864 }))
22865 }
22866
22867 fn range_for_rename(
22868 &self,
22869 buffer: &Entity<Buffer>,
22870 position: text::Anchor,
22871 cx: &mut App,
22872 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22873 Some(self.update(cx, |project, cx| {
22874 let buffer = buffer.clone();
22875 let task = project.prepare_rename(buffer.clone(), position, cx);
22876 cx.spawn(async move |_, cx| {
22877 Ok(match task.await? {
22878 PrepareRenameResponse::Success(range) => Some(range),
22879 PrepareRenameResponse::InvalidPosition => None,
22880 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22881 // Fallback on using TreeSitter info to determine identifier range
22882 buffer.read_with(cx, |buffer, _| {
22883 let snapshot = buffer.snapshot();
22884 let (range, kind) = snapshot.surrounding_word(position, false);
22885 if kind != Some(CharKind::Word) {
22886 return None;
22887 }
22888 Some(
22889 snapshot.anchor_before(range.start)
22890 ..snapshot.anchor_after(range.end),
22891 )
22892 })?
22893 }
22894 })
22895 })
22896 }))
22897 }
22898
22899 fn perform_rename(
22900 &self,
22901 buffer: &Entity<Buffer>,
22902 position: text::Anchor,
22903 new_name: String,
22904 cx: &mut App,
22905 ) -> Option<Task<Result<ProjectTransaction>>> {
22906 Some(self.update(cx, |project, cx| {
22907 project.perform_rename(buffer.clone(), position, new_name, cx)
22908 }))
22909 }
22910}
22911
22912fn inlay_hint_settings(
22913 location: Anchor,
22914 snapshot: &MultiBufferSnapshot,
22915 cx: &mut Context<Editor>,
22916) -> InlayHintSettings {
22917 let file = snapshot.file_at(location);
22918 let language = snapshot.language_at(location).map(|l| l.name());
22919 language_settings(language, file, cx).inlay_hints
22920}
22921
22922fn consume_contiguous_rows(
22923 contiguous_row_selections: &mut Vec<Selection<Point>>,
22924 selection: &Selection<Point>,
22925 display_map: &DisplaySnapshot,
22926 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22927) -> (MultiBufferRow, MultiBufferRow) {
22928 contiguous_row_selections.push(selection.clone());
22929 let start_row = starting_row(selection, display_map);
22930 let mut end_row = ending_row(selection, display_map);
22931
22932 while let Some(next_selection) = selections.peek() {
22933 if next_selection.start.row <= end_row.0 {
22934 end_row = ending_row(next_selection, display_map);
22935 contiguous_row_selections.push(selections.next().unwrap().clone());
22936 } else {
22937 break;
22938 }
22939 }
22940 (start_row, end_row)
22941}
22942
22943fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22944 if selection.start.column > 0 {
22945 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22946 } else {
22947 MultiBufferRow(selection.start.row)
22948 }
22949}
22950
22951fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22952 if next_selection.end.column > 0 || next_selection.is_empty() {
22953 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22954 } else {
22955 MultiBufferRow(next_selection.end.row)
22956 }
22957}
22958
22959impl EditorSnapshot {
22960 pub fn remote_selections_in_range<'a>(
22961 &'a self,
22962 range: &'a Range<Anchor>,
22963 collaboration_hub: &dyn CollaborationHub,
22964 cx: &'a App,
22965 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22966 let participant_names = collaboration_hub.user_names(cx);
22967 let participant_indices = collaboration_hub.user_participant_indices(cx);
22968 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22969 let collaborators_by_replica_id = collaborators_by_peer_id
22970 .values()
22971 .map(|collaborator| (collaborator.replica_id, collaborator))
22972 .collect::<HashMap<_, _>>();
22973 self.buffer_snapshot
22974 .selections_in_range(range, false)
22975 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22976 if replica_id == AGENT_REPLICA_ID {
22977 Some(RemoteSelection {
22978 replica_id,
22979 selection,
22980 cursor_shape,
22981 line_mode,
22982 collaborator_id: CollaboratorId::Agent,
22983 user_name: Some("Agent".into()),
22984 color: cx.theme().players().agent(),
22985 })
22986 } else {
22987 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22988 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22989 let user_name = participant_names.get(&collaborator.user_id).cloned();
22990 Some(RemoteSelection {
22991 replica_id,
22992 selection,
22993 cursor_shape,
22994 line_mode,
22995 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22996 user_name,
22997 color: if let Some(index) = participant_index {
22998 cx.theme().players().color_for_participant(index.0)
22999 } else {
23000 cx.theme().players().absent()
23001 },
23002 })
23003 }
23004 })
23005 }
23006
23007 pub fn hunks_for_ranges(
23008 &self,
23009 ranges: impl IntoIterator<Item = Range<Point>>,
23010 ) -> Vec<MultiBufferDiffHunk> {
23011 let mut hunks = Vec::new();
23012 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23013 HashMap::default();
23014 for query_range in ranges {
23015 let query_rows =
23016 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23017 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23018 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23019 ) {
23020 // Include deleted hunks that are adjacent to the query range, because
23021 // otherwise they would be missed.
23022 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23023 if hunk.status().is_deleted() {
23024 intersects_range |= hunk.row_range.start == query_rows.end;
23025 intersects_range |= hunk.row_range.end == query_rows.start;
23026 }
23027 if intersects_range {
23028 if !processed_buffer_rows
23029 .entry(hunk.buffer_id)
23030 .or_default()
23031 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23032 {
23033 continue;
23034 }
23035 hunks.push(hunk);
23036 }
23037 }
23038 }
23039
23040 hunks
23041 }
23042
23043 fn display_diff_hunks_for_rows<'a>(
23044 &'a self,
23045 display_rows: Range<DisplayRow>,
23046 folded_buffers: &'a HashSet<BufferId>,
23047 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23048 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23049 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23050
23051 self.buffer_snapshot
23052 .diff_hunks_in_range(buffer_start..buffer_end)
23053 .filter_map(|hunk| {
23054 if folded_buffers.contains(&hunk.buffer_id) {
23055 return None;
23056 }
23057
23058 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23059 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23060
23061 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23062 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23063
23064 let display_hunk = if hunk_display_start.column() != 0 {
23065 DisplayDiffHunk::Folded {
23066 display_row: hunk_display_start.row(),
23067 }
23068 } else {
23069 let mut end_row = hunk_display_end.row();
23070 if hunk_display_end.column() > 0 {
23071 end_row.0 += 1;
23072 }
23073 let is_created_file = hunk.is_created_file();
23074 DisplayDiffHunk::Unfolded {
23075 status: hunk.status(),
23076 diff_base_byte_range: hunk.diff_base_byte_range,
23077 display_row_range: hunk_display_start.row()..end_row,
23078 multi_buffer_range: Anchor::range_in_buffer(
23079 hunk.excerpt_id,
23080 hunk.buffer_id,
23081 hunk.buffer_range,
23082 ),
23083 is_created_file,
23084 }
23085 };
23086
23087 Some(display_hunk)
23088 })
23089 }
23090
23091 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23092 self.display_snapshot.buffer_snapshot.language_at(position)
23093 }
23094
23095 pub fn is_focused(&self) -> bool {
23096 self.is_focused
23097 }
23098
23099 pub fn placeholder_text(&self) -> Option<String> {
23100 self.placeholder_display_snapshot
23101 .as_ref()
23102 .map(|display_map| display_map.text())
23103 }
23104
23105 pub fn scroll_position(&self) -> gpui::Point<f32> {
23106 self.scroll_anchor.scroll_position(&self.display_snapshot)
23107 }
23108
23109 fn gutter_dimensions(
23110 &self,
23111 font_id: FontId,
23112 font_size: Pixels,
23113 max_line_number_width: Pixels,
23114 cx: &App,
23115 ) -> Option<GutterDimensions> {
23116 if !self.show_gutter {
23117 return None;
23118 }
23119
23120 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23121 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23122
23123 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23124 matches!(
23125 ProjectSettings::get_global(cx).git.git_gutter,
23126 GitGutterSetting::TrackedFiles
23127 )
23128 });
23129 let gutter_settings = EditorSettings::get_global(cx).gutter;
23130 let show_line_numbers = self
23131 .show_line_numbers
23132 .unwrap_or(gutter_settings.line_numbers);
23133 let line_gutter_width = if show_line_numbers {
23134 // Avoid flicker-like gutter resizes when the line number gains another digit by
23135 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23136 let min_width_for_number_on_gutter =
23137 ch_advance * gutter_settings.min_line_number_digits as f32;
23138 max_line_number_width.max(min_width_for_number_on_gutter)
23139 } else {
23140 0.0.into()
23141 };
23142
23143 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23144 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23145
23146 let git_blame_entries_width =
23147 self.git_blame_gutter_max_author_length
23148 .map(|max_author_length| {
23149 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23150 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23151
23152 /// The number of characters to dedicate to gaps and margins.
23153 const SPACING_WIDTH: usize = 4;
23154
23155 let max_char_count = max_author_length.min(renderer.max_author_length())
23156 + ::git::SHORT_SHA_LENGTH
23157 + MAX_RELATIVE_TIMESTAMP.len()
23158 + SPACING_WIDTH;
23159
23160 ch_advance * max_char_count
23161 });
23162
23163 let is_singleton = self.buffer_snapshot.is_singleton();
23164
23165 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23166 left_padding += if !is_singleton {
23167 ch_width * 4.0
23168 } else if show_runnables || show_breakpoints {
23169 ch_width * 3.0
23170 } else if show_git_gutter && show_line_numbers {
23171 ch_width * 2.0
23172 } else if show_git_gutter || show_line_numbers {
23173 ch_width
23174 } else {
23175 px(0.)
23176 };
23177
23178 let shows_folds = is_singleton && gutter_settings.folds;
23179
23180 let right_padding = if shows_folds && show_line_numbers {
23181 ch_width * 4.0
23182 } else if shows_folds || (!is_singleton && show_line_numbers) {
23183 ch_width * 3.0
23184 } else if show_line_numbers {
23185 ch_width
23186 } else {
23187 px(0.)
23188 };
23189
23190 Some(GutterDimensions {
23191 left_padding,
23192 right_padding,
23193 width: line_gutter_width + left_padding + right_padding,
23194 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23195 git_blame_entries_width,
23196 })
23197 }
23198
23199 pub fn render_crease_toggle(
23200 &self,
23201 buffer_row: MultiBufferRow,
23202 row_contains_cursor: bool,
23203 editor: Entity<Editor>,
23204 window: &mut Window,
23205 cx: &mut App,
23206 ) -> Option<AnyElement> {
23207 let folded = self.is_line_folded(buffer_row);
23208 let mut is_foldable = false;
23209
23210 if let Some(crease) = self
23211 .crease_snapshot
23212 .query_row(buffer_row, &self.buffer_snapshot)
23213 {
23214 is_foldable = true;
23215 match crease {
23216 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23217 if let Some(render_toggle) = render_toggle {
23218 let toggle_callback =
23219 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23220 if folded {
23221 editor.update(cx, |editor, cx| {
23222 editor.fold_at(buffer_row, window, cx)
23223 });
23224 } else {
23225 editor.update(cx, |editor, cx| {
23226 editor.unfold_at(buffer_row, window, cx)
23227 });
23228 }
23229 });
23230 return Some((render_toggle)(
23231 buffer_row,
23232 folded,
23233 toggle_callback,
23234 window,
23235 cx,
23236 ));
23237 }
23238 }
23239 }
23240 }
23241
23242 is_foldable |= self.starts_indent(buffer_row);
23243
23244 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23245 Some(
23246 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23247 .toggle_state(folded)
23248 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23249 if folded {
23250 this.unfold_at(buffer_row, window, cx);
23251 } else {
23252 this.fold_at(buffer_row, window, cx);
23253 }
23254 }))
23255 .into_any_element(),
23256 )
23257 } else {
23258 None
23259 }
23260 }
23261
23262 pub fn render_crease_trailer(
23263 &self,
23264 buffer_row: MultiBufferRow,
23265 window: &mut Window,
23266 cx: &mut App,
23267 ) -> Option<AnyElement> {
23268 let folded = self.is_line_folded(buffer_row);
23269 if let Crease::Inline { render_trailer, .. } = self
23270 .crease_snapshot
23271 .query_row(buffer_row, &self.buffer_snapshot)?
23272 {
23273 let render_trailer = render_trailer.as_ref()?;
23274 Some(render_trailer(buffer_row, folded, window, cx))
23275 } else {
23276 None
23277 }
23278 }
23279}
23280
23281impl Deref for EditorSnapshot {
23282 type Target = DisplaySnapshot;
23283
23284 fn deref(&self) -> &Self::Target {
23285 &self.display_snapshot
23286 }
23287}
23288
23289#[derive(Clone, Debug, PartialEq, Eq)]
23290pub enum EditorEvent {
23291 InputIgnored {
23292 text: Arc<str>,
23293 },
23294 InputHandled {
23295 utf16_range_to_replace: Option<Range<isize>>,
23296 text: Arc<str>,
23297 },
23298 ExcerptsAdded {
23299 buffer: Entity<Buffer>,
23300 predecessor: ExcerptId,
23301 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23302 },
23303 ExcerptsRemoved {
23304 ids: Vec<ExcerptId>,
23305 removed_buffer_ids: Vec<BufferId>,
23306 },
23307 BufferFoldToggled {
23308 ids: Vec<ExcerptId>,
23309 folded: bool,
23310 },
23311 ExcerptsEdited {
23312 ids: Vec<ExcerptId>,
23313 },
23314 ExcerptsExpanded {
23315 ids: Vec<ExcerptId>,
23316 },
23317 BufferEdited,
23318 Edited {
23319 transaction_id: clock::Lamport,
23320 },
23321 Reparsed(BufferId),
23322 Focused,
23323 FocusedIn,
23324 Blurred,
23325 DirtyChanged,
23326 Saved,
23327 TitleChanged,
23328 SelectionsChanged {
23329 local: bool,
23330 },
23331 ScrollPositionChanged {
23332 local: bool,
23333 autoscroll: bool,
23334 },
23335 TransactionUndone {
23336 transaction_id: clock::Lamport,
23337 },
23338 TransactionBegun {
23339 transaction_id: clock::Lamport,
23340 },
23341 CursorShapeChanged,
23342 BreadcrumbsChanged,
23343 PushedToNavHistory {
23344 anchor: Anchor,
23345 is_deactivate: bool,
23346 },
23347}
23348
23349impl EventEmitter<EditorEvent> for Editor {}
23350
23351impl Focusable for Editor {
23352 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23353 self.focus_handle.clone()
23354 }
23355}
23356
23357impl Render for Editor {
23358 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23359 let settings = ThemeSettings::get_global(cx);
23360
23361 let mut text_style = match self.mode {
23362 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23363 color: cx.theme().colors().editor_foreground,
23364 font_family: settings.ui_font.family.clone(),
23365 font_features: settings.ui_font.features.clone(),
23366 font_fallbacks: settings.ui_font.fallbacks.clone(),
23367 font_size: rems(0.875).into(),
23368 font_weight: settings.ui_font.weight,
23369 line_height: relative(settings.buffer_line_height.value()),
23370 ..Default::default()
23371 },
23372 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23373 color: cx.theme().colors().editor_foreground,
23374 font_family: settings.buffer_font.family.clone(),
23375 font_features: settings.buffer_font.features.clone(),
23376 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23377 font_size: settings.buffer_font_size(cx).into(),
23378 font_weight: settings.buffer_font.weight,
23379 line_height: relative(settings.buffer_line_height.value()),
23380 ..Default::default()
23381 },
23382 };
23383 if let Some(text_style_refinement) = &self.text_style_refinement {
23384 text_style.refine(text_style_refinement)
23385 }
23386
23387 let background = match self.mode {
23388 EditorMode::SingleLine => cx.theme().system().transparent,
23389 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23390 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23391 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23392 };
23393
23394 EditorElement::new(
23395 &cx.entity(),
23396 EditorStyle {
23397 background,
23398 border: cx.theme().colors().border,
23399 local_player: cx.theme().players().local(),
23400 text: text_style,
23401 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23402 syntax: cx.theme().syntax().clone(),
23403 status: cx.theme().status().clone(),
23404 inlay_hints_style: make_inlay_hints_style(cx),
23405 edit_prediction_styles: make_suggestion_styles(cx),
23406 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23407 show_underlines: self.diagnostics_enabled(),
23408 },
23409 )
23410 }
23411}
23412
23413impl EntityInputHandler for Editor {
23414 fn text_for_range(
23415 &mut self,
23416 range_utf16: Range<usize>,
23417 adjusted_range: &mut Option<Range<usize>>,
23418 _: &mut Window,
23419 cx: &mut Context<Self>,
23420 ) -> Option<String> {
23421 let snapshot = self.buffer.read(cx).read(cx);
23422 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23423 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23424 if (start.0..end.0) != range_utf16 {
23425 adjusted_range.replace(start.0..end.0);
23426 }
23427 Some(snapshot.text_for_range(start..end).collect())
23428 }
23429
23430 fn selected_text_range(
23431 &mut self,
23432 ignore_disabled_input: bool,
23433 _: &mut Window,
23434 cx: &mut Context<Self>,
23435 ) -> Option<UTF16Selection> {
23436 // Prevent the IME menu from appearing when holding down an alphabetic key
23437 // while input is disabled.
23438 if !ignore_disabled_input && !self.input_enabled {
23439 return None;
23440 }
23441
23442 let selection = self.selections.newest::<OffsetUtf16>(cx);
23443 let range = selection.range();
23444
23445 Some(UTF16Selection {
23446 range: range.start.0..range.end.0,
23447 reversed: selection.reversed,
23448 })
23449 }
23450
23451 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23452 let snapshot = self.buffer.read(cx).read(cx);
23453 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23454 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23455 }
23456
23457 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23458 self.clear_highlights::<InputComposition>(cx);
23459 self.ime_transaction.take();
23460 }
23461
23462 fn replace_text_in_range(
23463 &mut self,
23464 range_utf16: Option<Range<usize>>,
23465 text: &str,
23466 window: &mut Window,
23467 cx: &mut Context<Self>,
23468 ) {
23469 if !self.input_enabled {
23470 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23471 return;
23472 }
23473
23474 self.transact(window, cx, |this, window, cx| {
23475 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23476 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23477 Some(this.selection_replacement_ranges(range_utf16, cx))
23478 } else {
23479 this.marked_text_ranges(cx)
23480 };
23481
23482 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23483 let newest_selection_id = this.selections.newest_anchor().id;
23484 this.selections
23485 .all::<OffsetUtf16>(cx)
23486 .iter()
23487 .zip(ranges_to_replace.iter())
23488 .find_map(|(selection, range)| {
23489 if selection.id == newest_selection_id {
23490 Some(
23491 (range.start.0 as isize - selection.head().0 as isize)
23492 ..(range.end.0 as isize - selection.head().0 as isize),
23493 )
23494 } else {
23495 None
23496 }
23497 })
23498 });
23499
23500 cx.emit(EditorEvent::InputHandled {
23501 utf16_range_to_replace: range_to_replace,
23502 text: text.into(),
23503 });
23504
23505 if let Some(new_selected_ranges) = new_selected_ranges {
23506 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23507 selections.select_ranges(new_selected_ranges)
23508 });
23509 this.backspace(&Default::default(), window, cx);
23510 }
23511
23512 this.handle_input(text, window, cx);
23513 });
23514
23515 if let Some(transaction) = self.ime_transaction {
23516 self.buffer.update(cx, |buffer, cx| {
23517 buffer.group_until_transaction(transaction, cx);
23518 });
23519 }
23520
23521 self.unmark_text(window, cx);
23522 }
23523
23524 fn replace_and_mark_text_in_range(
23525 &mut self,
23526 range_utf16: Option<Range<usize>>,
23527 text: &str,
23528 new_selected_range_utf16: Option<Range<usize>>,
23529 window: &mut Window,
23530 cx: &mut Context<Self>,
23531 ) {
23532 if !self.input_enabled {
23533 return;
23534 }
23535
23536 let transaction = self.transact(window, cx, |this, window, cx| {
23537 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23538 let snapshot = this.buffer.read(cx).read(cx);
23539 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23540 for marked_range in &mut marked_ranges {
23541 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23542 marked_range.start.0 += relative_range_utf16.start;
23543 marked_range.start =
23544 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23545 marked_range.end =
23546 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23547 }
23548 }
23549 Some(marked_ranges)
23550 } else if let Some(range_utf16) = range_utf16 {
23551 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23552 Some(this.selection_replacement_ranges(range_utf16, cx))
23553 } else {
23554 None
23555 };
23556
23557 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23558 let newest_selection_id = this.selections.newest_anchor().id;
23559 this.selections
23560 .all::<OffsetUtf16>(cx)
23561 .iter()
23562 .zip(ranges_to_replace.iter())
23563 .find_map(|(selection, range)| {
23564 if selection.id == newest_selection_id {
23565 Some(
23566 (range.start.0 as isize - selection.head().0 as isize)
23567 ..(range.end.0 as isize - selection.head().0 as isize),
23568 )
23569 } else {
23570 None
23571 }
23572 })
23573 });
23574
23575 cx.emit(EditorEvent::InputHandled {
23576 utf16_range_to_replace: range_to_replace,
23577 text: text.into(),
23578 });
23579
23580 if let Some(ranges) = ranges_to_replace {
23581 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23582 s.select_ranges(ranges)
23583 });
23584 }
23585
23586 let marked_ranges = {
23587 let snapshot = this.buffer.read(cx).read(cx);
23588 this.selections
23589 .disjoint_anchors_arc()
23590 .iter()
23591 .map(|selection| {
23592 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23593 })
23594 .collect::<Vec<_>>()
23595 };
23596
23597 if text.is_empty() {
23598 this.unmark_text(window, cx);
23599 } else {
23600 this.highlight_text::<InputComposition>(
23601 marked_ranges.clone(),
23602 HighlightStyle {
23603 underline: Some(UnderlineStyle {
23604 thickness: px(1.),
23605 color: None,
23606 wavy: false,
23607 }),
23608 ..Default::default()
23609 },
23610 cx,
23611 );
23612 }
23613
23614 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23615 let use_autoclose = this.use_autoclose;
23616 let use_auto_surround = this.use_auto_surround;
23617 this.set_use_autoclose(false);
23618 this.set_use_auto_surround(false);
23619 this.handle_input(text, window, cx);
23620 this.set_use_autoclose(use_autoclose);
23621 this.set_use_auto_surround(use_auto_surround);
23622
23623 if let Some(new_selected_range) = new_selected_range_utf16 {
23624 let snapshot = this.buffer.read(cx).read(cx);
23625 let new_selected_ranges = marked_ranges
23626 .into_iter()
23627 .map(|marked_range| {
23628 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23629 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23630 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23631 snapshot.clip_offset_utf16(new_start, Bias::Left)
23632 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23633 })
23634 .collect::<Vec<_>>();
23635
23636 drop(snapshot);
23637 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23638 selections.select_ranges(new_selected_ranges)
23639 });
23640 }
23641 });
23642
23643 self.ime_transaction = self.ime_transaction.or(transaction);
23644 if let Some(transaction) = self.ime_transaction {
23645 self.buffer.update(cx, |buffer, cx| {
23646 buffer.group_until_transaction(transaction, cx);
23647 });
23648 }
23649
23650 if self.text_highlights::<InputComposition>(cx).is_none() {
23651 self.ime_transaction.take();
23652 }
23653 }
23654
23655 fn bounds_for_range(
23656 &mut self,
23657 range_utf16: Range<usize>,
23658 element_bounds: gpui::Bounds<Pixels>,
23659 window: &mut Window,
23660 cx: &mut Context<Self>,
23661 ) -> Option<gpui::Bounds<Pixels>> {
23662 let text_layout_details = self.text_layout_details(window);
23663 let CharacterDimensions {
23664 em_width,
23665 em_advance,
23666 line_height,
23667 } = self.character_dimensions(window);
23668
23669 let snapshot = self.snapshot(window, cx);
23670 let scroll_position = snapshot.scroll_position();
23671 let scroll_left = scroll_position.x * em_advance;
23672
23673 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23674 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23675 + self.gutter_dimensions.full_width();
23676 let y = line_height * (start.row().as_f32() - scroll_position.y);
23677
23678 Some(Bounds {
23679 origin: element_bounds.origin + point(x, y),
23680 size: size(em_width, line_height),
23681 })
23682 }
23683
23684 fn character_index_for_point(
23685 &mut self,
23686 point: gpui::Point<Pixels>,
23687 _window: &mut Window,
23688 _cx: &mut Context<Self>,
23689 ) -> Option<usize> {
23690 let position_map = self.last_position_map.as_ref()?;
23691 if !position_map.text_hitbox.contains(&point) {
23692 return None;
23693 }
23694 let display_point = position_map.point_for_position(point).previous_valid;
23695 let anchor = position_map
23696 .snapshot
23697 .display_point_to_anchor(display_point, Bias::Left);
23698 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23699 Some(utf16_offset.0)
23700 }
23701}
23702
23703trait SelectionExt {
23704 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23705 fn spanned_rows(
23706 &self,
23707 include_end_if_at_line_start: bool,
23708 map: &DisplaySnapshot,
23709 ) -> Range<MultiBufferRow>;
23710}
23711
23712impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23713 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23714 let start = self
23715 .start
23716 .to_point(&map.buffer_snapshot)
23717 .to_display_point(map);
23718 let end = self
23719 .end
23720 .to_point(&map.buffer_snapshot)
23721 .to_display_point(map);
23722 if self.reversed {
23723 end..start
23724 } else {
23725 start..end
23726 }
23727 }
23728
23729 fn spanned_rows(
23730 &self,
23731 include_end_if_at_line_start: bool,
23732 map: &DisplaySnapshot,
23733 ) -> Range<MultiBufferRow> {
23734 let start = self.start.to_point(&map.buffer_snapshot);
23735 let mut end = self.end.to_point(&map.buffer_snapshot);
23736 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23737 end.row -= 1;
23738 }
23739
23740 let buffer_start = map.prev_line_boundary(start).0;
23741 let buffer_end = map.next_line_boundary(end).0;
23742 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23743 }
23744}
23745
23746impl<T: InvalidationRegion> InvalidationStack<T> {
23747 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23748 where
23749 S: Clone + ToOffset,
23750 {
23751 while let Some(region) = self.last() {
23752 let all_selections_inside_invalidation_ranges =
23753 if selections.len() == region.ranges().len() {
23754 selections
23755 .iter()
23756 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23757 .all(|(selection, invalidation_range)| {
23758 let head = selection.head().to_offset(buffer);
23759 invalidation_range.start <= head && invalidation_range.end >= head
23760 })
23761 } else {
23762 false
23763 };
23764
23765 if all_selections_inside_invalidation_ranges {
23766 break;
23767 } else {
23768 self.pop();
23769 }
23770 }
23771 }
23772}
23773
23774impl<T> Default for InvalidationStack<T> {
23775 fn default() -> Self {
23776 Self(Default::default())
23777 }
23778}
23779
23780impl<T> Deref for InvalidationStack<T> {
23781 type Target = Vec<T>;
23782
23783 fn deref(&self) -> &Self::Target {
23784 &self.0
23785 }
23786}
23787
23788impl<T> DerefMut for InvalidationStack<T> {
23789 fn deref_mut(&mut self) -> &mut Self::Target {
23790 &mut self.0
23791 }
23792}
23793
23794impl InvalidationRegion for SnippetState {
23795 fn ranges(&self) -> &[Range<Anchor>] {
23796 &self.ranges[self.active_index]
23797 }
23798}
23799
23800fn edit_prediction_edit_text(
23801 current_snapshot: &BufferSnapshot,
23802 edits: &[(Range<Anchor>, String)],
23803 edit_preview: &EditPreview,
23804 include_deletions: bool,
23805 cx: &App,
23806) -> HighlightedText {
23807 let edits = edits
23808 .iter()
23809 .map(|(anchor, text)| {
23810 (
23811 anchor.start.text_anchor..anchor.end.text_anchor,
23812 text.clone(),
23813 )
23814 })
23815 .collect::<Vec<_>>();
23816
23817 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23818}
23819
23820fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23821 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23822 // Just show the raw edit text with basic styling
23823 let mut text = String::new();
23824 let mut highlights = Vec::new();
23825
23826 let insertion_highlight_style = HighlightStyle {
23827 color: Some(cx.theme().colors().text),
23828 ..Default::default()
23829 };
23830
23831 for (_, edit_text) in edits {
23832 let start_offset = text.len();
23833 text.push_str(edit_text);
23834 let end_offset = text.len();
23835
23836 if start_offset < end_offset {
23837 highlights.push((start_offset..end_offset, insertion_highlight_style));
23838 }
23839 }
23840
23841 HighlightedText {
23842 text: text.into(),
23843 highlights,
23844 }
23845}
23846
23847pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23848 match severity {
23849 lsp::DiagnosticSeverity::ERROR => colors.error,
23850 lsp::DiagnosticSeverity::WARNING => colors.warning,
23851 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23852 lsp::DiagnosticSeverity::HINT => colors.info,
23853 _ => colors.ignored,
23854 }
23855}
23856
23857pub fn styled_runs_for_code_label<'a>(
23858 label: &'a CodeLabel,
23859 syntax_theme: &'a theme::SyntaxTheme,
23860) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23861 let fade_out = HighlightStyle {
23862 fade_out: Some(0.35),
23863 ..Default::default()
23864 };
23865
23866 let mut prev_end = label.filter_range.end;
23867 label
23868 .runs
23869 .iter()
23870 .enumerate()
23871 .flat_map(move |(ix, (range, highlight_id))| {
23872 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23873 style
23874 } else {
23875 return Default::default();
23876 };
23877 let muted_style = style.highlight(fade_out);
23878
23879 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23880 if range.start >= label.filter_range.end {
23881 if range.start > prev_end {
23882 runs.push((prev_end..range.start, fade_out));
23883 }
23884 runs.push((range.clone(), muted_style));
23885 } else if range.end <= label.filter_range.end {
23886 runs.push((range.clone(), style));
23887 } else {
23888 runs.push((range.start..label.filter_range.end, style));
23889 runs.push((label.filter_range.end..range.end, muted_style));
23890 }
23891 prev_end = cmp::max(prev_end, range.end);
23892
23893 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23894 runs.push((prev_end..label.text.len(), fade_out));
23895 }
23896
23897 runs
23898 })
23899}
23900
23901pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23902 let mut prev_index = 0;
23903 let mut prev_codepoint: Option<char> = None;
23904 text.char_indices()
23905 .chain([(text.len(), '\0')])
23906 .filter_map(move |(index, codepoint)| {
23907 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23908 let is_boundary = index == text.len()
23909 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23910 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23911 if is_boundary {
23912 let chunk = &text[prev_index..index];
23913 prev_index = index;
23914 Some(chunk)
23915 } else {
23916 None
23917 }
23918 })
23919}
23920
23921pub trait RangeToAnchorExt: Sized {
23922 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23923
23924 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23925 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23926 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23927 }
23928}
23929
23930impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23931 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23932 let start_offset = self.start.to_offset(snapshot);
23933 let end_offset = self.end.to_offset(snapshot);
23934 if start_offset == end_offset {
23935 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23936 } else {
23937 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23938 }
23939 }
23940}
23941
23942pub trait RowExt {
23943 fn as_f32(&self) -> f32;
23944
23945 fn next_row(&self) -> Self;
23946
23947 fn previous_row(&self) -> Self;
23948
23949 fn minus(&self, other: Self) -> u32;
23950}
23951
23952impl RowExt for DisplayRow {
23953 fn as_f32(&self) -> f32 {
23954 self.0 as f32
23955 }
23956
23957 fn next_row(&self) -> Self {
23958 Self(self.0 + 1)
23959 }
23960
23961 fn previous_row(&self) -> Self {
23962 Self(self.0.saturating_sub(1))
23963 }
23964
23965 fn minus(&self, other: Self) -> u32 {
23966 self.0 - other.0
23967 }
23968}
23969
23970impl RowExt for MultiBufferRow {
23971 fn as_f32(&self) -> f32 {
23972 self.0 as f32
23973 }
23974
23975 fn next_row(&self) -> Self {
23976 Self(self.0 + 1)
23977 }
23978
23979 fn previous_row(&self) -> Self {
23980 Self(self.0.saturating_sub(1))
23981 }
23982
23983 fn minus(&self, other: Self) -> u32 {
23984 self.0 - other.0
23985 }
23986}
23987
23988trait RowRangeExt {
23989 type Row;
23990
23991 fn len(&self) -> usize;
23992
23993 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23994}
23995
23996impl RowRangeExt for Range<MultiBufferRow> {
23997 type Row = MultiBufferRow;
23998
23999 fn len(&self) -> usize {
24000 (self.end.0 - self.start.0) as usize
24001 }
24002
24003 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24004 (self.start.0..self.end.0).map(MultiBufferRow)
24005 }
24006}
24007
24008impl RowRangeExt for Range<DisplayRow> {
24009 type Row = DisplayRow;
24010
24011 fn len(&self) -> usize {
24012 (self.end.0 - self.start.0) as usize
24013 }
24014
24015 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24016 (self.start.0..self.end.0).map(DisplayRow)
24017 }
24018}
24019
24020/// If select range has more than one line, we
24021/// just point the cursor to range.start.
24022fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24023 if range.start.row == range.end.row {
24024 range
24025 } else {
24026 range.start..range.start
24027 }
24028}
24029pub struct KillRing(ClipboardItem);
24030impl Global for KillRing {}
24031
24032const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24033
24034enum BreakpointPromptEditAction {
24035 Log,
24036 Condition,
24037 HitCondition,
24038}
24039
24040struct BreakpointPromptEditor {
24041 pub(crate) prompt: Entity<Editor>,
24042 editor: WeakEntity<Editor>,
24043 breakpoint_anchor: Anchor,
24044 breakpoint: Breakpoint,
24045 edit_action: BreakpointPromptEditAction,
24046 block_ids: HashSet<CustomBlockId>,
24047 editor_margins: Arc<Mutex<EditorMargins>>,
24048 _subscriptions: Vec<Subscription>,
24049}
24050
24051impl BreakpointPromptEditor {
24052 const MAX_LINES: u8 = 4;
24053
24054 fn new(
24055 editor: WeakEntity<Editor>,
24056 breakpoint_anchor: Anchor,
24057 breakpoint: Breakpoint,
24058 edit_action: BreakpointPromptEditAction,
24059 window: &mut Window,
24060 cx: &mut Context<Self>,
24061 ) -> Self {
24062 let base_text = match edit_action {
24063 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24064 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24065 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24066 }
24067 .map(|msg| msg.to_string())
24068 .unwrap_or_default();
24069
24070 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24071 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24072
24073 let prompt = cx.new(|cx| {
24074 let mut prompt = Editor::new(
24075 EditorMode::AutoHeight {
24076 min_lines: 1,
24077 max_lines: Some(Self::MAX_LINES as usize),
24078 },
24079 buffer,
24080 None,
24081 window,
24082 cx,
24083 );
24084 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24085 prompt.set_show_cursor_when_unfocused(false, cx);
24086 prompt.set_placeholder_text(
24087 match edit_action {
24088 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24089 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24090 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24091 },
24092 window,
24093 cx,
24094 );
24095
24096 prompt
24097 });
24098
24099 Self {
24100 prompt,
24101 editor,
24102 breakpoint_anchor,
24103 breakpoint,
24104 edit_action,
24105 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24106 block_ids: Default::default(),
24107 _subscriptions: vec![],
24108 }
24109 }
24110
24111 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24112 self.block_ids.extend(block_ids)
24113 }
24114
24115 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24116 if let Some(editor) = self.editor.upgrade() {
24117 let message = self
24118 .prompt
24119 .read(cx)
24120 .buffer
24121 .read(cx)
24122 .as_singleton()
24123 .expect("A multi buffer in breakpoint prompt isn't possible")
24124 .read(cx)
24125 .as_rope()
24126 .to_string();
24127
24128 editor.update(cx, |editor, cx| {
24129 editor.edit_breakpoint_at_anchor(
24130 self.breakpoint_anchor,
24131 self.breakpoint.clone(),
24132 match self.edit_action {
24133 BreakpointPromptEditAction::Log => {
24134 BreakpointEditAction::EditLogMessage(message.into())
24135 }
24136 BreakpointPromptEditAction::Condition => {
24137 BreakpointEditAction::EditCondition(message.into())
24138 }
24139 BreakpointPromptEditAction::HitCondition => {
24140 BreakpointEditAction::EditHitCondition(message.into())
24141 }
24142 },
24143 cx,
24144 );
24145
24146 editor.remove_blocks(self.block_ids.clone(), None, cx);
24147 cx.focus_self(window);
24148 });
24149 }
24150 }
24151
24152 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24153 self.editor
24154 .update(cx, |editor, cx| {
24155 editor.remove_blocks(self.block_ids.clone(), None, cx);
24156 window.focus(&editor.focus_handle);
24157 })
24158 .log_err();
24159 }
24160
24161 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24162 let settings = ThemeSettings::get_global(cx);
24163 let text_style = TextStyle {
24164 color: if self.prompt.read(cx).read_only(cx) {
24165 cx.theme().colors().text_disabled
24166 } else {
24167 cx.theme().colors().text
24168 },
24169 font_family: settings.buffer_font.family.clone(),
24170 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24171 font_size: settings.buffer_font_size(cx).into(),
24172 font_weight: settings.buffer_font.weight,
24173 line_height: relative(settings.buffer_line_height.value()),
24174 ..Default::default()
24175 };
24176 EditorElement::new(
24177 &self.prompt,
24178 EditorStyle {
24179 background: cx.theme().colors().editor_background,
24180 local_player: cx.theme().players().local(),
24181 text: text_style,
24182 ..Default::default()
24183 },
24184 )
24185 }
24186}
24187
24188impl Render for BreakpointPromptEditor {
24189 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24190 let editor_margins = *self.editor_margins.lock();
24191 let gutter_dimensions = editor_margins.gutter;
24192 h_flex()
24193 .key_context("Editor")
24194 .bg(cx.theme().colors().editor_background)
24195 .border_y_1()
24196 .border_color(cx.theme().status().info_border)
24197 .size_full()
24198 .py(window.line_height() / 2.5)
24199 .on_action(cx.listener(Self::confirm))
24200 .on_action(cx.listener(Self::cancel))
24201 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24202 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24203 }
24204}
24205
24206impl Focusable for BreakpointPromptEditor {
24207 fn focus_handle(&self, cx: &App) -> FocusHandle {
24208 self.prompt.focus_handle(cx)
24209 }
24210}
24211
24212fn all_edits_insertions_or_deletions(
24213 edits: &Vec<(Range<Anchor>, String)>,
24214 snapshot: &MultiBufferSnapshot,
24215) -> bool {
24216 let mut all_insertions = true;
24217 let mut all_deletions = true;
24218
24219 for (range, new_text) in edits.iter() {
24220 let range_is_empty = range.to_offset(snapshot).is_empty();
24221 let text_is_empty = new_text.is_empty();
24222
24223 if range_is_empty != text_is_empty {
24224 if range_is_empty {
24225 all_deletions = false;
24226 } else {
24227 all_insertions = false;
24228 }
24229 } else {
24230 return false;
24231 }
24232
24233 if !all_insertions && !all_deletions {
24234 return false;
24235 }
24236 }
24237 all_insertions || all_deletions
24238}
24239
24240struct MissingEditPredictionKeybindingTooltip;
24241
24242impl Render for MissingEditPredictionKeybindingTooltip {
24243 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24244 ui::tooltip_container(window, cx, |container, _, cx| {
24245 container
24246 .flex_shrink_0()
24247 .max_w_80()
24248 .min_h(rems_from_px(124.))
24249 .justify_between()
24250 .child(
24251 v_flex()
24252 .flex_1()
24253 .text_ui_sm(cx)
24254 .child(Label::new("Conflict with Accept Keybinding"))
24255 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24256 )
24257 .child(
24258 h_flex()
24259 .pb_1()
24260 .gap_1()
24261 .items_end()
24262 .w_full()
24263 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24264 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24265 }))
24266 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24267 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24268 })),
24269 )
24270 })
24271 }
24272}
24273
24274#[derive(Debug, Clone, Copy, PartialEq)]
24275pub struct LineHighlight {
24276 pub background: Background,
24277 pub border: Option<gpui::Hsla>,
24278 pub include_gutter: bool,
24279 pub type_id: Option<TypeId>,
24280}
24281
24282struct LineManipulationResult {
24283 pub new_text: String,
24284 pub line_count_before: usize,
24285 pub line_count_after: usize,
24286}
24287
24288fn render_diff_hunk_controls(
24289 row: u32,
24290 status: &DiffHunkStatus,
24291 hunk_range: Range<Anchor>,
24292 is_created_file: bool,
24293 line_height: Pixels,
24294 editor: &Entity<Editor>,
24295 _window: &mut Window,
24296 cx: &mut App,
24297) -> AnyElement {
24298 h_flex()
24299 .h(line_height)
24300 .mr_1()
24301 .gap_1()
24302 .px_0p5()
24303 .pb_1()
24304 .border_x_1()
24305 .border_b_1()
24306 .border_color(cx.theme().colors().border_variant)
24307 .rounded_b_lg()
24308 .bg(cx.theme().colors().editor_background)
24309 .gap_1()
24310 .block_mouse_except_scroll()
24311 .shadow_md()
24312 .child(if status.has_secondary_hunk() {
24313 Button::new(("stage", row as u64), "Stage")
24314 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24315 .tooltip({
24316 let focus_handle = editor.focus_handle(cx);
24317 move |window, cx| {
24318 Tooltip::for_action_in(
24319 "Stage Hunk",
24320 &::git::ToggleStaged,
24321 &focus_handle,
24322 window,
24323 cx,
24324 )
24325 }
24326 })
24327 .on_click({
24328 let editor = editor.clone();
24329 move |_event, _window, cx| {
24330 editor.update(cx, |editor, cx| {
24331 editor.stage_or_unstage_diff_hunks(
24332 true,
24333 vec![hunk_range.start..hunk_range.start],
24334 cx,
24335 );
24336 });
24337 }
24338 })
24339 } else {
24340 Button::new(("unstage", row as u64), "Unstage")
24341 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24342 .tooltip({
24343 let focus_handle = editor.focus_handle(cx);
24344 move |window, cx| {
24345 Tooltip::for_action_in(
24346 "Unstage Hunk",
24347 &::git::ToggleStaged,
24348 &focus_handle,
24349 window,
24350 cx,
24351 )
24352 }
24353 })
24354 .on_click({
24355 let editor = editor.clone();
24356 move |_event, _window, cx| {
24357 editor.update(cx, |editor, cx| {
24358 editor.stage_or_unstage_diff_hunks(
24359 false,
24360 vec![hunk_range.start..hunk_range.start],
24361 cx,
24362 );
24363 });
24364 }
24365 })
24366 })
24367 .child(
24368 Button::new(("restore", row as u64), "Restore")
24369 .tooltip({
24370 let focus_handle = editor.focus_handle(cx);
24371 move |window, cx| {
24372 Tooltip::for_action_in(
24373 "Restore Hunk",
24374 &::git::Restore,
24375 &focus_handle,
24376 window,
24377 cx,
24378 )
24379 }
24380 })
24381 .on_click({
24382 let editor = editor.clone();
24383 move |_event, window, cx| {
24384 editor.update(cx, |editor, cx| {
24385 let snapshot = editor.snapshot(window, cx);
24386 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24387 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24388 });
24389 }
24390 })
24391 .disabled(is_created_file),
24392 )
24393 .when(
24394 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24395 |el| {
24396 el.child(
24397 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24398 .shape(IconButtonShape::Square)
24399 .icon_size(IconSize::Small)
24400 // .disabled(!has_multiple_hunks)
24401 .tooltip({
24402 let focus_handle = editor.focus_handle(cx);
24403 move |window, cx| {
24404 Tooltip::for_action_in(
24405 "Next Hunk",
24406 &GoToHunk,
24407 &focus_handle,
24408 window,
24409 cx,
24410 )
24411 }
24412 })
24413 .on_click({
24414 let editor = editor.clone();
24415 move |_event, window, cx| {
24416 editor.update(cx, |editor, cx| {
24417 let snapshot = editor.snapshot(window, cx);
24418 let position =
24419 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24420 editor.go_to_hunk_before_or_after_position(
24421 &snapshot,
24422 position,
24423 Direction::Next,
24424 window,
24425 cx,
24426 );
24427 editor.expand_selected_diff_hunks(cx);
24428 });
24429 }
24430 }),
24431 )
24432 .child(
24433 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24434 .shape(IconButtonShape::Square)
24435 .icon_size(IconSize::Small)
24436 // .disabled(!has_multiple_hunks)
24437 .tooltip({
24438 let focus_handle = editor.focus_handle(cx);
24439 move |window, cx| {
24440 Tooltip::for_action_in(
24441 "Previous Hunk",
24442 &GoToPreviousHunk,
24443 &focus_handle,
24444 window,
24445 cx,
24446 )
24447 }
24448 })
24449 .on_click({
24450 let editor = editor.clone();
24451 move |_event, window, cx| {
24452 editor.update(cx, |editor, cx| {
24453 let snapshot = editor.snapshot(window, cx);
24454 let point =
24455 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24456 editor.go_to_hunk_before_or_after_position(
24457 &snapshot,
24458 point,
24459 Direction::Prev,
24460 window,
24461 cx,
24462 );
24463 editor.expand_selected_diff_hunks(cx);
24464 });
24465 }
24466 }),
24467 )
24468 },
24469 )
24470 .into_any_element()
24471}
24472
24473pub fn multibuffer_context_lines(cx: &App) -> u32 {
24474 EditorSettings::try_get(cx)
24475 .map(|settings| settings.excerpt_context_lines)
24476 .unwrap_or(2)
24477 .min(32)
24478}