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, parse_zed_link};
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, CharScopeContext, CodeLabel, CursorShape,
125 DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
126 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
127 TextObject, 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::{self, 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 let mut style = cx.theme().syntax().get("hint");
596
597 if style.color.is_none() {
598 style.color = Some(cx.theme().status().hint);
599 }
600
601 if !show_background {
602 style.background_color = None;
603 return style;
604 }
605
606 if style.background_color.is_none() {
607 style.background_color = Some(cx.theme().status().hint_background);
608 }
609
610 style
611}
612
613pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
614 EditPredictionStyles {
615 insertion: HighlightStyle {
616 color: Some(cx.theme().status().predictive),
617 ..HighlightStyle::default()
618 },
619 whitespace: HighlightStyle {
620 background_color: Some(cx.theme().status().created_background),
621 ..HighlightStyle::default()
622 },
623 }
624}
625
626type CompletionId = usize;
627
628pub(crate) enum EditDisplayMode {
629 TabAccept,
630 DiffPopover,
631 Inline,
632}
633
634enum EditPrediction {
635 Edit {
636 edits: Vec<(Range<Anchor>, String)>,
637 edit_preview: Option<EditPreview>,
638 display_mode: EditDisplayMode,
639 snapshot: BufferSnapshot,
640 },
641 Move {
642 target: Anchor,
643 snapshot: BufferSnapshot,
644 },
645}
646
647struct EditPredictionState {
648 inlay_ids: Vec<InlayId>,
649 completion: EditPrediction,
650 completion_id: Option<SharedString>,
651 invalidation_range: Range<Anchor>,
652}
653
654enum EditPredictionSettings {
655 Disabled,
656 Enabled {
657 show_in_menu: bool,
658 preview_requires_modifier: bool,
659 },
660}
661
662enum EditPredictionHighlight {}
663
664#[derive(Debug, Clone)]
665struct InlineDiagnostic {
666 message: SharedString,
667 group_id: usize,
668 is_primary: bool,
669 start: Point,
670 severity: lsp::DiagnosticSeverity,
671}
672
673pub enum MenuEditPredictionsPolicy {
674 Never,
675 ByProvider,
676}
677
678pub enum EditPredictionPreview {
679 /// Modifier is not pressed
680 Inactive { released_too_fast: bool },
681 /// Modifier pressed
682 Active {
683 since: Instant,
684 previous_scroll_position: Option<ScrollAnchor>,
685 },
686}
687
688impl EditPredictionPreview {
689 pub fn released_too_fast(&self) -> bool {
690 match self {
691 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
692 EditPredictionPreview::Active { .. } => false,
693 }
694 }
695
696 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
697 if let EditPredictionPreview::Active {
698 previous_scroll_position,
699 ..
700 } = self
701 {
702 *previous_scroll_position = scroll_position;
703 }
704 }
705}
706
707pub struct ContextMenuOptions {
708 pub min_entries_visible: usize,
709 pub max_entries_visible: usize,
710 pub placement: Option<ContextMenuPlacement>,
711}
712
713#[derive(Debug, Clone, PartialEq, Eq)]
714pub enum ContextMenuPlacement {
715 Above,
716 Below,
717}
718
719#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
720struct EditorActionId(usize);
721
722impl EditorActionId {
723 pub fn post_inc(&mut self) -> Self {
724 let answer = self.0;
725
726 *self = Self(answer + 1);
727
728 Self(answer)
729 }
730}
731
732// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
733// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
734
735type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
736type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
737
738#[derive(Default)]
739struct ScrollbarMarkerState {
740 scrollbar_size: Size<Pixels>,
741 dirty: bool,
742 markers: Arc<[PaintQuad]>,
743 pending_refresh: Option<Task<Result<()>>>,
744}
745
746impl ScrollbarMarkerState {
747 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
748 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
749 }
750}
751
752#[derive(Clone, Copy, PartialEq, Eq)]
753pub enum MinimapVisibility {
754 Disabled,
755 Enabled {
756 /// The configuration currently present in the users settings.
757 setting_configuration: bool,
758 /// Whether to override the currently set visibility from the users setting.
759 toggle_override: bool,
760 },
761}
762
763impl MinimapVisibility {
764 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
765 if mode.is_full() {
766 Self::Enabled {
767 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
768 toggle_override: false,
769 }
770 } else {
771 Self::Disabled
772 }
773 }
774
775 fn hidden(&self) -> Self {
776 match *self {
777 Self::Enabled {
778 setting_configuration,
779 ..
780 } => Self::Enabled {
781 setting_configuration,
782 toggle_override: setting_configuration,
783 },
784 Self::Disabled => Self::Disabled,
785 }
786 }
787
788 fn disabled(&self) -> bool {
789 matches!(*self, Self::Disabled)
790 }
791
792 fn settings_visibility(&self) -> bool {
793 match *self {
794 Self::Enabled {
795 setting_configuration,
796 ..
797 } => setting_configuration,
798 _ => false,
799 }
800 }
801
802 fn visible(&self) -> bool {
803 match *self {
804 Self::Enabled {
805 setting_configuration,
806 toggle_override,
807 } => setting_configuration ^ toggle_override,
808 _ => false,
809 }
810 }
811
812 fn toggle_visibility(&self) -> Self {
813 match *self {
814 Self::Enabled {
815 toggle_override,
816 setting_configuration,
817 } => Self::Enabled {
818 setting_configuration,
819 toggle_override: !toggle_override,
820 },
821 Self::Disabled => Self::Disabled,
822 }
823 }
824}
825
826#[derive(Clone, Debug)]
827struct RunnableTasks {
828 templates: Vec<(TaskSourceKind, TaskTemplate)>,
829 offset: multi_buffer::Anchor,
830 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
831 column: u32,
832 // Values of all named captures, including those starting with '_'
833 extra_variables: HashMap<String, String>,
834 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
835 context_range: Range<BufferOffset>,
836}
837
838impl RunnableTasks {
839 fn resolve<'a>(
840 &'a self,
841 cx: &'a task::TaskContext,
842 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
843 self.templates.iter().filter_map(|(kind, template)| {
844 template
845 .resolve_task(&kind.to_id_base(), cx)
846 .map(|task| (kind.clone(), task))
847 })
848 }
849}
850
851#[derive(Clone)]
852pub struct ResolvedTasks {
853 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
854 position: Anchor,
855}
856
857#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
858struct BufferOffset(usize);
859
860/// Addons allow storing per-editor state in other crates (e.g. Vim)
861pub trait Addon: 'static {
862 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
863
864 fn render_buffer_header_controls(
865 &self,
866 _: &ExcerptInfo,
867 _: &Window,
868 _: &App,
869 ) -> Option<AnyElement> {
870 None
871 }
872
873 fn to_any(&self) -> &dyn std::any::Any;
874
875 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
876 None
877 }
878}
879
880struct ChangeLocation {
881 current: Option<Vec<Anchor>>,
882 original: Vec<Anchor>,
883}
884impl ChangeLocation {
885 fn locations(&self) -> &[Anchor] {
886 self.current.as_ref().unwrap_or(&self.original)
887 }
888}
889
890/// A set of caret positions, registered when the editor was edited.
891pub struct ChangeList {
892 changes: Vec<ChangeLocation>,
893 /// Currently "selected" change.
894 position: Option<usize>,
895}
896
897impl ChangeList {
898 pub fn new() -> Self {
899 Self {
900 changes: Vec::new(),
901 position: None,
902 }
903 }
904
905 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
906 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
907 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
908 if self.changes.is_empty() {
909 return None;
910 }
911
912 let prev = self.position.unwrap_or(self.changes.len());
913 let next = if direction == Direction::Prev {
914 prev.saturating_sub(count)
915 } else {
916 (prev + count).min(self.changes.len() - 1)
917 };
918 self.position = Some(next);
919 self.changes.get(next).map(|change| change.locations())
920 }
921
922 /// Adds a new change to the list, resetting the change list position.
923 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
924 self.position.take();
925 if let Some(last) = self.changes.last_mut()
926 && group
927 {
928 last.current = Some(new_positions)
929 } else {
930 self.changes.push(ChangeLocation {
931 original: new_positions,
932 current: None,
933 });
934 }
935 }
936
937 pub fn last(&self) -> Option<&[Anchor]> {
938 self.changes.last().map(|change| change.locations())
939 }
940
941 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
942 self.changes.last().map(|change| change.original.as_slice())
943 }
944
945 pub fn invert_last_group(&mut self) {
946 if let Some(last) = self.changes.last_mut()
947 && let Some(current) = last.current.as_mut()
948 {
949 mem::swap(&mut last.original, current);
950 }
951 }
952}
953
954#[derive(Clone)]
955struct InlineBlamePopoverState {
956 scroll_handle: ScrollHandle,
957 commit_message: Option<ParsedCommitMessage>,
958 markdown: Entity<Markdown>,
959}
960
961struct InlineBlamePopover {
962 position: gpui::Point<Pixels>,
963 hide_task: Option<Task<()>>,
964 popover_bounds: Option<Bounds<Pixels>>,
965 popover_state: InlineBlamePopoverState,
966 keyboard_grace: bool,
967}
968
969enum SelectionDragState {
970 /// State when no drag related activity is detected.
971 None,
972 /// State when the mouse is down on a selection that is about to be dragged.
973 ReadyToDrag {
974 selection: Selection<Anchor>,
975 click_position: gpui::Point<Pixels>,
976 mouse_down_time: Instant,
977 },
978 /// State when the mouse is dragging the selection in the editor.
979 Dragging {
980 selection: Selection<Anchor>,
981 drop_cursor: Selection<Anchor>,
982 hide_drop_cursor: bool,
983 },
984}
985
986enum ColumnarSelectionState {
987 FromMouse {
988 selection_tail: Anchor,
989 display_point: Option<DisplayPoint>,
990 },
991 FromSelection {
992 selection_tail: Anchor,
993 },
994}
995
996/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
997/// a breakpoint on them.
998#[derive(Clone, Copy, Debug, PartialEq, Eq)]
999struct PhantomBreakpointIndicator {
1000 display_row: DisplayRow,
1001 /// There's a small debounce between hovering over the line and showing the indicator.
1002 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1003 is_active: bool,
1004 collides_with_existing_breakpoint: bool,
1005}
1006
1007/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1008///
1009/// See the [module level documentation](self) for more information.
1010pub struct Editor {
1011 focus_handle: FocusHandle,
1012 last_focused_descendant: Option<WeakFocusHandle>,
1013 /// The text buffer being edited
1014 buffer: Entity<MultiBuffer>,
1015 /// Map of how text in the buffer should be displayed.
1016 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1017 pub display_map: Entity<DisplayMap>,
1018 placeholder_display_map: Option<Entity<DisplayMap>>,
1019 pub selections: SelectionsCollection,
1020 pub scroll_manager: ScrollManager,
1021 /// When inline assist editors are linked, they all render cursors because
1022 /// typing enters text into each of them, even the ones that aren't focused.
1023 pub(crate) show_cursor_when_unfocused: bool,
1024 columnar_selection_state: Option<ColumnarSelectionState>,
1025 add_selections_state: Option<AddSelectionsState>,
1026 select_next_state: Option<SelectNextState>,
1027 select_prev_state: Option<SelectNextState>,
1028 selection_history: SelectionHistory,
1029 defer_selection_effects: bool,
1030 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1031 autoclose_regions: Vec<AutocloseRegion>,
1032 snippet_stack: InvalidationStack<SnippetState>,
1033 select_syntax_node_history: SelectSyntaxNodeHistory,
1034 ime_transaction: Option<TransactionId>,
1035 pub diagnostics_max_severity: DiagnosticSeverity,
1036 active_diagnostics: ActiveDiagnostic,
1037 show_inline_diagnostics: bool,
1038 inline_diagnostics_update: Task<()>,
1039 inline_diagnostics_enabled: bool,
1040 diagnostics_enabled: bool,
1041 word_completions_enabled: bool,
1042 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1043 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1044 hard_wrap: Option<usize>,
1045 project: Option<Entity<Project>>,
1046 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1047 completion_provider: Option<Rc<dyn CompletionProvider>>,
1048 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1049 blink_manager: Entity<BlinkManager>,
1050 show_cursor_names: bool,
1051 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1052 pub show_local_selections: bool,
1053 mode: EditorMode,
1054 show_breadcrumbs: bool,
1055 show_gutter: bool,
1056 show_scrollbars: ScrollbarAxes,
1057 minimap_visibility: MinimapVisibility,
1058 offset_content: bool,
1059 disable_expand_excerpt_buttons: bool,
1060 show_line_numbers: Option<bool>,
1061 use_relative_line_numbers: Option<bool>,
1062 show_git_diff_gutter: Option<bool>,
1063 show_code_actions: Option<bool>,
1064 show_runnables: Option<bool>,
1065 show_breakpoints: Option<bool>,
1066 show_wrap_guides: Option<bool>,
1067 show_indent_guides: Option<bool>,
1068 highlight_order: usize,
1069 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1070 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1071 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1072 scrollbar_marker_state: ScrollbarMarkerState,
1073 active_indent_guides_state: ActiveIndentGuidesState,
1074 nav_history: Option<ItemNavHistory>,
1075 context_menu: RefCell<Option<CodeContextMenu>>,
1076 context_menu_options: Option<ContextMenuOptions>,
1077 mouse_context_menu: Option<MouseContextMenu>,
1078 completion_tasks: Vec<(CompletionId, Task<()>)>,
1079 inline_blame_popover: Option<InlineBlamePopover>,
1080 inline_blame_popover_show_task: Option<Task<()>>,
1081 signature_help_state: SignatureHelpState,
1082 auto_signature_help: Option<bool>,
1083 find_all_references_task_sources: Vec<Anchor>,
1084 next_completion_id: CompletionId,
1085 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1086 code_actions_task: Option<Task<Result<()>>>,
1087 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1088 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1089 document_highlights_task: Option<Task<()>>,
1090 linked_editing_range_task: Option<Task<Option<()>>>,
1091 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1092 pending_rename: Option<RenameState>,
1093 searchable: bool,
1094 cursor_shape: CursorShape,
1095 current_line_highlight: Option<CurrentLineHighlight>,
1096 collapse_matches: bool,
1097 autoindent_mode: Option<AutoindentMode>,
1098 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1099 input_enabled: bool,
1100 use_modal_editing: bool,
1101 read_only: bool,
1102 leader_id: Option<CollaboratorId>,
1103 remote_id: Option<ViewId>,
1104 pub hover_state: HoverState,
1105 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1106 gutter_hovered: bool,
1107 hovered_link_state: Option<HoveredLinkState>,
1108 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1109 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1110 active_edit_prediction: Option<EditPredictionState>,
1111 /// Used to prevent flickering as the user types while the menu is open
1112 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1113 edit_prediction_settings: EditPredictionSettings,
1114 edit_predictions_hidden_for_vim_mode: bool,
1115 show_edit_predictions_override: Option<bool>,
1116 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1117 edit_prediction_preview: EditPredictionPreview,
1118 edit_prediction_indent_conflict: bool,
1119 edit_prediction_requires_modifier_in_indent_conflict: bool,
1120 inlay_hint_cache: InlayHintCache,
1121 next_inlay_id: usize,
1122 _subscriptions: Vec<Subscription>,
1123 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1124 gutter_dimensions: GutterDimensions,
1125 style: Option<EditorStyle>,
1126 text_style_refinement: Option<TextStyleRefinement>,
1127 next_editor_action_id: EditorActionId,
1128 editor_actions: Rc<
1129 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1130 >,
1131 use_autoclose: bool,
1132 use_auto_surround: bool,
1133 auto_replace_emoji_shortcode: bool,
1134 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1135 show_git_blame_gutter: bool,
1136 show_git_blame_inline: bool,
1137 show_git_blame_inline_delay_task: Option<Task<()>>,
1138 git_blame_inline_enabled: bool,
1139 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1140 serialize_dirty_buffers: bool,
1141 show_selection_menu: Option<bool>,
1142 blame: Option<Entity<GitBlame>>,
1143 blame_subscription: Option<Subscription>,
1144 custom_context_menu: Option<
1145 Box<
1146 dyn 'static
1147 + Fn(
1148 &mut Self,
1149 DisplayPoint,
1150 &mut Window,
1151 &mut Context<Self>,
1152 ) -> Option<Entity<ui::ContextMenu>>,
1153 >,
1154 >,
1155 last_bounds: Option<Bounds<Pixels>>,
1156 last_position_map: Option<Rc<PositionMap>>,
1157 expect_bounds_change: Option<Bounds<Pixels>>,
1158 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1159 tasks_update_task: Option<Task<()>>,
1160 breakpoint_store: Option<Entity<BreakpointStore>>,
1161 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1162 hovered_diff_hunk_row: Option<DisplayRow>,
1163 pull_diagnostics_task: Task<()>,
1164 in_project_search: bool,
1165 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1166 breadcrumb_header: Option<String>,
1167 focused_block: Option<FocusedBlock>,
1168 next_scroll_position: NextScrollCursorCenterTopBottom,
1169 addons: HashMap<TypeId, Box<dyn Addon>>,
1170 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1171 load_diff_task: Option<Shared<Task<()>>>,
1172 /// Whether we are temporarily displaying a diff other than git's
1173 temporary_diff_override: bool,
1174 selection_mark_mode: bool,
1175 toggle_fold_multiple_buffers: Task<()>,
1176 _scroll_cursor_center_top_bottom_task: Task<()>,
1177 serialize_selections: Task<()>,
1178 serialize_folds: Task<()>,
1179 mouse_cursor_hidden: bool,
1180 minimap: Option<Entity<Self>>,
1181 hide_mouse_mode: HideMouseMode,
1182 pub change_list: ChangeList,
1183 inline_value_cache: InlineValueCache,
1184 selection_drag_state: SelectionDragState,
1185 next_color_inlay_id: usize,
1186 colors: Option<LspColorData>,
1187 folding_newlines: Task<()>,
1188 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1189}
1190
1191#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1192enum NextScrollCursorCenterTopBottom {
1193 #[default]
1194 Center,
1195 Top,
1196 Bottom,
1197}
1198
1199impl NextScrollCursorCenterTopBottom {
1200 fn next(&self) -> Self {
1201 match self {
1202 Self::Center => Self::Top,
1203 Self::Top => Self::Bottom,
1204 Self::Bottom => Self::Center,
1205 }
1206 }
1207}
1208
1209#[derive(Clone)]
1210pub struct EditorSnapshot {
1211 pub mode: EditorMode,
1212 show_gutter: bool,
1213 show_line_numbers: Option<bool>,
1214 show_git_diff_gutter: Option<bool>,
1215 show_code_actions: Option<bool>,
1216 show_runnables: Option<bool>,
1217 show_breakpoints: Option<bool>,
1218 git_blame_gutter_max_author_length: Option<usize>,
1219 pub display_snapshot: DisplaySnapshot,
1220 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1221 is_focused: bool,
1222 scroll_anchor: ScrollAnchor,
1223 ongoing_scroll: OngoingScroll,
1224 current_line_highlight: CurrentLineHighlight,
1225 gutter_hovered: bool,
1226}
1227
1228#[derive(Default, Debug, Clone, Copy)]
1229pub struct GutterDimensions {
1230 pub left_padding: Pixels,
1231 pub right_padding: Pixels,
1232 pub width: Pixels,
1233 pub margin: Pixels,
1234 pub git_blame_entries_width: Option<Pixels>,
1235}
1236
1237impl GutterDimensions {
1238 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1239 Self {
1240 margin: Self::default_gutter_margin(font_id, font_size, cx),
1241 ..Default::default()
1242 }
1243 }
1244
1245 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1246 -cx.text_system().descent(font_id, font_size)
1247 }
1248 /// The full width of the space taken up by the gutter.
1249 pub fn full_width(&self) -> Pixels {
1250 self.margin + self.width
1251 }
1252
1253 /// The width of the space reserved for the fold indicators,
1254 /// use alongside 'justify_end' and `gutter_width` to
1255 /// right align content with the line numbers
1256 pub fn fold_area_width(&self) -> Pixels {
1257 self.margin + self.right_padding
1258 }
1259}
1260
1261struct CharacterDimensions {
1262 em_width: Pixels,
1263 em_advance: Pixels,
1264 line_height: Pixels,
1265}
1266
1267#[derive(Debug)]
1268pub struct RemoteSelection {
1269 pub replica_id: ReplicaId,
1270 pub selection: Selection<Anchor>,
1271 pub cursor_shape: CursorShape,
1272 pub collaborator_id: CollaboratorId,
1273 pub line_mode: bool,
1274 pub user_name: Option<SharedString>,
1275 pub color: PlayerColor,
1276}
1277
1278#[derive(Clone, Debug)]
1279struct SelectionHistoryEntry {
1280 selections: Arc<[Selection<Anchor>]>,
1281 select_next_state: Option<SelectNextState>,
1282 select_prev_state: Option<SelectNextState>,
1283 add_selections_state: Option<AddSelectionsState>,
1284}
1285
1286#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1287enum SelectionHistoryMode {
1288 Normal,
1289 Undoing,
1290 Redoing,
1291 Skipping,
1292}
1293
1294#[derive(Clone, PartialEq, Eq, Hash)]
1295struct HoveredCursor {
1296 replica_id: u16,
1297 selection_id: usize,
1298}
1299
1300impl Default for SelectionHistoryMode {
1301 fn default() -> Self {
1302 Self::Normal
1303 }
1304}
1305
1306#[derive(Debug)]
1307/// SelectionEffects controls the side-effects of updating the selection.
1308///
1309/// The default behaviour does "what you mostly want":
1310/// - it pushes to the nav history if the cursor moved by >10 lines
1311/// - it re-triggers completion requests
1312/// - it scrolls to fit
1313///
1314/// You might want to modify these behaviours. For example when doing a "jump"
1315/// like go to definition, we always want to add to nav history; but when scrolling
1316/// in vim mode we never do.
1317///
1318/// Similarly, you might want to disable scrolling if you don't want the viewport to
1319/// move.
1320#[derive(Clone)]
1321pub struct SelectionEffects {
1322 nav_history: Option<bool>,
1323 completions: bool,
1324 scroll: Option<Autoscroll>,
1325}
1326
1327impl Default for SelectionEffects {
1328 fn default() -> Self {
1329 Self {
1330 nav_history: None,
1331 completions: true,
1332 scroll: Some(Autoscroll::fit()),
1333 }
1334 }
1335}
1336impl SelectionEffects {
1337 pub fn scroll(scroll: Autoscroll) -> Self {
1338 Self {
1339 scroll: Some(scroll),
1340 ..Default::default()
1341 }
1342 }
1343
1344 pub fn no_scroll() -> Self {
1345 Self {
1346 scroll: None,
1347 ..Default::default()
1348 }
1349 }
1350
1351 pub fn completions(self, completions: bool) -> Self {
1352 Self {
1353 completions,
1354 ..self
1355 }
1356 }
1357
1358 pub fn nav_history(self, nav_history: bool) -> Self {
1359 Self {
1360 nav_history: Some(nav_history),
1361 ..self
1362 }
1363 }
1364}
1365
1366struct DeferredSelectionEffectsState {
1367 changed: bool,
1368 effects: SelectionEffects,
1369 old_cursor_position: Anchor,
1370 history_entry: SelectionHistoryEntry,
1371}
1372
1373#[derive(Default)]
1374struct SelectionHistory {
1375 #[allow(clippy::type_complexity)]
1376 selections_by_transaction:
1377 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1378 mode: SelectionHistoryMode,
1379 undo_stack: VecDeque<SelectionHistoryEntry>,
1380 redo_stack: VecDeque<SelectionHistoryEntry>,
1381}
1382
1383impl SelectionHistory {
1384 #[track_caller]
1385 fn insert_transaction(
1386 &mut self,
1387 transaction_id: TransactionId,
1388 selections: Arc<[Selection<Anchor>]>,
1389 ) {
1390 if selections.is_empty() {
1391 log::error!(
1392 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1393 std::panic::Location::caller()
1394 );
1395 return;
1396 }
1397 self.selections_by_transaction
1398 .insert(transaction_id, (selections, None));
1399 }
1400
1401 #[allow(clippy::type_complexity)]
1402 fn transaction(
1403 &self,
1404 transaction_id: TransactionId,
1405 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1406 self.selections_by_transaction.get(&transaction_id)
1407 }
1408
1409 #[allow(clippy::type_complexity)]
1410 fn transaction_mut(
1411 &mut self,
1412 transaction_id: TransactionId,
1413 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1414 self.selections_by_transaction.get_mut(&transaction_id)
1415 }
1416
1417 fn push(&mut self, entry: SelectionHistoryEntry) {
1418 if !entry.selections.is_empty() {
1419 match self.mode {
1420 SelectionHistoryMode::Normal => {
1421 self.push_undo(entry);
1422 self.redo_stack.clear();
1423 }
1424 SelectionHistoryMode::Undoing => self.push_redo(entry),
1425 SelectionHistoryMode::Redoing => self.push_undo(entry),
1426 SelectionHistoryMode::Skipping => {}
1427 }
1428 }
1429 }
1430
1431 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1432 if self
1433 .undo_stack
1434 .back()
1435 .is_none_or(|e| e.selections != entry.selections)
1436 {
1437 self.undo_stack.push_back(entry);
1438 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1439 self.undo_stack.pop_front();
1440 }
1441 }
1442 }
1443
1444 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1445 if self
1446 .redo_stack
1447 .back()
1448 .is_none_or(|e| e.selections != entry.selections)
1449 {
1450 self.redo_stack.push_back(entry);
1451 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1452 self.redo_stack.pop_front();
1453 }
1454 }
1455 }
1456}
1457
1458#[derive(Clone, Copy)]
1459pub struct RowHighlightOptions {
1460 pub autoscroll: bool,
1461 pub include_gutter: bool,
1462}
1463
1464impl Default for RowHighlightOptions {
1465 fn default() -> Self {
1466 Self {
1467 autoscroll: Default::default(),
1468 include_gutter: true,
1469 }
1470 }
1471}
1472
1473struct RowHighlight {
1474 index: usize,
1475 range: Range<Anchor>,
1476 color: Hsla,
1477 options: RowHighlightOptions,
1478 type_id: TypeId,
1479}
1480
1481#[derive(Clone, Debug)]
1482struct AddSelectionsState {
1483 groups: Vec<AddSelectionsGroup>,
1484}
1485
1486#[derive(Clone, Debug)]
1487struct AddSelectionsGroup {
1488 above: bool,
1489 stack: Vec<usize>,
1490}
1491
1492#[derive(Clone)]
1493struct SelectNextState {
1494 query: AhoCorasick,
1495 wordwise: bool,
1496 done: bool,
1497}
1498
1499impl std::fmt::Debug for SelectNextState {
1500 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1501 f.debug_struct(std::any::type_name::<Self>())
1502 .field("wordwise", &self.wordwise)
1503 .field("done", &self.done)
1504 .finish()
1505 }
1506}
1507
1508#[derive(Debug)]
1509struct AutocloseRegion {
1510 selection_id: usize,
1511 range: Range<Anchor>,
1512 pair: BracketPair,
1513}
1514
1515#[derive(Debug)]
1516struct SnippetState {
1517 ranges: Vec<Vec<Range<Anchor>>>,
1518 active_index: usize,
1519 choices: Vec<Option<Vec<String>>>,
1520}
1521
1522#[doc(hidden)]
1523pub struct RenameState {
1524 pub range: Range<Anchor>,
1525 pub old_name: Arc<str>,
1526 pub editor: Entity<Editor>,
1527 block_id: CustomBlockId,
1528}
1529
1530struct InvalidationStack<T>(Vec<T>);
1531
1532struct RegisteredEditPredictionProvider {
1533 provider: Arc<dyn EditPredictionProviderHandle>,
1534 _subscription: Subscription,
1535}
1536
1537#[derive(Debug, PartialEq, Eq)]
1538pub struct ActiveDiagnosticGroup {
1539 pub active_range: Range<Anchor>,
1540 pub active_message: String,
1541 pub group_id: usize,
1542 pub blocks: HashSet<CustomBlockId>,
1543}
1544
1545#[derive(Debug, PartialEq, Eq)]
1546
1547pub(crate) enum ActiveDiagnostic {
1548 None,
1549 All,
1550 Group(ActiveDiagnosticGroup),
1551}
1552
1553#[derive(Serialize, Deserialize, Clone, Debug)]
1554pub struct ClipboardSelection {
1555 /// The number of bytes in this selection.
1556 pub len: usize,
1557 /// Whether this was a full-line selection.
1558 pub is_entire_line: bool,
1559 /// The indentation of the first line when this content was originally copied.
1560 pub first_line_indent: u32,
1561}
1562
1563// selections, scroll behavior, was newest selection reversed
1564type SelectSyntaxNodeHistoryState = (
1565 Box<[Selection<usize>]>,
1566 SelectSyntaxNodeScrollBehavior,
1567 bool,
1568);
1569
1570#[derive(Default)]
1571struct SelectSyntaxNodeHistory {
1572 stack: Vec<SelectSyntaxNodeHistoryState>,
1573 // disable temporarily to allow changing selections without losing the stack
1574 pub disable_clearing: bool,
1575}
1576
1577impl SelectSyntaxNodeHistory {
1578 pub fn try_clear(&mut self) {
1579 if !self.disable_clearing {
1580 self.stack.clear();
1581 }
1582 }
1583
1584 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1585 self.stack.push(selection);
1586 }
1587
1588 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1589 self.stack.pop()
1590 }
1591}
1592
1593enum SelectSyntaxNodeScrollBehavior {
1594 CursorTop,
1595 FitSelection,
1596 CursorBottom,
1597}
1598
1599#[derive(Debug)]
1600pub(crate) struct NavigationData {
1601 cursor_anchor: Anchor,
1602 cursor_position: Point,
1603 scroll_anchor: ScrollAnchor,
1604 scroll_top_row: u32,
1605}
1606
1607#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1608pub enum GotoDefinitionKind {
1609 Symbol,
1610 Declaration,
1611 Type,
1612 Implementation,
1613}
1614
1615#[derive(Debug, Clone)]
1616enum InlayHintRefreshReason {
1617 ModifiersChanged(bool),
1618 Toggle(bool),
1619 SettingsChange(InlayHintSettings),
1620 NewLinesShown,
1621 BufferEdited(HashSet<Arc<Language>>),
1622 RefreshRequested,
1623 ExcerptsRemoved(Vec<ExcerptId>),
1624}
1625
1626impl InlayHintRefreshReason {
1627 fn description(&self) -> &'static str {
1628 match self {
1629 Self::ModifiersChanged(_) => "modifiers changed",
1630 Self::Toggle(_) => "toggle",
1631 Self::SettingsChange(_) => "settings change",
1632 Self::NewLinesShown => "new lines shown",
1633 Self::BufferEdited(_) => "buffer edited",
1634 Self::RefreshRequested => "refresh requested",
1635 Self::ExcerptsRemoved(_) => "excerpts removed",
1636 }
1637 }
1638}
1639
1640pub enum FormatTarget {
1641 Buffers(HashSet<Entity<Buffer>>),
1642 Ranges(Vec<Range<MultiBufferPoint>>),
1643}
1644
1645pub(crate) struct FocusedBlock {
1646 id: BlockId,
1647 focus_handle: WeakFocusHandle,
1648}
1649
1650#[derive(Clone)]
1651enum JumpData {
1652 MultiBufferRow {
1653 row: MultiBufferRow,
1654 line_offset_from_top: u32,
1655 },
1656 MultiBufferPoint {
1657 excerpt_id: ExcerptId,
1658 position: Point,
1659 anchor: text::Anchor,
1660 line_offset_from_top: u32,
1661 },
1662}
1663
1664pub enum MultibufferSelectionMode {
1665 First,
1666 All,
1667}
1668
1669#[derive(Clone, Copy, Debug, Default)]
1670pub struct RewrapOptions {
1671 pub override_language_settings: bool,
1672 pub preserve_existing_whitespace: bool,
1673}
1674
1675impl Editor {
1676 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1677 let buffer = cx.new(|cx| Buffer::local("", cx));
1678 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1679 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1680 }
1681
1682 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1683 let buffer = cx.new(|cx| Buffer::local("", cx));
1684 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1685 Self::new(EditorMode::full(), buffer, None, window, cx)
1686 }
1687
1688 pub fn auto_height(
1689 min_lines: usize,
1690 max_lines: usize,
1691 window: &mut Window,
1692 cx: &mut Context<Self>,
1693 ) -> Self {
1694 let buffer = cx.new(|cx| Buffer::local("", cx));
1695 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1696 Self::new(
1697 EditorMode::AutoHeight {
1698 min_lines,
1699 max_lines: Some(max_lines),
1700 },
1701 buffer,
1702 None,
1703 window,
1704 cx,
1705 )
1706 }
1707
1708 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1709 /// The editor grows as tall as needed to fit its content.
1710 pub fn auto_height_unbounded(
1711 min_lines: usize,
1712 window: &mut Window,
1713 cx: &mut Context<Self>,
1714 ) -> Self {
1715 let buffer = cx.new(|cx| Buffer::local("", cx));
1716 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1717 Self::new(
1718 EditorMode::AutoHeight {
1719 min_lines,
1720 max_lines: None,
1721 },
1722 buffer,
1723 None,
1724 window,
1725 cx,
1726 )
1727 }
1728
1729 pub fn for_buffer(
1730 buffer: Entity<Buffer>,
1731 project: Option<Entity<Project>>,
1732 window: &mut Window,
1733 cx: &mut Context<Self>,
1734 ) -> Self {
1735 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1736 Self::new(EditorMode::full(), buffer, project, window, cx)
1737 }
1738
1739 pub fn for_multibuffer(
1740 buffer: Entity<MultiBuffer>,
1741 project: Option<Entity<Project>>,
1742 window: &mut Window,
1743 cx: &mut Context<Self>,
1744 ) -> Self {
1745 Self::new(EditorMode::full(), buffer, project, window, cx)
1746 }
1747
1748 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1749 let mut clone = Self::new(
1750 self.mode.clone(),
1751 self.buffer.clone(),
1752 self.project.clone(),
1753 window,
1754 cx,
1755 );
1756 self.display_map.update(cx, |display_map, cx| {
1757 let snapshot = display_map.snapshot(cx);
1758 clone.display_map.update(cx, |display_map, cx| {
1759 display_map.set_state(&snapshot, cx);
1760 });
1761 });
1762 clone.folds_did_change(cx);
1763 clone.selections.clone_state(&self.selections);
1764 clone.scroll_manager.clone_state(&self.scroll_manager);
1765 clone.searchable = self.searchable;
1766 clone.read_only = self.read_only;
1767 clone
1768 }
1769
1770 pub fn new(
1771 mode: EditorMode,
1772 buffer: Entity<MultiBuffer>,
1773 project: Option<Entity<Project>>,
1774 window: &mut Window,
1775 cx: &mut Context<Self>,
1776 ) -> Self {
1777 Editor::new_internal(mode, buffer, project, None, window, cx)
1778 }
1779
1780 fn new_internal(
1781 mode: EditorMode,
1782 buffer: Entity<MultiBuffer>,
1783 project: Option<Entity<Project>>,
1784 display_map: Option<Entity<DisplayMap>>,
1785 window: &mut Window,
1786 cx: &mut Context<Self>,
1787 ) -> Self {
1788 debug_assert!(
1789 display_map.is_none() || mode.is_minimap(),
1790 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1791 );
1792
1793 let full_mode = mode.is_full();
1794 let is_minimap = mode.is_minimap();
1795 let diagnostics_max_severity = if full_mode {
1796 EditorSettings::get_global(cx)
1797 .diagnostics_max_severity
1798 .unwrap_or(DiagnosticSeverity::Hint)
1799 } else {
1800 DiagnosticSeverity::Off
1801 };
1802 let style = window.text_style();
1803 let font_size = style.font_size.to_pixels(window.rem_size());
1804 let editor = cx.entity().downgrade();
1805 let fold_placeholder = FoldPlaceholder {
1806 constrain_width: false,
1807 render: Arc::new(move |fold_id, fold_range, cx| {
1808 let editor = editor.clone();
1809 div()
1810 .id(fold_id)
1811 .bg(cx.theme().colors().ghost_element_background)
1812 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1813 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1814 .rounded_xs()
1815 .size_full()
1816 .cursor_pointer()
1817 .child("⋯")
1818 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1819 .on_click(move |_, _window, cx| {
1820 editor
1821 .update(cx, |editor, cx| {
1822 editor.unfold_ranges(
1823 &[fold_range.start..fold_range.end],
1824 true,
1825 false,
1826 cx,
1827 );
1828 cx.stop_propagation();
1829 })
1830 .ok();
1831 })
1832 .into_any()
1833 }),
1834 merge_adjacent: true,
1835 ..FoldPlaceholder::default()
1836 };
1837 let display_map = display_map.unwrap_or_else(|| {
1838 cx.new(|cx| {
1839 DisplayMap::new(
1840 buffer.clone(),
1841 style.font(),
1842 font_size,
1843 None,
1844 FILE_HEADER_HEIGHT,
1845 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1846 fold_placeholder,
1847 diagnostics_max_severity,
1848 cx,
1849 )
1850 })
1851 });
1852
1853 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1854
1855 let blink_manager = cx.new(|cx| {
1856 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1857 if is_minimap {
1858 blink_manager.disable(cx);
1859 }
1860 blink_manager
1861 });
1862
1863 let soft_wrap_mode_override =
1864 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1865
1866 let mut project_subscriptions = Vec::new();
1867 if full_mode && let Some(project) = project.as_ref() {
1868 project_subscriptions.push(cx.subscribe_in(
1869 project,
1870 window,
1871 |editor, _, event, window, cx| match event {
1872 project::Event::RefreshCodeLens => {
1873 // we always query lens with actions, without storing them, always refreshing them
1874 }
1875 project::Event::RefreshInlayHints => {
1876 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1877 }
1878 project::Event::LanguageServerAdded(..)
1879 | project::Event::LanguageServerRemoved(..) => {
1880 if editor.tasks_update_task.is_none() {
1881 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1882 }
1883 }
1884 project::Event::SnippetEdit(id, snippet_edits) => {
1885 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1886 let focus_handle = editor.focus_handle(cx);
1887 if focus_handle.is_focused(window) {
1888 let snapshot = buffer.read(cx).snapshot();
1889 for (range, snippet) in snippet_edits {
1890 let editor_range =
1891 language::range_from_lsp(*range).to_offset(&snapshot);
1892 editor
1893 .insert_snippet(
1894 &[editor_range],
1895 snippet.clone(),
1896 window,
1897 cx,
1898 )
1899 .ok();
1900 }
1901 }
1902 }
1903 }
1904 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1905 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1906 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1907 }
1908 }
1909
1910 project::Event::EntryRenamed(transaction) => {
1911 let Some(workspace) = editor.workspace() else {
1912 return;
1913 };
1914 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1915 else {
1916 return;
1917 };
1918 if active_editor.entity_id() == cx.entity_id() {
1919 let edited_buffers_already_open = {
1920 let other_editors: Vec<Entity<Editor>> = workspace
1921 .read(cx)
1922 .panes()
1923 .iter()
1924 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1925 .filter(|editor| editor.entity_id() != cx.entity_id())
1926 .collect();
1927
1928 transaction.0.keys().all(|buffer| {
1929 other_editors.iter().any(|editor| {
1930 let multi_buffer = editor.read(cx).buffer();
1931 multi_buffer.read(cx).is_singleton()
1932 && multi_buffer.read(cx).as_singleton().map_or(
1933 false,
1934 |singleton| {
1935 singleton.entity_id() == buffer.entity_id()
1936 },
1937 )
1938 })
1939 })
1940 };
1941
1942 if !edited_buffers_already_open {
1943 let workspace = workspace.downgrade();
1944 let transaction = transaction.clone();
1945 cx.defer_in(window, move |_, window, cx| {
1946 cx.spawn_in(window, async move |editor, cx| {
1947 Self::open_project_transaction(
1948 &editor,
1949 workspace,
1950 transaction,
1951 "Rename".to_string(),
1952 cx,
1953 )
1954 .await
1955 .ok()
1956 })
1957 .detach();
1958 });
1959 }
1960 }
1961 }
1962
1963 _ => {}
1964 },
1965 ));
1966 if let Some(task_inventory) = project
1967 .read(cx)
1968 .task_store()
1969 .read(cx)
1970 .task_inventory()
1971 .cloned()
1972 {
1973 project_subscriptions.push(cx.observe_in(
1974 &task_inventory,
1975 window,
1976 |editor, _, window, cx| {
1977 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1978 },
1979 ));
1980 };
1981
1982 project_subscriptions.push(cx.subscribe_in(
1983 &project.read(cx).breakpoint_store(),
1984 window,
1985 |editor, _, event, window, cx| match event {
1986 BreakpointStoreEvent::ClearDebugLines => {
1987 editor.clear_row_highlights::<ActiveDebugLine>();
1988 editor.refresh_inline_values(cx);
1989 }
1990 BreakpointStoreEvent::SetDebugLine => {
1991 if editor.go_to_active_debug_line(window, cx) {
1992 cx.stop_propagation();
1993 }
1994
1995 editor.refresh_inline_values(cx);
1996 }
1997 _ => {}
1998 },
1999 ));
2000 let git_store = project.read(cx).git_store().clone();
2001 let project = project.clone();
2002 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2003 if let GitStoreEvent::RepositoryUpdated(
2004 _,
2005 RepositoryEvent::Updated {
2006 new_instance: true, ..
2007 },
2008 _,
2009 ) = event
2010 {
2011 this.load_diff_task = Some(
2012 update_uncommitted_diff_for_buffer(
2013 cx.entity(),
2014 &project,
2015 this.buffer.read(cx).all_buffers(),
2016 this.buffer.clone(),
2017 cx,
2018 )
2019 .shared(),
2020 );
2021 }
2022 }));
2023 }
2024
2025 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2026
2027 let inlay_hint_settings =
2028 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2029 let focus_handle = cx.focus_handle();
2030 if !is_minimap {
2031 cx.on_focus(&focus_handle, window, Self::handle_focus)
2032 .detach();
2033 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2034 .detach();
2035 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2036 .detach();
2037 cx.on_blur(&focus_handle, window, Self::handle_blur)
2038 .detach();
2039 cx.observe_pending_input(window, Self::observe_pending_input)
2040 .detach();
2041 }
2042
2043 let show_indent_guides =
2044 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2045 Some(false)
2046 } else {
2047 None
2048 };
2049
2050 let breakpoint_store = match (&mode, project.as_ref()) {
2051 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2052 _ => None,
2053 };
2054
2055 let mut code_action_providers = Vec::new();
2056 let mut load_uncommitted_diff = None;
2057 if let Some(project) = project.clone() {
2058 load_uncommitted_diff = Some(
2059 update_uncommitted_diff_for_buffer(
2060 cx.entity(),
2061 &project,
2062 buffer.read(cx).all_buffers(),
2063 buffer.clone(),
2064 cx,
2065 )
2066 .shared(),
2067 );
2068 code_action_providers.push(Rc::new(project) as Rc<_>);
2069 }
2070
2071 let mut editor = Self {
2072 focus_handle,
2073 show_cursor_when_unfocused: false,
2074 last_focused_descendant: None,
2075 buffer: buffer.clone(),
2076 display_map: display_map.clone(),
2077 placeholder_display_map: None,
2078 selections,
2079 scroll_manager: ScrollManager::new(cx),
2080 columnar_selection_state: None,
2081 add_selections_state: None,
2082 select_next_state: None,
2083 select_prev_state: None,
2084 selection_history: SelectionHistory::default(),
2085 defer_selection_effects: false,
2086 deferred_selection_effects_state: None,
2087 autoclose_regions: Vec::new(),
2088 snippet_stack: InvalidationStack::default(),
2089 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2090 ime_transaction: None,
2091 active_diagnostics: ActiveDiagnostic::None,
2092 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2093 inline_diagnostics_update: Task::ready(()),
2094 inline_diagnostics: Vec::new(),
2095 soft_wrap_mode_override,
2096 diagnostics_max_severity,
2097 hard_wrap: None,
2098 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2099 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2100 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2101 project,
2102 blink_manager: blink_manager.clone(),
2103 show_local_selections: true,
2104 show_scrollbars: ScrollbarAxes {
2105 horizontal: full_mode,
2106 vertical: full_mode,
2107 },
2108 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2109 offset_content: !matches!(mode, EditorMode::SingleLine),
2110 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2111 show_gutter: full_mode,
2112 show_line_numbers: (!full_mode).then_some(false),
2113 use_relative_line_numbers: None,
2114 disable_expand_excerpt_buttons: !full_mode,
2115 show_git_diff_gutter: None,
2116 show_code_actions: None,
2117 show_runnables: None,
2118 show_breakpoints: None,
2119 show_wrap_guides: None,
2120 show_indent_guides,
2121 highlight_order: 0,
2122 highlighted_rows: HashMap::default(),
2123 background_highlights: HashMap::default(),
2124 gutter_highlights: HashMap::default(),
2125 scrollbar_marker_state: ScrollbarMarkerState::default(),
2126 active_indent_guides_state: ActiveIndentGuidesState::default(),
2127 nav_history: None,
2128 context_menu: RefCell::new(None),
2129 context_menu_options: None,
2130 mouse_context_menu: None,
2131 completion_tasks: Vec::new(),
2132 inline_blame_popover: None,
2133 inline_blame_popover_show_task: None,
2134 signature_help_state: SignatureHelpState::default(),
2135 auto_signature_help: None,
2136 find_all_references_task_sources: Vec::new(),
2137 next_completion_id: 0,
2138 next_inlay_id: 0,
2139 code_action_providers,
2140 available_code_actions: None,
2141 code_actions_task: None,
2142 quick_selection_highlight_task: None,
2143 debounced_selection_highlight_task: None,
2144 document_highlights_task: None,
2145 linked_editing_range_task: None,
2146 pending_rename: None,
2147 searchable: !is_minimap,
2148 cursor_shape: EditorSettings::get_global(cx)
2149 .cursor_shape
2150 .unwrap_or_default(),
2151 current_line_highlight: None,
2152 autoindent_mode: Some(AutoindentMode::EachLine),
2153 collapse_matches: false,
2154 workspace: None,
2155 input_enabled: !is_minimap,
2156 use_modal_editing: full_mode,
2157 read_only: is_minimap,
2158 use_autoclose: true,
2159 use_auto_surround: true,
2160 auto_replace_emoji_shortcode: false,
2161 jsx_tag_auto_close_enabled_in_any_buffer: false,
2162 leader_id: None,
2163 remote_id: None,
2164 hover_state: HoverState::default(),
2165 pending_mouse_down: None,
2166 hovered_link_state: None,
2167 edit_prediction_provider: None,
2168 active_edit_prediction: None,
2169 stale_edit_prediction_in_menu: None,
2170 edit_prediction_preview: EditPredictionPreview::Inactive {
2171 released_too_fast: false,
2172 },
2173 inline_diagnostics_enabled: full_mode,
2174 diagnostics_enabled: full_mode,
2175 word_completions_enabled: full_mode,
2176 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2177 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2178 gutter_hovered: false,
2179 pixel_position_of_newest_cursor: None,
2180 last_bounds: None,
2181 last_position_map: None,
2182 expect_bounds_change: None,
2183 gutter_dimensions: GutterDimensions::default(),
2184 style: None,
2185 show_cursor_names: false,
2186 hovered_cursors: HashMap::default(),
2187 next_editor_action_id: EditorActionId::default(),
2188 editor_actions: Rc::default(),
2189 edit_predictions_hidden_for_vim_mode: false,
2190 show_edit_predictions_override: None,
2191 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2192 edit_prediction_settings: EditPredictionSettings::Disabled,
2193 edit_prediction_indent_conflict: false,
2194 edit_prediction_requires_modifier_in_indent_conflict: true,
2195 custom_context_menu: None,
2196 show_git_blame_gutter: false,
2197 show_git_blame_inline: false,
2198 show_selection_menu: None,
2199 show_git_blame_inline_delay_task: None,
2200 git_blame_inline_enabled: full_mode
2201 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2202 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2203 serialize_dirty_buffers: !is_minimap
2204 && ProjectSettings::get_global(cx)
2205 .session
2206 .restore_unsaved_buffers,
2207 blame: None,
2208 blame_subscription: None,
2209 tasks: BTreeMap::default(),
2210
2211 breakpoint_store,
2212 gutter_breakpoint_indicator: (None, None),
2213 hovered_diff_hunk_row: None,
2214 _subscriptions: (!is_minimap)
2215 .then(|| {
2216 vec![
2217 cx.observe(&buffer, Self::on_buffer_changed),
2218 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2219 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2220 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2221 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2222 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2223 cx.observe_window_activation(window, |editor, window, cx| {
2224 let active = window.is_window_active();
2225 editor.blink_manager.update(cx, |blink_manager, cx| {
2226 if active {
2227 blink_manager.enable(cx);
2228 } else {
2229 blink_manager.disable(cx);
2230 }
2231 });
2232 if active {
2233 editor.show_mouse_cursor(cx);
2234 }
2235 }),
2236 ]
2237 })
2238 .unwrap_or_default(),
2239 tasks_update_task: None,
2240 pull_diagnostics_task: Task::ready(()),
2241 colors: None,
2242 next_color_inlay_id: 0,
2243 linked_edit_ranges: Default::default(),
2244 in_project_search: false,
2245 previous_search_ranges: None,
2246 breadcrumb_header: None,
2247 focused_block: None,
2248 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2249 addons: HashMap::default(),
2250 registered_buffers: HashMap::default(),
2251 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2252 selection_mark_mode: false,
2253 toggle_fold_multiple_buffers: Task::ready(()),
2254 serialize_selections: Task::ready(()),
2255 serialize_folds: Task::ready(()),
2256 text_style_refinement: None,
2257 load_diff_task: load_uncommitted_diff,
2258 temporary_diff_override: false,
2259 mouse_cursor_hidden: false,
2260 minimap: None,
2261 hide_mouse_mode: EditorSettings::get_global(cx)
2262 .hide_mouse
2263 .unwrap_or_default(),
2264 change_list: ChangeList::new(),
2265 mode,
2266 selection_drag_state: SelectionDragState::None,
2267 folding_newlines: Task::ready(()),
2268 lookup_key: None,
2269 };
2270
2271 if is_minimap {
2272 return editor;
2273 }
2274
2275 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2276 editor
2277 ._subscriptions
2278 .push(cx.observe(breakpoints, |_, _, cx| {
2279 cx.notify();
2280 }));
2281 }
2282 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2283 editor._subscriptions.extend(project_subscriptions);
2284
2285 editor._subscriptions.push(cx.subscribe_in(
2286 &cx.entity(),
2287 window,
2288 |editor, _, e: &EditorEvent, window, cx| match e {
2289 EditorEvent::ScrollPositionChanged { local, .. } => {
2290 if *local {
2291 let new_anchor = editor.scroll_manager.anchor();
2292 let snapshot = editor.snapshot(window, cx);
2293 editor.update_restoration_data(cx, move |data| {
2294 data.scroll_position = (
2295 new_anchor.top_row(&snapshot.buffer_snapshot),
2296 new_anchor.offset,
2297 );
2298 });
2299 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2300 editor.inline_blame_popover.take();
2301 }
2302 }
2303 EditorEvent::Edited { .. } => {
2304 if !vim_enabled(cx) {
2305 let (map, selections) = editor.selections.all_adjusted_display(cx);
2306 let pop_state = editor
2307 .change_list
2308 .last()
2309 .map(|previous| {
2310 previous.len() == selections.len()
2311 && previous.iter().enumerate().all(|(ix, p)| {
2312 p.to_display_point(&map).row()
2313 == selections[ix].head().row()
2314 })
2315 })
2316 .unwrap_or(false);
2317 let new_positions = selections
2318 .into_iter()
2319 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2320 .collect();
2321 editor
2322 .change_list
2323 .push_to_change_list(pop_state, new_positions);
2324 }
2325 }
2326 _ => (),
2327 },
2328 ));
2329
2330 if let Some(dap_store) = editor
2331 .project
2332 .as_ref()
2333 .map(|project| project.read(cx).dap_store())
2334 {
2335 let weak_editor = cx.weak_entity();
2336
2337 editor
2338 ._subscriptions
2339 .push(
2340 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2341 let session_entity = cx.entity();
2342 weak_editor
2343 .update(cx, |editor, cx| {
2344 editor._subscriptions.push(
2345 cx.subscribe(&session_entity, Self::on_debug_session_event),
2346 );
2347 })
2348 .ok();
2349 }),
2350 );
2351
2352 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2353 editor
2354 ._subscriptions
2355 .push(cx.subscribe(&session, Self::on_debug_session_event));
2356 }
2357 }
2358
2359 // skip adding the initial selection to selection history
2360 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2361 editor.end_selection(window, cx);
2362 editor.selection_history.mode = SelectionHistoryMode::Normal;
2363
2364 editor.scroll_manager.show_scrollbars(window, cx);
2365 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2366
2367 if full_mode {
2368 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2369 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2370
2371 if editor.git_blame_inline_enabled {
2372 editor.start_git_blame_inline(false, window, cx);
2373 }
2374
2375 editor.go_to_active_debug_line(window, cx);
2376
2377 if let Some(buffer) = buffer.read(cx).as_singleton()
2378 && let Some(project) = editor.project()
2379 {
2380 let handle = project.update(cx, |project, cx| {
2381 project.register_buffer_with_language_servers(&buffer, cx)
2382 });
2383 editor
2384 .registered_buffers
2385 .insert(buffer.read(cx).remote_id(), handle);
2386 }
2387
2388 editor.minimap =
2389 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2390 editor.colors = Some(LspColorData::new(cx));
2391 editor.update_lsp_data(false, None, window, cx);
2392 }
2393
2394 if editor.mode.is_full() {
2395 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2396 }
2397
2398 editor
2399 }
2400
2401 pub fn deploy_mouse_context_menu(
2402 &mut self,
2403 position: gpui::Point<Pixels>,
2404 context_menu: Entity<ContextMenu>,
2405 window: &mut Window,
2406 cx: &mut Context<Self>,
2407 ) {
2408 self.mouse_context_menu = Some(MouseContextMenu::new(
2409 self,
2410 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2411 context_menu,
2412 window,
2413 cx,
2414 ));
2415 }
2416
2417 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2418 self.mouse_context_menu
2419 .as_ref()
2420 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2421 }
2422
2423 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2424 if self
2425 .selections
2426 .pending_anchor()
2427 .is_some_and(|pending_selection| {
2428 let snapshot = self.buffer().read(cx).snapshot(cx);
2429 pending_selection.range().includes(range, &snapshot)
2430 })
2431 {
2432 return true;
2433 }
2434
2435 self.selections
2436 .disjoint_in_range::<usize>(range.clone(), cx)
2437 .into_iter()
2438 .any(|selection| {
2439 // This is needed to cover a corner case, if we just check for an existing
2440 // selection in the fold range, having a cursor at the start of the fold
2441 // marks it as selected. Non-empty selections don't cause this.
2442 let length = selection.end - selection.start;
2443 length > 0
2444 })
2445 }
2446
2447 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2448 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2449 }
2450
2451 fn key_context_internal(
2452 &self,
2453 has_active_edit_prediction: bool,
2454 window: &Window,
2455 cx: &App,
2456 ) -> KeyContext {
2457 let mut key_context = KeyContext::new_with_defaults();
2458 key_context.add("Editor");
2459 let mode = match self.mode {
2460 EditorMode::SingleLine => "single_line",
2461 EditorMode::AutoHeight { .. } => "auto_height",
2462 EditorMode::Minimap { .. } => "minimap",
2463 EditorMode::Full { .. } => "full",
2464 };
2465
2466 if EditorSettings::jupyter_enabled(cx) {
2467 key_context.add("jupyter");
2468 }
2469
2470 key_context.set("mode", mode);
2471 if self.pending_rename.is_some() {
2472 key_context.add("renaming");
2473 }
2474
2475 match self.context_menu.borrow().as_ref() {
2476 Some(CodeContextMenu::Completions(menu)) => {
2477 if menu.visible() {
2478 key_context.add("menu");
2479 key_context.add("showing_completions");
2480 }
2481 }
2482 Some(CodeContextMenu::CodeActions(menu)) => {
2483 if menu.visible() {
2484 key_context.add("menu");
2485 key_context.add("showing_code_actions")
2486 }
2487 }
2488 None => {}
2489 }
2490
2491 if self.signature_help_state.has_multiple_signatures() {
2492 key_context.add("showing_signature_help");
2493 }
2494
2495 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2496 if !self.focus_handle(cx).contains_focused(window, cx)
2497 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2498 {
2499 for addon in self.addons.values() {
2500 addon.extend_key_context(&mut key_context, cx)
2501 }
2502 }
2503
2504 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2505 if let Some(extension) = singleton_buffer
2506 .read(cx)
2507 .file()
2508 .and_then(|file| file.path().extension())
2509 {
2510 key_context.set("extension", extension.to_string());
2511 }
2512 } else {
2513 key_context.add("multibuffer");
2514 }
2515
2516 if has_active_edit_prediction {
2517 if self.edit_prediction_in_conflict() {
2518 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2519 } else {
2520 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2521 key_context.add("copilot_suggestion");
2522 }
2523 }
2524
2525 if self.selection_mark_mode {
2526 key_context.add("selection_mode");
2527 }
2528
2529 key_context
2530 }
2531
2532 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2533 self.last_bounds.as_ref()
2534 }
2535
2536 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2537 if self.mouse_cursor_hidden {
2538 self.mouse_cursor_hidden = false;
2539 cx.notify();
2540 }
2541 }
2542
2543 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2544 let hide_mouse_cursor = match origin {
2545 HideMouseCursorOrigin::TypingAction => {
2546 matches!(
2547 self.hide_mouse_mode,
2548 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2549 )
2550 }
2551 HideMouseCursorOrigin::MovementAction => {
2552 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2553 }
2554 };
2555 if self.mouse_cursor_hidden != hide_mouse_cursor {
2556 self.mouse_cursor_hidden = hide_mouse_cursor;
2557 cx.notify();
2558 }
2559 }
2560
2561 pub fn edit_prediction_in_conflict(&self) -> bool {
2562 if !self.show_edit_predictions_in_menu() {
2563 return false;
2564 }
2565
2566 let showing_completions = self
2567 .context_menu
2568 .borrow()
2569 .as_ref()
2570 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2571
2572 showing_completions
2573 || self.edit_prediction_requires_modifier()
2574 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2575 // bindings to insert tab characters.
2576 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2577 }
2578
2579 pub fn accept_edit_prediction_keybind(
2580 &self,
2581 accept_partial: bool,
2582 window: &Window,
2583 cx: &App,
2584 ) -> AcceptEditPredictionBinding {
2585 let key_context = self.key_context_internal(true, window, cx);
2586 let in_conflict = self.edit_prediction_in_conflict();
2587
2588 let bindings = if accept_partial {
2589 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2590 } else {
2591 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2592 };
2593
2594 // TODO: if the binding contains multiple keystrokes, display all of them, not
2595 // just the first one.
2596 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2597 !in_conflict
2598 || binding
2599 .keystrokes()
2600 .first()
2601 .is_some_and(|keystroke| keystroke.modifiers().modified())
2602 }))
2603 }
2604
2605 pub fn new_file(
2606 workspace: &mut Workspace,
2607 _: &workspace::NewFile,
2608 window: &mut Window,
2609 cx: &mut Context<Workspace>,
2610 ) {
2611 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2612 "Failed to create buffer",
2613 window,
2614 cx,
2615 |e, _, _| match e.error_code() {
2616 ErrorCode::RemoteUpgradeRequired => Some(format!(
2617 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2618 e.error_tag("required").unwrap_or("the latest version")
2619 )),
2620 _ => None,
2621 },
2622 );
2623 }
2624
2625 pub fn new_in_workspace(
2626 workspace: &mut Workspace,
2627 window: &mut Window,
2628 cx: &mut Context<Workspace>,
2629 ) -> Task<Result<Entity<Editor>>> {
2630 let project = workspace.project().clone();
2631 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2632
2633 cx.spawn_in(window, async move |workspace, cx| {
2634 let buffer = create.await?;
2635 workspace.update_in(cx, |workspace, window, cx| {
2636 let editor =
2637 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2638 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2639 editor
2640 })
2641 })
2642 }
2643
2644 fn new_file_vertical(
2645 workspace: &mut Workspace,
2646 _: &workspace::NewFileSplitVertical,
2647 window: &mut Window,
2648 cx: &mut Context<Workspace>,
2649 ) {
2650 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2651 }
2652
2653 fn new_file_horizontal(
2654 workspace: &mut Workspace,
2655 _: &workspace::NewFileSplitHorizontal,
2656 window: &mut Window,
2657 cx: &mut Context<Workspace>,
2658 ) {
2659 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2660 }
2661
2662 fn new_file_in_direction(
2663 workspace: &mut Workspace,
2664 direction: SplitDirection,
2665 window: &mut Window,
2666 cx: &mut Context<Workspace>,
2667 ) {
2668 let project = workspace.project().clone();
2669 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2670
2671 cx.spawn_in(window, async move |workspace, cx| {
2672 let buffer = create.await?;
2673 workspace.update_in(cx, move |workspace, window, cx| {
2674 workspace.split_item(
2675 direction,
2676 Box::new(
2677 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2678 ),
2679 window,
2680 cx,
2681 )
2682 })?;
2683 anyhow::Ok(())
2684 })
2685 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2686 match e.error_code() {
2687 ErrorCode::RemoteUpgradeRequired => Some(format!(
2688 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2689 e.error_tag("required").unwrap_or("the latest version")
2690 )),
2691 _ => None,
2692 }
2693 });
2694 }
2695
2696 pub fn leader_id(&self) -> Option<CollaboratorId> {
2697 self.leader_id
2698 }
2699
2700 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2701 &self.buffer
2702 }
2703
2704 pub fn project(&self) -> Option<&Entity<Project>> {
2705 self.project.as_ref()
2706 }
2707
2708 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2709 self.workspace.as_ref()?.0.upgrade()
2710 }
2711
2712 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2713 self.buffer().read(cx).title(cx)
2714 }
2715
2716 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2717 let git_blame_gutter_max_author_length = self
2718 .render_git_blame_gutter(cx)
2719 .then(|| {
2720 if let Some(blame) = self.blame.as_ref() {
2721 let max_author_length =
2722 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2723 Some(max_author_length)
2724 } else {
2725 None
2726 }
2727 })
2728 .flatten();
2729
2730 EditorSnapshot {
2731 mode: self.mode.clone(),
2732 show_gutter: self.show_gutter,
2733 show_line_numbers: self.show_line_numbers,
2734 show_git_diff_gutter: self.show_git_diff_gutter,
2735 show_code_actions: self.show_code_actions,
2736 show_runnables: self.show_runnables,
2737 show_breakpoints: self.show_breakpoints,
2738 git_blame_gutter_max_author_length,
2739 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2740 placeholder_display_snapshot: self
2741 .placeholder_display_map
2742 .as_ref()
2743 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2744 scroll_anchor: self.scroll_manager.anchor(),
2745 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2746 is_focused: self.focus_handle.is_focused(window),
2747 current_line_highlight: self
2748 .current_line_highlight
2749 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2750 gutter_hovered: self.gutter_hovered,
2751 }
2752 }
2753
2754 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2755 self.buffer.read(cx).language_at(point, cx)
2756 }
2757
2758 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2759 self.buffer.read(cx).read(cx).file_at(point).cloned()
2760 }
2761
2762 pub fn active_excerpt(
2763 &self,
2764 cx: &App,
2765 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2766 self.buffer
2767 .read(cx)
2768 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2769 }
2770
2771 pub fn mode(&self) -> &EditorMode {
2772 &self.mode
2773 }
2774
2775 pub fn set_mode(&mut self, mode: EditorMode) {
2776 self.mode = mode;
2777 }
2778
2779 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2780 self.collaboration_hub.as_deref()
2781 }
2782
2783 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2784 self.collaboration_hub = Some(hub);
2785 }
2786
2787 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2788 self.in_project_search = in_project_search;
2789 }
2790
2791 pub fn set_custom_context_menu(
2792 &mut self,
2793 f: impl 'static
2794 + Fn(
2795 &mut Self,
2796 DisplayPoint,
2797 &mut Window,
2798 &mut Context<Self>,
2799 ) -> Option<Entity<ui::ContextMenu>>,
2800 ) {
2801 self.custom_context_menu = Some(Box::new(f))
2802 }
2803
2804 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2805 self.completion_provider = provider;
2806 }
2807
2808 #[cfg(any(test, feature = "test-support"))]
2809 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2810 self.completion_provider.clone()
2811 }
2812
2813 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2814 self.semantics_provider.clone()
2815 }
2816
2817 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2818 self.semantics_provider = provider;
2819 }
2820
2821 pub fn set_edit_prediction_provider<T>(
2822 &mut self,
2823 provider: Option<Entity<T>>,
2824 window: &mut Window,
2825 cx: &mut Context<Self>,
2826 ) where
2827 T: EditPredictionProvider,
2828 {
2829 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2830 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2831 if this.focus_handle.is_focused(window) {
2832 this.update_visible_edit_prediction(window, cx);
2833 }
2834 }),
2835 provider: Arc::new(provider),
2836 });
2837 self.update_edit_prediction_settings(cx);
2838 self.refresh_edit_prediction(false, false, window, cx);
2839 }
2840
2841 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2842 self.placeholder_display_map
2843 .as_ref()
2844 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2845 }
2846
2847 pub fn set_placeholder_text(
2848 &mut self,
2849 placeholder_text: &str,
2850 window: &mut Window,
2851 cx: &mut Context<Self>,
2852 ) {
2853 let multibuffer = cx
2854 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2855
2856 let style = window.text_style();
2857
2858 self.placeholder_display_map = Some(cx.new(|cx| {
2859 DisplayMap::new(
2860 multibuffer,
2861 style.font(),
2862 style.font_size.to_pixels(window.rem_size()),
2863 None,
2864 FILE_HEADER_HEIGHT,
2865 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2866 Default::default(),
2867 DiagnosticSeverity::Off,
2868 cx,
2869 )
2870 }));
2871 cx.notify();
2872 }
2873
2874 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2875 self.cursor_shape = cursor_shape;
2876
2877 // Disrupt blink for immediate user feedback that the cursor shape has changed
2878 self.blink_manager.update(cx, BlinkManager::show_cursor);
2879
2880 cx.notify();
2881 }
2882
2883 pub fn set_current_line_highlight(
2884 &mut self,
2885 current_line_highlight: Option<CurrentLineHighlight>,
2886 ) {
2887 self.current_line_highlight = current_line_highlight;
2888 }
2889
2890 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2891 self.collapse_matches = collapse_matches;
2892 }
2893
2894 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2895 let buffers = self.buffer.read(cx).all_buffers();
2896 let Some(project) = self.project.as_ref() else {
2897 return;
2898 };
2899 project.update(cx, |project, cx| {
2900 for buffer in buffers {
2901 self.registered_buffers
2902 .entry(buffer.read(cx).remote_id())
2903 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2904 }
2905 })
2906 }
2907
2908 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2909 if self.collapse_matches {
2910 return range.start..range.start;
2911 }
2912 range.clone()
2913 }
2914
2915 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2916 if self.display_map.read(cx).clip_at_line_ends != clip {
2917 self.display_map
2918 .update(cx, |map, _| map.clip_at_line_ends = clip);
2919 }
2920 }
2921
2922 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2923 self.input_enabled = input_enabled;
2924 }
2925
2926 pub fn set_edit_predictions_hidden_for_vim_mode(
2927 &mut self,
2928 hidden: bool,
2929 window: &mut Window,
2930 cx: &mut Context<Self>,
2931 ) {
2932 if hidden != self.edit_predictions_hidden_for_vim_mode {
2933 self.edit_predictions_hidden_for_vim_mode = hidden;
2934 if hidden {
2935 self.update_visible_edit_prediction(window, cx);
2936 } else {
2937 self.refresh_edit_prediction(true, false, window, cx);
2938 }
2939 }
2940 }
2941
2942 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2943 self.menu_edit_predictions_policy = value;
2944 }
2945
2946 pub fn set_autoindent(&mut self, autoindent: bool) {
2947 if autoindent {
2948 self.autoindent_mode = Some(AutoindentMode::EachLine);
2949 } else {
2950 self.autoindent_mode = None;
2951 }
2952 }
2953
2954 pub fn read_only(&self, cx: &App) -> bool {
2955 self.read_only || self.buffer.read(cx).read_only()
2956 }
2957
2958 pub fn set_read_only(&mut self, read_only: bool) {
2959 self.read_only = read_only;
2960 }
2961
2962 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2963 self.use_autoclose = autoclose;
2964 }
2965
2966 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2967 self.use_auto_surround = auto_surround;
2968 }
2969
2970 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2971 self.auto_replace_emoji_shortcode = auto_replace;
2972 }
2973
2974 pub fn toggle_edit_predictions(
2975 &mut self,
2976 _: &ToggleEditPrediction,
2977 window: &mut Window,
2978 cx: &mut Context<Self>,
2979 ) {
2980 if self.show_edit_predictions_override.is_some() {
2981 self.set_show_edit_predictions(None, window, cx);
2982 } else {
2983 let show_edit_predictions = !self.edit_predictions_enabled();
2984 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2985 }
2986 }
2987
2988 pub fn set_show_edit_predictions(
2989 &mut self,
2990 show_edit_predictions: Option<bool>,
2991 window: &mut Window,
2992 cx: &mut Context<Self>,
2993 ) {
2994 self.show_edit_predictions_override = show_edit_predictions;
2995 self.update_edit_prediction_settings(cx);
2996
2997 if let Some(false) = show_edit_predictions {
2998 self.discard_edit_prediction(false, cx);
2999 } else {
3000 self.refresh_edit_prediction(false, true, window, cx);
3001 }
3002 }
3003
3004 fn edit_predictions_disabled_in_scope(
3005 &self,
3006 buffer: &Entity<Buffer>,
3007 buffer_position: language::Anchor,
3008 cx: &App,
3009 ) -> bool {
3010 let snapshot = buffer.read(cx).snapshot();
3011 let settings = snapshot.settings_at(buffer_position, cx);
3012
3013 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3014 return false;
3015 };
3016
3017 scope.override_name().is_some_and(|scope_name| {
3018 settings
3019 .edit_predictions_disabled_in
3020 .iter()
3021 .any(|s| s == scope_name)
3022 })
3023 }
3024
3025 pub fn set_use_modal_editing(&mut self, to: bool) {
3026 self.use_modal_editing = to;
3027 }
3028
3029 pub fn use_modal_editing(&self) -> bool {
3030 self.use_modal_editing
3031 }
3032
3033 fn selections_did_change(
3034 &mut self,
3035 local: bool,
3036 old_cursor_position: &Anchor,
3037 effects: SelectionEffects,
3038 window: &mut Window,
3039 cx: &mut Context<Self>,
3040 ) {
3041 window.invalidate_character_coordinates();
3042
3043 // Copy selections to primary selection buffer
3044 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3045 if local {
3046 let selections = self.selections.all::<usize>(cx);
3047 let buffer_handle = self.buffer.read(cx).read(cx);
3048
3049 let mut text = String::new();
3050 for (index, selection) in selections.iter().enumerate() {
3051 let text_for_selection = buffer_handle
3052 .text_for_range(selection.start..selection.end)
3053 .collect::<String>();
3054
3055 text.push_str(&text_for_selection);
3056 if index != selections.len() - 1 {
3057 text.push('\n');
3058 }
3059 }
3060
3061 if !text.is_empty() {
3062 cx.write_to_primary(ClipboardItem::new_string(text));
3063 }
3064 }
3065
3066 let selection_anchors = self.selections.disjoint_anchors_arc();
3067
3068 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3069 self.buffer.update(cx, |buffer, cx| {
3070 buffer.set_active_selections(
3071 &selection_anchors,
3072 self.selections.line_mode(),
3073 self.cursor_shape,
3074 cx,
3075 )
3076 });
3077 }
3078 let display_map = self
3079 .display_map
3080 .update(cx, |display_map, cx| display_map.snapshot(cx));
3081 let buffer = &display_map.buffer_snapshot;
3082 if self.selections.count() == 1 {
3083 self.add_selections_state = None;
3084 }
3085 self.select_next_state = None;
3086 self.select_prev_state = None;
3087 self.select_syntax_node_history.try_clear();
3088 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3089 self.snippet_stack.invalidate(&selection_anchors, buffer);
3090 self.take_rename(false, window, cx);
3091
3092 let newest_selection = self.selections.newest_anchor();
3093 let new_cursor_position = newest_selection.head();
3094 let selection_start = newest_selection.start;
3095
3096 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3097 self.push_to_nav_history(
3098 *old_cursor_position,
3099 Some(new_cursor_position.to_point(buffer)),
3100 false,
3101 effects.nav_history == Some(true),
3102 cx,
3103 );
3104 }
3105
3106 if local {
3107 if let Some(buffer_id) = new_cursor_position.buffer_id
3108 && !self.registered_buffers.contains_key(&buffer_id)
3109 && let Some(project) = self.project.as_ref()
3110 {
3111 project.update(cx, |project, cx| {
3112 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3113 return;
3114 };
3115 self.registered_buffers.insert(
3116 buffer_id,
3117 project.register_buffer_with_language_servers(&buffer, cx),
3118 );
3119 })
3120 }
3121
3122 let mut context_menu = self.context_menu.borrow_mut();
3123 let completion_menu = match context_menu.as_ref() {
3124 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3125 Some(CodeContextMenu::CodeActions(_)) => {
3126 *context_menu = None;
3127 None
3128 }
3129 None => None,
3130 };
3131 let completion_position = completion_menu.map(|menu| menu.initial_position);
3132 drop(context_menu);
3133
3134 if effects.completions
3135 && let Some(completion_position) = completion_position
3136 {
3137 let start_offset = selection_start.to_offset(buffer);
3138 let position_matches = start_offset == completion_position.to_offset(buffer);
3139 let continue_showing = if position_matches {
3140 if self.snippet_stack.is_empty() {
3141 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3142 == Some(CharKind::Word)
3143 } else {
3144 // Snippet choices can be shown even when the cursor is in whitespace.
3145 // Dismissing the menu with actions like backspace is handled by
3146 // invalidation regions.
3147 true
3148 }
3149 } else {
3150 false
3151 };
3152
3153 if continue_showing {
3154 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3155 } else {
3156 self.hide_context_menu(window, cx);
3157 }
3158 }
3159
3160 hide_hover(self, cx);
3161
3162 if old_cursor_position.to_display_point(&display_map).row()
3163 != new_cursor_position.to_display_point(&display_map).row()
3164 {
3165 self.available_code_actions.take();
3166 }
3167 self.refresh_code_actions(window, cx);
3168 self.refresh_document_highlights(cx);
3169 self.refresh_selected_text_highlights(false, window, cx);
3170 refresh_matching_bracket_highlights(self, window, cx);
3171 self.update_visible_edit_prediction(window, cx);
3172 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3173 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3174 self.inline_blame_popover.take();
3175 if self.git_blame_inline_enabled {
3176 self.start_inline_blame_timer(window, cx);
3177 }
3178 }
3179
3180 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3181 cx.emit(EditorEvent::SelectionsChanged { local });
3182
3183 let selections = &self.selections.disjoint_anchors_arc();
3184 if selections.len() == 1 {
3185 cx.emit(SearchEvent::ActiveMatchChanged)
3186 }
3187 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3188 let inmemory_selections = selections
3189 .iter()
3190 .map(|s| {
3191 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3192 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3193 })
3194 .collect();
3195 self.update_restoration_data(cx, |data| {
3196 data.selections = inmemory_selections;
3197 });
3198
3199 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3200 && let Some(workspace_id) =
3201 self.workspace.as_ref().and_then(|workspace| workspace.1)
3202 {
3203 let snapshot = self.buffer().read(cx).snapshot(cx);
3204 let selections = selections.clone();
3205 let background_executor = cx.background_executor().clone();
3206 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3207 self.serialize_selections = cx.background_spawn(async move {
3208 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3209 let db_selections = selections
3210 .iter()
3211 .map(|selection| {
3212 (
3213 selection.start.to_offset(&snapshot),
3214 selection.end.to_offset(&snapshot),
3215 )
3216 })
3217 .collect();
3218
3219 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3220 .await
3221 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3222 .log_err();
3223 });
3224 }
3225 }
3226
3227 cx.notify();
3228 }
3229
3230 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3231 use text::ToOffset as _;
3232 use text::ToPoint as _;
3233
3234 if self.mode.is_minimap()
3235 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3236 {
3237 return;
3238 }
3239
3240 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3241 return;
3242 };
3243
3244 let snapshot = singleton.read(cx).snapshot();
3245 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3246 let display_snapshot = display_map.snapshot(cx);
3247
3248 display_snapshot
3249 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3250 .map(|fold| {
3251 fold.range.start.text_anchor.to_point(&snapshot)
3252 ..fold.range.end.text_anchor.to_point(&snapshot)
3253 })
3254 .collect()
3255 });
3256 self.update_restoration_data(cx, |data| {
3257 data.folds = inmemory_folds;
3258 });
3259
3260 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3261 return;
3262 };
3263 let background_executor = cx.background_executor().clone();
3264 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3265 let db_folds = self.display_map.update(cx, |display_map, cx| {
3266 display_map
3267 .snapshot(cx)
3268 .folds_in_range(0..snapshot.len())
3269 .map(|fold| {
3270 (
3271 fold.range.start.text_anchor.to_offset(&snapshot),
3272 fold.range.end.text_anchor.to_offset(&snapshot),
3273 )
3274 })
3275 .collect()
3276 });
3277 self.serialize_folds = cx.background_spawn(async move {
3278 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3279 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3280 .await
3281 .with_context(|| {
3282 format!(
3283 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3284 )
3285 })
3286 .log_err();
3287 });
3288 }
3289
3290 pub fn sync_selections(
3291 &mut self,
3292 other: Entity<Editor>,
3293 cx: &mut Context<Self>,
3294 ) -> gpui::Subscription {
3295 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3296 self.selections.change_with(cx, |selections| {
3297 selections.select_anchors(other_selections);
3298 });
3299
3300 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3301 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3302 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3303 if other_selections.is_empty() {
3304 return;
3305 }
3306 this.selections.change_with(cx, |selections| {
3307 selections.select_anchors(other_selections);
3308 });
3309 }
3310 });
3311
3312 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3313 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3314 let these_selections = this.selections.disjoint_anchors().to_vec();
3315 if these_selections.is_empty() {
3316 return;
3317 }
3318 other.update(cx, |other_editor, cx| {
3319 other_editor.selections.change_with(cx, |selections| {
3320 selections.select_anchors(these_selections);
3321 })
3322 });
3323 }
3324 });
3325
3326 Subscription::join(other_subscription, this_subscription)
3327 }
3328
3329 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3330 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3331 /// effects of selection change occur at the end of the transaction.
3332 pub fn change_selections<R>(
3333 &mut self,
3334 effects: SelectionEffects,
3335 window: &mut Window,
3336 cx: &mut Context<Self>,
3337 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3338 ) -> R {
3339 if let Some(state) = &mut self.deferred_selection_effects_state {
3340 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3341 state.effects.completions = effects.completions;
3342 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3343 let (changed, result) = self.selections.change_with(cx, change);
3344 state.changed |= changed;
3345 return result;
3346 }
3347 let mut state = DeferredSelectionEffectsState {
3348 changed: false,
3349 effects,
3350 old_cursor_position: self.selections.newest_anchor().head(),
3351 history_entry: SelectionHistoryEntry {
3352 selections: self.selections.disjoint_anchors_arc(),
3353 select_next_state: self.select_next_state.clone(),
3354 select_prev_state: self.select_prev_state.clone(),
3355 add_selections_state: self.add_selections_state.clone(),
3356 },
3357 };
3358 let (changed, result) = self.selections.change_with(cx, change);
3359 state.changed = state.changed || changed;
3360 if self.defer_selection_effects {
3361 self.deferred_selection_effects_state = Some(state);
3362 } else {
3363 self.apply_selection_effects(state, window, cx);
3364 }
3365 result
3366 }
3367
3368 /// Defers the effects of selection change, so that the effects of multiple calls to
3369 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3370 /// to selection history and the state of popovers based on selection position aren't
3371 /// erroneously updated.
3372 pub fn with_selection_effects_deferred<R>(
3373 &mut self,
3374 window: &mut Window,
3375 cx: &mut Context<Self>,
3376 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3377 ) -> R {
3378 let already_deferred = self.defer_selection_effects;
3379 self.defer_selection_effects = true;
3380 let result = update(self, window, cx);
3381 if !already_deferred {
3382 self.defer_selection_effects = false;
3383 if let Some(state) = self.deferred_selection_effects_state.take() {
3384 self.apply_selection_effects(state, window, cx);
3385 }
3386 }
3387 result
3388 }
3389
3390 fn apply_selection_effects(
3391 &mut self,
3392 state: DeferredSelectionEffectsState,
3393 window: &mut Window,
3394 cx: &mut Context<Self>,
3395 ) {
3396 if state.changed {
3397 self.selection_history.push(state.history_entry);
3398
3399 if let Some(autoscroll) = state.effects.scroll {
3400 self.request_autoscroll(autoscroll, cx);
3401 }
3402
3403 let old_cursor_position = &state.old_cursor_position;
3404
3405 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3406
3407 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3408 self.show_signature_help(&ShowSignatureHelp, window, cx);
3409 }
3410 }
3411 }
3412
3413 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3414 where
3415 I: IntoIterator<Item = (Range<S>, T)>,
3416 S: ToOffset,
3417 T: Into<Arc<str>>,
3418 {
3419 if self.read_only(cx) {
3420 return;
3421 }
3422
3423 self.buffer
3424 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3425 }
3426
3427 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3428 where
3429 I: IntoIterator<Item = (Range<S>, T)>,
3430 S: ToOffset,
3431 T: Into<Arc<str>>,
3432 {
3433 if self.read_only(cx) {
3434 return;
3435 }
3436
3437 self.buffer.update(cx, |buffer, cx| {
3438 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3439 });
3440 }
3441
3442 pub fn edit_with_block_indent<I, S, T>(
3443 &mut self,
3444 edits: I,
3445 original_indent_columns: Vec<Option<u32>>,
3446 cx: &mut Context<Self>,
3447 ) where
3448 I: IntoIterator<Item = (Range<S>, T)>,
3449 S: ToOffset,
3450 T: Into<Arc<str>>,
3451 {
3452 if self.read_only(cx) {
3453 return;
3454 }
3455
3456 self.buffer.update(cx, |buffer, cx| {
3457 buffer.edit(
3458 edits,
3459 Some(AutoindentMode::Block {
3460 original_indent_columns,
3461 }),
3462 cx,
3463 )
3464 });
3465 }
3466
3467 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3468 self.hide_context_menu(window, cx);
3469
3470 match phase {
3471 SelectPhase::Begin {
3472 position,
3473 add,
3474 click_count,
3475 } => self.begin_selection(position, add, click_count, window, cx),
3476 SelectPhase::BeginColumnar {
3477 position,
3478 goal_column,
3479 reset,
3480 mode,
3481 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3482 SelectPhase::Extend {
3483 position,
3484 click_count,
3485 } => self.extend_selection(position, click_count, window, cx),
3486 SelectPhase::Update {
3487 position,
3488 goal_column,
3489 scroll_delta,
3490 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3491 SelectPhase::End => self.end_selection(window, cx),
3492 }
3493 }
3494
3495 fn extend_selection(
3496 &mut self,
3497 position: DisplayPoint,
3498 click_count: usize,
3499 window: &mut Window,
3500 cx: &mut Context<Self>,
3501 ) {
3502 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3503 let tail = self.selections.newest::<usize>(cx).tail();
3504 self.begin_selection(position, false, click_count, window, cx);
3505
3506 let position = position.to_offset(&display_map, Bias::Left);
3507 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3508
3509 let mut pending_selection = self
3510 .selections
3511 .pending_anchor()
3512 .cloned()
3513 .expect("extend_selection not called with pending selection");
3514 if position >= tail {
3515 pending_selection.start = tail_anchor;
3516 } else {
3517 pending_selection.end = tail_anchor;
3518 pending_selection.reversed = true;
3519 }
3520
3521 let mut pending_mode = self.selections.pending_mode().unwrap();
3522 match &mut pending_mode {
3523 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3524 _ => {}
3525 }
3526
3527 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3528 SelectionEffects::scroll(Autoscroll::fit())
3529 } else {
3530 SelectionEffects::no_scroll()
3531 };
3532
3533 self.change_selections(effects, window, cx, |s| {
3534 s.set_pending(pending_selection.clone(), pending_mode)
3535 });
3536 }
3537
3538 fn begin_selection(
3539 &mut self,
3540 position: DisplayPoint,
3541 add: bool,
3542 click_count: usize,
3543 window: &mut Window,
3544 cx: &mut Context<Self>,
3545 ) {
3546 if !self.focus_handle.is_focused(window) {
3547 self.last_focused_descendant = None;
3548 window.focus(&self.focus_handle);
3549 }
3550
3551 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3552 let buffer = &display_map.buffer_snapshot;
3553 let position = display_map.clip_point(position, Bias::Left);
3554
3555 let start;
3556 let end;
3557 let mode;
3558 let mut auto_scroll;
3559 match click_count {
3560 1 => {
3561 start = buffer.anchor_before(position.to_point(&display_map));
3562 end = start;
3563 mode = SelectMode::Character;
3564 auto_scroll = true;
3565 }
3566 2 => {
3567 let position = display_map
3568 .clip_point(position, Bias::Left)
3569 .to_offset(&display_map, Bias::Left);
3570 let (range, _) = buffer.surrounding_word(position, None);
3571 start = buffer.anchor_before(range.start);
3572 end = buffer.anchor_before(range.end);
3573 mode = SelectMode::Word(start..end);
3574 auto_scroll = true;
3575 }
3576 3 => {
3577 let position = display_map
3578 .clip_point(position, Bias::Left)
3579 .to_point(&display_map);
3580 let line_start = display_map.prev_line_boundary(position).0;
3581 let next_line_start = buffer.clip_point(
3582 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3583 Bias::Left,
3584 );
3585 start = buffer.anchor_before(line_start);
3586 end = buffer.anchor_before(next_line_start);
3587 mode = SelectMode::Line(start..end);
3588 auto_scroll = true;
3589 }
3590 _ => {
3591 start = buffer.anchor_before(0);
3592 end = buffer.anchor_before(buffer.len());
3593 mode = SelectMode::All;
3594 auto_scroll = false;
3595 }
3596 }
3597 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3598
3599 let point_to_delete: Option<usize> = {
3600 let selected_points: Vec<Selection<Point>> =
3601 self.selections.disjoint_in_range(start..end, cx);
3602
3603 if !add || click_count > 1 {
3604 None
3605 } else if !selected_points.is_empty() {
3606 Some(selected_points[0].id)
3607 } else {
3608 let clicked_point_already_selected =
3609 self.selections.disjoint_anchors().iter().find(|selection| {
3610 selection.start.to_point(buffer) == start.to_point(buffer)
3611 || selection.end.to_point(buffer) == end.to_point(buffer)
3612 });
3613
3614 clicked_point_already_selected.map(|selection| selection.id)
3615 }
3616 };
3617
3618 let selections_count = self.selections.count();
3619 let effects = if auto_scroll {
3620 SelectionEffects::default()
3621 } else {
3622 SelectionEffects::no_scroll()
3623 };
3624
3625 self.change_selections(effects, window, cx, |s| {
3626 if let Some(point_to_delete) = point_to_delete {
3627 s.delete(point_to_delete);
3628
3629 if selections_count == 1 {
3630 s.set_pending_anchor_range(start..end, mode);
3631 }
3632 } else {
3633 if !add {
3634 s.clear_disjoint();
3635 }
3636
3637 s.set_pending_anchor_range(start..end, mode);
3638 }
3639 });
3640 }
3641
3642 fn begin_columnar_selection(
3643 &mut self,
3644 position: DisplayPoint,
3645 goal_column: u32,
3646 reset: bool,
3647 mode: ColumnarMode,
3648 window: &mut Window,
3649 cx: &mut Context<Self>,
3650 ) {
3651 if !self.focus_handle.is_focused(window) {
3652 self.last_focused_descendant = None;
3653 window.focus(&self.focus_handle);
3654 }
3655
3656 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3657
3658 if reset {
3659 let pointer_position = display_map
3660 .buffer_snapshot
3661 .anchor_before(position.to_point(&display_map));
3662
3663 self.change_selections(
3664 SelectionEffects::scroll(Autoscroll::newest()),
3665 window,
3666 cx,
3667 |s| {
3668 s.clear_disjoint();
3669 s.set_pending_anchor_range(
3670 pointer_position..pointer_position,
3671 SelectMode::Character,
3672 );
3673 },
3674 );
3675 };
3676
3677 let tail = self.selections.newest::<Point>(cx).tail();
3678 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3679 self.columnar_selection_state = match mode {
3680 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3681 selection_tail: selection_anchor,
3682 display_point: if reset {
3683 if position.column() != goal_column {
3684 Some(DisplayPoint::new(position.row(), goal_column))
3685 } else {
3686 None
3687 }
3688 } else {
3689 None
3690 },
3691 }),
3692 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3693 selection_tail: selection_anchor,
3694 }),
3695 };
3696
3697 if !reset {
3698 self.select_columns(position, goal_column, &display_map, window, cx);
3699 }
3700 }
3701
3702 fn update_selection(
3703 &mut self,
3704 position: DisplayPoint,
3705 goal_column: u32,
3706 scroll_delta: gpui::Point<f32>,
3707 window: &mut Window,
3708 cx: &mut Context<Self>,
3709 ) {
3710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3711
3712 if self.columnar_selection_state.is_some() {
3713 self.select_columns(position, goal_column, &display_map, window, cx);
3714 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3715 let buffer = &display_map.buffer_snapshot;
3716 let head;
3717 let tail;
3718 let mode = self.selections.pending_mode().unwrap();
3719 match &mode {
3720 SelectMode::Character => {
3721 head = position.to_point(&display_map);
3722 tail = pending.tail().to_point(buffer);
3723 }
3724 SelectMode::Word(original_range) => {
3725 let offset = display_map
3726 .clip_point(position, Bias::Left)
3727 .to_offset(&display_map, Bias::Left);
3728 let original_range = original_range.to_offset(buffer);
3729
3730 let head_offset = if buffer.is_inside_word(offset, None)
3731 || original_range.contains(&offset)
3732 {
3733 let (word_range, _) = buffer.surrounding_word(offset, None);
3734 if word_range.start < original_range.start {
3735 word_range.start
3736 } else {
3737 word_range.end
3738 }
3739 } else {
3740 offset
3741 };
3742
3743 head = head_offset.to_point(buffer);
3744 if head_offset <= original_range.start {
3745 tail = original_range.end.to_point(buffer);
3746 } else {
3747 tail = original_range.start.to_point(buffer);
3748 }
3749 }
3750 SelectMode::Line(original_range) => {
3751 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3752
3753 let position = display_map
3754 .clip_point(position, Bias::Left)
3755 .to_point(&display_map);
3756 let line_start = display_map.prev_line_boundary(position).0;
3757 let next_line_start = buffer.clip_point(
3758 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3759 Bias::Left,
3760 );
3761
3762 if line_start < original_range.start {
3763 head = line_start
3764 } else {
3765 head = next_line_start
3766 }
3767
3768 if head <= original_range.start {
3769 tail = original_range.end;
3770 } else {
3771 tail = original_range.start;
3772 }
3773 }
3774 SelectMode::All => {
3775 return;
3776 }
3777 };
3778
3779 if head < tail {
3780 pending.start = buffer.anchor_before(head);
3781 pending.end = buffer.anchor_before(tail);
3782 pending.reversed = true;
3783 } else {
3784 pending.start = buffer.anchor_before(tail);
3785 pending.end = buffer.anchor_before(head);
3786 pending.reversed = false;
3787 }
3788
3789 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3790 s.set_pending(pending.clone(), mode);
3791 });
3792 } else {
3793 log::error!("update_selection dispatched with no pending selection");
3794 return;
3795 }
3796
3797 self.apply_scroll_delta(scroll_delta, window, cx);
3798 cx.notify();
3799 }
3800
3801 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3802 self.columnar_selection_state.take();
3803 if self.selections.pending_anchor().is_some() {
3804 let selections = self.selections.all::<usize>(cx);
3805 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3806 s.select(selections);
3807 s.clear_pending();
3808 });
3809 }
3810 }
3811
3812 fn select_columns(
3813 &mut self,
3814 head: DisplayPoint,
3815 goal_column: u32,
3816 display_map: &DisplaySnapshot,
3817 window: &mut Window,
3818 cx: &mut Context<Self>,
3819 ) {
3820 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3821 return;
3822 };
3823
3824 let tail = match columnar_state {
3825 ColumnarSelectionState::FromMouse {
3826 selection_tail,
3827 display_point,
3828 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3829 ColumnarSelectionState::FromSelection { selection_tail } => {
3830 selection_tail.to_display_point(display_map)
3831 }
3832 };
3833
3834 let start_row = cmp::min(tail.row(), head.row());
3835 let end_row = cmp::max(tail.row(), head.row());
3836 let start_column = cmp::min(tail.column(), goal_column);
3837 let end_column = cmp::max(tail.column(), goal_column);
3838 let reversed = start_column < tail.column();
3839
3840 let selection_ranges = (start_row.0..=end_row.0)
3841 .map(DisplayRow)
3842 .filter_map(|row| {
3843 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3844 || start_column <= display_map.line_len(row))
3845 && !display_map.is_block_line(row)
3846 {
3847 let start = display_map
3848 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3849 .to_point(display_map);
3850 let end = display_map
3851 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3852 .to_point(display_map);
3853 if reversed {
3854 Some(end..start)
3855 } else {
3856 Some(start..end)
3857 }
3858 } else {
3859 None
3860 }
3861 })
3862 .collect::<Vec<_>>();
3863
3864 let ranges = match columnar_state {
3865 ColumnarSelectionState::FromMouse { .. } => {
3866 let mut non_empty_ranges = selection_ranges
3867 .iter()
3868 .filter(|selection_range| selection_range.start != selection_range.end)
3869 .peekable();
3870 if non_empty_ranges.peek().is_some() {
3871 non_empty_ranges.cloned().collect()
3872 } else {
3873 selection_ranges
3874 }
3875 }
3876 _ => selection_ranges,
3877 };
3878
3879 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3880 s.select_ranges(ranges);
3881 });
3882 cx.notify();
3883 }
3884
3885 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3886 self.selections
3887 .all_adjusted(cx)
3888 .iter()
3889 .any(|selection| !selection.is_empty())
3890 }
3891
3892 pub fn has_pending_nonempty_selection(&self) -> bool {
3893 let pending_nonempty_selection = match self.selections.pending_anchor() {
3894 Some(Selection { start, end, .. }) => start != end,
3895 None => false,
3896 };
3897
3898 pending_nonempty_selection
3899 || (self.columnar_selection_state.is_some()
3900 && self.selections.disjoint_anchors().len() > 1)
3901 }
3902
3903 pub fn has_pending_selection(&self) -> bool {
3904 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3905 }
3906
3907 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3908 self.selection_mark_mode = false;
3909 self.selection_drag_state = SelectionDragState::None;
3910
3911 if self.clear_expanded_diff_hunks(cx) {
3912 cx.notify();
3913 return;
3914 }
3915 if self.dismiss_menus_and_popups(true, window, cx) {
3916 return;
3917 }
3918
3919 if self.mode.is_full()
3920 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3921 {
3922 return;
3923 }
3924
3925 cx.propagate();
3926 }
3927
3928 pub fn dismiss_menus_and_popups(
3929 &mut self,
3930 is_user_requested: bool,
3931 window: &mut Window,
3932 cx: &mut Context<Self>,
3933 ) -> bool {
3934 if self.take_rename(false, window, cx).is_some() {
3935 return true;
3936 }
3937
3938 if hide_hover(self, cx) {
3939 return true;
3940 }
3941
3942 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3943 return true;
3944 }
3945
3946 if self.hide_context_menu(window, cx).is_some() {
3947 return true;
3948 }
3949
3950 if self.mouse_context_menu.take().is_some() {
3951 return true;
3952 }
3953
3954 if is_user_requested && self.discard_edit_prediction(true, cx) {
3955 return true;
3956 }
3957
3958 if self.snippet_stack.pop().is_some() {
3959 return true;
3960 }
3961
3962 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3963 self.dismiss_diagnostics(cx);
3964 return true;
3965 }
3966
3967 false
3968 }
3969
3970 fn linked_editing_ranges_for(
3971 &self,
3972 selection: Range<text::Anchor>,
3973 cx: &App,
3974 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3975 if self.linked_edit_ranges.is_empty() {
3976 return None;
3977 }
3978 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3979 selection.end.buffer_id.and_then(|end_buffer_id| {
3980 if selection.start.buffer_id != Some(end_buffer_id) {
3981 return None;
3982 }
3983 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3984 let snapshot = buffer.read(cx).snapshot();
3985 self.linked_edit_ranges
3986 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3987 .map(|ranges| (ranges, snapshot, buffer))
3988 })?;
3989 use text::ToOffset as TO;
3990 // find offset from the start of current range to current cursor position
3991 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3992
3993 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3994 let start_difference = start_offset - start_byte_offset;
3995 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3996 let end_difference = end_offset - start_byte_offset;
3997 // Current range has associated linked ranges.
3998 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3999 for range in linked_ranges.iter() {
4000 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4001 let end_offset = start_offset + end_difference;
4002 let start_offset = start_offset + start_difference;
4003 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4004 continue;
4005 }
4006 if self.selections.disjoint_anchor_ranges().any(|s| {
4007 if s.start.buffer_id != selection.start.buffer_id
4008 || s.end.buffer_id != selection.end.buffer_id
4009 {
4010 return false;
4011 }
4012 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4013 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4014 }) {
4015 continue;
4016 }
4017 let start = buffer_snapshot.anchor_after(start_offset);
4018 let end = buffer_snapshot.anchor_after(end_offset);
4019 linked_edits
4020 .entry(buffer.clone())
4021 .or_default()
4022 .push(start..end);
4023 }
4024 Some(linked_edits)
4025 }
4026
4027 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4028 let text: Arc<str> = text.into();
4029
4030 if self.read_only(cx) {
4031 return;
4032 }
4033
4034 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4035
4036 let selections = self.selections.all_adjusted(cx);
4037 let mut bracket_inserted = false;
4038 let mut edits = Vec::new();
4039 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4040 let mut new_selections = Vec::with_capacity(selections.len());
4041 let mut new_autoclose_regions = Vec::new();
4042 let snapshot = self.buffer.read(cx).read(cx);
4043 let mut clear_linked_edit_ranges = false;
4044
4045 for (selection, autoclose_region) in
4046 self.selections_with_autoclose_regions(selections, &snapshot)
4047 {
4048 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4049 // Determine if the inserted text matches the opening or closing
4050 // bracket of any of this language's bracket pairs.
4051 let mut bracket_pair = None;
4052 let mut is_bracket_pair_start = false;
4053 let mut is_bracket_pair_end = false;
4054 if !text.is_empty() {
4055 let mut bracket_pair_matching_end = None;
4056 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4057 // and they are removing the character that triggered IME popup.
4058 for (pair, enabled) in scope.brackets() {
4059 if !pair.close && !pair.surround {
4060 continue;
4061 }
4062
4063 if enabled && pair.start.ends_with(text.as_ref()) {
4064 let prefix_len = pair.start.len() - text.len();
4065 let preceding_text_matches_prefix = prefix_len == 0
4066 || (selection.start.column >= (prefix_len as u32)
4067 && snapshot.contains_str_at(
4068 Point::new(
4069 selection.start.row,
4070 selection.start.column - (prefix_len as u32),
4071 ),
4072 &pair.start[..prefix_len],
4073 ));
4074 if preceding_text_matches_prefix {
4075 bracket_pair = Some(pair.clone());
4076 is_bracket_pair_start = true;
4077 break;
4078 }
4079 }
4080 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4081 {
4082 // take first bracket pair matching end, but don't break in case a later bracket
4083 // pair matches start
4084 bracket_pair_matching_end = Some(pair.clone());
4085 }
4086 }
4087 if let Some(end) = bracket_pair_matching_end
4088 && bracket_pair.is_none()
4089 {
4090 bracket_pair = Some(end);
4091 is_bracket_pair_end = true;
4092 }
4093 }
4094
4095 if let Some(bracket_pair) = bracket_pair {
4096 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4097 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4098 let auto_surround =
4099 self.use_auto_surround && snapshot_settings.use_auto_surround;
4100 if selection.is_empty() {
4101 if is_bracket_pair_start {
4102 // If the inserted text is a suffix of an opening bracket and the
4103 // selection is preceded by the rest of the opening bracket, then
4104 // insert the closing bracket.
4105 let following_text_allows_autoclose = snapshot
4106 .chars_at(selection.start)
4107 .next()
4108 .is_none_or(|c| scope.should_autoclose_before(c));
4109
4110 let preceding_text_allows_autoclose = selection.start.column == 0
4111 || snapshot
4112 .reversed_chars_at(selection.start)
4113 .next()
4114 .is_none_or(|c| {
4115 bracket_pair.start != bracket_pair.end
4116 || !snapshot
4117 .char_classifier_at(selection.start)
4118 .is_word(c)
4119 });
4120
4121 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4122 && bracket_pair.start.len() == 1
4123 {
4124 let target = bracket_pair.start.chars().next().unwrap();
4125 let current_line_count = snapshot
4126 .reversed_chars_at(selection.start)
4127 .take_while(|&c| c != '\n')
4128 .filter(|&c| c == target)
4129 .count();
4130 current_line_count % 2 == 1
4131 } else {
4132 false
4133 };
4134
4135 if autoclose
4136 && bracket_pair.close
4137 && following_text_allows_autoclose
4138 && preceding_text_allows_autoclose
4139 && !is_closing_quote
4140 {
4141 let anchor = snapshot.anchor_before(selection.end);
4142 new_selections.push((selection.map(|_| anchor), text.len()));
4143 new_autoclose_regions.push((
4144 anchor,
4145 text.len(),
4146 selection.id,
4147 bracket_pair.clone(),
4148 ));
4149 edits.push((
4150 selection.range(),
4151 format!("{}{}", text, bracket_pair.end).into(),
4152 ));
4153 bracket_inserted = true;
4154 continue;
4155 }
4156 }
4157
4158 if let Some(region) = autoclose_region {
4159 // If the selection is followed by an auto-inserted closing bracket,
4160 // then don't insert that closing bracket again; just move the selection
4161 // past the closing bracket.
4162 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4163 && text.as_ref() == region.pair.end.as_str()
4164 && snapshot.contains_str_at(region.range.end, text.as_ref());
4165 if should_skip {
4166 let anchor = snapshot.anchor_after(selection.end);
4167 new_selections
4168 .push((selection.map(|_| anchor), region.pair.end.len()));
4169 continue;
4170 }
4171 }
4172
4173 let always_treat_brackets_as_autoclosed = snapshot
4174 .language_settings_at(selection.start, cx)
4175 .always_treat_brackets_as_autoclosed;
4176 if always_treat_brackets_as_autoclosed
4177 && is_bracket_pair_end
4178 && snapshot.contains_str_at(selection.end, text.as_ref())
4179 {
4180 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4181 // and the inserted text is a closing bracket and the selection is followed
4182 // by the closing bracket then move the selection past the closing bracket.
4183 let anchor = snapshot.anchor_after(selection.end);
4184 new_selections.push((selection.map(|_| anchor), text.len()));
4185 continue;
4186 }
4187 }
4188 // If an opening bracket is 1 character long and is typed while
4189 // text is selected, then surround that text with the bracket pair.
4190 else if auto_surround
4191 && bracket_pair.surround
4192 && is_bracket_pair_start
4193 && bracket_pair.start.chars().count() == 1
4194 {
4195 edits.push((selection.start..selection.start, text.clone()));
4196 edits.push((
4197 selection.end..selection.end,
4198 bracket_pair.end.as_str().into(),
4199 ));
4200 bracket_inserted = true;
4201 new_selections.push((
4202 Selection {
4203 id: selection.id,
4204 start: snapshot.anchor_after(selection.start),
4205 end: snapshot.anchor_before(selection.end),
4206 reversed: selection.reversed,
4207 goal: selection.goal,
4208 },
4209 0,
4210 ));
4211 continue;
4212 }
4213 }
4214 }
4215
4216 if self.auto_replace_emoji_shortcode
4217 && selection.is_empty()
4218 && text.as_ref().ends_with(':')
4219 && let Some(possible_emoji_short_code) =
4220 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4221 && !possible_emoji_short_code.is_empty()
4222 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4223 {
4224 let emoji_shortcode_start = Point::new(
4225 selection.start.row,
4226 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4227 );
4228
4229 // Remove shortcode from buffer
4230 edits.push((
4231 emoji_shortcode_start..selection.start,
4232 "".to_string().into(),
4233 ));
4234 new_selections.push((
4235 Selection {
4236 id: selection.id,
4237 start: snapshot.anchor_after(emoji_shortcode_start),
4238 end: snapshot.anchor_before(selection.start),
4239 reversed: selection.reversed,
4240 goal: selection.goal,
4241 },
4242 0,
4243 ));
4244
4245 // Insert emoji
4246 let selection_start_anchor = snapshot.anchor_after(selection.start);
4247 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4248 edits.push((selection.start..selection.end, emoji.to_string().into()));
4249
4250 continue;
4251 }
4252
4253 // If not handling any auto-close operation, then just replace the selected
4254 // text with the given input and move the selection to the end of the
4255 // newly inserted text.
4256 let anchor = snapshot.anchor_after(selection.end);
4257 if !self.linked_edit_ranges.is_empty() {
4258 let start_anchor = snapshot.anchor_before(selection.start);
4259
4260 let is_word_char = text.chars().next().is_none_or(|char| {
4261 let classifier = snapshot
4262 .char_classifier_at(start_anchor.to_offset(&snapshot))
4263 .scope_context(Some(CharScopeContext::LinkedEdit));
4264 classifier.is_word(char)
4265 });
4266
4267 if is_word_char {
4268 if let Some(ranges) = self
4269 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4270 {
4271 for (buffer, edits) in ranges {
4272 linked_edits
4273 .entry(buffer.clone())
4274 .or_default()
4275 .extend(edits.into_iter().map(|range| (range, text.clone())));
4276 }
4277 }
4278 } else {
4279 clear_linked_edit_ranges = true;
4280 }
4281 }
4282
4283 new_selections.push((selection.map(|_| anchor), 0));
4284 edits.push((selection.start..selection.end, text.clone()));
4285 }
4286
4287 drop(snapshot);
4288
4289 self.transact(window, cx, |this, window, cx| {
4290 if clear_linked_edit_ranges {
4291 this.linked_edit_ranges.clear();
4292 }
4293 let initial_buffer_versions =
4294 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4295
4296 this.buffer.update(cx, |buffer, cx| {
4297 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4298 });
4299 for (buffer, edits) in linked_edits {
4300 buffer.update(cx, |buffer, cx| {
4301 let snapshot = buffer.snapshot();
4302 let edits = edits
4303 .into_iter()
4304 .map(|(range, text)| {
4305 use text::ToPoint as TP;
4306 let end_point = TP::to_point(&range.end, &snapshot);
4307 let start_point = TP::to_point(&range.start, &snapshot);
4308 (start_point..end_point, text)
4309 })
4310 .sorted_by_key(|(range, _)| range.start);
4311 buffer.edit(edits, None, cx);
4312 })
4313 }
4314 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4315 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4316 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4317 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4318 .zip(new_selection_deltas)
4319 .map(|(selection, delta)| Selection {
4320 id: selection.id,
4321 start: selection.start + delta,
4322 end: selection.end + delta,
4323 reversed: selection.reversed,
4324 goal: SelectionGoal::None,
4325 })
4326 .collect::<Vec<_>>();
4327
4328 let mut i = 0;
4329 for (position, delta, selection_id, pair) in new_autoclose_regions {
4330 let position = position.to_offset(&map.buffer_snapshot) + delta;
4331 let start = map.buffer_snapshot.anchor_before(position);
4332 let end = map.buffer_snapshot.anchor_after(position);
4333 while let Some(existing_state) = this.autoclose_regions.get(i) {
4334 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4335 Ordering::Less => i += 1,
4336 Ordering::Greater => break,
4337 Ordering::Equal => {
4338 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4339 Ordering::Less => i += 1,
4340 Ordering::Equal => break,
4341 Ordering::Greater => break,
4342 }
4343 }
4344 }
4345 }
4346 this.autoclose_regions.insert(
4347 i,
4348 AutocloseRegion {
4349 selection_id,
4350 range: start..end,
4351 pair,
4352 },
4353 );
4354 }
4355
4356 let had_active_edit_prediction = this.has_active_edit_prediction();
4357 this.change_selections(
4358 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4359 window,
4360 cx,
4361 |s| s.select(new_selections),
4362 );
4363
4364 if !bracket_inserted
4365 && let Some(on_type_format_task) =
4366 this.trigger_on_type_formatting(text.to_string(), window, cx)
4367 {
4368 on_type_format_task.detach_and_log_err(cx);
4369 }
4370
4371 let editor_settings = EditorSettings::get_global(cx);
4372 if bracket_inserted
4373 && (editor_settings.auto_signature_help
4374 || editor_settings.show_signature_help_after_edits)
4375 {
4376 this.show_signature_help(&ShowSignatureHelp, window, cx);
4377 }
4378
4379 let trigger_in_words =
4380 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4381 if this.hard_wrap.is_some() {
4382 let latest: Range<Point> = this.selections.newest(cx).range();
4383 if latest.is_empty()
4384 && this
4385 .buffer()
4386 .read(cx)
4387 .snapshot(cx)
4388 .line_len(MultiBufferRow(latest.start.row))
4389 == latest.start.column
4390 {
4391 this.rewrap_impl(
4392 RewrapOptions {
4393 override_language_settings: true,
4394 preserve_existing_whitespace: true,
4395 },
4396 cx,
4397 )
4398 }
4399 }
4400 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4401 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4402 this.refresh_edit_prediction(true, false, window, cx);
4403 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4404 });
4405 }
4406
4407 fn find_possible_emoji_shortcode_at_position(
4408 snapshot: &MultiBufferSnapshot,
4409 position: Point,
4410 ) -> Option<String> {
4411 let mut chars = Vec::new();
4412 let mut found_colon = false;
4413 for char in snapshot.reversed_chars_at(position).take(100) {
4414 // Found a possible emoji shortcode in the middle of the buffer
4415 if found_colon {
4416 if char.is_whitespace() {
4417 chars.reverse();
4418 return Some(chars.iter().collect());
4419 }
4420 // If the previous character is not a whitespace, we are in the middle of a word
4421 // and we only want to complete the shortcode if the word is made up of other emojis
4422 let mut containing_word = String::new();
4423 for ch in snapshot
4424 .reversed_chars_at(position)
4425 .skip(chars.len() + 1)
4426 .take(100)
4427 {
4428 if ch.is_whitespace() {
4429 break;
4430 }
4431 containing_word.push(ch);
4432 }
4433 let containing_word = containing_word.chars().rev().collect::<String>();
4434 if util::word_consists_of_emojis(containing_word.as_str()) {
4435 chars.reverse();
4436 return Some(chars.iter().collect());
4437 }
4438 }
4439
4440 if char.is_whitespace() || !char.is_ascii() {
4441 return None;
4442 }
4443 if char == ':' {
4444 found_colon = true;
4445 } else {
4446 chars.push(char);
4447 }
4448 }
4449 // Found a possible emoji shortcode at the beginning of the buffer
4450 chars.reverse();
4451 Some(chars.iter().collect())
4452 }
4453
4454 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4455 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4456 self.transact(window, cx, |this, window, cx| {
4457 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4458 let selections = this.selections.all::<usize>(cx);
4459 let multi_buffer = this.buffer.read(cx);
4460 let buffer = multi_buffer.snapshot(cx);
4461 selections
4462 .iter()
4463 .map(|selection| {
4464 let start_point = selection.start.to_point(&buffer);
4465 let mut existing_indent =
4466 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4467 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4468 let start = selection.start;
4469 let end = selection.end;
4470 let selection_is_empty = start == end;
4471 let language_scope = buffer.language_scope_at(start);
4472 let (
4473 comment_delimiter,
4474 doc_delimiter,
4475 insert_extra_newline,
4476 indent_on_newline,
4477 indent_on_extra_newline,
4478 ) = if let Some(language) = &language_scope {
4479 let mut insert_extra_newline =
4480 insert_extra_newline_brackets(&buffer, start..end, language)
4481 || insert_extra_newline_tree_sitter(&buffer, start..end);
4482
4483 // Comment extension on newline is allowed only for cursor selections
4484 let comment_delimiter = maybe!({
4485 if !selection_is_empty {
4486 return None;
4487 }
4488
4489 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4490 return None;
4491 }
4492
4493 let delimiters = language.line_comment_prefixes();
4494 let max_len_of_delimiter =
4495 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4496 let (snapshot, range) =
4497 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4498
4499 let num_of_whitespaces = snapshot
4500 .chars_for_range(range.clone())
4501 .take_while(|c| c.is_whitespace())
4502 .count();
4503 let comment_candidate = snapshot
4504 .chars_for_range(range.clone())
4505 .skip(num_of_whitespaces)
4506 .take(max_len_of_delimiter)
4507 .collect::<String>();
4508 let (delimiter, trimmed_len) = delimiters
4509 .iter()
4510 .filter_map(|delimiter| {
4511 let prefix = delimiter.trim_end();
4512 if comment_candidate.starts_with(prefix) {
4513 Some((delimiter, prefix.len()))
4514 } else {
4515 None
4516 }
4517 })
4518 .max_by_key(|(_, len)| *len)?;
4519
4520 if let Some(BlockCommentConfig {
4521 start: block_start, ..
4522 }) = language.block_comment()
4523 {
4524 let block_start_trimmed = block_start.trim_end();
4525 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4526 let line_content = snapshot
4527 .chars_for_range(range)
4528 .skip(num_of_whitespaces)
4529 .take(block_start_trimmed.len())
4530 .collect::<String>();
4531
4532 if line_content.starts_with(block_start_trimmed) {
4533 return None;
4534 }
4535 }
4536 }
4537
4538 let cursor_is_placed_after_comment_marker =
4539 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4540 if cursor_is_placed_after_comment_marker {
4541 Some(delimiter.clone())
4542 } else {
4543 None
4544 }
4545 });
4546
4547 let mut indent_on_newline = IndentSize::spaces(0);
4548 let mut indent_on_extra_newline = IndentSize::spaces(0);
4549
4550 let doc_delimiter = maybe!({
4551 if !selection_is_empty {
4552 return None;
4553 }
4554
4555 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4556 return None;
4557 }
4558
4559 let BlockCommentConfig {
4560 start: start_tag,
4561 end: end_tag,
4562 prefix: delimiter,
4563 tab_size: len,
4564 } = language.documentation_comment()?;
4565 let is_within_block_comment = buffer
4566 .language_scope_at(start_point)
4567 .is_some_and(|scope| scope.override_name() == Some("comment"));
4568 if !is_within_block_comment {
4569 return None;
4570 }
4571
4572 let (snapshot, range) =
4573 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4574
4575 let num_of_whitespaces = snapshot
4576 .chars_for_range(range.clone())
4577 .take_while(|c| c.is_whitespace())
4578 .count();
4579
4580 // 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.
4581 let column = start_point.column;
4582 let cursor_is_after_start_tag = {
4583 let start_tag_len = start_tag.len();
4584 let start_tag_line = snapshot
4585 .chars_for_range(range.clone())
4586 .skip(num_of_whitespaces)
4587 .take(start_tag_len)
4588 .collect::<String>();
4589 if start_tag_line.starts_with(start_tag.as_ref()) {
4590 num_of_whitespaces + start_tag_len <= column as usize
4591 } else {
4592 false
4593 }
4594 };
4595
4596 let cursor_is_after_delimiter = {
4597 let delimiter_trim = delimiter.trim_end();
4598 let delimiter_line = snapshot
4599 .chars_for_range(range.clone())
4600 .skip(num_of_whitespaces)
4601 .take(delimiter_trim.len())
4602 .collect::<String>();
4603 if delimiter_line.starts_with(delimiter_trim) {
4604 num_of_whitespaces + delimiter_trim.len() <= column as usize
4605 } else {
4606 false
4607 }
4608 };
4609
4610 let cursor_is_before_end_tag_if_exists = {
4611 let mut char_position = 0u32;
4612 let mut end_tag_offset = None;
4613
4614 'outer: for chunk in snapshot.text_for_range(range) {
4615 if let Some(byte_pos) = chunk.find(&**end_tag) {
4616 let chars_before_match =
4617 chunk[..byte_pos].chars().count() as u32;
4618 end_tag_offset =
4619 Some(char_position + chars_before_match);
4620 break 'outer;
4621 }
4622 char_position += chunk.chars().count() as u32;
4623 }
4624
4625 if let Some(end_tag_offset) = end_tag_offset {
4626 let cursor_is_before_end_tag = column <= end_tag_offset;
4627 if cursor_is_after_start_tag {
4628 if cursor_is_before_end_tag {
4629 insert_extra_newline = true;
4630 }
4631 let cursor_is_at_start_of_end_tag =
4632 column == end_tag_offset;
4633 if cursor_is_at_start_of_end_tag {
4634 indent_on_extra_newline.len = *len;
4635 }
4636 }
4637 cursor_is_before_end_tag
4638 } else {
4639 true
4640 }
4641 };
4642
4643 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4644 && cursor_is_before_end_tag_if_exists
4645 {
4646 if cursor_is_after_start_tag {
4647 indent_on_newline.len = *len;
4648 }
4649 Some(delimiter.clone())
4650 } else {
4651 None
4652 }
4653 });
4654
4655 (
4656 comment_delimiter,
4657 doc_delimiter,
4658 insert_extra_newline,
4659 indent_on_newline,
4660 indent_on_extra_newline,
4661 )
4662 } else {
4663 (
4664 None,
4665 None,
4666 false,
4667 IndentSize::default(),
4668 IndentSize::default(),
4669 )
4670 };
4671
4672 let prevent_auto_indent = doc_delimiter.is_some();
4673 let delimiter = comment_delimiter.or(doc_delimiter);
4674
4675 let capacity_for_delimiter =
4676 delimiter.as_deref().map(str::len).unwrap_or_default();
4677 let mut new_text = String::with_capacity(
4678 1 + capacity_for_delimiter
4679 + existing_indent.len as usize
4680 + indent_on_newline.len as usize
4681 + indent_on_extra_newline.len as usize,
4682 );
4683 new_text.push('\n');
4684 new_text.extend(existing_indent.chars());
4685 new_text.extend(indent_on_newline.chars());
4686
4687 if let Some(delimiter) = &delimiter {
4688 new_text.push_str(delimiter);
4689 }
4690
4691 if insert_extra_newline {
4692 new_text.push('\n');
4693 new_text.extend(existing_indent.chars());
4694 new_text.extend(indent_on_extra_newline.chars());
4695 }
4696
4697 let anchor = buffer.anchor_after(end);
4698 let new_selection = selection.map(|_| anchor);
4699 (
4700 ((start..end, new_text), prevent_auto_indent),
4701 (insert_extra_newline, new_selection),
4702 )
4703 })
4704 .unzip()
4705 };
4706
4707 let mut auto_indent_edits = Vec::new();
4708 let mut edits = Vec::new();
4709 for (edit, prevent_auto_indent) in edits_with_flags {
4710 if prevent_auto_indent {
4711 edits.push(edit);
4712 } else {
4713 auto_indent_edits.push(edit);
4714 }
4715 }
4716 if !edits.is_empty() {
4717 this.edit(edits, cx);
4718 }
4719 if !auto_indent_edits.is_empty() {
4720 this.edit_with_autoindent(auto_indent_edits, cx);
4721 }
4722
4723 let buffer = this.buffer.read(cx).snapshot(cx);
4724 let new_selections = selection_info
4725 .into_iter()
4726 .map(|(extra_newline_inserted, new_selection)| {
4727 let mut cursor = new_selection.end.to_point(&buffer);
4728 if extra_newline_inserted {
4729 cursor.row -= 1;
4730 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4731 }
4732 new_selection.map(|_| cursor)
4733 })
4734 .collect();
4735
4736 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4737 this.refresh_edit_prediction(true, false, window, cx);
4738 });
4739 }
4740
4741 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4742 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4743
4744 let buffer = self.buffer.read(cx);
4745 let snapshot = buffer.snapshot(cx);
4746
4747 let mut edits = Vec::new();
4748 let mut rows = Vec::new();
4749
4750 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4751 let cursor = selection.head();
4752 let row = cursor.row;
4753
4754 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4755
4756 let newline = "\n".to_string();
4757 edits.push((start_of_line..start_of_line, newline));
4758
4759 rows.push(row + rows_inserted as u32);
4760 }
4761
4762 self.transact(window, cx, |editor, window, cx| {
4763 editor.edit(edits, cx);
4764
4765 editor.change_selections(Default::default(), window, cx, |s| {
4766 let mut index = 0;
4767 s.move_cursors_with(|map, _, _| {
4768 let row = rows[index];
4769 index += 1;
4770
4771 let point = Point::new(row, 0);
4772 let boundary = map.next_line_boundary(point).1;
4773 let clipped = map.clip_point(boundary, Bias::Left);
4774
4775 (clipped, SelectionGoal::None)
4776 });
4777 });
4778
4779 let mut indent_edits = Vec::new();
4780 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4781 for row in rows {
4782 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4783 for (row, indent) in indents {
4784 if indent.len == 0 {
4785 continue;
4786 }
4787
4788 let text = match indent.kind {
4789 IndentKind::Space => " ".repeat(indent.len as usize),
4790 IndentKind::Tab => "\t".repeat(indent.len as usize),
4791 };
4792 let point = Point::new(row.0, 0);
4793 indent_edits.push((point..point, text));
4794 }
4795 }
4796 editor.edit(indent_edits, cx);
4797 });
4798 }
4799
4800 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4801 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4802
4803 let buffer = self.buffer.read(cx);
4804 let snapshot = buffer.snapshot(cx);
4805
4806 let mut edits = Vec::new();
4807 let mut rows = Vec::new();
4808 let mut rows_inserted = 0;
4809
4810 for selection in self.selections.all_adjusted(cx) {
4811 let cursor = selection.head();
4812 let row = cursor.row;
4813
4814 let point = Point::new(row + 1, 0);
4815 let start_of_line = snapshot.clip_point(point, Bias::Left);
4816
4817 let newline = "\n".to_string();
4818 edits.push((start_of_line..start_of_line, newline));
4819
4820 rows_inserted += 1;
4821 rows.push(row + rows_inserted);
4822 }
4823
4824 self.transact(window, cx, |editor, window, cx| {
4825 editor.edit(edits, cx);
4826
4827 editor.change_selections(Default::default(), window, cx, |s| {
4828 let mut index = 0;
4829 s.move_cursors_with(|map, _, _| {
4830 let row = rows[index];
4831 index += 1;
4832
4833 let point = Point::new(row, 0);
4834 let boundary = map.next_line_boundary(point).1;
4835 let clipped = map.clip_point(boundary, Bias::Left);
4836
4837 (clipped, SelectionGoal::None)
4838 });
4839 });
4840
4841 let mut indent_edits = Vec::new();
4842 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4843 for row in rows {
4844 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4845 for (row, indent) in indents {
4846 if indent.len == 0 {
4847 continue;
4848 }
4849
4850 let text = match indent.kind {
4851 IndentKind::Space => " ".repeat(indent.len as usize),
4852 IndentKind::Tab => "\t".repeat(indent.len as usize),
4853 };
4854 let point = Point::new(row.0, 0);
4855 indent_edits.push((point..point, text));
4856 }
4857 }
4858 editor.edit(indent_edits, cx);
4859 });
4860 }
4861
4862 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4863 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4864 original_indent_columns: Vec::new(),
4865 });
4866 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4867 }
4868
4869 fn insert_with_autoindent_mode(
4870 &mut self,
4871 text: &str,
4872 autoindent_mode: Option<AutoindentMode>,
4873 window: &mut Window,
4874 cx: &mut Context<Self>,
4875 ) {
4876 if self.read_only(cx) {
4877 return;
4878 }
4879
4880 let text: Arc<str> = text.into();
4881 self.transact(window, cx, |this, window, cx| {
4882 let old_selections = this.selections.all_adjusted(cx);
4883 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4884 let anchors = {
4885 let snapshot = buffer.read(cx);
4886 old_selections
4887 .iter()
4888 .map(|s| {
4889 let anchor = snapshot.anchor_after(s.head());
4890 s.map(|_| anchor)
4891 })
4892 .collect::<Vec<_>>()
4893 };
4894 buffer.edit(
4895 old_selections
4896 .iter()
4897 .map(|s| (s.start..s.end, text.clone())),
4898 autoindent_mode,
4899 cx,
4900 );
4901 anchors
4902 });
4903
4904 this.change_selections(Default::default(), window, cx, |s| {
4905 s.select_anchors(selection_anchors);
4906 });
4907
4908 cx.notify();
4909 });
4910 }
4911
4912 fn trigger_completion_on_input(
4913 &mut self,
4914 text: &str,
4915 trigger_in_words: bool,
4916 window: &mut Window,
4917 cx: &mut Context<Self>,
4918 ) {
4919 let completions_source = self
4920 .context_menu
4921 .borrow()
4922 .as_ref()
4923 .and_then(|menu| match menu {
4924 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4925 CodeContextMenu::CodeActions(_) => None,
4926 });
4927
4928 match completions_source {
4929 Some(CompletionsMenuSource::Words { .. }) => {
4930 self.open_or_update_completions_menu(
4931 Some(CompletionsMenuSource::Words {
4932 ignore_threshold: false,
4933 }),
4934 None,
4935 window,
4936 cx,
4937 );
4938 }
4939 Some(CompletionsMenuSource::Normal)
4940 | Some(CompletionsMenuSource::SnippetChoices)
4941 | None
4942 if self.is_completion_trigger(
4943 text,
4944 trigger_in_words,
4945 completions_source.is_some(),
4946 cx,
4947 ) =>
4948 {
4949 self.show_completions(
4950 &ShowCompletions {
4951 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4952 },
4953 window,
4954 cx,
4955 )
4956 }
4957 _ => {
4958 self.hide_context_menu(window, cx);
4959 }
4960 }
4961 }
4962
4963 fn is_completion_trigger(
4964 &self,
4965 text: &str,
4966 trigger_in_words: bool,
4967 menu_is_open: bool,
4968 cx: &mut Context<Self>,
4969 ) -> bool {
4970 let position = self.selections.newest_anchor().head();
4971 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4972 return false;
4973 };
4974
4975 if let Some(completion_provider) = &self.completion_provider {
4976 completion_provider.is_completion_trigger(
4977 &buffer,
4978 position.text_anchor,
4979 text,
4980 trigger_in_words,
4981 menu_is_open,
4982 cx,
4983 )
4984 } else {
4985 false
4986 }
4987 }
4988
4989 /// If any empty selections is touching the start of its innermost containing autoclose
4990 /// region, expand it to select the brackets.
4991 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4992 let selections = self.selections.all::<usize>(cx);
4993 let buffer = self.buffer.read(cx).read(cx);
4994 let new_selections = self
4995 .selections_with_autoclose_regions(selections, &buffer)
4996 .map(|(mut selection, region)| {
4997 if !selection.is_empty() {
4998 return selection;
4999 }
5000
5001 if let Some(region) = region {
5002 let mut range = region.range.to_offset(&buffer);
5003 if selection.start == range.start && range.start >= region.pair.start.len() {
5004 range.start -= region.pair.start.len();
5005 if buffer.contains_str_at(range.start, ®ion.pair.start)
5006 && buffer.contains_str_at(range.end, ®ion.pair.end)
5007 {
5008 range.end += region.pair.end.len();
5009 selection.start = range.start;
5010 selection.end = range.end;
5011
5012 return selection;
5013 }
5014 }
5015 }
5016
5017 let always_treat_brackets_as_autoclosed = buffer
5018 .language_settings_at(selection.start, cx)
5019 .always_treat_brackets_as_autoclosed;
5020
5021 if !always_treat_brackets_as_autoclosed {
5022 return selection;
5023 }
5024
5025 if let Some(scope) = buffer.language_scope_at(selection.start) {
5026 for (pair, enabled) in scope.brackets() {
5027 if !enabled || !pair.close {
5028 continue;
5029 }
5030
5031 if buffer.contains_str_at(selection.start, &pair.end) {
5032 let pair_start_len = pair.start.len();
5033 if buffer.contains_str_at(
5034 selection.start.saturating_sub(pair_start_len),
5035 &pair.start,
5036 ) {
5037 selection.start -= pair_start_len;
5038 selection.end += pair.end.len();
5039
5040 return selection;
5041 }
5042 }
5043 }
5044 }
5045
5046 selection
5047 })
5048 .collect();
5049
5050 drop(buffer);
5051 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5052 selections.select(new_selections)
5053 });
5054 }
5055
5056 /// Iterate the given selections, and for each one, find the smallest surrounding
5057 /// autoclose region. This uses the ordering of the selections and the autoclose
5058 /// regions to avoid repeated comparisons.
5059 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5060 &'a self,
5061 selections: impl IntoIterator<Item = Selection<D>>,
5062 buffer: &'a MultiBufferSnapshot,
5063 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5064 let mut i = 0;
5065 let mut regions = self.autoclose_regions.as_slice();
5066 selections.into_iter().map(move |selection| {
5067 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5068
5069 let mut enclosing = None;
5070 while let Some(pair_state) = regions.get(i) {
5071 if pair_state.range.end.to_offset(buffer) < range.start {
5072 regions = ®ions[i + 1..];
5073 i = 0;
5074 } else if pair_state.range.start.to_offset(buffer) > range.end {
5075 break;
5076 } else {
5077 if pair_state.selection_id == selection.id {
5078 enclosing = Some(pair_state);
5079 }
5080 i += 1;
5081 }
5082 }
5083
5084 (selection, enclosing)
5085 })
5086 }
5087
5088 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5089 fn invalidate_autoclose_regions(
5090 &mut self,
5091 mut selections: &[Selection<Anchor>],
5092 buffer: &MultiBufferSnapshot,
5093 ) {
5094 self.autoclose_regions.retain(|state| {
5095 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5096 return false;
5097 }
5098
5099 let mut i = 0;
5100 while let Some(selection) = selections.get(i) {
5101 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5102 selections = &selections[1..];
5103 continue;
5104 }
5105 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5106 break;
5107 }
5108 if selection.id == state.selection_id {
5109 return true;
5110 } else {
5111 i += 1;
5112 }
5113 }
5114 false
5115 });
5116 }
5117
5118 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5119 let offset = position.to_offset(buffer);
5120 let (word_range, kind) =
5121 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5122 if offset > word_range.start && kind == Some(CharKind::Word) {
5123 Some(
5124 buffer
5125 .text_for_range(word_range.start..offset)
5126 .collect::<String>(),
5127 )
5128 } else {
5129 None
5130 }
5131 }
5132
5133 pub fn toggle_inline_values(
5134 &mut self,
5135 _: &ToggleInlineValues,
5136 _: &mut Window,
5137 cx: &mut Context<Self>,
5138 ) {
5139 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5140
5141 self.refresh_inline_values(cx);
5142 }
5143
5144 pub fn toggle_inlay_hints(
5145 &mut self,
5146 _: &ToggleInlayHints,
5147 _: &mut Window,
5148 cx: &mut Context<Self>,
5149 ) {
5150 self.refresh_inlay_hints(
5151 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5152 cx,
5153 );
5154 }
5155
5156 pub fn inlay_hints_enabled(&self) -> bool {
5157 self.inlay_hint_cache.enabled
5158 }
5159
5160 pub fn inline_values_enabled(&self) -> bool {
5161 self.inline_value_cache.enabled
5162 }
5163
5164 #[cfg(any(test, feature = "test-support"))]
5165 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5166 self.display_map
5167 .read(cx)
5168 .current_inlays()
5169 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5170 .cloned()
5171 .collect()
5172 }
5173
5174 #[cfg(any(test, feature = "test-support"))]
5175 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5176 self.display_map
5177 .read(cx)
5178 .current_inlays()
5179 .cloned()
5180 .collect()
5181 }
5182
5183 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5184 if self.semantics_provider.is_none() || !self.mode.is_full() {
5185 return;
5186 }
5187
5188 let reason_description = reason.description();
5189 let ignore_debounce = matches!(
5190 reason,
5191 InlayHintRefreshReason::SettingsChange(_)
5192 | InlayHintRefreshReason::Toggle(_)
5193 | InlayHintRefreshReason::ExcerptsRemoved(_)
5194 | InlayHintRefreshReason::ModifiersChanged(_)
5195 );
5196 let (invalidate_cache, required_languages) = match reason {
5197 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5198 match self.inlay_hint_cache.modifiers_override(enabled) {
5199 Some(enabled) => {
5200 if enabled {
5201 (InvalidationStrategy::RefreshRequested, None)
5202 } else {
5203 self.splice_inlays(
5204 &self
5205 .visible_inlay_hints(cx)
5206 .iter()
5207 .map(|inlay| inlay.id)
5208 .collect::<Vec<InlayId>>(),
5209 Vec::new(),
5210 cx,
5211 );
5212 return;
5213 }
5214 }
5215 None => return,
5216 }
5217 }
5218 InlayHintRefreshReason::Toggle(enabled) => {
5219 if self.inlay_hint_cache.toggle(enabled) {
5220 if enabled {
5221 (InvalidationStrategy::RefreshRequested, None)
5222 } else {
5223 self.splice_inlays(
5224 &self
5225 .visible_inlay_hints(cx)
5226 .iter()
5227 .map(|inlay| inlay.id)
5228 .collect::<Vec<InlayId>>(),
5229 Vec::new(),
5230 cx,
5231 );
5232 return;
5233 }
5234 } else {
5235 return;
5236 }
5237 }
5238 InlayHintRefreshReason::SettingsChange(new_settings) => {
5239 match self.inlay_hint_cache.update_settings(
5240 &self.buffer,
5241 new_settings,
5242 self.visible_inlay_hints(cx),
5243 cx,
5244 ) {
5245 ControlFlow::Break(Some(InlaySplice {
5246 to_remove,
5247 to_insert,
5248 })) => {
5249 self.splice_inlays(&to_remove, to_insert, cx);
5250 return;
5251 }
5252 ControlFlow::Break(None) => return,
5253 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5254 }
5255 }
5256 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5257 if let Some(InlaySplice {
5258 to_remove,
5259 to_insert,
5260 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5261 {
5262 self.splice_inlays(&to_remove, to_insert, cx);
5263 }
5264 self.display_map.update(cx, |display_map, _| {
5265 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5266 });
5267 return;
5268 }
5269 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5270 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5271 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5272 }
5273 InlayHintRefreshReason::RefreshRequested => {
5274 (InvalidationStrategy::RefreshRequested, None)
5275 }
5276 };
5277
5278 if let Some(InlaySplice {
5279 to_remove,
5280 to_insert,
5281 }) = self.inlay_hint_cache.spawn_hint_refresh(
5282 reason_description,
5283 self.visible_excerpts(required_languages.as_ref(), cx),
5284 invalidate_cache,
5285 ignore_debounce,
5286 cx,
5287 ) {
5288 self.splice_inlays(&to_remove, to_insert, cx);
5289 }
5290 }
5291
5292 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5293 self.display_map
5294 .read(cx)
5295 .current_inlays()
5296 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5297 .cloned()
5298 .collect()
5299 }
5300
5301 pub fn visible_excerpts(
5302 &self,
5303 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5304 cx: &mut Context<Editor>,
5305 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5306 let Some(project) = self.project() else {
5307 return HashMap::default();
5308 };
5309 let project = project.read(cx);
5310 let multi_buffer = self.buffer().read(cx);
5311 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5312 let multi_buffer_visible_start = self
5313 .scroll_manager
5314 .anchor()
5315 .anchor
5316 .to_point(&multi_buffer_snapshot);
5317 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5318 multi_buffer_visible_start
5319 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5320 Bias::Left,
5321 );
5322 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5323 multi_buffer_snapshot
5324 .range_to_buffer_ranges(multi_buffer_visible_range)
5325 .into_iter()
5326 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5327 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5328 let buffer_file = project::File::from_dyn(buffer.file())?;
5329 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5330 let worktree_entry = buffer_worktree
5331 .read(cx)
5332 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5333 if worktree_entry.is_ignored {
5334 return None;
5335 }
5336
5337 let language = buffer.language()?;
5338 if let Some(restrict_to_languages) = restrict_to_languages
5339 && !restrict_to_languages.contains(language)
5340 {
5341 return None;
5342 }
5343 Some((
5344 excerpt_id,
5345 (
5346 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5347 buffer.version().clone(),
5348 excerpt_visible_range,
5349 ),
5350 ))
5351 })
5352 .collect()
5353 }
5354
5355 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5356 TextLayoutDetails {
5357 text_system: window.text_system().clone(),
5358 editor_style: self.style.clone().unwrap(),
5359 rem_size: window.rem_size(),
5360 scroll_anchor: self.scroll_manager.anchor(),
5361 visible_rows: self.visible_line_count(),
5362 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5363 }
5364 }
5365
5366 pub fn splice_inlays(
5367 &self,
5368 to_remove: &[InlayId],
5369 to_insert: Vec<Inlay>,
5370 cx: &mut Context<Self>,
5371 ) {
5372 self.display_map.update(cx, |display_map, cx| {
5373 display_map.splice_inlays(to_remove, to_insert, cx)
5374 });
5375 cx.notify();
5376 }
5377
5378 fn trigger_on_type_formatting(
5379 &self,
5380 input: String,
5381 window: &mut Window,
5382 cx: &mut Context<Self>,
5383 ) -> Option<Task<Result<()>>> {
5384 if input.len() != 1 {
5385 return None;
5386 }
5387
5388 let project = self.project()?;
5389 let position = self.selections.newest_anchor().head();
5390 let (buffer, buffer_position) = self
5391 .buffer
5392 .read(cx)
5393 .text_anchor_for_position(position, cx)?;
5394
5395 let settings = language_settings::language_settings(
5396 buffer
5397 .read(cx)
5398 .language_at(buffer_position)
5399 .map(|l| l.name()),
5400 buffer.read(cx).file(),
5401 cx,
5402 );
5403 if !settings.use_on_type_format {
5404 return None;
5405 }
5406
5407 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5408 // hence we do LSP request & edit on host side only — add formats to host's history.
5409 let push_to_lsp_host_history = true;
5410 // If this is not the host, append its history with new edits.
5411 let push_to_client_history = project.read(cx).is_via_collab();
5412
5413 let on_type_formatting = project.update(cx, |project, cx| {
5414 project.on_type_format(
5415 buffer.clone(),
5416 buffer_position,
5417 input,
5418 push_to_lsp_host_history,
5419 cx,
5420 )
5421 });
5422 Some(cx.spawn_in(window, async move |editor, cx| {
5423 if let Some(transaction) = on_type_formatting.await? {
5424 if push_to_client_history {
5425 buffer
5426 .update(cx, |buffer, _| {
5427 buffer.push_transaction(transaction, Instant::now());
5428 buffer.finalize_last_transaction();
5429 })
5430 .ok();
5431 }
5432 editor.update(cx, |editor, cx| {
5433 editor.refresh_document_highlights(cx);
5434 })?;
5435 }
5436 Ok(())
5437 }))
5438 }
5439
5440 pub fn show_word_completions(
5441 &mut self,
5442 _: &ShowWordCompletions,
5443 window: &mut Window,
5444 cx: &mut Context<Self>,
5445 ) {
5446 self.open_or_update_completions_menu(
5447 Some(CompletionsMenuSource::Words {
5448 ignore_threshold: true,
5449 }),
5450 None,
5451 window,
5452 cx,
5453 );
5454 }
5455
5456 pub fn show_completions(
5457 &mut self,
5458 options: &ShowCompletions,
5459 window: &mut Window,
5460 cx: &mut Context<Self>,
5461 ) {
5462 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5463 }
5464
5465 fn open_or_update_completions_menu(
5466 &mut self,
5467 requested_source: Option<CompletionsMenuSource>,
5468 trigger: Option<&str>,
5469 window: &mut Window,
5470 cx: &mut Context<Self>,
5471 ) {
5472 if self.pending_rename.is_some() {
5473 return;
5474 }
5475
5476 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5477
5478 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5479 // inserted and selected. To handle that case, the start of the selection is used so that
5480 // the menu starts with all choices.
5481 let position = self
5482 .selections
5483 .newest_anchor()
5484 .start
5485 .bias_right(&multibuffer_snapshot);
5486 if position.diff_base_anchor.is_some() {
5487 return;
5488 }
5489 let buffer_position = multibuffer_snapshot.anchor_before(position);
5490 let Some(buffer) = buffer_position
5491 .buffer_id
5492 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5493 else {
5494 return;
5495 };
5496 let buffer_snapshot = buffer.read(cx).snapshot();
5497
5498 let query: Option<Arc<String>> =
5499 Self::completion_query(&multibuffer_snapshot, buffer_position)
5500 .map(|query| query.into());
5501
5502 drop(multibuffer_snapshot);
5503
5504 // Hide the current completions menu when query is empty. Without this, cached
5505 // completions from before the trigger char may be reused (#32774).
5506 if query.is_none() {
5507 let menu_is_open = matches!(
5508 self.context_menu.borrow().as_ref(),
5509 Some(CodeContextMenu::Completions(_))
5510 );
5511 if menu_is_open {
5512 self.hide_context_menu(window, cx);
5513 }
5514 }
5515
5516 let mut ignore_word_threshold = false;
5517 let provider = match requested_source {
5518 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5519 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5520 ignore_word_threshold = ignore_threshold;
5521 None
5522 }
5523 Some(CompletionsMenuSource::SnippetChoices) => {
5524 log::error!("bug: SnippetChoices requested_source is not handled");
5525 None
5526 }
5527 };
5528
5529 let sort_completions = provider
5530 .as_ref()
5531 .is_some_and(|provider| provider.sort_completions());
5532
5533 let filter_completions = provider
5534 .as_ref()
5535 .is_none_or(|provider| provider.filter_completions());
5536
5537 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5538 if filter_completions {
5539 menu.filter(query.clone(), provider.clone(), window, cx);
5540 }
5541 // When `is_incomplete` is false, no need to re-query completions when the current query
5542 // is a suffix of the initial query.
5543 if !menu.is_incomplete {
5544 // If the new query is a suffix of the old query (typing more characters) and
5545 // the previous result was complete, the existing completions can be filtered.
5546 //
5547 // Note that this is always true for snippet completions.
5548 let query_matches = match (&menu.initial_query, &query) {
5549 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5550 (None, _) => true,
5551 _ => false,
5552 };
5553 if query_matches {
5554 let position_matches = if menu.initial_position == position {
5555 true
5556 } else {
5557 let snapshot = self.buffer.read(cx).read(cx);
5558 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5559 };
5560 if position_matches {
5561 return;
5562 }
5563 }
5564 }
5565 };
5566
5567 let trigger_kind = match trigger {
5568 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5569 CompletionTriggerKind::TRIGGER_CHARACTER
5570 }
5571 _ => CompletionTriggerKind::INVOKED,
5572 };
5573 let completion_context = CompletionContext {
5574 trigger_character: trigger.and_then(|trigger| {
5575 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5576 Some(String::from(trigger))
5577 } else {
5578 None
5579 }
5580 }),
5581 trigger_kind,
5582 };
5583
5584 let Anchor {
5585 excerpt_id: buffer_excerpt_id,
5586 text_anchor: buffer_position,
5587 ..
5588 } = buffer_position;
5589
5590 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5591 buffer_snapshot.surrounding_word(buffer_position, None)
5592 {
5593 let word_to_exclude = buffer_snapshot
5594 .text_for_range(word_range.clone())
5595 .collect::<String>();
5596 (
5597 buffer_snapshot.anchor_before(word_range.start)
5598 ..buffer_snapshot.anchor_after(buffer_position),
5599 Some(word_to_exclude),
5600 )
5601 } else {
5602 (buffer_position..buffer_position, None)
5603 };
5604
5605 let language = buffer_snapshot
5606 .language_at(buffer_position)
5607 .map(|language| language.name());
5608
5609 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5610 .completions
5611 .clone();
5612
5613 let show_completion_documentation = buffer_snapshot
5614 .settings_at(buffer_position, cx)
5615 .show_completion_documentation;
5616
5617 // The document can be large, so stay in reasonable bounds when searching for words,
5618 // otherwise completion pop-up might be slow to appear.
5619 const WORD_LOOKUP_ROWS: u32 = 5_000;
5620 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5621 let min_word_search = buffer_snapshot.clip_point(
5622 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5623 Bias::Left,
5624 );
5625 let max_word_search = buffer_snapshot.clip_point(
5626 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5627 Bias::Right,
5628 );
5629 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5630 ..buffer_snapshot.point_to_offset(max_word_search);
5631
5632 let skip_digits = query
5633 .as_ref()
5634 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5635
5636 let omit_word_completions = !self.word_completions_enabled
5637 || (!ignore_word_threshold
5638 && match &query {
5639 Some(query) => query.chars().count() < completion_settings.words_min_length,
5640 None => completion_settings.words_min_length != 0,
5641 });
5642
5643 let (mut words, provider_responses) = match &provider {
5644 Some(provider) => {
5645 let provider_responses = provider.completions(
5646 buffer_excerpt_id,
5647 &buffer,
5648 buffer_position,
5649 completion_context,
5650 window,
5651 cx,
5652 );
5653
5654 let words = match (omit_word_completions, completion_settings.words) {
5655 (true, _) | (_, WordsCompletionMode::Disabled) => {
5656 Task::ready(BTreeMap::default())
5657 }
5658 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5659 .background_spawn(async move {
5660 buffer_snapshot.words_in_range(WordsQuery {
5661 fuzzy_contents: None,
5662 range: word_search_range,
5663 skip_digits,
5664 })
5665 }),
5666 };
5667
5668 (words, provider_responses)
5669 }
5670 None => {
5671 let words = if omit_word_completions {
5672 Task::ready(BTreeMap::default())
5673 } else {
5674 cx.background_spawn(async move {
5675 buffer_snapshot.words_in_range(WordsQuery {
5676 fuzzy_contents: None,
5677 range: word_search_range,
5678 skip_digits,
5679 })
5680 })
5681 };
5682 (words, Task::ready(Ok(Vec::new())))
5683 }
5684 };
5685
5686 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5687
5688 let id = post_inc(&mut self.next_completion_id);
5689 let task = cx.spawn_in(window, async move |editor, cx| {
5690 let Ok(()) = editor.update(cx, |this, _| {
5691 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5692 }) else {
5693 return;
5694 };
5695
5696 // TODO: Ideally completions from different sources would be selectively re-queried, so
5697 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5698 let mut completions = Vec::new();
5699 let mut is_incomplete = false;
5700 let mut display_options: Option<CompletionDisplayOptions> = None;
5701 if let Some(provider_responses) = provider_responses.await.log_err()
5702 && !provider_responses.is_empty()
5703 {
5704 for response in provider_responses {
5705 completions.extend(response.completions);
5706 is_incomplete = is_incomplete || response.is_incomplete;
5707 match display_options.as_mut() {
5708 None => {
5709 display_options = Some(response.display_options);
5710 }
5711 Some(options) => options.merge(&response.display_options),
5712 }
5713 }
5714 if completion_settings.words == WordsCompletionMode::Fallback {
5715 words = Task::ready(BTreeMap::default());
5716 }
5717 }
5718 let display_options = display_options.unwrap_or_default();
5719
5720 let mut words = words.await;
5721 if let Some(word_to_exclude) = &word_to_exclude {
5722 words.remove(word_to_exclude);
5723 }
5724 for lsp_completion in &completions {
5725 words.remove(&lsp_completion.new_text);
5726 }
5727 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5728 replace_range: word_replace_range.clone(),
5729 new_text: word.clone(),
5730 label: CodeLabel::plain(word, None),
5731 icon_path: None,
5732 documentation: None,
5733 source: CompletionSource::BufferWord {
5734 word_range,
5735 resolved: false,
5736 },
5737 insert_text_mode: Some(InsertTextMode::AS_IS),
5738 confirm: None,
5739 }));
5740
5741 let menu = if completions.is_empty() {
5742 None
5743 } else {
5744 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5745 let languages = editor
5746 .workspace
5747 .as_ref()
5748 .and_then(|(workspace, _)| workspace.upgrade())
5749 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5750 let menu = CompletionsMenu::new(
5751 id,
5752 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5753 sort_completions,
5754 show_completion_documentation,
5755 position,
5756 query.clone(),
5757 is_incomplete,
5758 buffer.clone(),
5759 completions.into(),
5760 display_options,
5761 snippet_sort_order,
5762 languages,
5763 language,
5764 cx,
5765 );
5766
5767 let query = if filter_completions { query } else { None };
5768 let matches_task = if let Some(query) = query {
5769 menu.do_async_filtering(query, cx)
5770 } else {
5771 Task::ready(menu.unfiltered_matches())
5772 };
5773 (menu, matches_task)
5774 }) else {
5775 return;
5776 };
5777
5778 let matches = matches_task.await;
5779
5780 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5781 // Newer menu already set, so exit.
5782 if let Some(CodeContextMenu::Completions(prev_menu)) =
5783 editor.context_menu.borrow().as_ref()
5784 && prev_menu.id > id
5785 {
5786 return;
5787 };
5788
5789 // Only valid to take prev_menu because it the new menu is immediately set
5790 // below, or the menu is hidden.
5791 if let Some(CodeContextMenu::Completions(prev_menu)) =
5792 editor.context_menu.borrow_mut().take()
5793 {
5794 let position_matches =
5795 if prev_menu.initial_position == menu.initial_position {
5796 true
5797 } else {
5798 let snapshot = editor.buffer.read(cx).read(cx);
5799 prev_menu.initial_position.to_offset(&snapshot)
5800 == menu.initial_position.to_offset(&snapshot)
5801 };
5802 if position_matches {
5803 // Preserve markdown cache before `set_filter_results` because it will
5804 // try to populate the documentation cache.
5805 menu.preserve_markdown_cache(prev_menu);
5806 }
5807 };
5808
5809 menu.set_filter_results(matches, provider, window, cx);
5810 }) else {
5811 return;
5812 };
5813
5814 menu.visible().then_some(menu)
5815 };
5816
5817 editor
5818 .update_in(cx, |editor, window, cx| {
5819 if editor.focus_handle.is_focused(window)
5820 && let Some(menu) = menu
5821 {
5822 *editor.context_menu.borrow_mut() =
5823 Some(CodeContextMenu::Completions(menu));
5824
5825 crate::hover_popover::hide_hover(editor, cx);
5826 if editor.show_edit_predictions_in_menu() {
5827 editor.update_visible_edit_prediction(window, cx);
5828 } else {
5829 editor.discard_edit_prediction(false, cx);
5830 }
5831
5832 cx.notify();
5833 return;
5834 }
5835
5836 if editor.completion_tasks.len() <= 1 {
5837 // If there are no more completion tasks and the last menu was empty, we should hide it.
5838 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5839 // If it was already hidden and we don't show edit predictions in the menu,
5840 // we should also show the edit prediction when available.
5841 if was_hidden && editor.show_edit_predictions_in_menu() {
5842 editor.update_visible_edit_prediction(window, cx);
5843 }
5844 }
5845 })
5846 .ok();
5847 });
5848
5849 self.completion_tasks.push((id, task));
5850 }
5851
5852 #[cfg(feature = "test-support")]
5853 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5854 let menu = self.context_menu.borrow();
5855 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5856 let completions = menu.completions.borrow();
5857 Some(completions.to_vec())
5858 } else {
5859 None
5860 }
5861 }
5862
5863 pub fn with_completions_menu_matching_id<R>(
5864 &self,
5865 id: CompletionId,
5866 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5867 ) -> R {
5868 let mut context_menu = self.context_menu.borrow_mut();
5869 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5870 return f(None);
5871 };
5872 if completions_menu.id != id {
5873 return f(None);
5874 }
5875 f(Some(completions_menu))
5876 }
5877
5878 pub fn confirm_completion(
5879 &mut self,
5880 action: &ConfirmCompletion,
5881 window: &mut Window,
5882 cx: &mut Context<Self>,
5883 ) -> Option<Task<Result<()>>> {
5884 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5885 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5886 }
5887
5888 pub fn confirm_completion_insert(
5889 &mut self,
5890 _: &ConfirmCompletionInsert,
5891 window: &mut Window,
5892 cx: &mut Context<Self>,
5893 ) -> Option<Task<Result<()>>> {
5894 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5895 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5896 }
5897
5898 pub fn confirm_completion_replace(
5899 &mut self,
5900 _: &ConfirmCompletionReplace,
5901 window: &mut Window,
5902 cx: &mut Context<Self>,
5903 ) -> Option<Task<Result<()>>> {
5904 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5905 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5906 }
5907
5908 pub fn compose_completion(
5909 &mut self,
5910 action: &ComposeCompletion,
5911 window: &mut Window,
5912 cx: &mut Context<Self>,
5913 ) -> Option<Task<Result<()>>> {
5914 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5915 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5916 }
5917
5918 fn do_completion(
5919 &mut self,
5920 item_ix: Option<usize>,
5921 intent: CompletionIntent,
5922 window: &mut Window,
5923 cx: &mut Context<Editor>,
5924 ) -> Option<Task<Result<()>>> {
5925 use language::ToOffset as _;
5926
5927 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5928 else {
5929 return None;
5930 };
5931
5932 let candidate_id = {
5933 let entries = completions_menu.entries.borrow();
5934 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5935 if self.show_edit_predictions_in_menu() {
5936 self.discard_edit_prediction(true, cx);
5937 }
5938 mat.candidate_id
5939 };
5940
5941 let completion = completions_menu
5942 .completions
5943 .borrow()
5944 .get(candidate_id)?
5945 .clone();
5946 cx.stop_propagation();
5947
5948 let buffer_handle = completions_menu.buffer.clone();
5949
5950 let CompletionEdit {
5951 new_text,
5952 snippet,
5953 replace_range,
5954 } = process_completion_for_edit(
5955 &completion,
5956 intent,
5957 &buffer_handle,
5958 &completions_menu.initial_position.text_anchor,
5959 cx,
5960 );
5961
5962 let buffer = buffer_handle.read(cx);
5963 let snapshot = self.buffer.read(cx).snapshot(cx);
5964 let newest_anchor = self.selections.newest_anchor();
5965 let replace_range_multibuffer = {
5966 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5967 let multibuffer_anchor = snapshot
5968 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5969 .unwrap()
5970 ..snapshot
5971 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5972 .unwrap();
5973 multibuffer_anchor.start.to_offset(&snapshot)
5974 ..multibuffer_anchor.end.to_offset(&snapshot)
5975 };
5976 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5977 return None;
5978 }
5979
5980 let old_text = buffer
5981 .text_for_range(replace_range.clone())
5982 .collect::<String>();
5983 let lookbehind = newest_anchor
5984 .start
5985 .text_anchor
5986 .to_offset(buffer)
5987 .saturating_sub(replace_range.start);
5988 let lookahead = replace_range
5989 .end
5990 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5991 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5992 let suffix = &old_text[lookbehind.min(old_text.len())..];
5993
5994 let selections = self.selections.all::<usize>(cx);
5995 let mut ranges = Vec::new();
5996 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5997
5998 for selection in &selections {
5999 let range = if selection.id == newest_anchor.id {
6000 replace_range_multibuffer.clone()
6001 } else {
6002 let mut range = selection.range();
6003
6004 // if prefix is present, don't duplicate it
6005 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6006 range.start = range.start.saturating_sub(lookbehind);
6007
6008 // if suffix is also present, mimic the newest cursor and replace it
6009 if selection.id != newest_anchor.id
6010 && snapshot.contains_str_at(range.end, suffix)
6011 {
6012 range.end += lookahead;
6013 }
6014 }
6015 range
6016 };
6017
6018 ranges.push(range.clone());
6019
6020 if !self.linked_edit_ranges.is_empty() {
6021 let start_anchor = snapshot.anchor_before(range.start);
6022 let end_anchor = snapshot.anchor_after(range.end);
6023 if let Some(ranges) = self
6024 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6025 {
6026 for (buffer, edits) in ranges {
6027 linked_edits
6028 .entry(buffer.clone())
6029 .or_default()
6030 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6031 }
6032 }
6033 }
6034 }
6035
6036 let common_prefix_len = old_text
6037 .chars()
6038 .zip(new_text.chars())
6039 .take_while(|(a, b)| a == b)
6040 .map(|(a, _)| a.len_utf8())
6041 .sum::<usize>();
6042
6043 cx.emit(EditorEvent::InputHandled {
6044 utf16_range_to_replace: None,
6045 text: new_text[common_prefix_len..].into(),
6046 });
6047
6048 self.transact(window, cx, |editor, window, cx| {
6049 if let Some(mut snippet) = snippet {
6050 snippet.text = new_text.to_string();
6051 editor
6052 .insert_snippet(&ranges, snippet, window, cx)
6053 .log_err();
6054 } else {
6055 editor.buffer.update(cx, |multi_buffer, cx| {
6056 let auto_indent = match completion.insert_text_mode {
6057 Some(InsertTextMode::AS_IS) => None,
6058 _ => editor.autoindent_mode.clone(),
6059 };
6060 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6061 multi_buffer.edit(edits, auto_indent, cx);
6062 });
6063 }
6064 for (buffer, edits) in linked_edits {
6065 buffer.update(cx, |buffer, cx| {
6066 let snapshot = buffer.snapshot();
6067 let edits = edits
6068 .into_iter()
6069 .map(|(range, text)| {
6070 use text::ToPoint as TP;
6071 let end_point = TP::to_point(&range.end, &snapshot);
6072 let start_point = TP::to_point(&range.start, &snapshot);
6073 (start_point..end_point, text)
6074 })
6075 .sorted_by_key(|(range, _)| range.start);
6076 buffer.edit(edits, None, cx);
6077 })
6078 }
6079
6080 editor.refresh_edit_prediction(true, false, window, cx);
6081 });
6082 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6083
6084 let show_new_completions_on_confirm = completion
6085 .confirm
6086 .as_ref()
6087 .is_some_and(|confirm| confirm(intent, window, cx));
6088 if show_new_completions_on_confirm {
6089 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6090 }
6091
6092 let provider = self.completion_provider.as_ref()?;
6093 drop(completion);
6094 let apply_edits = provider.apply_additional_edits_for_completion(
6095 buffer_handle,
6096 completions_menu.completions.clone(),
6097 candidate_id,
6098 true,
6099 cx,
6100 );
6101
6102 let editor_settings = EditorSettings::get_global(cx);
6103 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6104 // After the code completion is finished, users often want to know what signatures are needed.
6105 // so we should automatically call signature_help
6106 self.show_signature_help(&ShowSignatureHelp, window, cx);
6107 }
6108
6109 Some(cx.foreground_executor().spawn(async move {
6110 apply_edits.await?;
6111 Ok(())
6112 }))
6113 }
6114
6115 pub fn toggle_code_actions(
6116 &mut self,
6117 action: &ToggleCodeActions,
6118 window: &mut Window,
6119 cx: &mut Context<Self>,
6120 ) {
6121 let quick_launch = action.quick_launch;
6122 let mut context_menu = self.context_menu.borrow_mut();
6123 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6124 if code_actions.deployed_from == action.deployed_from {
6125 // Toggle if we're selecting the same one
6126 *context_menu = None;
6127 cx.notify();
6128 return;
6129 } else {
6130 // Otherwise, clear it and start a new one
6131 *context_menu = None;
6132 cx.notify();
6133 }
6134 }
6135 drop(context_menu);
6136 let snapshot = self.snapshot(window, cx);
6137 let deployed_from = action.deployed_from.clone();
6138 let action = action.clone();
6139 self.completion_tasks.clear();
6140 self.discard_edit_prediction(false, cx);
6141
6142 let multibuffer_point = match &action.deployed_from {
6143 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6144 DisplayPoint::new(*row, 0).to_point(&snapshot)
6145 }
6146 _ => self.selections.newest::<Point>(cx).head(),
6147 };
6148 let Some((buffer, buffer_row)) = snapshot
6149 .buffer_snapshot
6150 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6151 .and_then(|(buffer_snapshot, range)| {
6152 self.buffer()
6153 .read(cx)
6154 .buffer(buffer_snapshot.remote_id())
6155 .map(|buffer| (buffer, range.start.row))
6156 })
6157 else {
6158 return;
6159 };
6160 let buffer_id = buffer.read(cx).remote_id();
6161 let tasks = self
6162 .tasks
6163 .get(&(buffer_id, buffer_row))
6164 .map(|t| Arc::new(t.to_owned()));
6165
6166 if !self.focus_handle.is_focused(window) {
6167 return;
6168 }
6169 let project = self.project.clone();
6170
6171 let code_actions_task = match deployed_from {
6172 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6173 _ => self.code_actions(buffer_row, window, cx),
6174 };
6175
6176 let runnable_task = match deployed_from {
6177 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6178 _ => {
6179 let mut task_context_task = Task::ready(None);
6180 if let Some(tasks) = &tasks
6181 && let Some(project) = project
6182 {
6183 task_context_task =
6184 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6185 }
6186
6187 cx.spawn_in(window, {
6188 let buffer = buffer.clone();
6189 async move |editor, cx| {
6190 let task_context = task_context_task.await;
6191
6192 let resolved_tasks =
6193 tasks
6194 .zip(task_context.clone())
6195 .map(|(tasks, task_context)| ResolvedTasks {
6196 templates: tasks.resolve(&task_context).collect(),
6197 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6198 multibuffer_point.row,
6199 tasks.column,
6200 )),
6201 });
6202 let debug_scenarios = editor
6203 .update(cx, |editor, cx| {
6204 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6205 })?
6206 .await;
6207 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6208 }
6209 })
6210 }
6211 };
6212
6213 cx.spawn_in(window, async move |editor, cx| {
6214 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6215 let code_actions = code_actions_task.await;
6216 let spawn_straight_away = quick_launch
6217 && resolved_tasks
6218 .as_ref()
6219 .is_some_and(|tasks| tasks.templates.len() == 1)
6220 && code_actions
6221 .as_ref()
6222 .is_none_or(|actions| actions.is_empty())
6223 && debug_scenarios.is_empty();
6224
6225 editor.update_in(cx, |editor, window, cx| {
6226 crate::hover_popover::hide_hover(editor, cx);
6227 let actions = CodeActionContents::new(
6228 resolved_tasks,
6229 code_actions,
6230 debug_scenarios,
6231 task_context.unwrap_or_default(),
6232 );
6233
6234 // Don't show the menu if there are no actions available
6235 if actions.is_empty() {
6236 cx.notify();
6237 return Task::ready(Ok(()));
6238 }
6239
6240 *editor.context_menu.borrow_mut() =
6241 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6242 buffer,
6243 actions,
6244 selected_item: Default::default(),
6245 scroll_handle: UniformListScrollHandle::default(),
6246 deployed_from,
6247 }));
6248 cx.notify();
6249 if spawn_straight_away
6250 && let Some(task) = editor.confirm_code_action(
6251 &ConfirmCodeAction { item_ix: Some(0) },
6252 window,
6253 cx,
6254 )
6255 {
6256 return task;
6257 }
6258
6259 Task::ready(Ok(()))
6260 })
6261 })
6262 .detach_and_log_err(cx);
6263 }
6264
6265 fn debug_scenarios(
6266 &mut self,
6267 resolved_tasks: &Option<ResolvedTasks>,
6268 buffer: &Entity<Buffer>,
6269 cx: &mut App,
6270 ) -> Task<Vec<task::DebugScenario>> {
6271 maybe!({
6272 let project = self.project()?;
6273 let dap_store = project.read(cx).dap_store();
6274 let mut scenarios = vec![];
6275 let resolved_tasks = resolved_tasks.as_ref()?;
6276 let buffer = buffer.read(cx);
6277 let language = buffer.language()?;
6278 let file = buffer.file();
6279 let debug_adapter = language_settings(language.name().into(), file, cx)
6280 .debuggers
6281 .first()
6282 .map(SharedString::from)
6283 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6284
6285 dap_store.update(cx, |dap_store, cx| {
6286 for (_, task) in &resolved_tasks.templates {
6287 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6288 task.original_task().clone(),
6289 debug_adapter.clone().into(),
6290 task.display_label().to_owned().into(),
6291 cx,
6292 );
6293 scenarios.push(maybe_scenario);
6294 }
6295 });
6296 Some(cx.background_spawn(async move {
6297 futures::future::join_all(scenarios)
6298 .await
6299 .into_iter()
6300 .flatten()
6301 .collect::<Vec<_>>()
6302 }))
6303 })
6304 .unwrap_or_else(|| Task::ready(vec![]))
6305 }
6306
6307 fn code_actions(
6308 &mut self,
6309 buffer_row: u32,
6310 window: &mut Window,
6311 cx: &mut Context<Self>,
6312 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6313 let mut task = self.code_actions_task.take();
6314 cx.spawn_in(window, async move |editor, cx| {
6315 while let Some(prev_task) = task {
6316 prev_task.await.log_err();
6317 task = editor
6318 .update(cx, |this, _| this.code_actions_task.take())
6319 .ok()?;
6320 }
6321
6322 editor
6323 .update(cx, |editor, cx| {
6324 editor
6325 .available_code_actions
6326 .clone()
6327 .and_then(|(location, code_actions)| {
6328 let snapshot = location.buffer.read(cx).snapshot();
6329 let point_range = location.range.to_point(&snapshot);
6330 let point_range = point_range.start.row..=point_range.end.row;
6331 if point_range.contains(&buffer_row) {
6332 Some(code_actions)
6333 } else {
6334 None
6335 }
6336 })
6337 })
6338 .ok()
6339 .flatten()
6340 })
6341 }
6342
6343 pub fn confirm_code_action(
6344 &mut self,
6345 action: &ConfirmCodeAction,
6346 window: &mut Window,
6347 cx: &mut Context<Self>,
6348 ) -> Option<Task<Result<()>>> {
6349 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6350
6351 let actions_menu =
6352 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6353 menu
6354 } else {
6355 return None;
6356 };
6357
6358 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6359 let action = actions_menu.actions.get(action_ix)?;
6360 let title = action.label();
6361 let buffer = actions_menu.buffer;
6362 let workspace = self.workspace()?;
6363
6364 match action {
6365 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6366 workspace.update(cx, |workspace, cx| {
6367 workspace.schedule_resolved_task(
6368 task_source_kind,
6369 resolved_task,
6370 false,
6371 window,
6372 cx,
6373 );
6374
6375 Some(Task::ready(Ok(())))
6376 })
6377 }
6378 CodeActionsItem::CodeAction {
6379 excerpt_id,
6380 action,
6381 provider,
6382 } => {
6383 let apply_code_action =
6384 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6385 let workspace = workspace.downgrade();
6386 Some(cx.spawn_in(window, async move |editor, cx| {
6387 let project_transaction = apply_code_action.await?;
6388 Self::open_project_transaction(
6389 &editor,
6390 workspace,
6391 project_transaction,
6392 title,
6393 cx,
6394 )
6395 .await
6396 }))
6397 }
6398 CodeActionsItem::DebugScenario(scenario) => {
6399 let context = actions_menu.actions.context;
6400
6401 workspace.update(cx, |workspace, cx| {
6402 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6403 workspace.start_debug_session(
6404 scenario,
6405 context,
6406 Some(buffer),
6407 None,
6408 window,
6409 cx,
6410 );
6411 });
6412 Some(Task::ready(Ok(())))
6413 }
6414 }
6415 }
6416
6417 pub async fn open_project_transaction(
6418 editor: &WeakEntity<Editor>,
6419 workspace: WeakEntity<Workspace>,
6420 transaction: ProjectTransaction,
6421 title: String,
6422 cx: &mut AsyncWindowContext,
6423 ) -> Result<()> {
6424 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6425 cx.update(|_, cx| {
6426 entries.sort_unstable_by_key(|(buffer, _)| {
6427 buffer.read(cx).file().map(|f| f.path().clone())
6428 });
6429 })?;
6430
6431 // If the project transaction's edits are all contained within this editor, then
6432 // avoid opening a new editor to display them.
6433
6434 if let Some((buffer, transaction)) = entries.first() {
6435 if entries.len() == 1 {
6436 let excerpt = editor.update(cx, |editor, cx| {
6437 editor
6438 .buffer()
6439 .read(cx)
6440 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6441 })?;
6442 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6443 && excerpted_buffer == *buffer
6444 {
6445 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6446 let excerpt_range = excerpt_range.to_offset(buffer);
6447 buffer
6448 .edited_ranges_for_transaction::<usize>(transaction)
6449 .all(|range| {
6450 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6451 })
6452 })?;
6453
6454 if all_edits_within_excerpt {
6455 return Ok(());
6456 }
6457 }
6458 }
6459 } else {
6460 return Ok(());
6461 }
6462
6463 let mut ranges_to_highlight = Vec::new();
6464 let excerpt_buffer = cx.new(|cx| {
6465 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6466 for (buffer_handle, transaction) in &entries {
6467 let edited_ranges = buffer_handle
6468 .read(cx)
6469 .edited_ranges_for_transaction::<Point>(transaction)
6470 .collect::<Vec<_>>();
6471 let (ranges, _) = multibuffer.set_excerpts_for_path(
6472 PathKey::for_buffer(buffer_handle, cx),
6473 buffer_handle.clone(),
6474 edited_ranges,
6475 multibuffer_context_lines(cx),
6476 cx,
6477 );
6478
6479 ranges_to_highlight.extend(ranges);
6480 }
6481 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6482 multibuffer
6483 })?;
6484
6485 workspace.update_in(cx, |workspace, window, cx| {
6486 let project = workspace.project().clone();
6487 let editor =
6488 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6489 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6490 editor.update(cx, |editor, cx| {
6491 editor.highlight_background::<Self>(
6492 &ranges_to_highlight,
6493 |theme| theme.colors().editor_highlighted_line_background,
6494 cx,
6495 );
6496 });
6497 })?;
6498
6499 Ok(())
6500 }
6501
6502 pub fn clear_code_action_providers(&mut self) {
6503 self.code_action_providers.clear();
6504 self.available_code_actions.take();
6505 }
6506
6507 pub fn add_code_action_provider(
6508 &mut self,
6509 provider: Rc<dyn CodeActionProvider>,
6510 window: &mut Window,
6511 cx: &mut Context<Self>,
6512 ) {
6513 if self
6514 .code_action_providers
6515 .iter()
6516 .any(|existing_provider| existing_provider.id() == provider.id())
6517 {
6518 return;
6519 }
6520
6521 self.code_action_providers.push(provider);
6522 self.refresh_code_actions(window, cx);
6523 }
6524
6525 pub fn remove_code_action_provider(
6526 &mut self,
6527 id: Arc<str>,
6528 window: &mut Window,
6529 cx: &mut Context<Self>,
6530 ) {
6531 self.code_action_providers
6532 .retain(|provider| provider.id() != id);
6533 self.refresh_code_actions(window, cx);
6534 }
6535
6536 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6537 !self.code_action_providers.is_empty()
6538 && EditorSettings::get_global(cx).toolbar.code_actions
6539 }
6540
6541 pub fn has_available_code_actions(&self) -> bool {
6542 self.available_code_actions
6543 .as_ref()
6544 .is_some_and(|(_, actions)| !actions.is_empty())
6545 }
6546
6547 fn render_inline_code_actions(
6548 &self,
6549 icon_size: ui::IconSize,
6550 display_row: DisplayRow,
6551 is_active: bool,
6552 cx: &mut Context<Self>,
6553 ) -> AnyElement {
6554 let show_tooltip = !self.context_menu_visible();
6555 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6556 .icon_size(icon_size)
6557 .shape(ui::IconButtonShape::Square)
6558 .icon_color(ui::Color::Hidden)
6559 .toggle_state(is_active)
6560 .when(show_tooltip, |this| {
6561 this.tooltip({
6562 let focus_handle = self.focus_handle.clone();
6563 move |window, cx| {
6564 Tooltip::for_action_in(
6565 "Toggle Code Actions",
6566 &ToggleCodeActions {
6567 deployed_from: None,
6568 quick_launch: false,
6569 },
6570 &focus_handle,
6571 window,
6572 cx,
6573 )
6574 }
6575 })
6576 })
6577 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6578 window.focus(&editor.focus_handle(cx));
6579 editor.toggle_code_actions(
6580 &crate::actions::ToggleCodeActions {
6581 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6582 display_row,
6583 )),
6584 quick_launch: false,
6585 },
6586 window,
6587 cx,
6588 );
6589 }))
6590 .into_any_element()
6591 }
6592
6593 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6594 &self.context_menu
6595 }
6596
6597 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6598 let newest_selection = self.selections.newest_anchor().clone();
6599 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6600 let buffer = self.buffer.read(cx);
6601 if newest_selection.head().diff_base_anchor.is_some() {
6602 return None;
6603 }
6604 let (start_buffer, start) =
6605 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6606 let (end_buffer, end) =
6607 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6608 if start_buffer != end_buffer {
6609 return None;
6610 }
6611
6612 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6613 cx.background_executor()
6614 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6615 .await;
6616
6617 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6618 let providers = this.code_action_providers.clone();
6619 let tasks = this
6620 .code_action_providers
6621 .iter()
6622 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6623 .collect::<Vec<_>>();
6624 (providers, tasks)
6625 })?;
6626
6627 let mut actions = Vec::new();
6628 for (provider, provider_actions) in
6629 providers.into_iter().zip(future::join_all(tasks).await)
6630 {
6631 if let Some(provider_actions) = provider_actions.log_err() {
6632 actions.extend(provider_actions.into_iter().map(|action| {
6633 AvailableCodeAction {
6634 excerpt_id: newest_selection.start.excerpt_id,
6635 action,
6636 provider: provider.clone(),
6637 }
6638 }));
6639 }
6640 }
6641
6642 this.update(cx, |this, cx| {
6643 this.available_code_actions = if actions.is_empty() {
6644 None
6645 } else {
6646 Some((
6647 Location {
6648 buffer: start_buffer,
6649 range: start..end,
6650 },
6651 actions.into(),
6652 ))
6653 };
6654 cx.notify();
6655 })
6656 }));
6657 None
6658 }
6659
6660 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6661 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6662 self.show_git_blame_inline = false;
6663
6664 self.show_git_blame_inline_delay_task =
6665 Some(cx.spawn_in(window, async move |this, cx| {
6666 cx.background_executor().timer(delay).await;
6667
6668 this.update(cx, |this, cx| {
6669 this.show_git_blame_inline = true;
6670 cx.notify();
6671 })
6672 .log_err();
6673 }));
6674 }
6675 }
6676
6677 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6678 let snapshot = self.snapshot(window, cx);
6679 let cursor = self.selections.newest::<Point>(cx).head();
6680 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6681 else {
6682 return;
6683 };
6684
6685 let Some(blame) = self.blame.as_ref() else {
6686 return;
6687 };
6688
6689 let row_info = RowInfo {
6690 buffer_id: Some(buffer.remote_id()),
6691 buffer_row: Some(point.row),
6692 ..Default::default()
6693 };
6694 let Some((buffer, blame_entry)) = blame
6695 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6696 .flatten()
6697 else {
6698 return;
6699 };
6700
6701 let anchor = self.selections.newest_anchor().head();
6702 let position = self.to_pixel_point(anchor, &snapshot, window);
6703 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6704 self.show_blame_popover(
6705 buffer,
6706 &blame_entry,
6707 position + last_bounds.origin,
6708 true,
6709 cx,
6710 );
6711 };
6712 }
6713
6714 fn show_blame_popover(
6715 &mut self,
6716 buffer: BufferId,
6717 blame_entry: &BlameEntry,
6718 position: gpui::Point<Pixels>,
6719 ignore_timeout: bool,
6720 cx: &mut Context<Self>,
6721 ) {
6722 if let Some(state) = &mut self.inline_blame_popover {
6723 state.hide_task.take();
6724 } else {
6725 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6726 let blame_entry = blame_entry.clone();
6727 let show_task = cx.spawn(async move |editor, cx| {
6728 if !ignore_timeout {
6729 cx.background_executor()
6730 .timer(std::time::Duration::from_millis(blame_popover_delay))
6731 .await;
6732 }
6733 editor
6734 .update(cx, |editor, cx| {
6735 editor.inline_blame_popover_show_task.take();
6736 let Some(blame) = editor.blame.as_ref() else {
6737 return;
6738 };
6739 let blame = blame.read(cx);
6740 let details = blame.details_for_entry(buffer, &blame_entry);
6741 let markdown = cx.new(|cx| {
6742 Markdown::new(
6743 details
6744 .as_ref()
6745 .map(|message| message.message.clone())
6746 .unwrap_or_default(),
6747 None,
6748 None,
6749 cx,
6750 )
6751 });
6752 editor.inline_blame_popover = Some(InlineBlamePopover {
6753 position,
6754 hide_task: None,
6755 popover_bounds: None,
6756 popover_state: InlineBlamePopoverState {
6757 scroll_handle: ScrollHandle::new(),
6758 commit_message: details,
6759 markdown,
6760 },
6761 keyboard_grace: ignore_timeout,
6762 });
6763 cx.notify();
6764 })
6765 .ok();
6766 });
6767 self.inline_blame_popover_show_task = Some(show_task);
6768 }
6769 }
6770
6771 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6772 self.inline_blame_popover_show_task.take();
6773 if let Some(state) = &mut self.inline_blame_popover {
6774 let hide_task = cx.spawn(async move |editor, cx| {
6775 cx.background_executor()
6776 .timer(std::time::Duration::from_millis(100))
6777 .await;
6778 editor
6779 .update(cx, |editor, cx| {
6780 editor.inline_blame_popover.take();
6781 cx.notify();
6782 })
6783 .ok();
6784 });
6785 state.hide_task = Some(hide_task);
6786 }
6787 }
6788
6789 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6790 if self.pending_rename.is_some() {
6791 return None;
6792 }
6793
6794 let provider = self.semantics_provider.clone()?;
6795 let buffer = self.buffer.read(cx);
6796 let newest_selection = self.selections.newest_anchor().clone();
6797 let cursor_position = newest_selection.head();
6798 let (cursor_buffer, cursor_buffer_position) =
6799 buffer.text_anchor_for_position(cursor_position, cx)?;
6800 let (tail_buffer, tail_buffer_position) =
6801 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6802 if cursor_buffer != tail_buffer {
6803 return None;
6804 }
6805
6806 let snapshot = cursor_buffer.read(cx).snapshot();
6807 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6808 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6809 if start_word_range != end_word_range {
6810 self.document_highlights_task.take();
6811 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6812 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6813 return None;
6814 }
6815
6816 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6817 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6818 cx.background_executor()
6819 .timer(Duration::from_millis(debounce))
6820 .await;
6821
6822 let highlights = if let Some(highlights) = cx
6823 .update(|cx| {
6824 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6825 })
6826 .ok()
6827 .flatten()
6828 {
6829 highlights.await.log_err()
6830 } else {
6831 None
6832 };
6833
6834 if let Some(highlights) = highlights {
6835 this.update(cx, |this, cx| {
6836 if this.pending_rename.is_some() {
6837 return;
6838 }
6839
6840 let buffer = this.buffer.read(cx);
6841 if buffer
6842 .text_anchor_for_position(cursor_position, cx)
6843 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6844 {
6845 return;
6846 }
6847
6848 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6849 let mut write_ranges = Vec::new();
6850 let mut read_ranges = Vec::new();
6851 for highlight in highlights {
6852 let buffer_id = cursor_buffer.read(cx).remote_id();
6853 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6854 {
6855 let start = highlight
6856 .range
6857 .start
6858 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6859 let end = highlight
6860 .range
6861 .end
6862 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6863 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6864 continue;
6865 }
6866
6867 let range = Anchor {
6868 buffer_id: Some(buffer_id),
6869 excerpt_id,
6870 text_anchor: start,
6871 diff_base_anchor: None,
6872 }..Anchor {
6873 buffer_id: Some(buffer_id),
6874 excerpt_id,
6875 text_anchor: end,
6876 diff_base_anchor: None,
6877 };
6878 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6879 write_ranges.push(range);
6880 } else {
6881 read_ranges.push(range);
6882 }
6883 }
6884 }
6885
6886 this.highlight_background::<DocumentHighlightRead>(
6887 &read_ranges,
6888 |theme| theme.colors().editor_document_highlight_read_background,
6889 cx,
6890 );
6891 this.highlight_background::<DocumentHighlightWrite>(
6892 &write_ranges,
6893 |theme| theme.colors().editor_document_highlight_write_background,
6894 cx,
6895 );
6896 cx.notify();
6897 })
6898 .log_err();
6899 }
6900 }));
6901 None
6902 }
6903
6904 fn prepare_highlight_query_from_selection(
6905 &mut self,
6906 cx: &mut Context<Editor>,
6907 ) -> Option<(String, Range<Anchor>)> {
6908 if matches!(self.mode, EditorMode::SingleLine) {
6909 return None;
6910 }
6911 if !EditorSettings::get_global(cx).selection_highlight {
6912 return None;
6913 }
6914 if self.selections.count() != 1 || self.selections.line_mode() {
6915 return None;
6916 }
6917 let selection = self.selections.newest::<Point>(cx);
6918 if selection.is_empty() || selection.start.row != selection.end.row {
6919 return None;
6920 }
6921 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6922 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6923 let query = multi_buffer_snapshot
6924 .text_for_range(selection_anchor_range.clone())
6925 .collect::<String>();
6926 if query.trim().is_empty() {
6927 return None;
6928 }
6929 Some((query, selection_anchor_range))
6930 }
6931
6932 fn update_selection_occurrence_highlights(
6933 &mut self,
6934 query_text: String,
6935 query_range: Range<Anchor>,
6936 multi_buffer_range_to_query: Range<Point>,
6937 use_debounce: bool,
6938 window: &mut Window,
6939 cx: &mut Context<Editor>,
6940 ) -> Task<()> {
6941 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6942 cx.spawn_in(window, async move |editor, cx| {
6943 if use_debounce {
6944 cx.background_executor()
6945 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6946 .await;
6947 }
6948 let match_task = cx.background_spawn(async move {
6949 let buffer_ranges = multi_buffer_snapshot
6950 .range_to_buffer_ranges(multi_buffer_range_to_query)
6951 .into_iter()
6952 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6953 let mut match_ranges = Vec::new();
6954 let Ok(regex) = project::search::SearchQuery::text(
6955 query_text.clone(),
6956 false,
6957 false,
6958 false,
6959 Default::default(),
6960 Default::default(),
6961 false,
6962 None,
6963 ) else {
6964 return Vec::default();
6965 };
6966 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6967 match_ranges.extend(
6968 regex
6969 .search(buffer_snapshot, Some(search_range.clone()))
6970 .await
6971 .into_iter()
6972 .filter_map(|match_range| {
6973 let match_start = buffer_snapshot
6974 .anchor_after(search_range.start + match_range.start);
6975 let match_end = buffer_snapshot
6976 .anchor_before(search_range.start + match_range.end);
6977 let match_anchor_range = Anchor::range_in_buffer(
6978 excerpt_id,
6979 buffer_snapshot.remote_id(),
6980 match_start..match_end,
6981 );
6982 (match_anchor_range != query_range).then_some(match_anchor_range)
6983 }),
6984 );
6985 }
6986 match_ranges
6987 });
6988 let match_ranges = match_task.await;
6989 editor
6990 .update_in(cx, |editor, _, cx| {
6991 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6992 if !match_ranges.is_empty() {
6993 editor.highlight_background::<SelectedTextHighlight>(
6994 &match_ranges,
6995 |theme| theme.colors().editor_document_highlight_bracket_background,
6996 cx,
6997 )
6998 }
6999 })
7000 .log_err();
7001 })
7002 }
7003
7004 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7005 struct NewlineFold;
7006 let type_id = std::any::TypeId::of::<NewlineFold>();
7007 if !self.mode.is_single_line() {
7008 return;
7009 }
7010 let snapshot = self.snapshot(window, cx);
7011 if snapshot.buffer_snapshot.max_point().row == 0 {
7012 return;
7013 }
7014 let task = cx.background_spawn(async move {
7015 let new_newlines = snapshot
7016 .buffer_chars_at(0)
7017 .filter_map(|(c, i)| {
7018 if c == '\n' {
7019 Some(
7020 snapshot.buffer_snapshot.anchor_after(i)
7021 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7022 )
7023 } else {
7024 None
7025 }
7026 })
7027 .collect::<Vec<_>>();
7028 let existing_newlines = snapshot
7029 .folds_in_range(0..snapshot.buffer_snapshot.len())
7030 .filter_map(|fold| {
7031 if fold.placeholder.type_tag == Some(type_id) {
7032 Some(fold.range.start..fold.range.end)
7033 } else {
7034 None
7035 }
7036 })
7037 .collect::<Vec<_>>();
7038
7039 (new_newlines, existing_newlines)
7040 });
7041 self.folding_newlines = cx.spawn(async move |this, cx| {
7042 let (new_newlines, existing_newlines) = task.await;
7043 if new_newlines == existing_newlines {
7044 return;
7045 }
7046 let placeholder = FoldPlaceholder {
7047 render: Arc::new(move |_, _, cx| {
7048 div()
7049 .bg(cx.theme().status().hint_background)
7050 .border_b_1()
7051 .size_full()
7052 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7053 .border_color(cx.theme().status().hint)
7054 .child("\\n")
7055 .into_any()
7056 }),
7057 constrain_width: false,
7058 merge_adjacent: false,
7059 type_tag: Some(type_id),
7060 };
7061 let creases = new_newlines
7062 .into_iter()
7063 .map(|range| Crease::simple(range, placeholder.clone()))
7064 .collect();
7065 this.update(cx, |this, cx| {
7066 this.display_map.update(cx, |display_map, cx| {
7067 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7068 display_map.fold(creases, cx);
7069 });
7070 })
7071 .ok();
7072 });
7073 }
7074
7075 fn refresh_selected_text_highlights(
7076 &mut self,
7077 on_buffer_edit: bool,
7078 window: &mut Window,
7079 cx: &mut Context<Editor>,
7080 ) {
7081 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7082 else {
7083 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7084 self.quick_selection_highlight_task.take();
7085 self.debounced_selection_highlight_task.take();
7086 return;
7087 };
7088 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7089 if on_buffer_edit
7090 || self
7091 .quick_selection_highlight_task
7092 .as_ref()
7093 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7094 {
7095 let multi_buffer_visible_start = self
7096 .scroll_manager
7097 .anchor()
7098 .anchor
7099 .to_point(&multi_buffer_snapshot);
7100 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7101 multi_buffer_visible_start
7102 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7103 Bias::Left,
7104 );
7105 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7106 self.quick_selection_highlight_task = Some((
7107 query_range.clone(),
7108 self.update_selection_occurrence_highlights(
7109 query_text.clone(),
7110 query_range.clone(),
7111 multi_buffer_visible_range,
7112 false,
7113 window,
7114 cx,
7115 ),
7116 ));
7117 }
7118 if on_buffer_edit
7119 || self
7120 .debounced_selection_highlight_task
7121 .as_ref()
7122 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7123 {
7124 let multi_buffer_start = multi_buffer_snapshot
7125 .anchor_before(0)
7126 .to_point(&multi_buffer_snapshot);
7127 let multi_buffer_end = multi_buffer_snapshot
7128 .anchor_after(multi_buffer_snapshot.len())
7129 .to_point(&multi_buffer_snapshot);
7130 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7131 self.debounced_selection_highlight_task = Some((
7132 query_range.clone(),
7133 self.update_selection_occurrence_highlights(
7134 query_text,
7135 query_range,
7136 multi_buffer_full_range,
7137 true,
7138 window,
7139 cx,
7140 ),
7141 ));
7142 }
7143 }
7144
7145 pub fn refresh_edit_prediction(
7146 &mut self,
7147 debounce: bool,
7148 user_requested: bool,
7149 window: &mut Window,
7150 cx: &mut Context<Self>,
7151 ) -> Option<()> {
7152 if DisableAiSettings::get_global(cx).disable_ai {
7153 return None;
7154 }
7155
7156 let provider = self.edit_prediction_provider()?;
7157 let cursor = self.selections.newest_anchor().head();
7158 let (buffer, cursor_buffer_position) =
7159 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7160
7161 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7162 self.discard_edit_prediction(false, cx);
7163 return None;
7164 }
7165
7166 self.update_visible_edit_prediction(window, cx);
7167
7168 if !user_requested
7169 && (!self.should_show_edit_predictions()
7170 || !self.is_focused(window)
7171 || buffer.read(cx).is_empty())
7172 {
7173 self.discard_edit_prediction(false, cx);
7174 return None;
7175 }
7176
7177 provider.refresh(
7178 self.project.clone(),
7179 buffer,
7180 cursor_buffer_position,
7181 debounce,
7182 cx,
7183 );
7184 Some(())
7185 }
7186
7187 fn show_edit_predictions_in_menu(&self) -> bool {
7188 match self.edit_prediction_settings {
7189 EditPredictionSettings::Disabled => false,
7190 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7191 }
7192 }
7193
7194 pub fn edit_predictions_enabled(&self) -> bool {
7195 match self.edit_prediction_settings {
7196 EditPredictionSettings::Disabled => false,
7197 EditPredictionSettings::Enabled { .. } => true,
7198 }
7199 }
7200
7201 fn edit_prediction_requires_modifier(&self) -> bool {
7202 match self.edit_prediction_settings {
7203 EditPredictionSettings::Disabled => false,
7204 EditPredictionSettings::Enabled {
7205 preview_requires_modifier,
7206 ..
7207 } => preview_requires_modifier,
7208 }
7209 }
7210
7211 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7212 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7213 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7214 self.discard_edit_prediction(false, cx);
7215 } else {
7216 let selection = self.selections.newest_anchor();
7217 let cursor = selection.head();
7218
7219 if let Some((buffer, cursor_buffer_position)) =
7220 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7221 {
7222 self.edit_prediction_settings =
7223 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7224 }
7225 }
7226 }
7227
7228 fn edit_prediction_settings_at_position(
7229 &self,
7230 buffer: &Entity<Buffer>,
7231 buffer_position: language::Anchor,
7232 cx: &App,
7233 ) -> EditPredictionSettings {
7234 if !self.mode.is_full()
7235 || !self.show_edit_predictions_override.unwrap_or(true)
7236 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7237 {
7238 return EditPredictionSettings::Disabled;
7239 }
7240
7241 let buffer = buffer.read(cx);
7242
7243 let file = buffer.file();
7244
7245 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7246 return EditPredictionSettings::Disabled;
7247 };
7248
7249 let by_provider = matches!(
7250 self.menu_edit_predictions_policy,
7251 MenuEditPredictionsPolicy::ByProvider
7252 );
7253
7254 let show_in_menu = by_provider
7255 && self
7256 .edit_prediction_provider
7257 .as_ref()
7258 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7259
7260 let preview_requires_modifier =
7261 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7262
7263 EditPredictionSettings::Enabled {
7264 show_in_menu,
7265 preview_requires_modifier,
7266 }
7267 }
7268
7269 fn should_show_edit_predictions(&self) -> bool {
7270 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7271 }
7272
7273 pub fn edit_prediction_preview_is_active(&self) -> bool {
7274 matches!(
7275 self.edit_prediction_preview,
7276 EditPredictionPreview::Active { .. }
7277 )
7278 }
7279
7280 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7281 let cursor = self.selections.newest_anchor().head();
7282 if let Some((buffer, cursor_position)) =
7283 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7284 {
7285 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7286 } else {
7287 false
7288 }
7289 }
7290
7291 pub fn supports_minimap(&self, cx: &App) -> bool {
7292 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7293 }
7294
7295 fn edit_predictions_enabled_in_buffer(
7296 &self,
7297 buffer: &Entity<Buffer>,
7298 buffer_position: language::Anchor,
7299 cx: &App,
7300 ) -> bool {
7301 maybe!({
7302 if self.read_only(cx) {
7303 return Some(false);
7304 }
7305 let provider = self.edit_prediction_provider()?;
7306 if !provider.is_enabled(buffer, buffer_position, cx) {
7307 return Some(false);
7308 }
7309 let buffer = buffer.read(cx);
7310 let Some(file) = buffer.file() else {
7311 return Some(true);
7312 };
7313 let settings = all_language_settings(Some(file), cx);
7314 Some(settings.edit_predictions_enabled_for_file(file, cx))
7315 })
7316 .unwrap_or(false)
7317 }
7318
7319 fn cycle_edit_prediction(
7320 &mut self,
7321 direction: Direction,
7322 window: &mut Window,
7323 cx: &mut Context<Self>,
7324 ) -> Option<()> {
7325 let provider = self.edit_prediction_provider()?;
7326 let cursor = self.selections.newest_anchor().head();
7327 let (buffer, cursor_buffer_position) =
7328 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7329 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7330 return None;
7331 }
7332
7333 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7334 self.update_visible_edit_prediction(window, cx);
7335
7336 Some(())
7337 }
7338
7339 pub fn show_edit_prediction(
7340 &mut self,
7341 _: &ShowEditPrediction,
7342 window: &mut Window,
7343 cx: &mut Context<Self>,
7344 ) {
7345 if !self.has_active_edit_prediction() {
7346 self.refresh_edit_prediction(false, true, window, cx);
7347 return;
7348 }
7349
7350 self.update_visible_edit_prediction(window, cx);
7351 }
7352
7353 pub fn display_cursor_names(
7354 &mut self,
7355 _: &DisplayCursorNames,
7356 window: &mut Window,
7357 cx: &mut Context<Self>,
7358 ) {
7359 self.show_cursor_names(window, cx);
7360 }
7361
7362 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7363 self.show_cursor_names = true;
7364 cx.notify();
7365 cx.spawn_in(window, async move |this, cx| {
7366 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7367 this.update(cx, |this, cx| {
7368 this.show_cursor_names = false;
7369 cx.notify()
7370 })
7371 .ok()
7372 })
7373 .detach();
7374 }
7375
7376 pub fn next_edit_prediction(
7377 &mut self,
7378 _: &NextEditPrediction,
7379 window: &mut Window,
7380 cx: &mut Context<Self>,
7381 ) {
7382 if self.has_active_edit_prediction() {
7383 self.cycle_edit_prediction(Direction::Next, 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 previous_edit_prediction(
7395 &mut self,
7396 _: &PreviousEditPrediction,
7397 window: &mut Window,
7398 cx: &mut Context<Self>,
7399 ) {
7400 if self.has_active_edit_prediction() {
7401 self.cycle_edit_prediction(Direction::Prev, window, cx);
7402 } else {
7403 let is_copilot_disabled = self
7404 .refresh_edit_prediction(false, true, window, cx)
7405 .is_none();
7406 if is_copilot_disabled {
7407 cx.propagate();
7408 }
7409 }
7410 }
7411
7412 pub fn accept_edit_prediction(
7413 &mut self,
7414 _: &AcceptEditPrediction,
7415 window: &mut Window,
7416 cx: &mut Context<Self>,
7417 ) {
7418 if self.show_edit_predictions_in_menu() {
7419 self.hide_context_menu(window, cx);
7420 }
7421
7422 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7423 return;
7424 };
7425
7426 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7427
7428 match &active_edit_prediction.completion {
7429 EditPrediction::Move { target, .. } => {
7430 let target = *target;
7431
7432 if let Some(position_map) = &self.last_position_map {
7433 if position_map
7434 .visible_row_range
7435 .contains(&target.to_display_point(&position_map.snapshot).row())
7436 || !self.edit_prediction_requires_modifier()
7437 {
7438 self.unfold_ranges(&[target..target], true, false, cx);
7439 // Note that this is also done in vim's handler of the Tab action.
7440 self.change_selections(
7441 SelectionEffects::scroll(Autoscroll::newest()),
7442 window,
7443 cx,
7444 |selections| {
7445 selections.select_anchor_ranges([target..target]);
7446 },
7447 );
7448 self.clear_row_highlights::<EditPredictionPreview>();
7449
7450 self.edit_prediction_preview
7451 .set_previous_scroll_position(None);
7452 } else {
7453 self.edit_prediction_preview
7454 .set_previous_scroll_position(Some(
7455 position_map.snapshot.scroll_anchor,
7456 ));
7457
7458 self.highlight_rows::<EditPredictionPreview>(
7459 target..target,
7460 cx.theme().colors().editor_highlighted_line_background,
7461 RowHighlightOptions {
7462 autoscroll: true,
7463 ..Default::default()
7464 },
7465 cx,
7466 );
7467 self.request_autoscroll(Autoscroll::fit(), cx);
7468 }
7469 }
7470 }
7471 EditPrediction::Edit { edits, .. } => {
7472 if let Some(provider) = self.edit_prediction_provider() {
7473 provider.accept(cx);
7474 }
7475
7476 // Store the transaction ID and selections before applying the edit
7477 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7478
7479 let snapshot = self.buffer.read(cx).snapshot(cx);
7480 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7481
7482 self.buffer.update(cx, |buffer, cx| {
7483 buffer.edit(edits.iter().cloned(), None, cx)
7484 });
7485
7486 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7487 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7488 });
7489
7490 let selections = self.selections.disjoint_anchors_arc();
7491 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7492 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7493 if has_new_transaction {
7494 self.selection_history
7495 .insert_transaction(transaction_id_now, selections);
7496 }
7497 }
7498
7499 self.update_visible_edit_prediction(window, cx);
7500 if self.active_edit_prediction.is_none() {
7501 self.refresh_edit_prediction(true, true, window, cx);
7502 }
7503
7504 cx.notify();
7505 }
7506 }
7507
7508 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7509 }
7510
7511 pub fn accept_partial_edit_prediction(
7512 &mut self,
7513 _: &AcceptPartialEditPrediction,
7514 window: &mut Window,
7515 cx: &mut Context<Self>,
7516 ) {
7517 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7518 return;
7519 };
7520 if self.selections.count() != 1 {
7521 return;
7522 }
7523
7524 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7525
7526 match &active_edit_prediction.completion {
7527 EditPrediction::Move { target, .. } => {
7528 let target = *target;
7529 self.change_selections(
7530 SelectionEffects::scroll(Autoscroll::newest()),
7531 window,
7532 cx,
7533 |selections| {
7534 selections.select_anchor_ranges([target..target]);
7535 },
7536 );
7537 }
7538 EditPrediction::Edit { edits, .. } => {
7539 // Find an insertion that starts at the cursor position.
7540 let snapshot = self.buffer.read(cx).snapshot(cx);
7541 let cursor_offset = self.selections.newest::<usize>(cx).head();
7542 let insertion = edits.iter().find_map(|(range, text)| {
7543 let range = range.to_offset(&snapshot);
7544 if range.is_empty() && range.start == cursor_offset {
7545 Some(text)
7546 } else {
7547 None
7548 }
7549 });
7550
7551 if let Some(text) = insertion {
7552 let mut partial_completion = text
7553 .chars()
7554 .by_ref()
7555 .take_while(|c| c.is_alphabetic())
7556 .collect::<String>();
7557 if partial_completion.is_empty() {
7558 partial_completion = text
7559 .chars()
7560 .by_ref()
7561 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7562 .collect::<String>();
7563 }
7564
7565 cx.emit(EditorEvent::InputHandled {
7566 utf16_range_to_replace: None,
7567 text: partial_completion.clone().into(),
7568 });
7569
7570 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7571
7572 self.refresh_edit_prediction(true, true, window, cx);
7573 cx.notify();
7574 } else {
7575 self.accept_edit_prediction(&Default::default(), window, cx);
7576 }
7577 }
7578 }
7579 }
7580
7581 fn discard_edit_prediction(
7582 &mut self,
7583 should_report_edit_prediction_event: bool,
7584 cx: &mut Context<Self>,
7585 ) -> bool {
7586 if should_report_edit_prediction_event {
7587 let completion_id = self
7588 .active_edit_prediction
7589 .as_ref()
7590 .and_then(|active_completion| active_completion.completion_id.clone());
7591
7592 self.report_edit_prediction_event(completion_id, false, cx);
7593 }
7594
7595 if let Some(provider) = self.edit_prediction_provider() {
7596 provider.discard(cx);
7597 }
7598
7599 self.take_active_edit_prediction(cx)
7600 }
7601
7602 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7603 let Some(provider) = self.edit_prediction_provider() else {
7604 return;
7605 };
7606
7607 let Some((_, buffer, _)) = self
7608 .buffer
7609 .read(cx)
7610 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7611 else {
7612 return;
7613 };
7614
7615 let extension = buffer
7616 .read(cx)
7617 .file()
7618 .and_then(|file| Some(file.path().extension()?.to_string()));
7619
7620 let event_type = match accepted {
7621 true => "Edit Prediction Accepted",
7622 false => "Edit Prediction Discarded",
7623 };
7624 telemetry::event!(
7625 event_type,
7626 provider = provider.name(),
7627 prediction_id = id,
7628 suggestion_accepted = accepted,
7629 file_extension = extension,
7630 );
7631 }
7632
7633 pub fn has_active_edit_prediction(&self) -> bool {
7634 self.active_edit_prediction.is_some()
7635 }
7636
7637 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7638 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7639 return false;
7640 };
7641
7642 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7643 self.clear_highlights::<EditPredictionHighlight>(cx);
7644 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7645 true
7646 }
7647
7648 /// Returns true when we're displaying the edit prediction popover below the cursor
7649 /// like we are not previewing and the LSP autocomplete menu is visible
7650 /// or we are in `when_holding_modifier` mode.
7651 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7652 if self.edit_prediction_preview_is_active()
7653 || !self.show_edit_predictions_in_menu()
7654 || !self.edit_predictions_enabled()
7655 {
7656 return false;
7657 }
7658
7659 if self.has_visible_completions_menu() {
7660 return true;
7661 }
7662
7663 has_completion && self.edit_prediction_requires_modifier()
7664 }
7665
7666 fn handle_modifiers_changed(
7667 &mut self,
7668 modifiers: Modifiers,
7669 position_map: &PositionMap,
7670 window: &mut Window,
7671 cx: &mut Context<Self>,
7672 ) {
7673 if self.show_edit_predictions_in_menu() {
7674 self.update_edit_prediction_preview(&modifiers, window, cx);
7675 }
7676
7677 self.update_selection_mode(&modifiers, position_map, window, cx);
7678
7679 let mouse_position = window.mouse_position();
7680 if !position_map.text_hitbox.is_hovered(window) {
7681 return;
7682 }
7683
7684 self.update_hovered_link(
7685 position_map.point_for_position(mouse_position),
7686 &position_map.snapshot,
7687 modifiers,
7688 window,
7689 cx,
7690 )
7691 }
7692
7693 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7694 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7695 if invert {
7696 match multi_cursor_setting {
7697 MultiCursorModifier::Alt => modifiers.alt,
7698 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7699 }
7700 } else {
7701 match multi_cursor_setting {
7702 MultiCursorModifier::Alt => modifiers.secondary(),
7703 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7704 }
7705 }
7706 }
7707
7708 fn columnar_selection_mode(
7709 modifiers: &Modifiers,
7710 cx: &mut Context<Self>,
7711 ) -> Option<ColumnarMode> {
7712 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7713 if Self::multi_cursor_modifier(false, modifiers, cx) {
7714 Some(ColumnarMode::FromMouse)
7715 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7716 Some(ColumnarMode::FromSelection)
7717 } else {
7718 None
7719 }
7720 } else {
7721 None
7722 }
7723 }
7724
7725 fn update_selection_mode(
7726 &mut self,
7727 modifiers: &Modifiers,
7728 position_map: &PositionMap,
7729 window: &mut Window,
7730 cx: &mut Context<Self>,
7731 ) {
7732 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7733 return;
7734 };
7735 if self.selections.pending_anchor().is_none() {
7736 return;
7737 }
7738
7739 let mouse_position = window.mouse_position();
7740 let point_for_position = position_map.point_for_position(mouse_position);
7741 let position = point_for_position.previous_valid;
7742
7743 self.select(
7744 SelectPhase::BeginColumnar {
7745 position,
7746 reset: false,
7747 mode,
7748 goal_column: point_for_position.exact_unclipped.column(),
7749 },
7750 window,
7751 cx,
7752 );
7753 }
7754
7755 fn update_edit_prediction_preview(
7756 &mut self,
7757 modifiers: &Modifiers,
7758 window: &mut Window,
7759 cx: &mut Context<Self>,
7760 ) {
7761 let mut modifiers_held = false;
7762 if let Some(accept_keystroke) = self
7763 .accept_edit_prediction_keybind(false, window, cx)
7764 .keystroke()
7765 {
7766 modifiers_held = modifiers_held
7767 || (accept_keystroke.modifiers() == modifiers
7768 && accept_keystroke.modifiers().modified());
7769 };
7770 if let Some(accept_partial_keystroke) = self
7771 .accept_edit_prediction_keybind(true, window, cx)
7772 .keystroke()
7773 {
7774 modifiers_held = modifiers_held
7775 || (accept_partial_keystroke.modifiers() == modifiers
7776 && accept_partial_keystroke.modifiers().modified());
7777 }
7778
7779 if modifiers_held {
7780 if matches!(
7781 self.edit_prediction_preview,
7782 EditPredictionPreview::Inactive { .. }
7783 ) {
7784 self.edit_prediction_preview = EditPredictionPreview::Active {
7785 previous_scroll_position: None,
7786 since: Instant::now(),
7787 };
7788
7789 self.update_visible_edit_prediction(window, cx);
7790 cx.notify();
7791 }
7792 } else if let EditPredictionPreview::Active {
7793 previous_scroll_position,
7794 since,
7795 } = self.edit_prediction_preview
7796 {
7797 if let (Some(previous_scroll_position), Some(position_map)) =
7798 (previous_scroll_position, self.last_position_map.as_ref())
7799 {
7800 self.set_scroll_position(
7801 previous_scroll_position
7802 .scroll_position(&position_map.snapshot.display_snapshot),
7803 window,
7804 cx,
7805 );
7806 }
7807
7808 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7809 released_too_fast: since.elapsed() < Duration::from_millis(200),
7810 };
7811 self.clear_row_highlights::<EditPredictionPreview>();
7812 self.update_visible_edit_prediction(window, cx);
7813 cx.notify();
7814 }
7815 }
7816
7817 fn update_visible_edit_prediction(
7818 &mut self,
7819 _window: &mut Window,
7820 cx: &mut Context<Self>,
7821 ) -> Option<()> {
7822 if DisableAiSettings::get_global(cx).disable_ai {
7823 return None;
7824 }
7825
7826 if self.ime_transaction.is_some() {
7827 self.discard_edit_prediction(false, cx);
7828 return None;
7829 }
7830
7831 let selection = self.selections.newest_anchor();
7832 let cursor = selection.head();
7833 let multibuffer = self.buffer.read(cx).snapshot(cx);
7834 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7835 let excerpt_id = cursor.excerpt_id;
7836
7837 let show_in_menu = self.show_edit_predictions_in_menu();
7838 let completions_menu_has_precedence = !show_in_menu
7839 && (self.context_menu.borrow().is_some()
7840 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7841
7842 if completions_menu_has_precedence
7843 || !offset_selection.is_empty()
7844 || self
7845 .active_edit_prediction
7846 .as_ref()
7847 .is_some_and(|completion| {
7848 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7849 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7850 !invalidation_range.contains(&offset_selection.head())
7851 })
7852 {
7853 self.discard_edit_prediction(false, cx);
7854 return None;
7855 }
7856
7857 self.take_active_edit_prediction(cx);
7858 let Some(provider) = self.edit_prediction_provider() else {
7859 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7860 return None;
7861 };
7862
7863 let (buffer, cursor_buffer_position) =
7864 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7865
7866 self.edit_prediction_settings =
7867 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7868
7869 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7870
7871 if self.edit_prediction_indent_conflict {
7872 let cursor_point = cursor.to_point(&multibuffer);
7873
7874 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7875
7876 if let Some((_, indent)) = indents.iter().next()
7877 && indent.len == cursor_point.column
7878 {
7879 self.edit_prediction_indent_conflict = false;
7880 }
7881 }
7882
7883 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7884 let edits = edit_prediction
7885 .edits
7886 .into_iter()
7887 .flat_map(|(range, new_text)| {
7888 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7889 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7890 Some((start..end, new_text))
7891 })
7892 .collect::<Vec<_>>();
7893 if edits.is_empty() {
7894 return None;
7895 }
7896
7897 let first_edit_start = edits.first().unwrap().0.start;
7898 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7899 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7900
7901 let last_edit_end = edits.last().unwrap().0.end;
7902 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7903 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7904
7905 let cursor_row = cursor.to_point(&multibuffer).row;
7906
7907 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7908
7909 let mut inlay_ids = Vec::new();
7910 let invalidation_row_range;
7911 let move_invalidation_row_range = if cursor_row < edit_start_row {
7912 Some(cursor_row..edit_end_row)
7913 } else if cursor_row > edit_end_row {
7914 Some(edit_start_row..cursor_row)
7915 } else {
7916 None
7917 };
7918 let supports_jump = self
7919 .edit_prediction_provider
7920 .as_ref()
7921 .map(|provider| provider.provider.supports_jump_to_edit())
7922 .unwrap_or(true);
7923
7924 let is_move = supports_jump
7925 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7926 let completion = if is_move {
7927 invalidation_row_range =
7928 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7929 let target = first_edit_start;
7930 EditPrediction::Move { target, snapshot }
7931 } else {
7932 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7933 && !self.edit_predictions_hidden_for_vim_mode;
7934
7935 if show_completions_in_buffer {
7936 if edits
7937 .iter()
7938 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7939 {
7940 let mut inlays = Vec::new();
7941 for (range, new_text) in &edits {
7942 let inlay = Inlay::edit_prediction(
7943 post_inc(&mut self.next_inlay_id),
7944 range.start,
7945 new_text.as_str(),
7946 );
7947 inlay_ids.push(inlay.id);
7948 inlays.push(inlay);
7949 }
7950
7951 self.splice_inlays(&[], inlays, cx);
7952 } else {
7953 let background_color = cx.theme().status().deleted_background;
7954 self.highlight_text::<EditPredictionHighlight>(
7955 edits.iter().map(|(range, _)| range.clone()).collect(),
7956 HighlightStyle {
7957 background_color: Some(background_color),
7958 ..Default::default()
7959 },
7960 cx,
7961 );
7962 }
7963 }
7964
7965 invalidation_row_range = edit_start_row..edit_end_row;
7966
7967 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7968 if provider.show_tab_accept_marker() {
7969 EditDisplayMode::TabAccept
7970 } else {
7971 EditDisplayMode::Inline
7972 }
7973 } else {
7974 EditDisplayMode::DiffPopover
7975 };
7976
7977 EditPrediction::Edit {
7978 edits,
7979 edit_preview: edit_prediction.edit_preview,
7980 display_mode,
7981 snapshot,
7982 }
7983 };
7984
7985 let invalidation_range = multibuffer
7986 .anchor_before(Point::new(invalidation_row_range.start, 0))
7987 ..multibuffer.anchor_after(Point::new(
7988 invalidation_row_range.end,
7989 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7990 ));
7991
7992 self.stale_edit_prediction_in_menu = None;
7993 self.active_edit_prediction = Some(EditPredictionState {
7994 inlay_ids,
7995 completion,
7996 completion_id: edit_prediction.id,
7997 invalidation_range,
7998 });
7999
8000 cx.notify();
8001
8002 Some(())
8003 }
8004
8005 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8006 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8007 }
8008
8009 fn clear_tasks(&mut self) {
8010 self.tasks.clear()
8011 }
8012
8013 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8014 if self.tasks.insert(key, value).is_some() {
8015 // This case should hopefully be rare, but just in case...
8016 log::error!(
8017 "multiple different run targets found on a single line, only the last target will be rendered"
8018 )
8019 }
8020 }
8021
8022 /// Get all display points of breakpoints that will be rendered within editor
8023 ///
8024 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8025 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8026 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8027 fn active_breakpoints(
8028 &self,
8029 range: Range<DisplayRow>,
8030 window: &mut Window,
8031 cx: &mut Context<Self>,
8032 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8033 let mut breakpoint_display_points = HashMap::default();
8034
8035 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8036 return breakpoint_display_points;
8037 };
8038
8039 let snapshot = self.snapshot(window, cx);
8040
8041 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8042 let Some(project) = self.project() else {
8043 return breakpoint_display_points;
8044 };
8045
8046 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8047 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8048
8049 for (buffer_snapshot, range, excerpt_id) in
8050 multi_buffer_snapshot.range_to_buffer_ranges(range)
8051 {
8052 let Some(buffer) = project
8053 .read(cx)
8054 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8055 else {
8056 continue;
8057 };
8058 let breakpoints = breakpoint_store.read(cx).breakpoints(
8059 &buffer,
8060 Some(
8061 buffer_snapshot.anchor_before(range.start)
8062 ..buffer_snapshot.anchor_after(range.end),
8063 ),
8064 buffer_snapshot,
8065 cx,
8066 );
8067 for (breakpoint, state) in breakpoints {
8068 let multi_buffer_anchor =
8069 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8070 let position = multi_buffer_anchor
8071 .to_point(multi_buffer_snapshot)
8072 .to_display_point(&snapshot);
8073
8074 breakpoint_display_points.insert(
8075 position.row(),
8076 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8077 );
8078 }
8079 }
8080
8081 breakpoint_display_points
8082 }
8083
8084 fn breakpoint_context_menu(
8085 &self,
8086 anchor: Anchor,
8087 window: &mut Window,
8088 cx: &mut Context<Self>,
8089 ) -> Entity<ui::ContextMenu> {
8090 let weak_editor = cx.weak_entity();
8091 let focus_handle = self.focus_handle(cx);
8092
8093 let row = self
8094 .buffer
8095 .read(cx)
8096 .snapshot(cx)
8097 .summary_for_anchor::<Point>(&anchor)
8098 .row;
8099
8100 let breakpoint = self
8101 .breakpoint_at_row(row, window, cx)
8102 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8103
8104 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8105 "Edit Log Breakpoint"
8106 } else {
8107 "Set Log Breakpoint"
8108 };
8109
8110 let condition_breakpoint_msg = if breakpoint
8111 .as_ref()
8112 .is_some_and(|bp| bp.1.condition.is_some())
8113 {
8114 "Edit Condition Breakpoint"
8115 } else {
8116 "Set Condition Breakpoint"
8117 };
8118
8119 let hit_condition_breakpoint_msg = if breakpoint
8120 .as_ref()
8121 .is_some_and(|bp| bp.1.hit_condition.is_some())
8122 {
8123 "Edit Hit Condition Breakpoint"
8124 } else {
8125 "Set Hit Condition Breakpoint"
8126 };
8127
8128 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8129 "Unset Breakpoint"
8130 } else {
8131 "Set Breakpoint"
8132 };
8133
8134 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8135
8136 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8137 BreakpointState::Enabled => Some("Disable"),
8138 BreakpointState::Disabled => Some("Enable"),
8139 });
8140
8141 let (anchor, breakpoint) =
8142 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8143
8144 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8145 menu.on_blur_subscription(Subscription::new(|| {}))
8146 .context(focus_handle)
8147 .when(run_to_cursor, |this| {
8148 let weak_editor = weak_editor.clone();
8149 this.entry("Run to cursor", None, move |window, cx| {
8150 weak_editor
8151 .update(cx, |editor, cx| {
8152 editor.change_selections(
8153 SelectionEffects::no_scroll(),
8154 window,
8155 cx,
8156 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8157 );
8158 })
8159 .ok();
8160
8161 window.dispatch_action(Box::new(RunToCursor), cx);
8162 })
8163 .separator()
8164 })
8165 .when_some(toggle_state_msg, |this, msg| {
8166 this.entry(msg, None, {
8167 let weak_editor = weak_editor.clone();
8168 let breakpoint = breakpoint.clone();
8169 move |_window, cx| {
8170 weak_editor
8171 .update(cx, |this, cx| {
8172 this.edit_breakpoint_at_anchor(
8173 anchor,
8174 breakpoint.as_ref().clone(),
8175 BreakpointEditAction::InvertState,
8176 cx,
8177 );
8178 })
8179 .log_err();
8180 }
8181 })
8182 })
8183 .entry(set_breakpoint_msg, None, {
8184 let weak_editor = weak_editor.clone();
8185 let breakpoint = breakpoint.clone();
8186 move |_window, cx| {
8187 weak_editor
8188 .update(cx, |this, cx| {
8189 this.edit_breakpoint_at_anchor(
8190 anchor,
8191 breakpoint.as_ref().clone(),
8192 BreakpointEditAction::Toggle,
8193 cx,
8194 );
8195 })
8196 .log_err();
8197 }
8198 })
8199 .entry(log_breakpoint_msg, None, {
8200 let breakpoint = breakpoint.clone();
8201 let weak_editor = weak_editor.clone();
8202 move |window, cx| {
8203 weak_editor
8204 .update(cx, |this, cx| {
8205 this.add_edit_breakpoint_block(
8206 anchor,
8207 breakpoint.as_ref(),
8208 BreakpointPromptEditAction::Log,
8209 window,
8210 cx,
8211 );
8212 })
8213 .log_err();
8214 }
8215 })
8216 .entry(condition_breakpoint_msg, None, {
8217 let breakpoint = breakpoint.clone();
8218 let weak_editor = weak_editor.clone();
8219 move |window, cx| {
8220 weak_editor
8221 .update(cx, |this, cx| {
8222 this.add_edit_breakpoint_block(
8223 anchor,
8224 breakpoint.as_ref(),
8225 BreakpointPromptEditAction::Condition,
8226 window,
8227 cx,
8228 );
8229 })
8230 .log_err();
8231 }
8232 })
8233 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8234 weak_editor
8235 .update(cx, |this, cx| {
8236 this.add_edit_breakpoint_block(
8237 anchor,
8238 breakpoint.as_ref(),
8239 BreakpointPromptEditAction::HitCondition,
8240 window,
8241 cx,
8242 );
8243 })
8244 .log_err();
8245 })
8246 })
8247 }
8248
8249 fn render_breakpoint(
8250 &self,
8251 position: Anchor,
8252 row: DisplayRow,
8253 breakpoint: &Breakpoint,
8254 state: Option<BreakpointSessionState>,
8255 cx: &mut Context<Self>,
8256 ) -> IconButton {
8257 let is_rejected = state.is_some_and(|s| !s.verified);
8258 // Is it a breakpoint that shows up when hovering over gutter?
8259 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8260 (false, false),
8261 |PhantomBreakpointIndicator {
8262 is_active,
8263 display_row,
8264 collides_with_existing_breakpoint,
8265 }| {
8266 (
8267 is_active && display_row == row,
8268 collides_with_existing_breakpoint,
8269 )
8270 },
8271 );
8272
8273 let (color, icon) = {
8274 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8275 (false, false) => ui::IconName::DebugBreakpoint,
8276 (true, false) => ui::IconName::DebugLogBreakpoint,
8277 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8278 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8279 };
8280
8281 let color = if is_phantom {
8282 Color::Hint
8283 } else if is_rejected {
8284 Color::Disabled
8285 } else {
8286 Color::Debugger
8287 };
8288
8289 (color, icon)
8290 };
8291
8292 let breakpoint = Arc::from(breakpoint.clone());
8293
8294 let alt_as_text = gpui::Keystroke {
8295 modifiers: Modifiers::secondary_key(),
8296 ..Default::default()
8297 };
8298 let primary_action_text = if breakpoint.is_disabled() {
8299 "Enable breakpoint"
8300 } else if is_phantom && !collides_with_existing {
8301 "Set breakpoint"
8302 } else {
8303 "Unset breakpoint"
8304 };
8305 let focus_handle = self.focus_handle.clone();
8306
8307 let meta = if is_rejected {
8308 SharedString::from("No executable code is associated with this line.")
8309 } else if collides_with_existing && !breakpoint.is_disabled() {
8310 SharedString::from(format!(
8311 "{alt_as_text}-click to disable,\nright-click for more options."
8312 ))
8313 } else {
8314 SharedString::from("Right-click for more options.")
8315 };
8316 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8317 .icon_size(IconSize::XSmall)
8318 .size(ui::ButtonSize::None)
8319 .when(is_rejected, |this| {
8320 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8321 })
8322 .icon_color(color)
8323 .style(ButtonStyle::Transparent)
8324 .on_click(cx.listener({
8325 move |editor, event: &ClickEvent, window, cx| {
8326 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8327 BreakpointEditAction::InvertState
8328 } else {
8329 BreakpointEditAction::Toggle
8330 };
8331
8332 window.focus(&editor.focus_handle(cx));
8333 editor.edit_breakpoint_at_anchor(
8334 position,
8335 breakpoint.as_ref().clone(),
8336 edit_action,
8337 cx,
8338 );
8339 }
8340 }))
8341 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8342 editor.set_breakpoint_context_menu(
8343 row,
8344 Some(position),
8345 event.position(),
8346 window,
8347 cx,
8348 );
8349 }))
8350 .tooltip(move |window, cx| {
8351 Tooltip::with_meta_in(
8352 primary_action_text,
8353 Some(&ToggleBreakpoint),
8354 meta.clone(),
8355 &focus_handle,
8356 window,
8357 cx,
8358 )
8359 })
8360 }
8361
8362 fn build_tasks_context(
8363 project: &Entity<Project>,
8364 buffer: &Entity<Buffer>,
8365 buffer_row: u32,
8366 tasks: &Arc<RunnableTasks>,
8367 cx: &mut Context<Self>,
8368 ) -> Task<Option<task::TaskContext>> {
8369 let position = Point::new(buffer_row, tasks.column);
8370 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8371 let location = Location {
8372 buffer: buffer.clone(),
8373 range: range_start..range_start,
8374 };
8375 // Fill in the environmental variables from the tree-sitter captures
8376 let mut captured_task_variables = TaskVariables::default();
8377 for (capture_name, value) in tasks.extra_variables.clone() {
8378 captured_task_variables.insert(
8379 task::VariableName::Custom(capture_name.into()),
8380 value.clone(),
8381 );
8382 }
8383 project.update(cx, |project, cx| {
8384 project.task_store().update(cx, |task_store, cx| {
8385 task_store.task_context_for_location(captured_task_variables, location, cx)
8386 })
8387 })
8388 }
8389
8390 pub fn spawn_nearest_task(
8391 &mut self,
8392 action: &SpawnNearestTask,
8393 window: &mut Window,
8394 cx: &mut Context<Self>,
8395 ) {
8396 let Some((workspace, _)) = self.workspace.clone() else {
8397 return;
8398 };
8399 let Some(project) = self.project.clone() else {
8400 return;
8401 };
8402
8403 // Try to find a closest, enclosing node using tree-sitter that has a task
8404 let Some((buffer, buffer_row, tasks)) = self
8405 .find_enclosing_node_task(cx)
8406 // Or find the task that's closest in row-distance.
8407 .or_else(|| self.find_closest_task(cx))
8408 else {
8409 return;
8410 };
8411
8412 let reveal_strategy = action.reveal;
8413 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8414 cx.spawn_in(window, async move |_, cx| {
8415 let context = task_context.await?;
8416 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8417
8418 let resolved = &mut resolved_task.resolved;
8419 resolved.reveal = reveal_strategy;
8420
8421 workspace
8422 .update_in(cx, |workspace, window, cx| {
8423 workspace.schedule_resolved_task(
8424 task_source_kind,
8425 resolved_task,
8426 false,
8427 window,
8428 cx,
8429 );
8430 })
8431 .ok()
8432 })
8433 .detach();
8434 }
8435
8436 fn find_closest_task(
8437 &mut self,
8438 cx: &mut Context<Self>,
8439 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8440 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8441
8442 let ((buffer_id, row), tasks) = self
8443 .tasks
8444 .iter()
8445 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8446
8447 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8448 let tasks = Arc::new(tasks.to_owned());
8449 Some((buffer, *row, tasks))
8450 }
8451
8452 fn find_enclosing_node_task(
8453 &mut self,
8454 cx: &mut Context<Self>,
8455 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8456 let snapshot = self.buffer.read(cx).snapshot(cx);
8457 let offset = self.selections.newest::<usize>(cx).head();
8458 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8459 let buffer_id = excerpt.buffer().remote_id();
8460
8461 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8462 let mut cursor = layer.node().walk();
8463
8464 while cursor.goto_first_child_for_byte(offset).is_some() {
8465 if cursor.node().end_byte() == offset {
8466 cursor.goto_next_sibling();
8467 }
8468 }
8469
8470 // Ascend to the smallest ancestor that contains the range and has a task.
8471 loop {
8472 let node = cursor.node();
8473 let node_range = node.byte_range();
8474 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8475
8476 // Check if this node contains our offset
8477 if node_range.start <= offset && node_range.end >= offset {
8478 // If it contains offset, check for task
8479 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8480 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8481 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8482 }
8483 }
8484
8485 if !cursor.goto_parent() {
8486 break;
8487 }
8488 }
8489 None
8490 }
8491
8492 fn render_run_indicator(
8493 &self,
8494 _style: &EditorStyle,
8495 is_active: bool,
8496 row: DisplayRow,
8497 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8498 cx: &mut Context<Self>,
8499 ) -> IconButton {
8500 let color = Color::Muted;
8501 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8502
8503 IconButton::new(
8504 ("run_indicator", row.0 as usize),
8505 ui::IconName::PlayOutlined,
8506 )
8507 .shape(ui::IconButtonShape::Square)
8508 .icon_size(IconSize::XSmall)
8509 .icon_color(color)
8510 .toggle_state(is_active)
8511 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8512 let quick_launch = match e {
8513 ClickEvent::Keyboard(_) => true,
8514 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8515 };
8516
8517 window.focus(&editor.focus_handle(cx));
8518 editor.toggle_code_actions(
8519 &ToggleCodeActions {
8520 deployed_from: Some(CodeActionSource::RunMenu(row)),
8521 quick_launch,
8522 },
8523 window,
8524 cx,
8525 );
8526 }))
8527 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8528 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8529 }))
8530 }
8531
8532 pub fn context_menu_visible(&self) -> bool {
8533 !self.edit_prediction_preview_is_active()
8534 && self
8535 .context_menu
8536 .borrow()
8537 .as_ref()
8538 .is_some_and(|menu| menu.visible())
8539 }
8540
8541 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8542 self.context_menu
8543 .borrow()
8544 .as_ref()
8545 .map(|menu| menu.origin())
8546 }
8547
8548 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8549 self.context_menu_options = Some(options);
8550 }
8551
8552 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8553 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8554
8555 fn render_edit_prediction_popover(
8556 &mut self,
8557 text_bounds: &Bounds<Pixels>,
8558 content_origin: gpui::Point<Pixels>,
8559 right_margin: Pixels,
8560 editor_snapshot: &EditorSnapshot,
8561 visible_row_range: Range<DisplayRow>,
8562 scroll_top: f32,
8563 scroll_bottom: f32,
8564 line_layouts: &[LineWithInvisibles],
8565 line_height: Pixels,
8566 scroll_pixel_position: gpui::Point<Pixels>,
8567 newest_selection_head: Option<DisplayPoint>,
8568 editor_width: Pixels,
8569 style: &EditorStyle,
8570 window: &mut Window,
8571 cx: &mut App,
8572 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8573 if self.mode().is_minimap() {
8574 return None;
8575 }
8576 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8577
8578 if self.edit_prediction_visible_in_cursor_popover(true) {
8579 return None;
8580 }
8581
8582 match &active_edit_prediction.completion {
8583 EditPrediction::Move { target, .. } => {
8584 let target_display_point = target.to_display_point(editor_snapshot);
8585
8586 if self.edit_prediction_requires_modifier() {
8587 if !self.edit_prediction_preview_is_active() {
8588 return None;
8589 }
8590
8591 self.render_edit_prediction_modifier_jump_popover(
8592 text_bounds,
8593 content_origin,
8594 visible_row_range,
8595 line_layouts,
8596 line_height,
8597 scroll_pixel_position,
8598 newest_selection_head,
8599 target_display_point,
8600 window,
8601 cx,
8602 )
8603 } else {
8604 self.render_edit_prediction_eager_jump_popover(
8605 text_bounds,
8606 content_origin,
8607 editor_snapshot,
8608 visible_row_range,
8609 scroll_top,
8610 scroll_bottom,
8611 line_height,
8612 scroll_pixel_position,
8613 target_display_point,
8614 editor_width,
8615 window,
8616 cx,
8617 )
8618 }
8619 }
8620 EditPrediction::Edit {
8621 display_mode: EditDisplayMode::Inline,
8622 ..
8623 } => None,
8624 EditPrediction::Edit {
8625 display_mode: EditDisplayMode::TabAccept,
8626 edits,
8627 ..
8628 } => {
8629 let range = &edits.first()?.0;
8630 let target_display_point = range.end.to_display_point(editor_snapshot);
8631
8632 self.render_edit_prediction_end_of_line_popover(
8633 "Accept",
8634 editor_snapshot,
8635 visible_row_range,
8636 target_display_point,
8637 line_height,
8638 scroll_pixel_position,
8639 content_origin,
8640 editor_width,
8641 window,
8642 cx,
8643 )
8644 }
8645 EditPrediction::Edit {
8646 edits,
8647 edit_preview,
8648 display_mode: EditDisplayMode::DiffPopover,
8649 snapshot,
8650 } => self.render_edit_prediction_diff_popover(
8651 text_bounds,
8652 content_origin,
8653 right_margin,
8654 editor_snapshot,
8655 visible_row_range,
8656 line_layouts,
8657 line_height,
8658 scroll_pixel_position,
8659 newest_selection_head,
8660 editor_width,
8661 style,
8662 edits,
8663 edit_preview,
8664 snapshot,
8665 window,
8666 cx,
8667 ),
8668 }
8669 }
8670
8671 fn render_edit_prediction_modifier_jump_popover(
8672 &mut self,
8673 text_bounds: &Bounds<Pixels>,
8674 content_origin: gpui::Point<Pixels>,
8675 visible_row_range: Range<DisplayRow>,
8676 line_layouts: &[LineWithInvisibles],
8677 line_height: Pixels,
8678 scroll_pixel_position: gpui::Point<Pixels>,
8679 newest_selection_head: Option<DisplayPoint>,
8680 target_display_point: DisplayPoint,
8681 window: &mut Window,
8682 cx: &mut App,
8683 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8684 let scrolled_content_origin =
8685 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8686
8687 const SCROLL_PADDING_Y: Pixels = px(12.);
8688
8689 if target_display_point.row() < visible_row_range.start {
8690 return self.render_edit_prediction_scroll_popover(
8691 |_| SCROLL_PADDING_Y,
8692 IconName::ArrowUp,
8693 visible_row_range,
8694 line_layouts,
8695 newest_selection_head,
8696 scrolled_content_origin,
8697 window,
8698 cx,
8699 );
8700 } else if target_display_point.row() >= visible_row_range.end {
8701 return self.render_edit_prediction_scroll_popover(
8702 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8703 IconName::ArrowDown,
8704 visible_row_range,
8705 line_layouts,
8706 newest_selection_head,
8707 scrolled_content_origin,
8708 window,
8709 cx,
8710 );
8711 }
8712
8713 const POLE_WIDTH: Pixels = px(2.);
8714
8715 let line_layout =
8716 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8717 let target_column = target_display_point.column() as usize;
8718
8719 let target_x = line_layout.x_for_index(target_column);
8720 let target_y =
8721 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8722
8723 let flag_on_right = target_x < text_bounds.size.width / 2.;
8724
8725 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8726 border_color.l += 0.001;
8727
8728 let mut element = v_flex()
8729 .items_end()
8730 .when(flag_on_right, |el| el.items_start())
8731 .child(if flag_on_right {
8732 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8733 .rounded_bl(px(0.))
8734 .rounded_tl(px(0.))
8735 .border_l_2()
8736 .border_color(border_color)
8737 } else {
8738 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8739 .rounded_br(px(0.))
8740 .rounded_tr(px(0.))
8741 .border_r_2()
8742 .border_color(border_color)
8743 })
8744 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8745 .into_any();
8746
8747 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8748
8749 let mut origin = scrolled_content_origin + point(target_x, target_y)
8750 - point(
8751 if flag_on_right {
8752 POLE_WIDTH
8753 } else {
8754 size.width - POLE_WIDTH
8755 },
8756 size.height - line_height,
8757 );
8758
8759 origin.x = origin.x.max(content_origin.x);
8760
8761 element.prepaint_at(origin, window, cx);
8762
8763 Some((element, origin))
8764 }
8765
8766 fn render_edit_prediction_scroll_popover(
8767 &mut self,
8768 to_y: impl Fn(Size<Pixels>) -> Pixels,
8769 scroll_icon: IconName,
8770 visible_row_range: Range<DisplayRow>,
8771 line_layouts: &[LineWithInvisibles],
8772 newest_selection_head: Option<DisplayPoint>,
8773 scrolled_content_origin: gpui::Point<Pixels>,
8774 window: &mut Window,
8775 cx: &mut App,
8776 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8777 let mut element = self
8778 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8779 .into_any();
8780
8781 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8782
8783 let cursor = newest_selection_head?;
8784 let cursor_row_layout =
8785 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8786 let cursor_column = cursor.column() as usize;
8787
8788 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8789
8790 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8791
8792 element.prepaint_at(origin, window, cx);
8793 Some((element, origin))
8794 }
8795
8796 fn render_edit_prediction_eager_jump_popover(
8797 &mut self,
8798 text_bounds: &Bounds<Pixels>,
8799 content_origin: gpui::Point<Pixels>,
8800 editor_snapshot: &EditorSnapshot,
8801 visible_row_range: Range<DisplayRow>,
8802 scroll_top: f32,
8803 scroll_bottom: f32,
8804 line_height: Pixels,
8805 scroll_pixel_position: gpui::Point<Pixels>,
8806 target_display_point: DisplayPoint,
8807 editor_width: Pixels,
8808 window: &mut Window,
8809 cx: &mut App,
8810 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8811 if target_display_point.row().as_f32() < scroll_top {
8812 let mut element = self
8813 .render_edit_prediction_line_popover(
8814 "Jump to Edit",
8815 Some(IconName::ArrowUp),
8816 window,
8817 cx,
8818 )?
8819 .into_any();
8820
8821 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8822 let offset = point(
8823 (text_bounds.size.width - size.width) / 2.,
8824 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8825 );
8826
8827 let origin = text_bounds.origin + offset;
8828 element.prepaint_at(origin, window, cx);
8829 Some((element, origin))
8830 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8831 let mut element = self
8832 .render_edit_prediction_line_popover(
8833 "Jump to Edit",
8834 Some(IconName::ArrowDown),
8835 window,
8836 cx,
8837 )?
8838 .into_any();
8839
8840 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8841 let offset = point(
8842 (text_bounds.size.width - size.width) / 2.,
8843 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8844 );
8845
8846 let origin = text_bounds.origin + offset;
8847 element.prepaint_at(origin, window, cx);
8848 Some((element, origin))
8849 } else {
8850 self.render_edit_prediction_end_of_line_popover(
8851 "Jump to Edit",
8852 editor_snapshot,
8853 visible_row_range,
8854 target_display_point,
8855 line_height,
8856 scroll_pixel_position,
8857 content_origin,
8858 editor_width,
8859 window,
8860 cx,
8861 )
8862 }
8863 }
8864
8865 fn render_edit_prediction_end_of_line_popover(
8866 self: &mut Editor,
8867 label: &'static str,
8868 editor_snapshot: &EditorSnapshot,
8869 visible_row_range: Range<DisplayRow>,
8870 target_display_point: DisplayPoint,
8871 line_height: Pixels,
8872 scroll_pixel_position: gpui::Point<Pixels>,
8873 content_origin: gpui::Point<Pixels>,
8874 editor_width: Pixels,
8875 window: &mut Window,
8876 cx: &mut App,
8877 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8878 let target_line_end = DisplayPoint::new(
8879 target_display_point.row(),
8880 editor_snapshot.line_len(target_display_point.row()),
8881 );
8882
8883 let mut element = self
8884 .render_edit_prediction_line_popover(label, None, window, cx)?
8885 .into_any();
8886
8887 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8888
8889 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8890
8891 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8892 let mut origin = start_point
8893 + line_origin
8894 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8895 origin.x = origin.x.max(content_origin.x);
8896
8897 let max_x = content_origin.x + editor_width - size.width;
8898
8899 if origin.x > max_x {
8900 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8901
8902 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8903 origin.y += offset;
8904 IconName::ArrowUp
8905 } else {
8906 origin.y -= offset;
8907 IconName::ArrowDown
8908 };
8909
8910 element = self
8911 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8912 .into_any();
8913
8914 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8915
8916 origin.x = content_origin.x + editor_width - size.width - px(2.);
8917 }
8918
8919 element.prepaint_at(origin, window, cx);
8920 Some((element, origin))
8921 }
8922
8923 fn render_edit_prediction_diff_popover(
8924 self: &Editor,
8925 text_bounds: &Bounds<Pixels>,
8926 content_origin: gpui::Point<Pixels>,
8927 right_margin: Pixels,
8928 editor_snapshot: &EditorSnapshot,
8929 visible_row_range: Range<DisplayRow>,
8930 line_layouts: &[LineWithInvisibles],
8931 line_height: Pixels,
8932 scroll_pixel_position: gpui::Point<Pixels>,
8933 newest_selection_head: Option<DisplayPoint>,
8934 editor_width: Pixels,
8935 style: &EditorStyle,
8936 edits: &Vec<(Range<Anchor>, String)>,
8937 edit_preview: &Option<language::EditPreview>,
8938 snapshot: &language::BufferSnapshot,
8939 window: &mut Window,
8940 cx: &mut App,
8941 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8942 let edit_start = edits
8943 .first()
8944 .unwrap()
8945 .0
8946 .start
8947 .to_display_point(editor_snapshot);
8948 let edit_end = edits
8949 .last()
8950 .unwrap()
8951 .0
8952 .end
8953 .to_display_point(editor_snapshot);
8954
8955 let is_visible = visible_row_range.contains(&edit_start.row())
8956 || visible_row_range.contains(&edit_end.row());
8957 if !is_visible {
8958 return None;
8959 }
8960
8961 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8962 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8963 } else {
8964 // Fallback for providers without edit_preview
8965 crate::edit_prediction_fallback_text(edits, cx)
8966 };
8967
8968 let styled_text = highlighted_edits.to_styled_text(&style.text);
8969 let line_count = highlighted_edits.text.lines().count();
8970
8971 const BORDER_WIDTH: Pixels = px(1.);
8972
8973 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8974 let has_keybind = keybind.is_some();
8975
8976 let mut element = h_flex()
8977 .items_start()
8978 .child(
8979 h_flex()
8980 .bg(cx.theme().colors().editor_background)
8981 .border(BORDER_WIDTH)
8982 .shadow_xs()
8983 .border_color(cx.theme().colors().border)
8984 .rounded_l_lg()
8985 .when(line_count > 1, |el| el.rounded_br_lg())
8986 .pr_1()
8987 .child(styled_text),
8988 )
8989 .child(
8990 h_flex()
8991 .h(line_height + BORDER_WIDTH * 2.)
8992 .px_1p5()
8993 .gap_1()
8994 // Workaround: For some reason, there's a gap if we don't do this
8995 .ml(-BORDER_WIDTH)
8996 .shadow(vec![gpui::BoxShadow {
8997 color: gpui::black().opacity(0.05),
8998 offset: point(px(1.), px(1.)),
8999 blur_radius: px(2.),
9000 spread_radius: px(0.),
9001 }])
9002 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9003 .border(BORDER_WIDTH)
9004 .border_color(cx.theme().colors().border)
9005 .rounded_r_lg()
9006 .id("edit_prediction_diff_popover_keybind")
9007 .when(!has_keybind, |el| {
9008 let status_colors = cx.theme().status();
9009
9010 el.bg(status_colors.error_background)
9011 .border_color(status_colors.error.opacity(0.6))
9012 .child(Icon::new(IconName::Info).color(Color::Error))
9013 .cursor_default()
9014 .hoverable_tooltip(move |_window, cx| {
9015 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9016 })
9017 })
9018 .children(keybind),
9019 )
9020 .into_any();
9021
9022 let longest_row =
9023 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9024 let longest_line_width = if visible_row_range.contains(&longest_row) {
9025 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9026 } else {
9027 layout_line(
9028 longest_row,
9029 editor_snapshot,
9030 style,
9031 editor_width,
9032 |_| false,
9033 window,
9034 cx,
9035 )
9036 .width
9037 };
9038
9039 let viewport_bounds =
9040 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9041 right: -right_margin,
9042 ..Default::default()
9043 });
9044
9045 let x_after_longest =
9046 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9047 - scroll_pixel_position.x;
9048
9049 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9050
9051 // Fully visible if it can be displayed within the window (allow overlapping other
9052 // panes). However, this is only allowed if the popover starts within text_bounds.
9053 let can_position_to_the_right = x_after_longest < text_bounds.right()
9054 && x_after_longest + element_bounds.width < viewport_bounds.right();
9055
9056 let mut origin = if can_position_to_the_right {
9057 point(
9058 x_after_longest,
9059 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9060 - scroll_pixel_position.y,
9061 )
9062 } else {
9063 let cursor_row = newest_selection_head.map(|head| head.row());
9064 let above_edit = edit_start
9065 .row()
9066 .0
9067 .checked_sub(line_count as u32)
9068 .map(DisplayRow);
9069 let below_edit = Some(edit_end.row() + 1);
9070 let above_cursor =
9071 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9072 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9073
9074 // Place the edit popover adjacent to the edit if there is a location
9075 // available that is onscreen and does not obscure the cursor. Otherwise,
9076 // place it adjacent to the cursor.
9077 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9078 .into_iter()
9079 .flatten()
9080 .find(|&start_row| {
9081 let end_row = start_row + line_count as u32;
9082 visible_row_range.contains(&start_row)
9083 && visible_row_range.contains(&end_row)
9084 && cursor_row
9085 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9086 })?;
9087
9088 content_origin
9089 + point(
9090 -scroll_pixel_position.x,
9091 row_target.as_f32() * line_height - scroll_pixel_position.y,
9092 )
9093 };
9094
9095 origin.x -= BORDER_WIDTH;
9096
9097 window.defer_draw(element, origin, 1);
9098
9099 // Do not return an element, since it will already be drawn due to defer_draw.
9100 None
9101 }
9102
9103 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9104 px(30.)
9105 }
9106
9107 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9108 if self.read_only(cx) {
9109 cx.theme().players().read_only()
9110 } else {
9111 self.style.as_ref().unwrap().local_player
9112 }
9113 }
9114
9115 fn render_edit_prediction_accept_keybind(
9116 &self,
9117 window: &mut Window,
9118 cx: &App,
9119 ) -> Option<AnyElement> {
9120 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9121 let accept_keystroke = accept_binding.keystroke()?;
9122
9123 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9124
9125 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9126 Color::Accent
9127 } else {
9128 Color::Muted
9129 };
9130
9131 h_flex()
9132 .px_0p5()
9133 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9134 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9135 .text_size(TextSize::XSmall.rems(cx))
9136 .child(h_flex().children(ui::render_modifiers(
9137 accept_keystroke.modifiers(),
9138 PlatformStyle::platform(),
9139 Some(modifiers_color),
9140 Some(IconSize::XSmall.rems().into()),
9141 true,
9142 )))
9143 .when(is_platform_style_mac, |parent| {
9144 parent.child(accept_keystroke.key().to_string())
9145 })
9146 .when(!is_platform_style_mac, |parent| {
9147 parent.child(
9148 Key::new(
9149 util::capitalize(accept_keystroke.key()),
9150 Some(Color::Default),
9151 )
9152 .size(Some(IconSize::XSmall.rems().into())),
9153 )
9154 })
9155 .into_any()
9156 .into()
9157 }
9158
9159 fn render_edit_prediction_line_popover(
9160 &self,
9161 label: impl Into<SharedString>,
9162 icon: Option<IconName>,
9163 window: &mut Window,
9164 cx: &App,
9165 ) -> Option<Stateful<Div>> {
9166 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9167
9168 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9169 let has_keybind = keybind.is_some();
9170
9171 let result = h_flex()
9172 .id("ep-line-popover")
9173 .py_0p5()
9174 .pl_1()
9175 .pr(padding_right)
9176 .gap_1()
9177 .rounded_md()
9178 .border_1()
9179 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9180 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9181 .shadow_xs()
9182 .when(!has_keybind, |el| {
9183 let status_colors = cx.theme().status();
9184
9185 el.bg(status_colors.error_background)
9186 .border_color(status_colors.error.opacity(0.6))
9187 .pl_2()
9188 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9189 .cursor_default()
9190 .hoverable_tooltip(move |_window, cx| {
9191 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9192 })
9193 })
9194 .children(keybind)
9195 .child(
9196 Label::new(label)
9197 .size(LabelSize::Small)
9198 .when(!has_keybind, |el| {
9199 el.color(cx.theme().status().error.into()).strikethrough()
9200 }),
9201 )
9202 .when(!has_keybind, |el| {
9203 el.child(
9204 h_flex().ml_1().child(
9205 Icon::new(IconName::Info)
9206 .size(IconSize::Small)
9207 .color(cx.theme().status().error.into()),
9208 ),
9209 )
9210 })
9211 .when_some(icon, |element, icon| {
9212 element.child(
9213 div()
9214 .mt(px(1.5))
9215 .child(Icon::new(icon).size(IconSize::Small)),
9216 )
9217 });
9218
9219 Some(result)
9220 }
9221
9222 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9223 let accent_color = cx.theme().colors().text_accent;
9224 let editor_bg_color = cx.theme().colors().editor_background;
9225 editor_bg_color.blend(accent_color.opacity(0.1))
9226 }
9227
9228 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9229 let accent_color = cx.theme().colors().text_accent;
9230 let editor_bg_color = cx.theme().colors().editor_background;
9231 editor_bg_color.blend(accent_color.opacity(0.6))
9232 }
9233 fn get_prediction_provider_icon_name(
9234 provider: &Option<RegisteredEditPredictionProvider>,
9235 ) -> IconName {
9236 match provider {
9237 Some(provider) => match provider.provider.name() {
9238 "copilot" => IconName::Copilot,
9239 "supermaven" => IconName::Supermaven,
9240 _ => IconName::ZedPredict,
9241 },
9242 None => IconName::ZedPredict,
9243 }
9244 }
9245
9246 fn render_edit_prediction_cursor_popover(
9247 &self,
9248 min_width: Pixels,
9249 max_width: Pixels,
9250 cursor_point: Point,
9251 style: &EditorStyle,
9252 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9253 _window: &Window,
9254 cx: &mut Context<Editor>,
9255 ) -> Option<AnyElement> {
9256 let provider = self.edit_prediction_provider.as_ref()?;
9257 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9258
9259 let is_refreshing = provider.provider.is_refreshing(cx);
9260
9261 fn pending_completion_container(icon: IconName) -> Div {
9262 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9263 }
9264
9265 let completion = match &self.active_edit_prediction {
9266 Some(prediction) => {
9267 if !self.has_visible_completions_menu() {
9268 const RADIUS: Pixels = px(6.);
9269 const BORDER_WIDTH: Pixels = px(1.);
9270
9271 return Some(
9272 h_flex()
9273 .elevation_2(cx)
9274 .border(BORDER_WIDTH)
9275 .border_color(cx.theme().colors().border)
9276 .when(accept_keystroke.is_none(), |el| {
9277 el.border_color(cx.theme().status().error)
9278 })
9279 .rounded(RADIUS)
9280 .rounded_tl(px(0.))
9281 .overflow_hidden()
9282 .child(div().px_1p5().child(match &prediction.completion {
9283 EditPrediction::Move { target, snapshot } => {
9284 use text::ToPoint as _;
9285 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9286 {
9287 Icon::new(IconName::ZedPredictDown)
9288 } else {
9289 Icon::new(IconName::ZedPredictUp)
9290 }
9291 }
9292 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9293 }))
9294 .child(
9295 h_flex()
9296 .gap_1()
9297 .py_1()
9298 .px_2()
9299 .rounded_r(RADIUS - BORDER_WIDTH)
9300 .border_l_1()
9301 .border_color(cx.theme().colors().border)
9302 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9303 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9304 el.child(
9305 Label::new("Hold")
9306 .size(LabelSize::Small)
9307 .when(accept_keystroke.is_none(), |el| {
9308 el.strikethrough()
9309 })
9310 .line_height_style(LineHeightStyle::UiLabel),
9311 )
9312 })
9313 .id("edit_prediction_cursor_popover_keybind")
9314 .when(accept_keystroke.is_none(), |el| {
9315 let status_colors = cx.theme().status();
9316
9317 el.bg(status_colors.error_background)
9318 .border_color(status_colors.error.opacity(0.6))
9319 .child(Icon::new(IconName::Info).color(Color::Error))
9320 .cursor_default()
9321 .hoverable_tooltip(move |_window, cx| {
9322 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9323 .into()
9324 })
9325 })
9326 .when_some(
9327 accept_keystroke.as_ref(),
9328 |el, accept_keystroke| {
9329 el.child(h_flex().children(ui::render_modifiers(
9330 accept_keystroke.modifiers(),
9331 PlatformStyle::platform(),
9332 Some(Color::Default),
9333 Some(IconSize::XSmall.rems().into()),
9334 false,
9335 )))
9336 },
9337 ),
9338 )
9339 .into_any(),
9340 );
9341 }
9342
9343 self.render_edit_prediction_cursor_popover_preview(
9344 prediction,
9345 cursor_point,
9346 style,
9347 cx,
9348 )?
9349 }
9350
9351 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9352 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9353 stale_completion,
9354 cursor_point,
9355 style,
9356 cx,
9357 )?,
9358
9359 None => pending_completion_container(provider_icon)
9360 .child(Label::new("...").size(LabelSize::Small)),
9361 },
9362
9363 None => pending_completion_container(provider_icon)
9364 .child(Label::new("...").size(LabelSize::Small)),
9365 };
9366
9367 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9368 completion
9369 .with_animation(
9370 "loading-completion",
9371 Animation::new(Duration::from_secs(2))
9372 .repeat()
9373 .with_easing(pulsating_between(0.4, 0.8)),
9374 |label, delta| label.opacity(delta),
9375 )
9376 .into_any_element()
9377 } else {
9378 completion.into_any_element()
9379 };
9380
9381 let has_completion = self.active_edit_prediction.is_some();
9382
9383 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9384 Some(
9385 h_flex()
9386 .min_w(min_width)
9387 .max_w(max_width)
9388 .flex_1()
9389 .elevation_2(cx)
9390 .border_color(cx.theme().colors().border)
9391 .child(
9392 div()
9393 .flex_1()
9394 .py_1()
9395 .px_2()
9396 .overflow_hidden()
9397 .child(completion),
9398 )
9399 .when_some(accept_keystroke, |el, accept_keystroke| {
9400 if !accept_keystroke.modifiers().modified() {
9401 return el;
9402 }
9403
9404 el.child(
9405 h_flex()
9406 .h_full()
9407 .border_l_1()
9408 .rounded_r_lg()
9409 .border_color(cx.theme().colors().border)
9410 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9411 .gap_1()
9412 .py_1()
9413 .px_2()
9414 .child(
9415 h_flex()
9416 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9417 .when(is_platform_style_mac, |parent| parent.gap_1())
9418 .child(h_flex().children(ui::render_modifiers(
9419 accept_keystroke.modifiers(),
9420 PlatformStyle::platform(),
9421 Some(if !has_completion {
9422 Color::Muted
9423 } else {
9424 Color::Default
9425 }),
9426 None,
9427 false,
9428 ))),
9429 )
9430 .child(Label::new("Preview").into_any_element())
9431 .opacity(if has_completion { 1.0 } else { 0.4 }),
9432 )
9433 })
9434 .into_any(),
9435 )
9436 }
9437
9438 fn render_edit_prediction_cursor_popover_preview(
9439 &self,
9440 completion: &EditPredictionState,
9441 cursor_point: Point,
9442 style: &EditorStyle,
9443 cx: &mut Context<Editor>,
9444 ) -> Option<Div> {
9445 use text::ToPoint as _;
9446
9447 fn render_relative_row_jump(
9448 prefix: impl Into<String>,
9449 current_row: u32,
9450 target_row: u32,
9451 ) -> Div {
9452 let (row_diff, arrow) = if target_row < current_row {
9453 (current_row - target_row, IconName::ArrowUp)
9454 } else {
9455 (target_row - current_row, IconName::ArrowDown)
9456 };
9457
9458 h_flex()
9459 .child(
9460 Label::new(format!("{}{}", prefix.into(), row_diff))
9461 .color(Color::Muted)
9462 .size(LabelSize::Small),
9463 )
9464 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9465 }
9466
9467 let supports_jump = self
9468 .edit_prediction_provider
9469 .as_ref()
9470 .map(|provider| provider.provider.supports_jump_to_edit())
9471 .unwrap_or(true);
9472
9473 match &completion.completion {
9474 EditPrediction::Move {
9475 target, snapshot, ..
9476 } => {
9477 if !supports_jump {
9478 return None;
9479 }
9480
9481 Some(
9482 h_flex()
9483 .px_2()
9484 .gap_2()
9485 .flex_1()
9486 .child(
9487 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9488 Icon::new(IconName::ZedPredictDown)
9489 } else {
9490 Icon::new(IconName::ZedPredictUp)
9491 },
9492 )
9493 .child(Label::new("Jump to Edit")),
9494 )
9495 }
9496
9497 EditPrediction::Edit {
9498 edits,
9499 edit_preview,
9500 snapshot,
9501 display_mode: _,
9502 } => {
9503 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9504
9505 let (highlighted_edits, has_more_lines) =
9506 if let Some(edit_preview) = edit_preview.as_ref() {
9507 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9508 .first_line_preview()
9509 } else {
9510 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9511 };
9512
9513 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9514 .with_default_highlights(&style.text, highlighted_edits.highlights);
9515
9516 let preview = h_flex()
9517 .gap_1()
9518 .min_w_16()
9519 .child(styled_text)
9520 .when(has_more_lines, |parent| parent.child("…"));
9521
9522 let left = if supports_jump && first_edit_row != cursor_point.row {
9523 render_relative_row_jump("", cursor_point.row, first_edit_row)
9524 .into_any_element()
9525 } else {
9526 let icon_name =
9527 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9528 Icon::new(icon_name).into_any_element()
9529 };
9530
9531 Some(
9532 h_flex()
9533 .h_full()
9534 .flex_1()
9535 .gap_2()
9536 .pr_1()
9537 .overflow_x_hidden()
9538 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9539 .child(left)
9540 .child(preview),
9541 )
9542 }
9543 }
9544 }
9545
9546 pub fn render_context_menu(
9547 &self,
9548 style: &EditorStyle,
9549 max_height_in_lines: u32,
9550 window: &mut Window,
9551 cx: &mut Context<Editor>,
9552 ) -> Option<AnyElement> {
9553 let menu = self.context_menu.borrow();
9554 let menu = menu.as_ref()?;
9555 if !menu.visible() {
9556 return None;
9557 };
9558 Some(menu.render(style, max_height_in_lines, window, cx))
9559 }
9560
9561 fn render_context_menu_aside(
9562 &mut self,
9563 max_size: Size<Pixels>,
9564 window: &mut Window,
9565 cx: &mut Context<Editor>,
9566 ) -> Option<AnyElement> {
9567 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9568 if menu.visible() {
9569 menu.render_aside(max_size, window, cx)
9570 } else {
9571 None
9572 }
9573 })
9574 }
9575
9576 fn hide_context_menu(
9577 &mut self,
9578 window: &mut Window,
9579 cx: &mut Context<Self>,
9580 ) -> Option<CodeContextMenu> {
9581 cx.notify();
9582 self.completion_tasks.clear();
9583 let context_menu = self.context_menu.borrow_mut().take();
9584 self.stale_edit_prediction_in_menu.take();
9585 self.update_visible_edit_prediction(window, cx);
9586 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9587 && let Some(completion_provider) = &self.completion_provider
9588 {
9589 completion_provider.selection_changed(None, window, cx);
9590 }
9591 context_menu
9592 }
9593
9594 fn show_snippet_choices(
9595 &mut self,
9596 choices: &Vec<String>,
9597 selection: Range<Anchor>,
9598 cx: &mut Context<Self>,
9599 ) {
9600 let Some((_, buffer, _)) = self
9601 .buffer()
9602 .read(cx)
9603 .excerpt_containing(selection.start, cx)
9604 else {
9605 return;
9606 };
9607 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9608 else {
9609 return;
9610 };
9611 if buffer != end_buffer {
9612 log::error!("expected anchor range to have matching buffer IDs");
9613 return;
9614 }
9615
9616 let id = post_inc(&mut self.next_completion_id);
9617 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9618 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9619 CompletionsMenu::new_snippet_choices(
9620 id,
9621 true,
9622 choices,
9623 selection,
9624 buffer,
9625 snippet_sort_order,
9626 ),
9627 ));
9628 }
9629
9630 pub fn insert_snippet(
9631 &mut self,
9632 insertion_ranges: &[Range<usize>],
9633 snippet: Snippet,
9634 window: &mut Window,
9635 cx: &mut Context<Self>,
9636 ) -> Result<()> {
9637 struct Tabstop<T> {
9638 is_end_tabstop: bool,
9639 ranges: Vec<Range<T>>,
9640 choices: Option<Vec<String>>,
9641 }
9642
9643 let tabstops = self.buffer.update(cx, |buffer, cx| {
9644 let snippet_text: Arc<str> = snippet.text.clone().into();
9645 let edits = insertion_ranges
9646 .iter()
9647 .cloned()
9648 .map(|range| (range, snippet_text.clone()));
9649 let autoindent_mode = AutoindentMode::Block {
9650 original_indent_columns: Vec::new(),
9651 };
9652 buffer.edit(edits, Some(autoindent_mode), cx);
9653
9654 let snapshot = &*buffer.read(cx);
9655 let snippet = &snippet;
9656 snippet
9657 .tabstops
9658 .iter()
9659 .map(|tabstop| {
9660 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9661 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9662 });
9663 let mut tabstop_ranges = tabstop
9664 .ranges
9665 .iter()
9666 .flat_map(|tabstop_range| {
9667 let mut delta = 0_isize;
9668 insertion_ranges.iter().map(move |insertion_range| {
9669 let insertion_start = insertion_range.start as isize + delta;
9670 delta +=
9671 snippet.text.len() as isize - insertion_range.len() as isize;
9672
9673 let start = ((insertion_start + tabstop_range.start) as usize)
9674 .min(snapshot.len());
9675 let end = ((insertion_start + tabstop_range.end) as usize)
9676 .min(snapshot.len());
9677 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9678 })
9679 })
9680 .collect::<Vec<_>>();
9681 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9682
9683 Tabstop {
9684 is_end_tabstop,
9685 ranges: tabstop_ranges,
9686 choices: tabstop.choices.clone(),
9687 }
9688 })
9689 .collect::<Vec<_>>()
9690 });
9691 if let Some(tabstop) = tabstops.first() {
9692 self.change_selections(Default::default(), window, cx, |s| {
9693 // Reverse order so that the first range is the newest created selection.
9694 // Completions will use it and autoscroll will prioritize it.
9695 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9696 });
9697
9698 if let Some(choices) = &tabstop.choices
9699 && let Some(selection) = tabstop.ranges.first()
9700 {
9701 self.show_snippet_choices(choices, selection.clone(), cx)
9702 }
9703
9704 // If we're already at the last tabstop and it's at the end of the snippet,
9705 // we're done, we don't need to keep the state around.
9706 if !tabstop.is_end_tabstop {
9707 let choices = tabstops
9708 .iter()
9709 .map(|tabstop| tabstop.choices.clone())
9710 .collect();
9711
9712 let ranges = tabstops
9713 .into_iter()
9714 .map(|tabstop| tabstop.ranges)
9715 .collect::<Vec<_>>();
9716
9717 self.snippet_stack.push(SnippetState {
9718 active_index: 0,
9719 ranges,
9720 choices,
9721 });
9722 }
9723
9724 // Check whether the just-entered snippet ends with an auto-closable bracket.
9725 if self.autoclose_regions.is_empty() {
9726 let snapshot = self.buffer.read(cx).snapshot(cx);
9727 let mut all_selections = self.selections.all::<Point>(cx);
9728 for selection in &mut all_selections {
9729 let selection_head = selection.head();
9730 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9731 continue;
9732 };
9733
9734 let mut bracket_pair = None;
9735 let max_lookup_length = scope
9736 .brackets()
9737 .map(|(pair, _)| {
9738 pair.start
9739 .as_str()
9740 .chars()
9741 .count()
9742 .max(pair.end.as_str().chars().count())
9743 })
9744 .max();
9745 if let Some(max_lookup_length) = max_lookup_length {
9746 let next_text = snapshot
9747 .chars_at(selection_head)
9748 .take(max_lookup_length)
9749 .collect::<String>();
9750 let prev_text = snapshot
9751 .reversed_chars_at(selection_head)
9752 .take(max_lookup_length)
9753 .collect::<String>();
9754
9755 for (pair, enabled) in scope.brackets() {
9756 if enabled
9757 && pair.close
9758 && prev_text.starts_with(pair.start.as_str())
9759 && next_text.starts_with(pair.end.as_str())
9760 {
9761 bracket_pair = Some(pair.clone());
9762 break;
9763 }
9764 }
9765 }
9766
9767 if let Some(pair) = bracket_pair {
9768 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9769 let autoclose_enabled =
9770 self.use_autoclose && snapshot_settings.use_autoclose;
9771 if autoclose_enabled {
9772 let start = snapshot.anchor_after(selection_head);
9773 let end = snapshot.anchor_after(selection_head);
9774 self.autoclose_regions.push(AutocloseRegion {
9775 selection_id: selection.id,
9776 range: start..end,
9777 pair,
9778 });
9779 }
9780 }
9781 }
9782 }
9783 }
9784 Ok(())
9785 }
9786
9787 pub fn move_to_next_snippet_tabstop(
9788 &mut self,
9789 window: &mut Window,
9790 cx: &mut Context<Self>,
9791 ) -> bool {
9792 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9793 }
9794
9795 pub fn move_to_prev_snippet_tabstop(
9796 &mut self,
9797 window: &mut Window,
9798 cx: &mut Context<Self>,
9799 ) -> bool {
9800 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9801 }
9802
9803 pub fn move_to_snippet_tabstop(
9804 &mut self,
9805 bias: Bias,
9806 window: &mut Window,
9807 cx: &mut Context<Self>,
9808 ) -> bool {
9809 if let Some(mut snippet) = self.snippet_stack.pop() {
9810 match bias {
9811 Bias::Left => {
9812 if snippet.active_index > 0 {
9813 snippet.active_index -= 1;
9814 } else {
9815 self.snippet_stack.push(snippet);
9816 return false;
9817 }
9818 }
9819 Bias::Right => {
9820 if snippet.active_index + 1 < snippet.ranges.len() {
9821 snippet.active_index += 1;
9822 } else {
9823 self.snippet_stack.push(snippet);
9824 return false;
9825 }
9826 }
9827 }
9828 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9829 self.change_selections(Default::default(), window, cx, |s| {
9830 // Reverse order so that the first range is the newest created selection.
9831 // Completions will use it and autoscroll will prioritize it.
9832 s.select_ranges(current_ranges.iter().rev().cloned())
9833 });
9834
9835 if let Some(choices) = &snippet.choices[snippet.active_index]
9836 && let Some(selection) = current_ranges.first()
9837 {
9838 self.show_snippet_choices(choices, selection.clone(), cx);
9839 }
9840
9841 // If snippet state is not at the last tabstop, push it back on the stack
9842 if snippet.active_index + 1 < snippet.ranges.len() {
9843 self.snippet_stack.push(snippet);
9844 }
9845 return true;
9846 }
9847 }
9848
9849 false
9850 }
9851
9852 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9853 self.transact(window, cx, |this, window, cx| {
9854 this.select_all(&SelectAll, window, cx);
9855 this.insert("", window, cx);
9856 });
9857 }
9858
9859 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9860 if self.read_only(cx) {
9861 return;
9862 }
9863 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9864 self.transact(window, cx, |this, window, cx| {
9865 this.select_autoclose_pair(window, cx);
9866 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9867 if !this.linked_edit_ranges.is_empty() {
9868 let selections = this.selections.all::<MultiBufferPoint>(cx);
9869 let snapshot = this.buffer.read(cx).snapshot(cx);
9870
9871 for selection in selections.iter() {
9872 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9873 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9874 if selection_start.buffer_id != selection_end.buffer_id {
9875 continue;
9876 }
9877 if let Some(ranges) =
9878 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9879 {
9880 for (buffer, entries) in ranges {
9881 linked_ranges.entry(buffer).or_default().extend(entries);
9882 }
9883 }
9884 }
9885 }
9886
9887 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9888 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9889 for selection in &mut selections {
9890 if selection.is_empty() {
9891 let old_head = selection.head();
9892 let mut new_head =
9893 movement::left(&display_map, old_head.to_display_point(&display_map))
9894 .to_point(&display_map);
9895 if let Some((buffer, line_buffer_range)) = display_map
9896 .buffer_snapshot
9897 .buffer_line_for_row(MultiBufferRow(old_head.row))
9898 {
9899 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9900 let indent_len = match indent_size.kind {
9901 IndentKind::Space => {
9902 buffer.settings_at(line_buffer_range.start, cx).tab_size
9903 }
9904 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9905 };
9906 if old_head.column <= indent_size.len && old_head.column > 0 {
9907 let indent_len = indent_len.get();
9908 new_head = cmp::min(
9909 new_head,
9910 MultiBufferPoint::new(
9911 old_head.row,
9912 ((old_head.column - 1) / indent_len) * indent_len,
9913 ),
9914 );
9915 }
9916 }
9917
9918 selection.set_head(new_head, SelectionGoal::None);
9919 }
9920 }
9921
9922 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9923 this.insert("", window, cx);
9924 let empty_str: Arc<str> = Arc::from("");
9925 for (buffer, edits) in linked_ranges {
9926 let snapshot = buffer.read(cx).snapshot();
9927 use text::ToPoint as TP;
9928
9929 let edits = edits
9930 .into_iter()
9931 .map(|range| {
9932 let end_point = TP::to_point(&range.end, &snapshot);
9933 let mut start_point = TP::to_point(&range.start, &snapshot);
9934
9935 if end_point == start_point {
9936 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9937 .saturating_sub(1);
9938 start_point =
9939 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9940 };
9941
9942 (start_point..end_point, empty_str.clone())
9943 })
9944 .sorted_by_key(|(range, _)| range.start)
9945 .collect::<Vec<_>>();
9946 buffer.update(cx, |this, cx| {
9947 this.edit(edits, None, cx);
9948 })
9949 }
9950 this.refresh_edit_prediction(true, false, window, cx);
9951 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9952 });
9953 }
9954
9955 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9956 if self.read_only(cx) {
9957 return;
9958 }
9959 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9960 self.transact(window, cx, |this, window, cx| {
9961 this.change_selections(Default::default(), window, cx, |s| {
9962 s.move_with(|map, selection| {
9963 if selection.is_empty() {
9964 let cursor = movement::right(map, selection.head());
9965 selection.end = cursor;
9966 selection.reversed = true;
9967 selection.goal = SelectionGoal::None;
9968 }
9969 })
9970 });
9971 this.insert("", window, cx);
9972 this.refresh_edit_prediction(true, false, window, cx);
9973 });
9974 }
9975
9976 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9977 if self.mode.is_single_line() {
9978 cx.propagate();
9979 return;
9980 }
9981
9982 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9983 if self.move_to_prev_snippet_tabstop(window, cx) {
9984 return;
9985 }
9986 self.outdent(&Outdent, window, cx);
9987 }
9988
9989 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9990 if self.mode.is_single_line() {
9991 cx.propagate();
9992 return;
9993 }
9994
9995 if self.move_to_next_snippet_tabstop(window, cx) {
9996 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9997 return;
9998 }
9999 if self.read_only(cx) {
10000 return;
10001 }
10002 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10003 let mut selections = self.selections.all_adjusted(cx);
10004 let buffer = self.buffer.read(cx);
10005 let snapshot = buffer.snapshot(cx);
10006 let rows_iter = selections.iter().map(|s| s.head().row);
10007 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10008
10009 let has_some_cursor_in_whitespace = selections
10010 .iter()
10011 .filter(|selection| selection.is_empty())
10012 .any(|selection| {
10013 let cursor = selection.head();
10014 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10015 cursor.column < current_indent.len
10016 });
10017
10018 let mut edits = Vec::new();
10019 let mut prev_edited_row = 0;
10020 let mut row_delta = 0;
10021 for selection in &mut selections {
10022 if selection.start.row != prev_edited_row {
10023 row_delta = 0;
10024 }
10025 prev_edited_row = selection.end.row;
10026
10027 // If the selection is non-empty, then increase the indentation of the selected lines.
10028 if !selection.is_empty() {
10029 row_delta =
10030 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10031 continue;
10032 }
10033
10034 let cursor = selection.head();
10035 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10036 if let Some(suggested_indent) =
10037 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10038 {
10039 // Don't do anything if already at suggested indent
10040 // and there is any other cursor which is not
10041 if has_some_cursor_in_whitespace
10042 && cursor.column == current_indent.len
10043 && current_indent.len == suggested_indent.len
10044 {
10045 continue;
10046 }
10047
10048 // Adjust line and move cursor to suggested indent
10049 // if cursor is not at suggested indent
10050 if cursor.column < suggested_indent.len
10051 && cursor.column <= current_indent.len
10052 && current_indent.len <= suggested_indent.len
10053 {
10054 selection.start = Point::new(cursor.row, suggested_indent.len);
10055 selection.end = selection.start;
10056 if row_delta == 0 {
10057 edits.extend(Buffer::edit_for_indent_size_adjustment(
10058 cursor.row,
10059 current_indent,
10060 suggested_indent,
10061 ));
10062 row_delta = suggested_indent.len - current_indent.len;
10063 }
10064 continue;
10065 }
10066
10067 // If current indent is more than suggested indent
10068 // only move cursor to current indent and skip indent
10069 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10070 selection.start = Point::new(cursor.row, current_indent.len);
10071 selection.end = selection.start;
10072 continue;
10073 }
10074 }
10075
10076 // Otherwise, insert a hard or soft tab.
10077 let settings = buffer.language_settings_at(cursor, cx);
10078 let tab_size = if settings.hard_tabs {
10079 IndentSize::tab()
10080 } else {
10081 let tab_size = settings.tab_size.get();
10082 let indent_remainder = snapshot
10083 .text_for_range(Point::new(cursor.row, 0)..cursor)
10084 .flat_map(str::chars)
10085 .fold(row_delta % tab_size, |counter: u32, c| {
10086 if c == '\t' {
10087 0
10088 } else {
10089 (counter + 1) % tab_size
10090 }
10091 });
10092
10093 let chars_to_next_tab_stop = tab_size - indent_remainder;
10094 IndentSize::spaces(chars_to_next_tab_stop)
10095 };
10096 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10097 selection.end = selection.start;
10098 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10099 row_delta += tab_size.len;
10100 }
10101
10102 self.transact(window, cx, |this, window, cx| {
10103 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10104 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10105 this.refresh_edit_prediction(true, false, window, cx);
10106 });
10107 }
10108
10109 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10110 if self.read_only(cx) {
10111 return;
10112 }
10113 if self.mode.is_single_line() {
10114 cx.propagate();
10115 return;
10116 }
10117
10118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10119 let mut selections = self.selections.all::<Point>(cx);
10120 let mut prev_edited_row = 0;
10121 let mut row_delta = 0;
10122 let mut edits = Vec::new();
10123 let buffer = self.buffer.read(cx);
10124 let snapshot = buffer.snapshot(cx);
10125 for selection in &mut selections {
10126 if selection.start.row != prev_edited_row {
10127 row_delta = 0;
10128 }
10129 prev_edited_row = selection.end.row;
10130
10131 row_delta =
10132 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10133 }
10134
10135 self.transact(window, cx, |this, window, cx| {
10136 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10137 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10138 });
10139 }
10140
10141 fn indent_selection(
10142 buffer: &MultiBuffer,
10143 snapshot: &MultiBufferSnapshot,
10144 selection: &mut Selection<Point>,
10145 edits: &mut Vec<(Range<Point>, String)>,
10146 delta_for_start_row: u32,
10147 cx: &App,
10148 ) -> u32 {
10149 let settings = buffer.language_settings_at(selection.start, cx);
10150 let tab_size = settings.tab_size.get();
10151 let indent_kind = if settings.hard_tabs {
10152 IndentKind::Tab
10153 } else {
10154 IndentKind::Space
10155 };
10156 let mut start_row = selection.start.row;
10157 let mut end_row = selection.end.row + 1;
10158
10159 // If a selection ends at the beginning of a line, don't indent
10160 // that last line.
10161 if selection.end.column == 0 && selection.end.row > selection.start.row {
10162 end_row -= 1;
10163 }
10164
10165 // Avoid re-indenting a row that has already been indented by a
10166 // previous selection, but still update this selection's column
10167 // to reflect that indentation.
10168 if delta_for_start_row > 0 {
10169 start_row += 1;
10170 selection.start.column += delta_for_start_row;
10171 if selection.end.row == selection.start.row {
10172 selection.end.column += delta_for_start_row;
10173 }
10174 }
10175
10176 let mut delta_for_end_row = 0;
10177 let has_multiple_rows = start_row + 1 != end_row;
10178 for row in start_row..end_row {
10179 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10180 let indent_delta = match (current_indent.kind, indent_kind) {
10181 (IndentKind::Space, IndentKind::Space) => {
10182 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10183 IndentSize::spaces(columns_to_next_tab_stop)
10184 }
10185 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10186 (_, IndentKind::Tab) => IndentSize::tab(),
10187 };
10188
10189 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10190 0
10191 } else {
10192 selection.start.column
10193 };
10194 let row_start = Point::new(row, start);
10195 edits.push((
10196 row_start..row_start,
10197 indent_delta.chars().collect::<String>(),
10198 ));
10199
10200 // Update this selection's endpoints to reflect the indentation.
10201 if row == selection.start.row {
10202 selection.start.column += indent_delta.len;
10203 }
10204 if row == selection.end.row {
10205 selection.end.column += indent_delta.len;
10206 delta_for_end_row = indent_delta.len;
10207 }
10208 }
10209
10210 if selection.start.row == selection.end.row {
10211 delta_for_start_row + delta_for_end_row
10212 } else {
10213 delta_for_end_row
10214 }
10215 }
10216
10217 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10218 if self.read_only(cx) {
10219 return;
10220 }
10221 if self.mode.is_single_line() {
10222 cx.propagate();
10223 return;
10224 }
10225
10226 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10228 let selections = self.selections.all::<Point>(cx);
10229 let mut deletion_ranges = Vec::new();
10230 let mut last_outdent = None;
10231 {
10232 let buffer = self.buffer.read(cx);
10233 let snapshot = buffer.snapshot(cx);
10234 for selection in &selections {
10235 let settings = buffer.language_settings_at(selection.start, cx);
10236 let tab_size = settings.tab_size.get();
10237 let mut rows = selection.spanned_rows(false, &display_map);
10238
10239 // Avoid re-outdenting a row that has already been outdented by a
10240 // previous selection.
10241 if let Some(last_row) = last_outdent
10242 && last_row == rows.start
10243 {
10244 rows.start = rows.start.next_row();
10245 }
10246 let has_multiple_rows = rows.len() > 1;
10247 for row in rows.iter_rows() {
10248 let indent_size = snapshot.indent_size_for_line(row);
10249 if indent_size.len > 0 {
10250 let deletion_len = match indent_size.kind {
10251 IndentKind::Space => {
10252 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10253 if columns_to_prev_tab_stop == 0 {
10254 tab_size
10255 } else {
10256 columns_to_prev_tab_stop
10257 }
10258 }
10259 IndentKind::Tab => 1,
10260 };
10261 let start = if has_multiple_rows
10262 || deletion_len > selection.start.column
10263 || indent_size.len < selection.start.column
10264 {
10265 0
10266 } else {
10267 selection.start.column - deletion_len
10268 };
10269 deletion_ranges.push(
10270 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10271 );
10272 last_outdent = Some(row);
10273 }
10274 }
10275 }
10276 }
10277
10278 self.transact(window, cx, |this, window, cx| {
10279 this.buffer.update(cx, |buffer, cx| {
10280 let empty_str: Arc<str> = Arc::default();
10281 buffer.edit(
10282 deletion_ranges
10283 .into_iter()
10284 .map(|range| (range, empty_str.clone())),
10285 None,
10286 cx,
10287 );
10288 });
10289 let selections = this.selections.all::<usize>(cx);
10290 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10291 });
10292 }
10293
10294 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10295 if self.read_only(cx) {
10296 return;
10297 }
10298 if self.mode.is_single_line() {
10299 cx.propagate();
10300 return;
10301 }
10302
10303 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10304 let selections = self
10305 .selections
10306 .all::<usize>(cx)
10307 .into_iter()
10308 .map(|s| s.range());
10309
10310 self.transact(window, cx, |this, window, cx| {
10311 this.buffer.update(cx, |buffer, cx| {
10312 buffer.autoindent_ranges(selections, cx);
10313 });
10314 let selections = this.selections.all::<usize>(cx);
10315 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10316 });
10317 }
10318
10319 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10320 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10321 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10322 let selections = self.selections.all::<Point>(cx);
10323
10324 let mut new_cursors = Vec::new();
10325 let mut edit_ranges = Vec::new();
10326 let mut selections = selections.iter().peekable();
10327 while let Some(selection) = selections.next() {
10328 let mut rows = selection.spanned_rows(false, &display_map);
10329 let goal_display_column = selection.head().to_display_point(&display_map).column();
10330
10331 // Accumulate contiguous regions of rows that we want to delete.
10332 while let Some(next_selection) = selections.peek() {
10333 let next_rows = next_selection.spanned_rows(false, &display_map);
10334 if next_rows.start <= rows.end {
10335 rows.end = next_rows.end;
10336 selections.next().unwrap();
10337 } else {
10338 break;
10339 }
10340 }
10341
10342 let buffer = &display_map.buffer_snapshot;
10343 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10344 let edit_end;
10345 let cursor_buffer_row;
10346 if buffer.max_point().row >= rows.end.0 {
10347 // If there's a line after the range, delete the \n from the end of the row range
10348 // and position the cursor on the next line.
10349 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10350 cursor_buffer_row = rows.end;
10351 } else {
10352 // If there isn't a line after the range, delete the \n from the line before the
10353 // start of the row range and position the cursor there.
10354 edit_start = edit_start.saturating_sub(1);
10355 edit_end = buffer.len();
10356 cursor_buffer_row = rows.start.previous_row();
10357 }
10358
10359 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10360 *cursor.column_mut() =
10361 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10362
10363 new_cursors.push((
10364 selection.id,
10365 buffer.anchor_after(cursor.to_point(&display_map)),
10366 ));
10367 edit_ranges.push(edit_start..edit_end);
10368 }
10369
10370 self.transact(window, cx, |this, window, cx| {
10371 let buffer = this.buffer.update(cx, |buffer, cx| {
10372 let empty_str: Arc<str> = Arc::default();
10373 buffer.edit(
10374 edit_ranges
10375 .into_iter()
10376 .map(|range| (range, empty_str.clone())),
10377 None,
10378 cx,
10379 );
10380 buffer.snapshot(cx)
10381 });
10382 let new_selections = new_cursors
10383 .into_iter()
10384 .map(|(id, cursor)| {
10385 let cursor = cursor.to_point(&buffer);
10386 Selection {
10387 id,
10388 start: cursor,
10389 end: cursor,
10390 reversed: false,
10391 goal: SelectionGoal::None,
10392 }
10393 })
10394 .collect();
10395
10396 this.change_selections(Default::default(), window, cx, |s| {
10397 s.select(new_selections);
10398 });
10399 });
10400 }
10401
10402 pub fn join_lines_impl(
10403 &mut self,
10404 insert_whitespace: bool,
10405 window: &mut Window,
10406 cx: &mut Context<Self>,
10407 ) {
10408 if self.read_only(cx) {
10409 return;
10410 }
10411 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10412 for selection in self.selections.all::<Point>(cx) {
10413 let start = MultiBufferRow(selection.start.row);
10414 // Treat single line selections as if they include the next line. Otherwise this action
10415 // would do nothing for single line selections individual cursors.
10416 let end = if selection.start.row == selection.end.row {
10417 MultiBufferRow(selection.start.row + 1)
10418 } else {
10419 MultiBufferRow(selection.end.row)
10420 };
10421
10422 if let Some(last_row_range) = row_ranges.last_mut()
10423 && start <= last_row_range.end
10424 {
10425 last_row_range.end = end;
10426 continue;
10427 }
10428 row_ranges.push(start..end);
10429 }
10430
10431 let snapshot = self.buffer.read(cx).snapshot(cx);
10432 let mut cursor_positions = Vec::new();
10433 for row_range in &row_ranges {
10434 let anchor = snapshot.anchor_before(Point::new(
10435 row_range.end.previous_row().0,
10436 snapshot.line_len(row_range.end.previous_row()),
10437 ));
10438 cursor_positions.push(anchor..anchor);
10439 }
10440
10441 self.transact(window, cx, |this, window, cx| {
10442 for row_range in row_ranges.into_iter().rev() {
10443 for row in row_range.iter_rows().rev() {
10444 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10445 let next_line_row = row.next_row();
10446 let indent = snapshot.indent_size_for_line(next_line_row);
10447 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10448
10449 let replace =
10450 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10451 " "
10452 } else {
10453 ""
10454 };
10455
10456 this.buffer.update(cx, |buffer, cx| {
10457 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10458 });
10459 }
10460 }
10461
10462 this.change_selections(Default::default(), window, cx, |s| {
10463 s.select_anchor_ranges(cursor_positions)
10464 });
10465 });
10466 }
10467
10468 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10470 self.join_lines_impl(true, window, cx);
10471 }
10472
10473 pub fn sort_lines_case_sensitive(
10474 &mut self,
10475 _: &SortLinesCaseSensitive,
10476 window: &mut Window,
10477 cx: &mut Context<Self>,
10478 ) {
10479 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10480 }
10481
10482 pub fn sort_lines_by_length(
10483 &mut self,
10484 _: &SortLinesByLength,
10485 window: &mut Window,
10486 cx: &mut Context<Self>,
10487 ) {
10488 self.manipulate_immutable_lines(window, cx, |lines| {
10489 lines.sort_by_key(|&line| line.chars().count())
10490 })
10491 }
10492
10493 pub fn sort_lines_case_insensitive(
10494 &mut self,
10495 _: &SortLinesCaseInsensitive,
10496 window: &mut Window,
10497 cx: &mut Context<Self>,
10498 ) {
10499 self.manipulate_immutable_lines(window, cx, |lines| {
10500 lines.sort_by_key(|line| line.to_lowercase())
10501 })
10502 }
10503
10504 pub fn unique_lines_case_insensitive(
10505 &mut self,
10506 _: &UniqueLinesCaseInsensitive,
10507 window: &mut Window,
10508 cx: &mut Context<Self>,
10509 ) {
10510 self.manipulate_immutable_lines(window, cx, |lines| {
10511 let mut seen = HashSet::default();
10512 lines.retain(|line| seen.insert(line.to_lowercase()));
10513 })
10514 }
10515
10516 pub fn unique_lines_case_sensitive(
10517 &mut self,
10518 _: &UniqueLinesCaseSensitive,
10519 window: &mut Window,
10520 cx: &mut Context<Self>,
10521 ) {
10522 self.manipulate_immutable_lines(window, cx, |lines| {
10523 let mut seen = HashSet::default();
10524 lines.retain(|line| seen.insert(*line));
10525 })
10526 }
10527
10528 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10529 let snapshot = self.buffer.read(cx).snapshot(cx);
10530 for selection in self.selections.disjoint_anchors_arc().iter() {
10531 if snapshot
10532 .language_at(selection.start)
10533 .and_then(|lang| lang.config().wrap_characters.as_ref())
10534 .is_some()
10535 {
10536 return true;
10537 }
10538 }
10539 false
10540 }
10541
10542 fn wrap_selections_in_tag(
10543 &mut self,
10544 _: &WrapSelectionsInTag,
10545 window: &mut Window,
10546 cx: &mut Context<Self>,
10547 ) {
10548 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10549
10550 let snapshot = self.buffer.read(cx).snapshot(cx);
10551
10552 let mut edits = Vec::new();
10553 let mut boundaries = Vec::new();
10554
10555 for selection in self.selections.all::<Point>(cx).iter() {
10556 let Some(wrap_config) = snapshot
10557 .language_at(selection.start)
10558 .and_then(|lang| lang.config().wrap_characters.clone())
10559 else {
10560 continue;
10561 };
10562
10563 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10564 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10565
10566 let start_before = snapshot.anchor_before(selection.start);
10567 let end_after = snapshot.anchor_after(selection.end);
10568
10569 edits.push((start_before..start_before, open_tag));
10570 edits.push((end_after..end_after, close_tag));
10571
10572 boundaries.push((
10573 start_before,
10574 end_after,
10575 wrap_config.start_prefix.len(),
10576 wrap_config.end_suffix.len(),
10577 ));
10578 }
10579
10580 if edits.is_empty() {
10581 return;
10582 }
10583
10584 self.transact(window, cx, |this, window, cx| {
10585 let buffer = this.buffer.update(cx, |buffer, cx| {
10586 buffer.edit(edits, None, cx);
10587 buffer.snapshot(cx)
10588 });
10589
10590 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10591 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10592 boundaries.into_iter()
10593 {
10594 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10595 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10596 new_selections.push(open_offset..open_offset);
10597 new_selections.push(close_offset..close_offset);
10598 }
10599
10600 this.change_selections(Default::default(), window, cx, |s| {
10601 s.select_ranges(new_selections);
10602 });
10603
10604 this.request_autoscroll(Autoscroll::fit(), cx);
10605 });
10606 }
10607
10608 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10609 let Some(project) = self.project.clone() else {
10610 return;
10611 };
10612 self.reload(project, window, cx)
10613 .detach_and_notify_err(window, cx);
10614 }
10615
10616 pub fn restore_file(
10617 &mut self,
10618 _: &::git::RestoreFile,
10619 window: &mut Window,
10620 cx: &mut Context<Self>,
10621 ) {
10622 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10623 let mut buffer_ids = HashSet::default();
10624 let snapshot = self.buffer().read(cx).snapshot(cx);
10625 for selection in self.selections.all::<usize>(cx) {
10626 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10627 }
10628
10629 let buffer = self.buffer().read(cx);
10630 let ranges = buffer_ids
10631 .into_iter()
10632 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10633 .collect::<Vec<_>>();
10634
10635 self.restore_hunks_in_ranges(ranges, window, cx);
10636 }
10637
10638 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10639 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10640 let selections = self
10641 .selections
10642 .all(cx)
10643 .into_iter()
10644 .map(|s| s.range())
10645 .collect();
10646 self.restore_hunks_in_ranges(selections, window, cx);
10647 }
10648
10649 pub fn restore_hunks_in_ranges(
10650 &mut self,
10651 ranges: Vec<Range<Point>>,
10652 window: &mut Window,
10653 cx: &mut Context<Editor>,
10654 ) {
10655 let mut revert_changes = HashMap::default();
10656 let chunk_by = self
10657 .snapshot(window, cx)
10658 .hunks_for_ranges(ranges)
10659 .into_iter()
10660 .chunk_by(|hunk| hunk.buffer_id);
10661 for (buffer_id, hunks) in &chunk_by {
10662 let hunks = hunks.collect::<Vec<_>>();
10663 for hunk in &hunks {
10664 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10665 }
10666 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10667 }
10668 drop(chunk_by);
10669 if !revert_changes.is_empty() {
10670 self.transact(window, cx, |editor, window, cx| {
10671 editor.restore(revert_changes, window, cx);
10672 });
10673 }
10674 }
10675
10676 pub fn open_active_item_in_terminal(
10677 &mut self,
10678 _: &OpenInTerminal,
10679 window: &mut Window,
10680 cx: &mut Context<Self>,
10681 ) {
10682 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10683 let project_path = buffer.read(cx).project_path(cx)?;
10684 let project = self.project()?.read(cx);
10685 let entry = project.entry_for_path(&project_path, cx)?;
10686 let parent = match &entry.canonical_path {
10687 Some(canonical_path) => canonical_path.to_path_buf(),
10688 None => project.absolute_path(&project_path, cx)?,
10689 }
10690 .parent()?
10691 .to_path_buf();
10692 Some(parent)
10693 }) {
10694 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10695 }
10696 }
10697
10698 fn set_breakpoint_context_menu(
10699 &mut self,
10700 display_row: DisplayRow,
10701 position: Option<Anchor>,
10702 clicked_point: gpui::Point<Pixels>,
10703 window: &mut Window,
10704 cx: &mut Context<Self>,
10705 ) {
10706 let source = self
10707 .buffer
10708 .read(cx)
10709 .snapshot(cx)
10710 .anchor_before(Point::new(display_row.0, 0u32));
10711
10712 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10713
10714 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10715 self,
10716 source,
10717 clicked_point,
10718 context_menu,
10719 window,
10720 cx,
10721 );
10722 }
10723
10724 fn add_edit_breakpoint_block(
10725 &mut self,
10726 anchor: Anchor,
10727 breakpoint: &Breakpoint,
10728 edit_action: BreakpointPromptEditAction,
10729 window: &mut Window,
10730 cx: &mut Context<Self>,
10731 ) {
10732 let weak_editor = cx.weak_entity();
10733 let bp_prompt = cx.new(|cx| {
10734 BreakpointPromptEditor::new(
10735 weak_editor,
10736 anchor,
10737 breakpoint.clone(),
10738 edit_action,
10739 window,
10740 cx,
10741 )
10742 });
10743
10744 let height = bp_prompt.update(cx, |this, cx| {
10745 this.prompt
10746 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10747 });
10748 let cloned_prompt = bp_prompt.clone();
10749 let blocks = vec![BlockProperties {
10750 style: BlockStyle::Sticky,
10751 placement: BlockPlacement::Above(anchor),
10752 height: Some(height),
10753 render: Arc::new(move |cx| {
10754 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10755 cloned_prompt.clone().into_any_element()
10756 }),
10757 priority: 0,
10758 }];
10759
10760 let focus_handle = bp_prompt.focus_handle(cx);
10761 window.focus(&focus_handle);
10762
10763 let block_ids = self.insert_blocks(blocks, None, cx);
10764 bp_prompt.update(cx, |prompt, _| {
10765 prompt.add_block_ids(block_ids);
10766 });
10767 }
10768
10769 pub(crate) fn breakpoint_at_row(
10770 &self,
10771 row: u32,
10772 window: &mut Window,
10773 cx: &mut Context<Self>,
10774 ) -> Option<(Anchor, Breakpoint)> {
10775 let snapshot = self.snapshot(window, cx);
10776 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10777
10778 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10779 }
10780
10781 pub(crate) fn breakpoint_at_anchor(
10782 &self,
10783 breakpoint_position: Anchor,
10784 snapshot: &EditorSnapshot,
10785 cx: &mut Context<Self>,
10786 ) -> Option<(Anchor, Breakpoint)> {
10787 let buffer = self
10788 .buffer
10789 .read(cx)
10790 .buffer_for_anchor(breakpoint_position, cx)?;
10791
10792 let enclosing_excerpt = breakpoint_position.excerpt_id;
10793 let buffer_snapshot = buffer.read(cx).snapshot();
10794
10795 let row = buffer_snapshot
10796 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10797 .row;
10798
10799 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10800 let anchor_end = snapshot
10801 .buffer_snapshot
10802 .anchor_after(Point::new(row, line_len));
10803
10804 self.breakpoint_store
10805 .as_ref()?
10806 .read_with(cx, |breakpoint_store, cx| {
10807 breakpoint_store
10808 .breakpoints(
10809 &buffer,
10810 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10811 &buffer_snapshot,
10812 cx,
10813 )
10814 .next()
10815 .and_then(|(bp, _)| {
10816 let breakpoint_row = buffer_snapshot
10817 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10818 .row;
10819
10820 if breakpoint_row == row {
10821 snapshot
10822 .buffer_snapshot
10823 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10824 .map(|position| (position, bp.bp.clone()))
10825 } else {
10826 None
10827 }
10828 })
10829 })
10830 }
10831
10832 pub fn edit_log_breakpoint(
10833 &mut self,
10834 _: &EditLogBreakpoint,
10835 window: &mut Window,
10836 cx: &mut Context<Self>,
10837 ) {
10838 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10839 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10840 message: None,
10841 state: BreakpointState::Enabled,
10842 condition: None,
10843 hit_condition: None,
10844 });
10845
10846 self.add_edit_breakpoint_block(
10847 anchor,
10848 &breakpoint,
10849 BreakpointPromptEditAction::Log,
10850 window,
10851 cx,
10852 );
10853 }
10854 }
10855
10856 fn breakpoints_at_cursors(
10857 &self,
10858 window: &mut Window,
10859 cx: &mut Context<Self>,
10860 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10861 let snapshot = self.snapshot(window, cx);
10862 let cursors = self
10863 .selections
10864 .disjoint_anchors_arc()
10865 .iter()
10866 .map(|selection| {
10867 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10868
10869 let breakpoint_position = self
10870 .breakpoint_at_row(cursor_position.row, window, cx)
10871 .map(|bp| bp.0)
10872 .unwrap_or_else(|| {
10873 snapshot
10874 .display_snapshot
10875 .buffer_snapshot
10876 .anchor_after(Point::new(cursor_position.row, 0))
10877 });
10878
10879 let breakpoint = self
10880 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10881 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10882
10883 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10884 })
10885 // 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.
10886 .collect::<HashMap<Anchor, _>>();
10887
10888 cursors.into_iter().collect()
10889 }
10890
10891 pub fn enable_breakpoint(
10892 &mut self,
10893 _: &crate::actions::EnableBreakpoint,
10894 window: &mut Window,
10895 cx: &mut Context<Self>,
10896 ) {
10897 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10898 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10899 continue;
10900 };
10901 self.edit_breakpoint_at_anchor(
10902 anchor,
10903 breakpoint,
10904 BreakpointEditAction::InvertState,
10905 cx,
10906 );
10907 }
10908 }
10909
10910 pub fn disable_breakpoint(
10911 &mut self,
10912 _: &crate::actions::DisableBreakpoint,
10913 window: &mut Window,
10914 cx: &mut Context<Self>,
10915 ) {
10916 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10917 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10918 continue;
10919 };
10920 self.edit_breakpoint_at_anchor(
10921 anchor,
10922 breakpoint,
10923 BreakpointEditAction::InvertState,
10924 cx,
10925 );
10926 }
10927 }
10928
10929 pub fn toggle_breakpoint(
10930 &mut self,
10931 _: &crate::actions::ToggleBreakpoint,
10932 window: &mut Window,
10933 cx: &mut Context<Self>,
10934 ) {
10935 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10936 if let Some(breakpoint) = breakpoint {
10937 self.edit_breakpoint_at_anchor(
10938 anchor,
10939 breakpoint,
10940 BreakpointEditAction::Toggle,
10941 cx,
10942 );
10943 } else {
10944 self.edit_breakpoint_at_anchor(
10945 anchor,
10946 Breakpoint::new_standard(),
10947 BreakpointEditAction::Toggle,
10948 cx,
10949 );
10950 }
10951 }
10952 }
10953
10954 pub fn edit_breakpoint_at_anchor(
10955 &mut self,
10956 breakpoint_position: Anchor,
10957 breakpoint: Breakpoint,
10958 edit_action: BreakpointEditAction,
10959 cx: &mut Context<Self>,
10960 ) {
10961 let Some(breakpoint_store) = &self.breakpoint_store else {
10962 return;
10963 };
10964
10965 let Some(buffer) = self
10966 .buffer
10967 .read(cx)
10968 .buffer_for_anchor(breakpoint_position, cx)
10969 else {
10970 return;
10971 };
10972
10973 breakpoint_store.update(cx, |breakpoint_store, cx| {
10974 breakpoint_store.toggle_breakpoint(
10975 buffer,
10976 BreakpointWithPosition {
10977 position: breakpoint_position.text_anchor,
10978 bp: breakpoint,
10979 },
10980 edit_action,
10981 cx,
10982 );
10983 });
10984
10985 cx.notify();
10986 }
10987
10988 #[cfg(any(test, feature = "test-support"))]
10989 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10990 self.breakpoint_store.clone()
10991 }
10992
10993 pub fn prepare_restore_change(
10994 &self,
10995 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10996 hunk: &MultiBufferDiffHunk,
10997 cx: &mut App,
10998 ) -> Option<()> {
10999 if hunk.is_created_file() {
11000 return None;
11001 }
11002 let buffer = self.buffer.read(cx);
11003 let diff = buffer.diff_for(hunk.buffer_id)?;
11004 let buffer = buffer.buffer(hunk.buffer_id)?;
11005 let buffer = buffer.read(cx);
11006 let original_text = diff
11007 .read(cx)
11008 .base_text()
11009 .as_rope()
11010 .slice(hunk.diff_base_byte_range.clone());
11011 let buffer_snapshot = buffer.snapshot();
11012 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11013 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11014 probe
11015 .0
11016 .start
11017 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11018 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11019 }) {
11020 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11021 Some(())
11022 } else {
11023 None
11024 }
11025 }
11026
11027 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11028 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11029 }
11030
11031 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11032 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11033 }
11034
11035 fn manipulate_lines<M>(
11036 &mut self,
11037 window: &mut Window,
11038 cx: &mut Context<Self>,
11039 mut manipulate: M,
11040 ) where
11041 M: FnMut(&str) -> LineManipulationResult,
11042 {
11043 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11044
11045 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11046 let buffer = self.buffer.read(cx).snapshot(cx);
11047
11048 let mut edits = Vec::new();
11049
11050 let selections = self.selections.all::<Point>(cx);
11051 let mut selections = selections.iter().peekable();
11052 let mut contiguous_row_selections = Vec::new();
11053 let mut new_selections = Vec::new();
11054 let mut added_lines = 0;
11055 let mut removed_lines = 0;
11056
11057 while let Some(selection) = selections.next() {
11058 let (start_row, end_row) = consume_contiguous_rows(
11059 &mut contiguous_row_selections,
11060 selection,
11061 &display_map,
11062 &mut selections,
11063 );
11064
11065 let start_point = Point::new(start_row.0, 0);
11066 let end_point = Point::new(
11067 end_row.previous_row().0,
11068 buffer.line_len(end_row.previous_row()),
11069 );
11070 let text = buffer
11071 .text_for_range(start_point..end_point)
11072 .collect::<String>();
11073
11074 let LineManipulationResult {
11075 new_text,
11076 line_count_before,
11077 line_count_after,
11078 } = manipulate(&text);
11079
11080 edits.push((start_point..end_point, new_text));
11081
11082 // Selections must change based on added and removed line count
11083 let start_row =
11084 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11085 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11086 new_selections.push(Selection {
11087 id: selection.id,
11088 start: start_row,
11089 end: end_row,
11090 goal: SelectionGoal::None,
11091 reversed: selection.reversed,
11092 });
11093
11094 if line_count_after > line_count_before {
11095 added_lines += line_count_after - line_count_before;
11096 } else if line_count_before > line_count_after {
11097 removed_lines += line_count_before - line_count_after;
11098 }
11099 }
11100
11101 self.transact(window, cx, |this, window, cx| {
11102 let buffer = this.buffer.update(cx, |buffer, cx| {
11103 buffer.edit(edits, None, cx);
11104 buffer.snapshot(cx)
11105 });
11106
11107 // Recalculate offsets on newly edited buffer
11108 let new_selections = new_selections
11109 .iter()
11110 .map(|s| {
11111 let start_point = Point::new(s.start.0, 0);
11112 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11113 Selection {
11114 id: s.id,
11115 start: buffer.point_to_offset(start_point),
11116 end: buffer.point_to_offset(end_point),
11117 goal: s.goal,
11118 reversed: s.reversed,
11119 }
11120 })
11121 .collect();
11122
11123 this.change_selections(Default::default(), window, cx, |s| {
11124 s.select(new_selections);
11125 });
11126
11127 this.request_autoscroll(Autoscroll::fit(), cx);
11128 });
11129 }
11130
11131 fn manipulate_immutable_lines<Fn>(
11132 &mut self,
11133 window: &mut Window,
11134 cx: &mut Context<Self>,
11135 mut callback: Fn,
11136 ) where
11137 Fn: FnMut(&mut Vec<&str>),
11138 {
11139 self.manipulate_lines(window, cx, |text| {
11140 let mut lines: Vec<&str> = text.split('\n').collect();
11141 let line_count_before = lines.len();
11142
11143 callback(&mut lines);
11144
11145 LineManipulationResult {
11146 new_text: lines.join("\n"),
11147 line_count_before,
11148 line_count_after: lines.len(),
11149 }
11150 });
11151 }
11152
11153 fn manipulate_mutable_lines<Fn>(
11154 &mut self,
11155 window: &mut Window,
11156 cx: &mut Context<Self>,
11157 mut callback: Fn,
11158 ) where
11159 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11160 {
11161 self.manipulate_lines(window, cx, |text| {
11162 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11163 let line_count_before = lines.len();
11164
11165 callback(&mut lines);
11166
11167 LineManipulationResult {
11168 new_text: lines.join("\n"),
11169 line_count_before,
11170 line_count_after: lines.len(),
11171 }
11172 });
11173 }
11174
11175 pub fn convert_indentation_to_spaces(
11176 &mut self,
11177 _: &ConvertIndentationToSpaces,
11178 window: &mut Window,
11179 cx: &mut Context<Self>,
11180 ) {
11181 let settings = self.buffer.read(cx).language_settings(cx);
11182 let tab_size = settings.tab_size.get() as usize;
11183
11184 self.manipulate_mutable_lines(window, cx, |lines| {
11185 // Allocates a reasonably sized scratch buffer once for the whole loop
11186 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11187 // Avoids recomputing spaces that could be inserted many times
11188 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11189 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11190 .collect();
11191
11192 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11193 let mut chars = line.as_ref().chars();
11194 let mut col = 0;
11195 let mut changed = false;
11196
11197 for ch in chars.by_ref() {
11198 match ch {
11199 ' ' => {
11200 reindented_line.push(' ');
11201 col += 1;
11202 }
11203 '\t' => {
11204 // \t are converted to spaces depending on the current column
11205 let spaces_len = tab_size - (col % tab_size);
11206 reindented_line.extend(&space_cache[spaces_len - 1]);
11207 col += spaces_len;
11208 changed = true;
11209 }
11210 _ => {
11211 // If we dont append before break, the character is consumed
11212 reindented_line.push(ch);
11213 break;
11214 }
11215 }
11216 }
11217
11218 if !changed {
11219 reindented_line.clear();
11220 continue;
11221 }
11222 // Append the rest of the line and replace old reference with new one
11223 reindented_line.extend(chars);
11224 *line = Cow::Owned(reindented_line.clone());
11225 reindented_line.clear();
11226 }
11227 });
11228 }
11229
11230 pub fn convert_indentation_to_tabs(
11231 &mut self,
11232 _: &ConvertIndentationToTabs,
11233 window: &mut Window,
11234 cx: &mut Context<Self>,
11235 ) {
11236 let settings = self.buffer.read(cx).language_settings(cx);
11237 let tab_size = settings.tab_size.get() as usize;
11238
11239 self.manipulate_mutable_lines(window, cx, |lines| {
11240 // Allocates a reasonably sized buffer once for the whole loop
11241 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11242 // Avoids recomputing spaces that could be inserted many times
11243 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11244 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11245 .collect();
11246
11247 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11248 let mut chars = line.chars();
11249 let mut spaces_count = 0;
11250 let mut first_non_indent_char = None;
11251 let mut changed = false;
11252
11253 for ch in chars.by_ref() {
11254 match ch {
11255 ' ' => {
11256 // Keep track of spaces. Append \t when we reach tab_size
11257 spaces_count += 1;
11258 changed = true;
11259 if spaces_count == tab_size {
11260 reindented_line.push('\t');
11261 spaces_count = 0;
11262 }
11263 }
11264 '\t' => {
11265 reindented_line.push('\t');
11266 spaces_count = 0;
11267 }
11268 _ => {
11269 // Dont append it yet, we might have remaining spaces
11270 first_non_indent_char = Some(ch);
11271 break;
11272 }
11273 }
11274 }
11275
11276 if !changed {
11277 reindented_line.clear();
11278 continue;
11279 }
11280 // Remaining spaces that didn't make a full tab stop
11281 if spaces_count > 0 {
11282 reindented_line.extend(&space_cache[spaces_count - 1]);
11283 }
11284 // If we consume an extra character that was not indentation, add it back
11285 if let Some(extra_char) = first_non_indent_char {
11286 reindented_line.push(extra_char);
11287 }
11288 // Append the rest of the line and replace old reference with new one
11289 reindented_line.extend(chars);
11290 *line = Cow::Owned(reindented_line.clone());
11291 reindented_line.clear();
11292 }
11293 });
11294 }
11295
11296 pub fn convert_to_upper_case(
11297 &mut self,
11298 _: &ConvertToUpperCase,
11299 window: &mut Window,
11300 cx: &mut Context<Self>,
11301 ) {
11302 self.manipulate_text(window, cx, |text| text.to_uppercase())
11303 }
11304
11305 pub fn convert_to_lower_case(
11306 &mut self,
11307 _: &ConvertToLowerCase,
11308 window: &mut Window,
11309 cx: &mut Context<Self>,
11310 ) {
11311 self.manipulate_text(window, cx, |text| text.to_lowercase())
11312 }
11313
11314 pub fn convert_to_title_case(
11315 &mut self,
11316 _: &ConvertToTitleCase,
11317 window: &mut Window,
11318 cx: &mut Context<Self>,
11319 ) {
11320 self.manipulate_text(window, cx, |text| {
11321 text.split('\n')
11322 .map(|line| line.to_case(Case::Title))
11323 .join("\n")
11324 })
11325 }
11326
11327 pub fn convert_to_snake_case(
11328 &mut self,
11329 _: &ConvertToSnakeCase,
11330 window: &mut Window,
11331 cx: &mut Context<Self>,
11332 ) {
11333 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11334 }
11335
11336 pub fn convert_to_kebab_case(
11337 &mut self,
11338 _: &ConvertToKebabCase,
11339 window: &mut Window,
11340 cx: &mut Context<Self>,
11341 ) {
11342 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11343 }
11344
11345 pub fn convert_to_upper_camel_case(
11346 &mut self,
11347 _: &ConvertToUpperCamelCase,
11348 window: &mut Window,
11349 cx: &mut Context<Self>,
11350 ) {
11351 self.manipulate_text(window, cx, |text| {
11352 text.split('\n')
11353 .map(|line| line.to_case(Case::UpperCamel))
11354 .join("\n")
11355 })
11356 }
11357
11358 pub fn convert_to_lower_camel_case(
11359 &mut self,
11360 _: &ConvertToLowerCamelCase,
11361 window: &mut Window,
11362 cx: &mut Context<Self>,
11363 ) {
11364 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11365 }
11366
11367 pub fn convert_to_opposite_case(
11368 &mut self,
11369 _: &ConvertToOppositeCase,
11370 window: &mut Window,
11371 cx: &mut Context<Self>,
11372 ) {
11373 self.manipulate_text(window, cx, |text| {
11374 text.chars()
11375 .fold(String::with_capacity(text.len()), |mut t, c| {
11376 if c.is_uppercase() {
11377 t.extend(c.to_lowercase());
11378 } else {
11379 t.extend(c.to_uppercase());
11380 }
11381 t
11382 })
11383 })
11384 }
11385
11386 pub fn convert_to_sentence_case(
11387 &mut self,
11388 _: &ConvertToSentenceCase,
11389 window: &mut Window,
11390 cx: &mut Context<Self>,
11391 ) {
11392 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11393 }
11394
11395 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11396 self.manipulate_text(window, cx, |text| {
11397 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11398 if has_upper_case_characters {
11399 text.to_lowercase()
11400 } else {
11401 text.to_uppercase()
11402 }
11403 })
11404 }
11405
11406 pub fn convert_to_rot13(
11407 &mut self,
11408 _: &ConvertToRot13,
11409 window: &mut Window,
11410 cx: &mut Context<Self>,
11411 ) {
11412 self.manipulate_text(window, cx, |text| {
11413 text.chars()
11414 .map(|c| match c {
11415 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11416 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11417 _ => c,
11418 })
11419 .collect()
11420 })
11421 }
11422
11423 pub fn convert_to_rot47(
11424 &mut self,
11425 _: &ConvertToRot47,
11426 window: &mut Window,
11427 cx: &mut Context<Self>,
11428 ) {
11429 self.manipulate_text(window, cx, |text| {
11430 text.chars()
11431 .map(|c| {
11432 let code_point = c as u32;
11433 if code_point >= 33 && code_point <= 126 {
11434 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11435 }
11436 c
11437 })
11438 .collect()
11439 })
11440 }
11441
11442 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11443 where
11444 Fn: FnMut(&str) -> String,
11445 {
11446 let buffer = self.buffer.read(cx).snapshot(cx);
11447
11448 let mut new_selections = Vec::new();
11449 let mut edits = Vec::new();
11450 let mut selection_adjustment = 0i32;
11451
11452 for selection in self.selections.all_adjusted(cx) {
11453 let selection_is_empty = selection.is_empty();
11454
11455 let (start, end) = if selection_is_empty {
11456 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11457 (word_range.start, word_range.end)
11458 } else {
11459 (
11460 buffer.point_to_offset(selection.start),
11461 buffer.point_to_offset(selection.end),
11462 )
11463 };
11464
11465 let text = buffer.text_for_range(start..end).collect::<String>();
11466 let old_length = text.len() as i32;
11467 let text = callback(&text);
11468
11469 new_selections.push(Selection {
11470 start: (start as i32 - selection_adjustment) as usize,
11471 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11472 goal: SelectionGoal::None,
11473 id: selection.id,
11474 reversed: selection.reversed,
11475 });
11476
11477 selection_adjustment += old_length - text.len() as i32;
11478
11479 edits.push((start..end, text));
11480 }
11481
11482 self.transact(window, cx, |this, window, cx| {
11483 this.buffer.update(cx, |buffer, cx| {
11484 buffer.edit(edits, None, cx);
11485 });
11486
11487 this.change_selections(Default::default(), window, cx, |s| {
11488 s.select(new_selections);
11489 });
11490
11491 this.request_autoscroll(Autoscroll::fit(), cx);
11492 });
11493 }
11494
11495 pub fn move_selection_on_drop(
11496 &mut self,
11497 selection: &Selection<Anchor>,
11498 target: DisplayPoint,
11499 is_cut: bool,
11500 window: &mut Window,
11501 cx: &mut Context<Self>,
11502 ) {
11503 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11504 let buffer = &display_map.buffer_snapshot;
11505 let mut edits = Vec::new();
11506 let insert_point = display_map
11507 .clip_point(target, Bias::Left)
11508 .to_point(&display_map);
11509 let text = buffer
11510 .text_for_range(selection.start..selection.end)
11511 .collect::<String>();
11512 if is_cut {
11513 edits.push(((selection.start..selection.end), String::new()));
11514 }
11515 let insert_anchor = buffer.anchor_before(insert_point);
11516 edits.push(((insert_anchor..insert_anchor), text));
11517 let last_edit_start = insert_anchor.bias_left(buffer);
11518 let last_edit_end = insert_anchor.bias_right(buffer);
11519 self.transact(window, cx, |this, window, cx| {
11520 this.buffer.update(cx, |buffer, cx| {
11521 buffer.edit(edits, None, cx);
11522 });
11523 this.change_selections(Default::default(), window, cx, |s| {
11524 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11525 });
11526 });
11527 }
11528
11529 pub fn clear_selection_drag_state(&mut self) {
11530 self.selection_drag_state = SelectionDragState::None;
11531 }
11532
11533 pub fn duplicate(
11534 &mut self,
11535 upwards: bool,
11536 whole_lines: bool,
11537 window: &mut Window,
11538 cx: &mut Context<Self>,
11539 ) {
11540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11541
11542 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11543 let buffer = &display_map.buffer_snapshot;
11544 let selections = self.selections.all::<Point>(cx);
11545
11546 let mut edits = Vec::new();
11547 let mut selections_iter = selections.iter().peekable();
11548 while let Some(selection) = selections_iter.next() {
11549 let mut rows = selection.spanned_rows(false, &display_map);
11550 // duplicate line-wise
11551 if whole_lines || selection.start == selection.end {
11552 // Avoid duplicating the same lines twice.
11553 while let Some(next_selection) = selections_iter.peek() {
11554 let next_rows = next_selection.spanned_rows(false, &display_map);
11555 if next_rows.start < rows.end {
11556 rows.end = next_rows.end;
11557 selections_iter.next().unwrap();
11558 } else {
11559 break;
11560 }
11561 }
11562
11563 // Copy the text from the selected row region and splice it either at the start
11564 // or end of the region.
11565 let start = Point::new(rows.start.0, 0);
11566 let end = Point::new(
11567 rows.end.previous_row().0,
11568 buffer.line_len(rows.end.previous_row()),
11569 );
11570 let text = buffer
11571 .text_for_range(start..end)
11572 .chain(Some("\n"))
11573 .collect::<String>();
11574 let insert_location = if upwards {
11575 Point::new(rows.end.0, 0)
11576 } else {
11577 start
11578 };
11579 edits.push((insert_location..insert_location, text));
11580 } else {
11581 // duplicate character-wise
11582 let start = selection.start;
11583 let end = selection.end;
11584 let text = buffer.text_for_range(start..end).collect::<String>();
11585 edits.push((selection.end..selection.end, text));
11586 }
11587 }
11588
11589 self.transact(window, cx, |this, _, cx| {
11590 this.buffer.update(cx, |buffer, cx| {
11591 buffer.edit(edits, None, cx);
11592 });
11593
11594 this.request_autoscroll(Autoscroll::fit(), cx);
11595 });
11596 }
11597
11598 pub fn duplicate_line_up(
11599 &mut self,
11600 _: &DuplicateLineUp,
11601 window: &mut Window,
11602 cx: &mut Context<Self>,
11603 ) {
11604 self.duplicate(true, true, window, cx);
11605 }
11606
11607 pub fn duplicate_line_down(
11608 &mut self,
11609 _: &DuplicateLineDown,
11610 window: &mut Window,
11611 cx: &mut Context<Self>,
11612 ) {
11613 self.duplicate(false, true, window, cx);
11614 }
11615
11616 pub fn duplicate_selection(
11617 &mut self,
11618 _: &DuplicateSelection,
11619 window: &mut Window,
11620 cx: &mut Context<Self>,
11621 ) {
11622 self.duplicate(false, false, window, cx);
11623 }
11624
11625 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11626 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11627 if self.mode.is_single_line() {
11628 cx.propagate();
11629 return;
11630 }
11631
11632 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11633 let buffer = self.buffer.read(cx).snapshot(cx);
11634
11635 let mut edits = Vec::new();
11636 let mut unfold_ranges = Vec::new();
11637 let mut refold_creases = Vec::new();
11638
11639 let selections = self.selections.all::<Point>(cx);
11640 let mut selections = selections.iter().peekable();
11641 let mut contiguous_row_selections = Vec::new();
11642 let mut new_selections = Vec::new();
11643
11644 while let Some(selection) = selections.next() {
11645 // Find all the selections that span a contiguous row range
11646 let (start_row, end_row) = consume_contiguous_rows(
11647 &mut contiguous_row_selections,
11648 selection,
11649 &display_map,
11650 &mut selections,
11651 );
11652
11653 // Move the text spanned by the row range to be before the line preceding the row range
11654 if start_row.0 > 0 {
11655 let range_to_move = Point::new(
11656 start_row.previous_row().0,
11657 buffer.line_len(start_row.previous_row()),
11658 )
11659 ..Point::new(
11660 end_row.previous_row().0,
11661 buffer.line_len(end_row.previous_row()),
11662 );
11663 let insertion_point = display_map
11664 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11665 .0;
11666
11667 // Don't move lines across excerpts
11668 if buffer
11669 .excerpt_containing(insertion_point..range_to_move.end)
11670 .is_some()
11671 {
11672 let text = buffer
11673 .text_for_range(range_to_move.clone())
11674 .flat_map(|s| s.chars())
11675 .skip(1)
11676 .chain(['\n'])
11677 .collect::<String>();
11678
11679 edits.push((
11680 buffer.anchor_after(range_to_move.start)
11681 ..buffer.anchor_before(range_to_move.end),
11682 String::new(),
11683 ));
11684 let insertion_anchor = buffer.anchor_after(insertion_point);
11685 edits.push((insertion_anchor..insertion_anchor, text));
11686
11687 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11688
11689 // Move selections up
11690 new_selections.extend(contiguous_row_selections.drain(..).map(
11691 |mut selection| {
11692 selection.start.row -= row_delta;
11693 selection.end.row -= row_delta;
11694 selection
11695 },
11696 ));
11697
11698 // Move folds up
11699 unfold_ranges.push(range_to_move.clone());
11700 for fold in display_map.folds_in_range(
11701 buffer.anchor_before(range_to_move.start)
11702 ..buffer.anchor_after(range_to_move.end),
11703 ) {
11704 let mut start = fold.range.start.to_point(&buffer);
11705 let mut end = fold.range.end.to_point(&buffer);
11706 start.row -= row_delta;
11707 end.row -= row_delta;
11708 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11709 }
11710 }
11711 }
11712
11713 // If we didn't move line(s), preserve the existing selections
11714 new_selections.append(&mut contiguous_row_selections);
11715 }
11716
11717 self.transact(window, cx, |this, window, cx| {
11718 this.unfold_ranges(&unfold_ranges, true, true, cx);
11719 this.buffer.update(cx, |buffer, cx| {
11720 for (range, text) in edits {
11721 buffer.edit([(range, text)], None, cx);
11722 }
11723 });
11724 this.fold_creases(refold_creases, true, window, cx);
11725 this.change_selections(Default::default(), window, cx, |s| {
11726 s.select(new_selections);
11727 })
11728 });
11729 }
11730
11731 pub fn move_line_down(
11732 &mut self,
11733 _: &MoveLineDown,
11734 window: &mut Window,
11735 cx: &mut Context<Self>,
11736 ) {
11737 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11738 if self.mode.is_single_line() {
11739 cx.propagate();
11740 return;
11741 }
11742
11743 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11744 let buffer = self.buffer.read(cx).snapshot(cx);
11745
11746 let mut edits = Vec::new();
11747 let mut unfold_ranges = Vec::new();
11748 let mut refold_creases = Vec::new();
11749
11750 let selections = self.selections.all::<Point>(cx);
11751 let mut selections = selections.iter().peekable();
11752 let mut contiguous_row_selections = Vec::new();
11753 let mut new_selections = Vec::new();
11754
11755 while let Some(selection) = selections.next() {
11756 // Find all the selections that span a contiguous row range
11757 let (start_row, end_row) = consume_contiguous_rows(
11758 &mut contiguous_row_selections,
11759 selection,
11760 &display_map,
11761 &mut selections,
11762 );
11763
11764 // Move the text spanned by the row range to be after the last line of the row range
11765 if end_row.0 <= buffer.max_point().row {
11766 let range_to_move =
11767 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11768 let insertion_point = display_map
11769 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11770 .0;
11771
11772 // Don't move lines across excerpt boundaries
11773 if buffer
11774 .excerpt_containing(range_to_move.start..insertion_point)
11775 .is_some()
11776 {
11777 let mut text = String::from("\n");
11778 text.extend(buffer.text_for_range(range_to_move.clone()));
11779 text.pop(); // Drop trailing newline
11780 edits.push((
11781 buffer.anchor_after(range_to_move.start)
11782 ..buffer.anchor_before(range_to_move.end),
11783 String::new(),
11784 ));
11785 let insertion_anchor = buffer.anchor_after(insertion_point);
11786 edits.push((insertion_anchor..insertion_anchor, text));
11787
11788 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11789
11790 // Move selections down
11791 new_selections.extend(contiguous_row_selections.drain(..).map(
11792 |mut selection| {
11793 selection.start.row += row_delta;
11794 selection.end.row += row_delta;
11795 selection
11796 },
11797 ));
11798
11799 // Move folds down
11800 unfold_ranges.push(range_to_move.clone());
11801 for fold in display_map.folds_in_range(
11802 buffer.anchor_before(range_to_move.start)
11803 ..buffer.anchor_after(range_to_move.end),
11804 ) {
11805 let mut start = fold.range.start.to_point(&buffer);
11806 let mut end = fold.range.end.to_point(&buffer);
11807 start.row += row_delta;
11808 end.row += row_delta;
11809 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11810 }
11811 }
11812 }
11813
11814 // If we didn't move line(s), preserve the existing selections
11815 new_selections.append(&mut contiguous_row_selections);
11816 }
11817
11818 self.transact(window, cx, |this, window, cx| {
11819 this.unfold_ranges(&unfold_ranges, true, true, cx);
11820 this.buffer.update(cx, |buffer, cx| {
11821 for (range, text) in edits {
11822 buffer.edit([(range, text)], None, cx);
11823 }
11824 });
11825 this.fold_creases(refold_creases, true, window, cx);
11826 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11827 });
11828 }
11829
11830 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11831 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11832 let text_layout_details = &self.text_layout_details(window);
11833 self.transact(window, cx, |this, window, cx| {
11834 let edits = this.change_selections(Default::default(), window, cx, |s| {
11835 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11836 s.move_with(|display_map, selection| {
11837 if !selection.is_empty() {
11838 return;
11839 }
11840
11841 let mut head = selection.head();
11842 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11843 if head.column() == display_map.line_len(head.row()) {
11844 transpose_offset = display_map
11845 .buffer_snapshot
11846 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11847 }
11848
11849 if transpose_offset == 0 {
11850 return;
11851 }
11852
11853 *head.column_mut() += 1;
11854 head = display_map.clip_point(head, Bias::Right);
11855 let goal = SelectionGoal::HorizontalPosition(
11856 display_map
11857 .x_for_display_point(head, text_layout_details)
11858 .into(),
11859 );
11860 selection.collapse_to(head, goal);
11861
11862 let transpose_start = display_map
11863 .buffer_snapshot
11864 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11865 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11866 let transpose_end = display_map
11867 .buffer_snapshot
11868 .clip_offset(transpose_offset + 1, Bias::Right);
11869 if let Some(ch) =
11870 display_map.buffer_snapshot.chars_at(transpose_start).next()
11871 {
11872 edits.push((transpose_start..transpose_offset, String::new()));
11873 edits.push((transpose_end..transpose_end, ch.to_string()));
11874 }
11875 }
11876 });
11877 edits
11878 });
11879 this.buffer
11880 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11881 let selections = this.selections.all::<usize>(cx);
11882 this.change_selections(Default::default(), window, cx, |s| {
11883 s.select(selections);
11884 });
11885 });
11886 }
11887
11888 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11889 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11890 if self.mode.is_single_line() {
11891 cx.propagate();
11892 return;
11893 }
11894
11895 self.rewrap_impl(RewrapOptions::default(), cx)
11896 }
11897
11898 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11899 let buffer = self.buffer.read(cx).snapshot(cx);
11900 let selections = self.selections.all::<Point>(cx);
11901
11902 #[derive(Clone, Debug, PartialEq)]
11903 enum CommentFormat {
11904 /// single line comment, with prefix for line
11905 Line(String),
11906 /// single line within a block comment, with prefix for line
11907 BlockLine(String),
11908 /// a single line of a block comment that includes the initial delimiter
11909 BlockCommentWithStart(BlockCommentConfig),
11910 /// a single line of a block comment that includes the ending delimiter
11911 BlockCommentWithEnd(BlockCommentConfig),
11912 }
11913
11914 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11915 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11916 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11917 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11918 .peekable();
11919
11920 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11921 row
11922 } else {
11923 return Vec::new();
11924 };
11925
11926 let language_settings = buffer.language_settings_at(selection.head(), cx);
11927 let language_scope = buffer.language_scope_at(selection.head());
11928
11929 let indent_and_prefix_for_row =
11930 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11931 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11932 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11933 &language_scope
11934 {
11935 let indent_end = Point::new(row, indent.len);
11936 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11937 let line_text_after_indent = buffer
11938 .text_for_range(indent_end..line_end)
11939 .collect::<String>();
11940
11941 let is_within_comment_override = buffer
11942 .language_scope_at(indent_end)
11943 .is_some_and(|scope| scope.override_name() == Some("comment"));
11944 let comment_delimiters = if is_within_comment_override {
11945 // we are within a comment syntax node, but we don't
11946 // yet know what kind of comment: block, doc or line
11947 match (
11948 language_scope.documentation_comment(),
11949 language_scope.block_comment(),
11950 ) {
11951 (Some(config), _) | (_, Some(config))
11952 if buffer.contains_str_at(indent_end, &config.start) =>
11953 {
11954 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11955 }
11956 (Some(config), _) | (_, Some(config))
11957 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11958 {
11959 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11960 }
11961 (Some(config), _) | (_, Some(config))
11962 if buffer.contains_str_at(indent_end, &config.prefix) =>
11963 {
11964 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11965 }
11966 (_, _) => language_scope
11967 .line_comment_prefixes()
11968 .iter()
11969 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11970 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11971 }
11972 } else {
11973 // we not in an overridden comment node, but we may
11974 // be within a non-overridden line comment node
11975 language_scope
11976 .line_comment_prefixes()
11977 .iter()
11978 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11979 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11980 };
11981
11982 let rewrap_prefix = language_scope
11983 .rewrap_prefixes()
11984 .iter()
11985 .find_map(|prefix_regex| {
11986 prefix_regex.find(&line_text_after_indent).map(|mat| {
11987 if mat.start() == 0 {
11988 Some(mat.as_str().to_string())
11989 } else {
11990 None
11991 }
11992 })
11993 })
11994 .flatten();
11995 (comment_delimiters, rewrap_prefix)
11996 } else {
11997 (None, None)
11998 };
11999 (indent, comment_prefix, rewrap_prefix)
12000 };
12001
12002 let mut ranges = Vec::new();
12003 let from_empty_selection = selection.is_empty();
12004
12005 let mut current_range_start = first_row;
12006 let mut prev_row = first_row;
12007 let (
12008 mut current_range_indent,
12009 mut current_range_comment_delimiters,
12010 mut current_range_rewrap_prefix,
12011 ) = indent_and_prefix_for_row(first_row);
12012
12013 for row in non_blank_rows_iter.skip(1) {
12014 let has_paragraph_break = row > prev_row + 1;
12015
12016 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12017 indent_and_prefix_for_row(row);
12018
12019 let has_indent_change = row_indent != current_range_indent;
12020 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12021
12022 let has_boundary_change = has_comment_change
12023 || row_rewrap_prefix.is_some()
12024 || (has_indent_change && current_range_comment_delimiters.is_some());
12025
12026 if has_paragraph_break || has_boundary_change {
12027 ranges.push((
12028 language_settings.clone(),
12029 Point::new(current_range_start, 0)
12030 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12031 current_range_indent,
12032 current_range_comment_delimiters.clone(),
12033 current_range_rewrap_prefix.clone(),
12034 from_empty_selection,
12035 ));
12036 current_range_start = row;
12037 current_range_indent = row_indent;
12038 current_range_comment_delimiters = row_comment_delimiters;
12039 current_range_rewrap_prefix = row_rewrap_prefix;
12040 }
12041 prev_row = row;
12042 }
12043
12044 ranges.push((
12045 language_settings.clone(),
12046 Point::new(current_range_start, 0)
12047 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12048 current_range_indent,
12049 current_range_comment_delimiters,
12050 current_range_rewrap_prefix,
12051 from_empty_selection,
12052 ));
12053
12054 ranges
12055 });
12056
12057 let mut edits = Vec::new();
12058 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12059
12060 for (
12061 language_settings,
12062 wrap_range,
12063 mut indent_size,
12064 comment_prefix,
12065 rewrap_prefix,
12066 from_empty_selection,
12067 ) in wrap_ranges
12068 {
12069 let mut start_row = wrap_range.start.row;
12070 let mut end_row = wrap_range.end.row;
12071
12072 // Skip selections that overlap with a range that has already been rewrapped.
12073 let selection_range = start_row..end_row;
12074 if rewrapped_row_ranges
12075 .iter()
12076 .any(|range| range.overlaps(&selection_range))
12077 {
12078 continue;
12079 }
12080
12081 let tab_size = language_settings.tab_size;
12082
12083 let (line_prefix, inside_comment) = match &comment_prefix {
12084 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12085 (Some(prefix.as_str()), true)
12086 }
12087 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12088 (Some(prefix.as_ref()), true)
12089 }
12090 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12091 start: _,
12092 end: _,
12093 prefix,
12094 tab_size,
12095 })) => {
12096 indent_size.len += tab_size;
12097 (Some(prefix.as_ref()), true)
12098 }
12099 None => (None, false),
12100 };
12101 let indent_prefix = indent_size.chars().collect::<String>();
12102 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12103
12104 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12105 RewrapBehavior::InComments => inside_comment,
12106 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12107 RewrapBehavior::Anywhere => true,
12108 };
12109
12110 let should_rewrap = options.override_language_settings
12111 || allow_rewrap_based_on_language
12112 || self.hard_wrap.is_some();
12113 if !should_rewrap {
12114 continue;
12115 }
12116
12117 if from_empty_selection {
12118 'expand_upwards: while start_row > 0 {
12119 let prev_row = start_row - 1;
12120 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12121 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12122 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12123 {
12124 start_row = prev_row;
12125 } else {
12126 break 'expand_upwards;
12127 }
12128 }
12129
12130 'expand_downwards: while end_row < buffer.max_point().row {
12131 let next_row = end_row + 1;
12132 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12133 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12134 && !buffer.is_line_blank(MultiBufferRow(next_row))
12135 {
12136 end_row = next_row;
12137 } else {
12138 break 'expand_downwards;
12139 }
12140 }
12141 }
12142
12143 let start = Point::new(start_row, 0);
12144 let start_offset = start.to_offset(&buffer);
12145 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12146 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12147 let mut first_line_delimiter = None;
12148 let mut last_line_delimiter = None;
12149 let Some(lines_without_prefixes) = selection_text
12150 .lines()
12151 .enumerate()
12152 .map(|(ix, line)| {
12153 let line_trimmed = line.trim_start();
12154 if rewrap_prefix.is_some() && ix > 0 {
12155 Ok(line_trimmed)
12156 } else if let Some(
12157 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12158 start,
12159 prefix,
12160 end,
12161 tab_size,
12162 })
12163 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12164 start,
12165 prefix,
12166 end,
12167 tab_size,
12168 }),
12169 ) = &comment_prefix
12170 {
12171 let line_trimmed = line_trimmed
12172 .strip_prefix(start.as_ref())
12173 .map(|s| {
12174 let mut indent_size = indent_size;
12175 indent_size.len -= tab_size;
12176 let indent_prefix: String = indent_size.chars().collect();
12177 first_line_delimiter = Some((indent_prefix, start));
12178 s.trim_start()
12179 })
12180 .unwrap_or(line_trimmed);
12181 let line_trimmed = line_trimmed
12182 .strip_suffix(end.as_ref())
12183 .map(|s| {
12184 last_line_delimiter = Some(end);
12185 s.trim_end()
12186 })
12187 .unwrap_or(line_trimmed);
12188 let line_trimmed = line_trimmed
12189 .strip_prefix(prefix.as_ref())
12190 .unwrap_or(line_trimmed);
12191 Ok(line_trimmed)
12192 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12193 line_trimmed.strip_prefix(prefix).with_context(|| {
12194 format!("line did not start with prefix {prefix:?}: {line:?}")
12195 })
12196 } else {
12197 line_trimmed
12198 .strip_prefix(&line_prefix.trim_start())
12199 .with_context(|| {
12200 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12201 })
12202 }
12203 })
12204 .collect::<Result<Vec<_>, _>>()
12205 .log_err()
12206 else {
12207 continue;
12208 };
12209
12210 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12211 buffer
12212 .language_settings_at(Point::new(start_row, 0), cx)
12213 .preferred_line_length as usize
12214 });
12215
12216 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12217 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12218 } else {
12219 line_prefix.clone()
12220 };
12221
12222 let wrapped_text = {
12223 let mut wrapped_text = wrap_with_prefix(
12224 line_prefix,
12225 subsequent_lines_prefix,
12226 lines_without_prefixes.join("\n"),
12227 wrap_column,
12228 tab_size,
12229 options.preserve_existing_whitespace,
12230 );
12231
12232 if let Some((indent, delimiter)) = first_line_delimiter {
12233 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12234 }
12235 if let Some(last_line) = last_line_delimiter {
12236 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12237 }
12238
12239 wrapped_text
12240 };
12241
12242 // TODO: should always use char-based diff while still supporting cursor behavior that
12243 // matches vim.
12244 let mut diff_options = DiffOptions::default();
12245 if options.override_language_settings {
12246 diff_options.max_word_diff_len = 0;
12247 diff_options.max_word_diff_line_count = 0;
12248 } else {
12249 diff_options.max_word_diff_len = usize::MAX;
12250 diff_options.max_word_diff_line_count = usize::MAX;
12251 }
12252
12253 for (old_range, new_text) in
12254 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12255 {
12256 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12257 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12258 edits.push((edit_start..edit_end, new_text));
12259 }
12260
12261 rewrapped_row_ranges.push(start_row..=end_row);
12262 }
12263
12264 self.buffer
12265 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12266 }
12267
12268 pub fn cut_common(
12269 &mut self,
12270 cut_no_selection_line: bool,
12271 window: &mut Window,
12272 cx: &mut Context<Self>,
12273 ) -> ClipboardItem {
12274 let mut text = String::new();
12275 let buffer = self.buffer.read(cx).snapshot(cx);
12276 let mut selections = self.selections.all::<Point>(cx);
12277 let mut clipboard_selections = Vec::with_capacity(selections.len());
12278 {
12279 let max_point = buffer.max_point();
12280 let mut is_first = true;
12281 for selection in &mut selections {
12282 let is_entire_line =
12283 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12284 if is_entire_line {
12285 selection.start = Point::new(selection.start.row, 0);
12286 if !selection.is_empty() && selection.end.column == 0 {
12287 selection.end = cmp::min(max_point, selection.end);
12288 } else {
12289 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12290 }
12291 selection.goal = SelectionGoal::None;
12292 }
12293 if is_first {
12294 is_first = false;
12295 } else {
12296 text += "\n";
12297 }
12298 let mut len = 0;
12299 for chunk in buffer.text_for_range(selection.start..selection.end) {
12300 text.push_str(chunk);
12301 len += chunk.len();
12302 }
12303 clipboard_selections.push(ClipboardSelection {
12304 len,
12305 is_entire_line,
12306 first_line_indent: buffer
12307 .indent_size_for_line(MultiBufferRow(selection.start.row))
12308 .len,
12309 });
12310 }
12311 }
12312
12313 self.transact(window, cx, |this, window, cx| {
12314 this.change_selections(Default::default(), window, cx, |s| {
12315 s.select(selections);
12316 });
12317 this.insert("", window, cx);
12318 });
12319 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12320 }
12321
12322 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12323 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12324 let item = self.cut_common(true, window, cx);
12325 cx.write_to_clipboard(item);
12326 }
12327
12328 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12329 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12330 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12331 s.move_with(|snapshot, sel| {
12332 if sel.is_empty() {
12333 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12334 }
12335 if sel.is_empty() {
12336 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12337 }
12338 });
12339 });
12340 let item = self.cut_common(true, window, cx);
12341 cx.set_global(KillRing(item))
12342 }
12343
12344 pub fn kill_ring_yank(
12345 &mut self,
12346 _: &KillRingYank,
12347 window: &mut Window,
12348 cx: &mut Context<Self>,
12349 ) {
12350 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12351 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12352 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12353 (kill_ring.text().to_string(), kill_ring.metadata_json())
12354 } else {
12355 return;
12356 }
12357 } else {
12358 return;
12359 };
12360 self.do_paste(&text, metadata, false, window, cx);
12361 }
12362
12363 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12364 self.do_copy(true, cx);
12365 }
12366
12367 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12368 self.do_copy(false, cx);
12369 }
12370
12371 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12372 let selections = self.selections.all::<Point>(cx);
12373 let buffer = self.buffer.read(cx).read(cx);
12374 let mut text = String::new();
12375
12376 let mut clipboard_selections = Vec::with_capacity(selections.len());
12377 {
12378 let max_point = buffer.max_point();
12379 let mut is_first = true;
12380 for selection in &selections {
12381 let mut start = selection.start;
12382 let mut end = selection.end;
12383 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12384 if is_entire_line {
12385 start = Point::new(start.row, 0);
12386 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12387 }
12388
12389 let mut trimmed_selections = Vec::new();
12390 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12391 let row = MultiBufferRow(start.row);
12392 let first_indent = buffer.indent_size_for_line(row);
12393 if first_indent.len == 0 || start.column > first_indent.len {
12394 trimmed_selections.push(start..end);
12395 } else {
12396 trimmed_selections.push(
12397 Point::new(row.0, first_indent.len)
12398 ..Point::new(row.0, buffer.line_len(row)),
12399 );
12400 for row in start.row + 1..=end.row {
12401 let mut line_len = buffer.line_len(MultiBufferRow(row));
12402 if row == end.row {
12403 line_len = end.column;
12404 }
12405 if line_len == 0 {
12406 trimmed_selections
12407 .push(Point::new(row, 0)..Point::new(row, line_len));
12408 continue;
12409 }
12410 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12411 if row_indent_size.len >= first_indent.len {
12412 trimmed_selections.push(
12413 Point::new(row, first_indent.len)..Point::new(row, line_len),
12414 );
12415 } else {
12416 trimmed_selections.clear();
12417 trimmed_selections.push(start..end);
12418 break;
12419 }
12420 }
12421 }
12422 } else {
12423 trimmed_selections.push(start..end);
12424 }
12425
12426 for trimmed_range in trimmed_selections {
12427 if is_first {
12428 is_first = false;
12429 } else {
12430 text += "\n";
12431 }
12432 let mut len = 0;
12433 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12434 text.push_str(chunk);
12435 len += chunk.len();
12436 }
12437 clipboard_selections.push(ClipboardSelection {
12438 len,
12439 is_entire_line,
12440 first_line_indent: buffer
12441 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12442 .len,
12443 });
12444 }
12445 }
12446 }
12447
12448 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12449 text,
12450 clipboard_selections,
12451 ));
12452 }
12453
12454 pub fn do_paste(
12455 &mut self,
12456 text: &String,
12457 clipboard_selections: Option<Vec<ClipboardSelection>>,
12458 handle_entire_lines: bool,
12459 window: &mut Window,
12460 cx: &mut Context<Self>,
12461 ) {
12462 if self.read_only(cx) {
12463 return;
12464 }
12465
12466 let clipboard_text = Cow::Borrowed(text.as_str());
12467
12468 self.transact(window, cx, |this, window, cx| {
12469 let had_active_edit_prediction = this.has_active_edit_prediction();
12470 let old_selections = this.selections.all::<usize>(cx);
12471 let cursor_offset = this.selections.last::<usize>(cx).head();
12472
12473 if let Some(mut clipboard_selections) = clipboard_selections {
12474 let all_selections_were_entire_line =
12475 clipboard_selections.iter().all(|s| s.is_entire_line);
12476 let first_selection_indent_column =
12477 clipboard_selections.first().map(|s| s.first_line_indent);
12478 if clipboard_selections.len() != old_selections.len() {
12479 clipboard_selections.drain(..);
12480 }
12481 let mut auto_indent_on_paste = true;
12482
12483 this.buffer.update(cx, |buffer, cx| {
12484 let snapshot = buffer.read(cx);
12485 auto_indent_on_paste = snapshot
12486 .language_settings_at(cursor_offset, cx)
12487 .auto_indent_on_paste;
12488
12489 let mut start_offset = 0;
12490 let mut edits = Vec::new();
12491 let mut original_indent_columns = Vec::new();
12492 for (ix, selection) in old_selections.iter().enumerate() {
12493 let to_insert;
12494 let entire_line;
12495 let original_indent_column;
12496 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12497 let end_offset = start_offset + clipboard_selection.len;
12498 to_insert = &clipboard_text[start_offset..end_offset];
12499 entire_line = clipboard_selection.is_entire_line;
12500 start_offset = end_offset + 1;
12501 original_indent_column = Some(clipboard_selection.first_line_indent);
12502 } else {
12503 to_insert = &*clipboard_text;
12504 entire_line = all_selections_were_entire_line;
12505 original_indent_column = first_selection_indent_column
12506 }
12507
12508 let (range, to_insert) =
12509 if selection.is_empty() && handle_entire_lines && entire_line {
12510 // If the corresponding selection was empty when this slice of the
12511 // clipboard text was written, then the entire line containing the
12512 // selection was copied. If this selection is also currently empty,
12513 // then paste the line before the current line of the buffer.
12514 let column = selection.start.to_point(&snapshot).column as usize;
12515 let line_start = selection.start - column;
12516 (line_start..line_start, Cow::Borrowed(to_insert))
12517 } else {
12518 let language = snapshot.language_at(selection.head());
12519 let range = selection.range();
12520 if let Some(language) = language
12521 && language.name() == "Markdown".into()
12522 {
12523 edit_for_markdown_paste(
12524 &snapshot,
12525 range,
12526 to_insert,
12527 url::Url::parse(to_insert).ok(),
12528 )
12529 } else {
12530 (range, Cow::Borrowed(to_insert))
12531 }
12532 };
12533
12534 edits.push((range, to_insert));
12535 original_indent_columns.push(original_indent_column);
12536 }
12537 drop(snapshot);
12538
12539 buffer.edit(
12540 edits,
12541 if auto_indent_on_paste {
12542 Some(AutoindentMode::Block {
12543 original_indent_columns,
12544 })
12545 } else {
12546 None
12547 },
12548 cx,
12549 );
12550 });
12551
12552 let selections = this.selections.all::<usize>(cx);
12553 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12554 } else {
12555 let url = url::Url::parse(&clipboard_text).ok();
12556
12557 let auto_indent_mode = if !clipboard_text.is_empty() {
12558 Some(AutoindentMode::Block {
12559 original_indent_columns: Vec::new(),
12560 })
12561 } else {
12562 None
12563 };
12564
12565 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12566 let snapshot = buffer.snapshot(cx);
12567
12568 let anchors = old_selections
12569 .iter()
12570 .map(|s| {
12571 let anchor = snapshot.anchor_after(s.head());
12572 s.map(|_| anchor)
12573 })
12574 .collect::<Vec<_>>();
12575
12576 let mut edits = Vec::new();
12577
12578 for selection in old_selections.iter() {
12579 let language = snapshot.language_at(selection.head());
12580 let range = selection.range();
12581
12582 let (edit_range, edit_text) = if let Some(language) = language
12583 && language.name() == "Markdown".into()
12584 {
12585 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12586 } else {
12587 (range, clipboard_text.clone())
12588 };
12589
12590 edits.push((edit_range, edit_text));
12591 }
12592
12593 drop(snapshot);
12594 buffer.edit(edits, auto_indent_mode, cx);
12595
12596 anchors
12597 });
12598
12599 this.change_selections(Default::default(), window, cx, |s| {
12600 s.select_anchors(selection_anchors);
12601 });
12602 }
12603
12604 let trigger_in_words =
12605 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12606
12607 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12608 });
12609 }
12610
12611 pub fn diff_clipboard_with_selection(
12612 &mut self,
12613 _: &DiffClipboardWithSelection,
12614 window: &mut Window,
12615 cx: &mut Context<Self>,
12616 ) {
12617 let selections = self.selections.all::<usize>(cx);
12618
12619 if selections.is_empty() {
12620 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12621 return;
12622 };
12623
12624 let clipboard_text = match cx.read_from_clipboard() {
12625 Some(item) => match item.entries().first() {
12626 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12627 _ => None,
12628 },
12629 None => None,
12630 };
12631
12632 let Some(clipboard_text) = clipboard_text else {
12633 log::warn!("Clipboard doesn't contain text.");
12634 return;
12635 };
12636
12637 window.dispatch_action(
12638 Box::new(DiffClipboardWithSelectionData {
12639 clipboard_text,
12640 editor: cx.entity(),
12641 }),
12642 cx,
12643 );
12644 }
12645
12646 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12647 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12648 if let Some(item) = cx.read_from_clipboard() {
12649 let entries = item.entries();
12650
12651 match entries.first() {
12652 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12653 // of all the pasted entries.
12654 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12655 .do_paste(
12656 clipboard_string.text(),
12657 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12658 true,
12659 window,
12660 cx,
12661 ),
12662 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12663 }
12664 }
12665 }
12666
12667 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12668 if self.read_only(cx) {
12669 return;
12670 }
12671
12672 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12673
12674 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12675 if let Some((selections, _)) =
12676 self.selection_history.transaction(transaction_id).cloned()
12677 {
12678 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12679 s.select_anchors(selections.to_vec());
12680 });
12681 } else {
12682 log::error!(
12683 "No entry in selection_history found for undo. \
12684 This may correspond to a bug where undo does not update the selection. \
12685 If this is occurring, please add details to \
12686 https://github.com/zed-industries/zed/issues/22692"
12687 );
12688 }
12689 self.request_autoscroll(Autoscroll::fit(), cx);
12690 self.unmark_text(window, cx);
12691 self.refresh_edit_prediction(true, false, window, cx);
12692 cx.emit(EditorEvent::Edited { transaction_id });
12693 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12694 }
12695 }
12696
12697 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12698 if self.read_only(cx) {
12699 return;
12700 }
12701
12702 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12703
12704 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12705 if let Some((_, Some(selections))) =
12706 self.selection_history.transaction(transaction_id).cloned()
12707 {
12708 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12709 s.select_anchors(selections.to_vec());
12710 });
12711 } else {
12712 log::error!(
12713 "No entry in selection_history found for redo. \
12714 This may correspond to a bug where undo does not update the selection. \
12715 If this is occurring, please add details to \
12716 https://github.com/zed-industries/zed/issues/22692"
12717 );
12718 }
12719 self.request_autoscroll(Autoscroll::fit(), cx);
12720 self.unmark_text(window, cx);
12721 self.refresh_edit_prediction(true, false, window, cx);
12722 cx.emit(EditorEvent::Edited { transaction_id });
12723 }
12724 }
12725
12726 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12727 self.buffer
12728 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12729 }
12730
12731 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12732 self.buffer
12733 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12734 }
12735
12736 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12738 self.change_selections(Default::default(), window, cx, |s| {
12739 s.move_with(|map, selection| {
12740 let cursor = if selection.is_empty() {
12741 movement::left(map, selection.start)
12742 } else {
12743 selection.start
12744 };
12745 selection.collapse_to(cursor, SelectionGoal::None);
12746 });
12747 })
12748 }
12749
12750 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12752 self.change_selections(Default::default(), window, cx, |s| {
12753 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12754 })
12755 }
12756
12757 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12758 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12759 self.change_selections(Default::default(), window, cx, |s| {
12760 s.move_with(|map, selection| {
12761 let cursor = if selection.is_empty() {
12762 movement::right(map, selection.end)
12763 } else {
12764 selection.end
12765 };
12766 selection.collapse_to(cursor, SelectionGoal::None)
12767 });
12768 })
12769 }
12770
12771 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12773 self.change_selections(Default::default(), window, cx, |s| {
12774 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12775 })
12776 }
12777
12778 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12779 if self.take_rename(true, window, cx).is_some() {
12780 return;
12781 }
12782
12783 if self.mode.is_single_line() {
12784 cx.propagate();
12785 return;
12786 }
12787
12788 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12789
12790 let text_layout_details = &self.text_layout_details(window);
12791 let selection_count = self.selections.count();
12792 let first_selection = self.selections.first_anchor();
12793
12794 self.change_selections(Default::default(), window, cx, |s| {
12795 s.move_with(|map, selection| {
12796 if !selection.is_empty() {
12797 selection.goal = SelectionGoal::None;
12798 }
12799 let (cursor, goal) = movement::up(
12800 map,
12801 selection.start,
12802 selection.goal,
12803 false,
12804 text_layout_details,
12805 );
12806 selection.collapse_to(cursor, goal);
12807 });
12808 });
12809
12810 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12811 {
12812 cx.propagate();
12813 }
12814 }
12815
12816 pub fn move_up_by_lines(
12817 &mut self,
12818 action: &MoveUpByLines,
12819 window: &mut Window,
12820 cx: &mut Context<Self>,
12821 ) {
12822 if self.take_rename(true, window, cx).is_some() {
12823 return;
12824 }
12825
12826 if self.mode.is_single_line() {
12827 cx.propagate();
12828 return;
12829 }
12830
12831 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12832
12833 let text_layout_details = &self.text_layout_details(window);
12834
12835 self.change_selections(Default::default(), window, cx, |s| {
12836 s.move_with(|map, selection| {
12837 if !selection.is_empty() {
12838 selection.goal = SelectionGoal::None;
12839 }
12840 let (cursor, goal) = movement::up_by_rows(
12841 map,
12842 selection.start,
12843 action.lines,
12844 selection.goal,
12845 false,
12846 text_layout_details,
12847 );
12848 selection.collapse_to(cursor, goal);
12849 });
12850 })
12851 }
12852
12853 pub fn move_down_by_lines(
12854 &mut self,
12855 action: &MoveDownByLines,
12856 window: &mut Window,
12857 cx: &mut Context<Self>,
12858 ) {
12859 if self.take_rename(true, window, cx).is_some() {
12860 return;
12861 }
12862
12863 if self.mode.is_single_line() {
12864 cx.propagate();
12865 return;
12866 }
12867
12868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12869
12870 let text_layout_details = &self.text_layout_details(window);
12871
12872 self.change_selections(Default::default(), window, cx, |s| {
12873 s.move_with(|map, selection| {
12874 if !selection.is_empty() {
12875 selection.goal = SelectionGoal::None;
12876 }
12877 let (cursor, goal) = movement::down_by_rows(
12878 map,
12879 selection.start,
12880 action.lines,
12881 selection.goal,
12882 false,
12883 text_layout_details,
12884 );
12885 selection.collapse_to(cursor, goal);
12886 });
12887 })
12888 }
12889
12890 pub fn select_down_by_lines(
12891 &mut self,
12892 action: &SelectDownByLines,
12893 window: &mut Window,
12894 cx: &mut Context<Self>,
12895 ) {
12896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12897 let text_layout_details = &self.text_layout_details(window);
12898 self.change_selections(Default::default(), window, cx, |s| {
12899 s.move_heads_with(|map, head, goal| {
12900 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12901 })
12902 })
12903 }
12904
12905 pub fn select_up_by_lines(
12906 &mut self,
12907 action: &SelectUpByLines,
12908 window: &mut Window,
12909 cx: &mut Context<Self>,
12910 ) {
12911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12912 let text_layout_details = &self.text_layout_details(window);
12913 self.change_selections(Default::default(), window, cx, |s| {
12914 s.move_heads_with(|map, head, goal| {
12915 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12916 })
12917 })
12918 }
12919
12920 pub fn select_page_up(
12921 &mut self,
12922 _: &SelectPageUp,
12923 window: &mut Window,
12924 cx: &mut Context<Self>,
12925 ) {
12926 let Some(row_count) = self.visible_row_count() else {
12927 return;
12928 };
12929
12930 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12931
12932 let text_layout_details = &self.text_layout_details(window);
12933
12934 self.change_selections(Default::default(), window, cx, |s| {
12935 s.move_heads_with(|map, head, goal| {
12936 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12937 })
12938 })
12939 }
12940
12941 pub fn move_page_up(
12942 &mut self,
12943 action: &MovePageUp,
12944 window: &mut Window,
12945 cx: &mut Context<Self>,
12946 ) {
12947 if self.take_rename(true, window, cx).is_some() {
12948 return;
12949 }
12950
12951 if self
12952 .context_menu
12953 .borrow_mut()
12954 .as_mut()
12955 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12956 .unwrap_or(false)
12957 {
12958 return;
12959 }
12960
12961 if matches!(self.mode, EditorMode::SingleLine) {
12962 cx.propagate();
12963 return;
12964 }
12965
12966 let Some(row_count) = self.visible_row_count() else {
12967 return;
12968 };
12969
12970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12971
12972 let effects = if action.center_cursor {
12973 SelectionEffects::scroll(Autoscroll::center())
12974 } else {
12975 SelectionEffects::default()
12976 };
12977
12978 let text_layout_details = &self.text_layout_details(window);
12979
12980 self.change_selections(effects, window, cx, |s| {
12981 s.move_with(|map, selection| {
12982 if !selection.is_empty() {
12983 selection.goal = SelectionGoal::None;
12984 }
12985 let (cursor, goal) = movement::up_by_rows(
12986 map,
12987 selection.end,
12988 row_count,
12989 selection.goal,
12990 false,
12991 text_layout_details,
12992 );
12993 selection.collapse_to(cursor, goal);
12994 });
12995 });
12996 }
12997
12998 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13000 let text_layout_details = &self.text_layout_details(window);
13001 self.change_selections(Default::default(), window, cx, |s| {
13002 s.move_heads_with(|map, head, goal| {
13003 movement::up(map, head, goal, false, text_layout_details)
13004 })
13005 })
13006 }
13007
13008 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13009 self.take_rename(true, window, cx);
13010
13011 if self.mode.is_single_line() {
13012 cx.propagate();
13013 return;
13014 }
13015
13016 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13017
13018 let text_layout_details = &self.text_layout_details(window);
13019 let selection_count = self.selections.count();
13020 let first_selection = self.selections.first_anchor();
13021
13022 self.change_selections(Default::default(), window, cx, |s| {
13023 s.move_with(|map, selection| {
13024 if !selection.is_empty() {
13025 selection.goal = SelectionGoal::None;
13026 }
13027 let (cursor, goal) = movement::down(
13028 map,
13029 selection.end,
13030 selection.goal,
13031 false,
13032 text_layout_details,
13033 );
13034 selection.collapse_to(cursor, goal);
13035 });
13036 });
13037
13038 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13039 {
13040 cx.propagate();
13041 }
13042 }
13043
13044 pub fn select_page_down(
13045 &mut self,
13046 _: &SelectPageDown,
13047 window: &mut Window,
13048 cx: &mut Context<Self>,
13049 ) {
13050 let Some(row_count) = self.visible_row_count() else {
13051 return;
13052 };
13053
13054 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13055
13056 let text_layout_details = &self.text_layout_details(window);
13057
13058 self.change_selections(Default::default(), window, cx, |s| {
13059 s.move_heads_with(|map, head, goal| {
13060 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13061 })
13062 })
13063 }
13064
13065 pub fn move_page_down(
13066 &mut self,
13067 action: &MovePageDown,
13068 window: &mut Window,
13069 cx: &mut Context<Self>,
13070 ) {
13071 if self.take_rename(true, window, cx).is_some() {
13072 return;
13073 }
13074
13075 if self
13076 .context_menu
13077 .borrow_mut()
13078 .as_mut()
13079 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13080 .unwrap_or(false)
13081 {
13082 return;
13083 }
13084
13085 if matches!(self.mode, EditorMode::SingleLine) {
13086 cx.propagate();
13087 return;
13088 }
13089
13090 let Some(row_count) = self.visible_row_count() else {
13091 return;
13092 };
13093
13094 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13095
13096 let effects = if action.center_cursor {
13097 SelectionEffects::scroll(Autoscroll::center())
13098 } else {
13099 SelectionEffects::default()
13100 };
13101
13102 let text_layout_details = &self.text_layout_details(window);
13103 self.change_selections(effects, window, cx, |s| {
13104 s.move_with(|map, selection| {
13105 if !selection.is_empty() {
13106 selection.goal = SelectionGoal::None;
13107 }
13108 let (cursor, goal) = movement::down_by_rows(
13109 map,
13110 selection.end,
13111 row_count,
13112 selection.goal,
13113 false,
13114 text_layout_details,
13115 );
13116 selection.collapse_to(cursor, goal);
13117 });
13118 });
13119 }
13120
13121 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13123 let text_layout_details = &self.text_layout_details(window);
13124 self.change_selections(Default::default(), window, cx, |s| {
13125 s.move_heads_with(|map, head, goal| {
13126 movement::down(map, head, goal, false, text_layout_details)
13127 })
13128 });
13129 }
13130
13131 pub fn context_menu_first(
13132 &mut self,
13133 _: &ContextMenuFirst,
13134 window: &mut Window,
13135 cx: &mut Context<Self>,
13136 ) {
13137 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13138 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13139 }
13140 }
13141
13142 pub fn context_menu_prev(
13143 &mut self,
13144 _: &ContextMenuPrevious,
13145 window: &mut Window,
13146 cx: &mut Context<Self>,
13147 ) {
13148 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13149 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13150 }
13151 }
13152
13153 pub fn context_menu_next(
13154 &mut self,
13155 _: &ContextMenuNext,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13160 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13161 }
13162 }
13163
13164 pub fn context_menu_last(
13165 &mut self,
13166 _: &ContextMenuLast,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13171 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13172 }
13173 }
13174
13175 pub fn signature_help_prev(
13176 &mut self,
13177 _: &SignatureHelpPrevious,
13178 _: &mut Window,
13179 cx: &mut Context<Self>,
13180 ) {
13181 if let Some(popover) = self.signature_help_state.popover_mut() {
13182 if popover.current_signature == 0 {
13183 popover.current_signature = popover.signatures.len() - 1;
13184 } else {
13185 popover.current_signature -= 1;
13186 }
13187 cx.notify();
13188 }
13189 }
13190
13191 pub fn signature_help_next(
13192 &mut self,
13193 _: &SignatureHelpNext,
13194 _: &mut Window,
13195 cx: &mut Context<Self>,
13196 ) {
13197 if let Some(popover) = self.signature_help_state.popover_mut() {
13198 if popover.current_signature + 1 == popover.signatures.len() {
13199 popover.current_signature = 0;
13200 } else {
13201 popover.current_signature += 1;
13202 }
13203 cx.notify();
13204 }
13205 }
13206
13207 pub fn move_to_previous_word_start(
13208 &mut self,
13209 _: &MoveToPreviousWordStart,
13210 window: &mut Window,
13211 cx: &mut Context<Self>,
13212 ) {
13213 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13214 self.change_selections(Default::default(), window, cx, |s| {
13215 s.move_cursors_with(|map, head, _| {
13216 (
13217 movement::previous_word_start(map, head),
13218 SelectionGoal::None,
13219 )
13220 });
13221 })
13222 }
13223
13224 pub fn move_to_previous_subword_start(
13225 &mut self,
13226 _: &MoveToPreviousSubwordStart,
13227 window: &mut Window,
13228 cx: &mut Context<Self>,
13229 ) {
13230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13231 self.change_selections(Default::default(), window, cx, |s| {
13232 s.move_cursors_with(|map, head, _| {
13233 (
13234 movement::previous_subword_start(map, head),
13235 SelectionGoal::None,
13236 )
13237 });
13238 })
13239 }
13240
13241 pub fn select_to_previous_word_start(
13242 &mut self,
13243 _: &SelectToPreviousWordStart,
13244 window: &mut Window,
13245 cx: &mut Context<Self>,
13246 ) {
13247 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13248 self.change_selections(Default::default(), window, cx, |s| {
13249 s.move_heads_with(|map, head, _| {
13250 (
13251 movement::previous_word_start(map, head),
13252 SelectionGoal::None,
13253 )
13254 });
13255 })
13256 }
13257
13258 pub fn select_to_previous_subword_start(
13259 &mut self,
13260 _: &SelectToPreviousSubwordStart,
13261 window: &mut Window,
13262 cx: &mut Context<Self>,
13263 ) {
13264 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13265 self.change_selections(Default::default(), window, cx, |s| {
13266 s.move_heads_with(|map, head, _| {
13267 (
13268 movement::previous_subword_start(map, head),
13269 SelectionGoal::None,
13270 )
13271 });
13272 })
13273 }
13274
13275 pub fn delete_to_previous_word_start(
13276 &mut self,
13277 action: &DeleteToPreviousWordStart,
13278 window: &mut Window,
13279 cx: &mut Context<Self>,
13280 ) {
13281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13282 self.transact(window, cx, |this, window, cx| {
13283 this.select_autoclose_pair(window, cx);
13284 this.change_selections(Default::default(), window, cx, |s| {
13285 s.move_with(|map, selection| {
13286 if selection.is_empty() {
13287 let mut cursor = if action.ignore_newlines {
13288 movement::previous_word_start(map, selection.head())
13289 } else {
13290 movement::previous_word_start_or_newline(map, selection.head())
13291 };
13292 cursor = movement::adjust_greedy_deletion(
13293 map,
13294 selection.head(),
13295 cursor,
13296 action.ignore_brackets,
13297 );
13298 selection.set_head(cursor, SelectionGoal::None);
13299 }
13300 });
13301 });
13302 this.insert("", window, cx);
13303 });
13304 }
13305
13306 pub fn delete_to_previous_subword_start(
13307 &mut self,
13308 _: &DeleteToPreviousSubwordStart,
13309 window: &mut Window,
13310 cx: &mut Context<Self>,
13311 ) {
13312 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13313 self.transact(window, cx, |this, window, cx| {
13314 this.select_autoclose_pair(window, cx);
13315 this.change_selections(Default::default(), window, cx, |s| {
13316 s.move_with(|map, selection| {
13317 if selection.is_empty() {
13318 let mut cursor = movement::previous_subword_start(map, selection.head());
13319 cursor =
13320 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13321 selection.set_head(cursor, SelectionGoal::None);
13322 }
13323 });
13324 });
13325 this.insert("", window, cx);
13326 });
13327 }
13328
13329 pub fn move_to_next_word_end(
13330 &mut self,
13331 _: &MoveToNextWordEnd,
13332 window: &mut Window,
13333 cx: &mut Context<Self>,
13334 ) {
13335 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13336 self.change_selections(Default::default(), window, cx, |s| {
13337 s.move_cursors_with(|map, head, _| {
13338 (movement::next_word_end(map, head), SelectionGoal::None)
13339 });
13340 })
13341 }
13342
13343 pub fn move_to_next_subword_end(
13344 &mut self,
13345 _: &MoveToNextSubwordEnd,
13346 window: &mut Window,
13347 cx: &mut Context<Self>,
13348 ) {
13349 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13350 self.change_selections(Default::default(), window, cx, |s| {
13351 s.move_cursors_with(|map, head, _| {
13352 (movement::next_subword_end(map, head), SelectionGoal::None)
13353 });
13354 })
13355 }
13356
13357 pub fn select_to_next_word_end(
13358 &mut self,
13359 _: &SelectToNextWordEnd,
13360 window: &mut Window,
13361 cx: &mut Context<Self>,
13362 ) {
13363 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13364 self.change_selections(Default::default(), window, cx, |s| {
13365 s.move_heads_with(|map, head, _| {
13366 (movement::next_word_end(map, head), SelectionGoal::None)
13367 });
13368 })
13369 }
13370
13371 pub fn select_to_next_subword_end(
13372 &mut self,
13373 _: &SelectToNextSubwordEnd,
13374 window: &mut Window,
13375 cx: &mut Context<Self>,
13376 ) {
13377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13378 self.change_selections(Default::default(), window, cx, |s| {
13379 s.move_heads_with(|map, head, _| {
13380 (movement::next_subword_end(map, head), SelectionGoal::None)
13381 });
13382 })
13383 }
13384
13385 pub fn delete_to_next_word_end(
13386 &mut self,
13387 action: &DeleteToNextWordEnd,
13388 window: &mut Window,
13389 cx: &mut Context<Self>,
13390 ) {
13391 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13392 self.transact(window, cx, |this, window, cx| {
13393 this.change_selections(Default::default(), window, cx, |s| {
13394 s.move_with(|map, selection| {
13395 if selection.is_empty() {
13396 let mut cursor = if action.ignore_newlines {
13397 movement::next_word_end(map, selection.head())
13398 } else {
13399 movement::next_word_end_or_newline(map, selection.head())
13400 };
13401 cursor = movement::adjust_greedy_deletion(
13402 map,
13403 selection.head(),
13404 cursor,
13405 action.ignore_brackets,
13406 );
13407 selection.set_head(cursor, SelectionGoal::None);
13408 }
13409 });
13410 });
13411 this.insert("", window, cx);
13412 });
13413 }
13414
13415 pub fn delete_to_next_subword_end(
13416 &mut self,
13417 _: &DeleteToNextSubwordEnd,
13418 window: &mut Window,
13419 cx: &mut Context<Self>,
13420 ) {
13421 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13422 self.transact(window, cx, |this, window, cx| {
13423 this.change_selections(Default::default(), window, cx, |s| {
13424 s.move_with(|map, selection| {
13425 if selection.is_empty() {
13426 let mut cursor = movement::next_subword_end(map, selection.head());
13427 cursor =
13428 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13429 selection.set_head(cursor, SelectionGoal::None);
13430 }
13431 });
13432 });
13433 this.insert("", window, cx);
13434 });
13435 }
13436
13437 pub fn move_to_beginning_of_line(
13438 &mut self,
13439 action: &MoveToBeginningOfLine,
13440 window: &mut Window,
13441 cx: &mut Context<Self>,
13442 ) {
13443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13444 self.change_selections(Default::default(), window, cx, |s| {
13445 s.move_cursors_with(|map, head, _| {
13446 (
13447 movement::indented_line_beginning(
13448 map,
13449 head,
13450 action.stop_at_soft_wraps,
13451 action.stop_at_indent,
13452 ),
13453 SelectionGoal::None,
13454 )
13455 });
13456 })
13457 }
13458
13459 pub fn select_to_beginning_of_line(
13460 &mut self,
13461 action: &SelectToBeginningOfLine,
13462 window: &mut Window,
13463 cx: &mut Context<Self>,
13464 ) {
13465 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13466 self.change_selections(Default::default(), window, cx, |s| {
13467 s.move_heads_with(|map, head, _| {
13468 (
13469 movement::indented_line_beginning(
13470 map,
13471 head,
13472 action.stop_at_soft_wraps,
13473 action.stop_at_indent,
13474 ),
13475 SelectionGoal::None,
13476 )
13477 });
13478 });
13479 }
13480
13481 pub fn delete_to_beginning_of_line(
13482 &mut self,
13483 action: &DeleteToBeginningOfLine,
13484 window: &mut Window,
13485 cx: &mut Context<Self>,
13486 ) {
13487 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13488 self.transact(window, cx, |this, window, cx| {
13489 this.change_selections(Default::default(), window, cx, |s| {
13490 s.move_with(|_, selection| {
13491 selection.reversed = true;
13492 });
13493 });
13494
13495 this.select_to_beginning_of_line(
13496 &SelectToBeginningOfLine {
13497 stop_at_soft_wraps: false,
13498 stop_at_indent: action.stop_at_indent,
13499 },
13500 window,
13501 cx,
13502 );
13503 this.backspace(&Backspace, window, cx);
13504 });
13505 }
13506
13507 pub fn move_to_end_of_line(
13508 &mut self,
13509 action: &MoveToEndOfLine,
13510 window: &mut Window,
13511 cx: &mut Context<Self>,
13512 ) {
13513 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13514 self.change_selections(Default::default(), window, cx, |s| {
13515 s.move_cursors_with(|map, head, _| {
13516 (
13517 movement::line_end(map, head, action.stop_at_soft_wraps),
13518 SelectionGoal::None,
13519 )
13520 });
13521 })
13522 }
13523
13524 pub fn select_to_end_of_line(
13525 &mut self,
13526 action: &SelectToEndOfLine,
13527 window: &mut Window,
13528 cx: &mut Context<Self>,
13529 ) {
13530 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13531 self.change_selections(Default::default(), window, cx, |s| {
13532 s.move_heads_with(|map, head, _| {
13533 (
13534 movement::line_end(map, head, action.stop_at_soft_wraps),
13535 SelectionGoal::None,
13536 )
13537 });
13538 })
13539 }
13540
13541 pub fn delete_to_end_of_line(
13542 &mut self,
13543 _: &DeleteToEndOfLine,
13544 window: &mut Window,
13545 cx: &mut Context<Self>,
13546 ) {
13547 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13548 self.transact(window, cx, |this, window, cx| {
13549 this.select_to_end_of_line(
13550 &SelectToEndOfLine {
13551 stop_at_soft_wraps: false,
13552 },
13553 window,
13554 cx,
13555 );
13556 this.delete(&Delete, window, cx);
13557 });
13558 }
13559
13560 pub fn cut_to_end_of_line(
13561 &mut self,
13562 action: &CutToEndOfLine,
13563 window: &mut Window,
13564 cx: &mut Context<Self>,
13565 ) {
13566 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13567 self.transact(window, cx, |this, window, cx| {
13568 this.select_to_end_of_line(
13569 &SelectToEndOfLine {
13570 stop_at_soft_wraps: false,
13571 },
13572 window,
13573 cx,
13574 );
13575 if !action.stop_at_newlines {
13576 this.change_selections(Default::default(), window, cx, |s| {
13577 s.move_with(|_, sel| {
13578 if sel.is_empty() {
13579 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13580 }
13581 });
13582 });
13583 }
13584 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13585 let item = this.cut_common(false, window, cx);
13586 cx.write_to_clipboard(item);
13587 });
13588 }
13589
13590 pub fn move_to_start_of_paragraph(
13591 &mut self,
13592 _: &MoveToStartOfParagraph,
13593 window: &mut Window,
13594 cx: &mut Context<Self>,
13595 ) {
13596 if matches!(self.mode, EditorMode::SingleLine) {
13597 cx.propagate();
13598 return;
13599 }
13600 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13601 self.change_selections(Default::default(), window, cx, |s| {
13602 s.move_with(|map, selection| {
13603 selection.collapse_to(
13604 movement::start_of_paragraph(map, selection.head(), 1),
13605 SelectionGoal::None,
13606 )
13607 });
13608 })
13609 }
13610
13611 pub fn move_to_end_of_paragraph(
13612 &mut self,
13613 _: &MoveToEndOfParagraph,
13614 window: &mut Window,
13615 cx: &mut Context<Self>,
13616 ) {
13617 if matches!(self.mode, EditorMode::SingleLine) {
13618 cx.propagate();
13619 return;
13620 }
13621 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13622 self.change_selections(Default::default(), window, cx, |s| {
13623 s.move_with(|map, selection| {
13624 selection.collapse_to(
13625 movement::end_of_paragraph(map, selection.head(), 1),
13626 SelectionGoal::None,
13627 )
13628 });
13629 })
13630 }
13631
13632 pub fn select_to_start_of_paragraph(
13633 &mut self,
13634 _: &SelectToStartOfParagraph,
13635 window: &mut Window,
13636 cx: &mut Context<Self>,
13637 ) {
13638 if matches!(self.mode, EditorMode::SingleLine) {
13639 cx.propagate();
13640 return;
13641 }
13642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13643 self.change_selections(Default::default(), window, cx, |s| {
13644 s.move_heads_with(|map, head, _| {
13645 (
13646 movement::start_of_paragraph(map, head, 1),
13647 SelectionGoal::None,
13648 )
13649 });
13650 })
13651 }
13652
13653 pub fn select_to_end_of_paragraph(
13654 &mut self,
13655 _: &SelectToEndOfParagraph,
13656 window: &mut Window,
13657 cx: &mut Context<Self>,
13658 ) {
13659 if matches!(self.mode, EditorMode::SingleLine) {
13660 cx.propagate();
13661 return;
13662 }
13663 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13664 self.change_selections(Default::default(), window, cx, |s| {
13665 s.move_heads_with(|map, head, _| {
13666 (
13667 movement::end_of_paragraph(map, head, 1),
13668 SelectionGoal::None,
13669 )
13670 });
13671 })
13672 }
13673
13674 pub fn move_to_start_of_excerpt(
13675 &mut self,
13676 _: &MoveToStartOfExcerpt,
13677 window: &mut Window,
13678 cx: &mut Context<Self>,
13679 ) {
13680 if matches!(self.mode, EditorMode::SingleLine) {
13681 cx.propagate();
13682 return;
13683 }
13684 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13685 self.change_selections(Default::default(), window, cx, |s| {
13686 s.move_with(|map, selection| {
13687 selection.collapse_to(
13688 movement::start_of_excerpt(
13689 map,
13690 selection.head(),
13691 workspace::searchable::Direction::Prev,
13692 ),
13693 SelectionGoal::None,
13694 )
13695 });
13696 })
13697 }
13698
13699 pub fn move_to_start_of_next_excerpt(
13700 &mut self,
13701 _: &MoveToStartOfNextExcerpt,
13702 window: &mut Window,
13703 cx: &mut Context<Self>,
13704 ) {
13705 if matches!(self.mode, EditorMode::SingleLine) {
13706 cx.propagate();
13707 return;
13708 }
13709
13710 self.change_selections(Default::default(), window, cx, |s| {
13711 s.move_with(|map, selection| {
13712 selection.collapse_to(
13713 movement::start_of_excerpt(
13714 map,
13715 selection.head(),
13716 workspace::searchable::Direction::Next,
13717 ),
13718 SelectionGoal::None,
13719 )
13720 });
13721 })
13722 }
13723
13724 pub fn move_to_end_of_excerpt(
13725 &mut self,
13726 _: &MoveToEndOfExcerpt,
13727 window: &mut Window,
13728 cx: &mut Context<Self>,
13729 ) {
13730 if matches!(self.mode, EditorMode::SingleLine) {
13731 cx.propagate();
13732 return;
13733 }
13734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13735 self.change_selections(Default::default(), window, cx, |s| {
13736 s.move_with(|map, selection| {
13737 selection.collapse_to(
13738 movement::end_of_excerpt(
13739 map,
13740 selection.head(),
13741 workspace::searchable::Direction::Next,
13742 ),
13743 SelectionGoal::None,
13744 )
13745 });
13746 })
13747 }
13748
13749 pub fn move_to_end_of_previous_excerpt(
13750 &mut self,
13751 _: &MoveToEndOfPreviousExcerpt,
13752 window: &mut Window,
13753 cx: &mut Context<Self>,
13754 ) {
13755 if matches!(self.mode, EditorMode::SingleLine) {
13756 cx.propagate();
13757 return;
13758 }
13759 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13760 self.change_selections(Default::default(), window, cx, |s| {
13761 s.move_with(|map, selection| {
13762 selection.collapse_to(
13763 movement::end_of_excerpt(
13764 map,
13765 selection.head(),
13766 workspace::searchable::Direction::Prev,
13767 ),
13768 SelectionGoal::None,
13769 )
13770 });
13771 })
13772 }
13773
13774 pub fn select_to_start_of_excerpt(
13775 &mut self,
13776 _: &SelectToStartOfExcerpt,
13777 window: &mut Window,
13778 cx: &mut Context<Self>,
13779 ) {
13780 if matches!(self.mode, EditorMode::SingleLine) {
13781 cx.propagate();
13782 return;
13783 }
13784 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13785 self.change_selections(Default::default(), window, cx, |s| {
13786 s.move_heads_with(|map, head, _| {
13787 (
13788 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13789 SelectionGoal::None,
13790 )
13791 });
13792 })
13793 }
13794
13795 pub fn select_to_start_of_next_excerpt(
13796 &mut self,
13797 _: &SelectToStartOfNextExcerpt,
13798 window: &mut Window,
13799 cx: &mut Context<Self>,
13800 ) {
13801 if matches!(self.mode, EditorMode::SingleLine) {
13802 cx.propagate();
13803 return;
13804 }
13805 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13806 self.change_selections(Default::default(), window, cx, |s| {
13807 s.move_heads_with(|map, head, _| {
13808 (
13809 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13810 SelectionGoal::None,
13811 )
13812 });
13813 })
13814 }
13815
13816 pub fn select_to_end_of_excerpt(
13817 &mut self,
13818 _: &SelectToEndOfExcerpt,
13819 window: &mut Window,
13820 cx: &mut Context<Self>,
13821 ) {
13822 if matches!(self.mode, EditorMode::SingleLine) {
13823 cx.propagate();
13824 return;
13825 }
13826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13827 self.change_selections(Default::default(), window, cx, |s| {
13828 s.move_heads_with(|map, head, _| {
13829 (
13830 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13831 SelectionGoal::None,
13832 )
13833 });
13834 })
13835 }
13836
13837 pub fn select_to_end_of_previous_excerpt(
13838 &mut self,
13839 _: &SelectToEndOfPreviousExcerpt,
13840 window: &mut Window,
13841 cx: &mut Context<Self>,
13842 ) {
13843 if matches!(self.mode, EditorMode::SingleLine) {
13844 cx.propagate();
13845 return;
13846 }
13847 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13848 self.change_selections(Default::default(), window, cx, |s| {
13849 s.move_heads_with(|map, head, _| {
13850 (
13851 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13852 SelectionGoal::None,
13853 )
13854 });
13855 })
13856 }
13857
13858 pub fn move_to_beginning(
13859 &mut self,
13860 _: &MoveToBeginning,
13861 window: &mut Window,
13862 cx: &mut Context<Self>,
13863 ) {
13864 if matches!(self.mode, EditorMode::SingleLine) {
13865 cx.propagate();
13866 return;
13867 }
13868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13869 self.change_selections(Default::default(), window, cx, |s| {
13870 s.select_ranges(vec![0..0]);
13871 });
13872 }
13873
13874 pub fn select_to_beginning(
13875 &mut self,
13876 _: &SelectToBeginning,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) {
13880 let mut selection = self.selections.last::<Point>(cx);
13881 selection.set_head(Point::zero(), SelectionGoal::None);
13882 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13883 self.change_selections(Default::default(), window, cx, |s| {
13884 s.select(vec![selection]);
13885 });
13886 }
13887
13888 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13889 if matches!(self.mode, EditorMode::SingleLine) {
13890 cx.propagate();
13891 return;
13892 }
13893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13894 let cursor = self.buffer.read(cx).read(cx).len();
13895 self.change_selections(Default::default(), window, cx, |s| {
13896 s.select_ranges(vec![cursor..cursor])
13897 });
13898 }
13899
13900 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13901 self.nav_history = nav_history;
13902 }
13903
13904 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13905 self.nav_history.as_ref()
13906 }
13907
13908 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13909 self.push_to_nav_history(
13910 self.selections.newest_anchor().head(),
13911 None,
13912 false,
13913 true,
13914 cx,
13915 );
13916 }
13917
13918 fn push_to_nav_history(
13919 &mut self,
13920 cursor_anchor: Anchor,
13921 new_position: Option<Point>,
13922 is_deactivate: bool,
13923 always: bool,
13924 cx: &mut Context<Self>,
13925 ) {
13926 if let Some(nav_history) = self.nav_history.as_mut() {
13927 let buffer = self.buffer.read(cx).read(cx);
13928 let cursor_position = cursor_anchor.to_point(&buffer);
13929 let scroll_state = self.scroll_manager.anchor();
13930 let scroll_top_row = scroll_state.top_row(&buffer);
13931 drop(buffer);
13932
13933 if let Some(new_position) = new_position {
13934 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13935 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13936 return;
13937 }
13938 }
13939
13940 nav_history.push(
13941 Some(NavigationData {
13942 cursor_anchor,
13943 cursor_position,
13944 scroll_anchor: scroll_state,
13945 scroll_top_row,
13946 }),
13947 cx,
13948 );
13949 cx.emit(EditorEvent::PushedToNavHistory {
13950 anchor: cursor_anchor,
13951 is_deactivate,
13952 })
13953 }
13954 }
13955
13956 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13958 let buffer = self.buffer.read(cx).snapshot(cx);
13959 let mut selection = self.selections.first::<usize>(cx);
13960 selection.set_head(buffer.len(), SelectionGoal::None);
13961 self.change_selections(Default::default(), window, cx, |s| {
13962 s.select(vec![selection]);
13963 });
13964 }
13965
13966 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13968 let end = self.buffer.read(cx).read(cx).len();
13969 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13970 s.select_ranges(vec![0..end]);
13971 });
13972 }
13973
13974 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13976 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13977 let mut selections = self.selections.all::<Point>(cx);
13978 let max_point = display_map.buffer_snapshot.max_point();
13979 for selection in &mut selections {
13980 let rows = selection.spanned_rows(true, &display_map);
13981 selection.start = Point::new(rows.start.0, 0);
13982 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13983 selection.reversed = false;
13984 }
13985 self.change_selections(Default::default(), window, cx, |s| {
13986 s.select(selections);
13987 });
13988 }
13989
13990 pub fn split_selection_into_lines(
13991 &mut self,
13992 action: &SplitSelectionIntoLines,
13993 window: &mut Window,
13994 cx: &mut Context<Self>,
13995 ) {
13996 let selections = self
13997 .selections
13998 .all::<Point>(cx)
13999 .into_iter()
14000 .map(|selection| selection.start..selection.end)
14001 .collect::<Vec<_>>();
14002 self.unfold_ranges(&selections, true, true, cx);
14003
14004 let mut new_selection_ranges = Vec::new();
14005 {
14006 let buffer = self.buffer.read(cx).read(cx);
14007 for selection in selections {
14008 for row in selection.start.row..selection.end.row {
14009 let line_start = Point::new(row, 0);
14010 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14011
14012 if action.keep_selections {
14013 // Keep the selection range for each line
14014 let selection_start = if row == selection.start.row {
14015 selection.start
14016 } else {
14017 line_start
14018 };
14019 new_selection_ranges.push(selection_start..line_end);
14020 } else {
14021 // Collapse to cursor at end of line
14022 new_selection_ranges.push(line_end..line_end);
14023 }
14024 }
14025
14026 let is_multiline_selection = selection.start.row != selection.end.row;
14027 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14028 // so this action feels more ergonomic when paired with other selection operations
14029 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14030 if !should_skip_last {
14031 if action.keep_selections {
14032 if is_multiline_selection {
14033 let line_start = Point::new(selection.end.row, 0);
14034 new_selection_ranges.push(line_start..selection.end);
14035 } else {
14036 new_selection_ranges.push(selection.start..selection.end);
14037 }
14038 } else {
14039 new_selection_ranges.push(selection.end..selection.end);
14040 }
14041 }
14042 }
14043 }
14044 self.change_selections(Default::default(), window, cx, |s| {
14045 s.select_ranges(new_selection_ranges);
14046 });
14047 }
14048
14049 pub fn add_selection_above(
14050 &mut self,
14051 _: &AddSelectionAbove,
14052 window: &mut Window,
14053 cx: &mut Context<Self>,
14054 ) {
14055 self.add_selection(true, window, cx);
14056 }
14057
14058 pub fn add_selection_below(
14059 &mut self,
14060 _: &AddSelectionBelow,
14061 window: &mut Window,
14062 cx: &mut Context<Self>,
14063 ) {
14064 self.add_selection(false, window, cx);
14065 }
14066
14067 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14068 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14069
14070 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14071 let all_selections = self.selections.all::<Point>(cx);
14072 let text_layout_details = self.text_layout_details(window);
14073
14074 let (mut columnar_selections, new_selections_to_columnarize) = {
14075 if let Some(state) = self.add_selections_state.as_ref() {
14076 let columnar_selection_ids: HashSet<_> = state
14077 .groups
14078 .iter()
14079 .flat_map(|group| group.stack.iter())
14080 .copied()
14081 .collect();
14082
14083 all_selections
14084 .into_iter()
14085 .partition(|s| columnar_selection_ids.contains(&s.id))
14086 } else {
14087 (Vec::new(), all_selections)
14088 }
14089 };
14090
14091 let mut state = self
14092 .add_selections_state
14093 .take()
14094 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14095
14096 for selection in new_selections_to_columnarize {
14097 let range = selection.display_range(&display_map).sorted();
14098 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14099 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14100 let positions = start_x.min(end_x)..start_x.max(end_x);
14101 let mut stack = Vec::new();
14102 for row in range.start.row().0..=range.end.row().0 {
14103 if let Some(selection) = self.selections.build_columnar_selection(
14104 &display_map,
14105 DisplayRow(row),
14106 &positions,
14107 selection.reversed,
14108 &text_layout_details,
14109 ) {
14110 stack.push(selection.id);
14111 columnar_selections.push(selection);
14112 }
14113 }
14114 if !stack.is_empty() {
14115 if above {
14116 stack.reverse();
14117 }
14118 state.groups.push(AddSelectionsGroup { above, stack });
14119 }
14120 }
14121
14122 let mut final_selections = Vec::new();
14123 let end_row = if above {
14124 DisplayRow(0)
14125 } else {
14126 display_map.max_point().row()
14127 };
14128
14129 let mut last_added_item_per_group = HashMap::default();
14130 for group in state.groups.iter_mut() {
14131 if let Some(last_id) = group.stack.last() {
14132 last_added_item_per_group.insert(*last_id, group);
14133 }
14134 }
14135
14136 for selection in columnar_selections {
14137 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14138 if above == group.above {
14139 let range = selection.display_range(&display_map).sorted();
14140 debug_assert_eq!(range.start.row(), range.end.row());
14141 let mut row = range.start.row();
14142 let positions =
14143 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14144 px(start)..px(end)
14145 } else {
14146 let start_x =
14147 display_map.x_for_display_point(range.start, &text_layout_details);
14148 let end_x =
14149 display_map.x_for_display_point(range.end, &text_layout_details);
14150 start_x.min(end_x)..start_x.max(end_x)
14151 };
14152
14153 let mut maybe_new_selection = None;
14154 while row != end_row {
14155 if above {
14156 row.0 -= 1;
14157 } else {
14158 row.0 += 1;
14159 }
14160 if let Some(new_selection) = self.selections.build_columnar_selection(
14161 &display_map,
14162 row,
14163 &positions,
14164 selection.reversed,
14165 &text_layout_details,
14166 ) {
14167 maybe_new_selection = Some(new_selection);
14168 break;
14169 }
14170 }
14171
14172 if let Some(new_selection) = maybe_new_selection {
14173 group.stack.push(new_selection.id);
14174 if above {
14175 final_selections.push(new_selection);
14176 final_selections.push(selection);
14177 } else {
14178 final_selections.push(selection);
14179 final_selections.push(new_selection);
14180 }
14181 } else {
14182 final_selections.push(selection);
14183 }
14184 } else {
14185 group.stack.pop();
14186 }
14187 } else {
14188 final_selections.push(selection);
14189 }
14190 }
14191
14192 self.change_selections(Default::default(), window, cx, |s| {
14193 s.select(final_selections);
14194 });
14195
14196 let final_selection_ids: HashSet<_> = self
14197 .selections
14198 .all::<Point>(cx)
14199 .iter()
14200 .map(|s| s.id)
14201 .collect();
14202 state.groups.retain_mut(|group| {
14203 // selections might get merged above so we remove invalid items from stacks
14204 group.stack.retain(|id| final_selection_ids.contains(id));
14205
14206 // single selection in stack can be treated as initial state
14207 group.stack.len() > 1
14208 });
14209
14210 if !state.groups.is_empty() {
14211 self.add_selections_state = Some(state);
14212 }
14213 }
14214
14215 fn select_match_ranges(
14216 &mut self,
14217 range: Range<usize>,
14218 reversed: bool,
14219 replace_newest: bool,
14220 auto_scroll: Option<Autoscroll>,
14221 window: &mut Window,
14222 cx: &mut Context<Editor>,
14223 ) {
14224 self.unfold_ranges(
14225 std::slice::from_ref(&range),
14226 false,
14227 auto_scroll.is_some(),
14228 cx,
14229 );
14230 let effects = if let Some(scroll) = auto_scroll {
14231 SelectionEffects::scroll(scroll)
14232 } else {
14233 SelectionEffects::no_scroll()
14234 };
14235 self.change_selections(effects, window, cx, |s| {
14236 if replace_newest {
14237 s.delete(s.newest_anchor().id);
14238 }
14239 if reversed {
14240 s.insert_range(range.end..range.start);
14241 } else {
14242 s.insert_range(range);
14243 }
14244 });
14245 }
14246
14247 pub fn select_next_match_internal(
14248 &mut self,
14249 display_map: &DisplaySnapshot,
14250 replace_newest: bool,
14251 autoscroll: Option<Autoscroll>,
14252 window: &mut Window,
14253 cx: &mut Context<Self>,
14254 ) -> Result<()> {
14255 let buffer = &display_map.buffer_snapshot;
14256 let mut selections = self.selections.all::<usize>(cx);
14257 if let Some(mut select_next_state) = self.select_next_state.take() {
14258 let query = &select_next_state.query;
14259 if !select_next_state.done {
14260 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14261 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14262 let mut next_selected_range = None;
14263
14264 let bytes_after_last_selection =
14265 buffer.bytes_in_range(last_selection.end..buffer.len());
14266 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14267 let query_matches = query
14268 .stream_find_iter(bytes_after_last_selection)
14269 .map(|result| (last_selection.end, result))
14270 .chain(
14271 query
14272 .stream_find_iter(bytes_before_first_selection)
14273 .map(|result| (0, result)),
14274 );
14275
14276 for (start_offset, query_match) in query_matches {
14277 let query_match = query_match.unwrap(); // can only fail due to I/O
14278 let offset_range =
14279 start_offset + query_match.start()..start_offset + query_match.end();
14280
14281 if !select_next_state.wordwise
14282 || (!buffer.is_inside_word(offset_range.start, None)
14283 && !buffer.is_inside_word(offset_range.end, None))
14284 {
14285 // TODO: This is n^2, because we might check all the selections
14286 if !selections
14287 .iter()
14288 .any(|selection| selection.range().overlaps(&offset_range))
14289 {
14290 next_selected_range = Some(offset_range);
14291 break;
14292 }
14293 }
14294 }
14295
14296 if let Some(next_selected_range) = next_selected_range {
14297 self.select_match_ranges(
14298 next_selected_range,
14299 last_selection.reversed,
14300 replace_newest,
14301 autoscroll,
14302 window,
14303 cx,
14304 );
14305 } else {
14306 select_next_state.done = true;
14307 }
14308 }
14309
14310 self.select_next_state = Some(select_next_state);
14311 } else {
14312 let mut only_carets = true;
14313 let mut same_text_selected = true;
14314 let mut selected_text = None;
14315
14316 let mut selections_iter = selections.iter().peekable();
14317 while let Some(selection) = selections_iter.next() {
14318 if selection.start != selection.end {
14319 only_carets = false;
14320 }
14321
14322 if same_text_selected {
14323 if selected_text.is_none() {
14324 selected_text =
14325 Some(buffer.text_for_range(selection.range()).collect::<String>());
14326 }
14327
14328 if let Some(next_selection) = selections_iter.peek() {
14329 if next_selection.range().len() == selection.range().len() {
14330 let next_selected_text = buffer
14331 .text_for_range(next_selection.range())
14332 .collect::<String>();
14333 if Some(next_selected_text) != selected_text {
14334 same_text_selected = false;
14335 selected_text = None;
14336 }
14337 } else {
14338 same_text_selected = false;
14339 selected_text = None;
14340 }
14341 }
14342 }
14343 }
14344
14345 if only_carets {
14346 for selection in &mut selections {
14347 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14348 selection.start = word_range.start;
14349 selection.end = word_range.end;
14350 selection.goal = SelectionGoal::None;
14351 selection.reversed = false;
14352 self.select_match_ranges(
14353 selection.start..selection.end,
14354 selection.reversed,
14355 replace_newest,
14356 autoscroll,
14357 window,
14358 cx,
14359 );
14360 }
14361
14362 if selections.len() == 1 {
14363 let selection = selections
14364 .last()
14365 .expect("ensured that there's only one selection");
14366 let query = buffer
14367 .text_for_range(selection.start..selection.end)
14368 .collect::<String>();
14369 let is_empty = query.is_empty();
14370 let select_state = SelectNextState {
14371 query: AhoCorasick::new(&[query])?,
14372 wordwise: true,
14373 done: is_empty,
14374 };
14375 self.select_next_state = Some(select_state);
14376 } else {
14377 self.select_next_state = None;
14378 }
14379 } else if let Some(selected_text) = selected_text {
14380 self.select_next_state = Some(SelectNextState {
14381 query: AhoCorasick::new(&[selected_text])?,
14382 wordwise: false,
14383 done: false,
14384 });
14385 self.select_next_match_internal(
14386 display_map,
14387 replace_newest,
14388 autoscroll,
14389 window,
14390 cx,
14391 )?;
14392 }
14393 }
14394 Ok(())
14395 }
14396
14397 pub fn select_all_matches(
14398 &mut self,
14399 _action: &SelectAllMatches,
14400 window: &mut Window,
14401 cx: &mut Context<Self>,
14402 ) -> Result<()> {
14403 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14404
14405 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14406
14407 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14408 let Some(select_next_state) = self.select_next_state.as_mut() else {
14409 return Ok(());
14410 };
14411 if select_next_state.done {
14412 return Ok(());
14413 }
14414
14415 let mut new_selections = Vec::new();
14416
14417 let reversed = self.selections.oldest::<usize>(cx).reversed;
14418 let buffer = &display_map.buffer_snapshot;
14419 let query_matches = select_next_state
14420 .query
14421 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14422
14423 for query_match in query_matches.into_iter() {
14424 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14425 let offset_range = if reversed {
14426 query_match.end()..query_match.start()
14427 } else {
14428 query_match.start()..query_match.end()
14429 };
14430
14431 if !select_next_state.wordwise
14432 || (!buffer.is_inside_word(offset_range.start, None)
14433 && !buffer.is_inside_word(offset_range.end, None))
14434 {
14435 new_selections.push(offset_range.start..offset_range.end);
14436 }
14437 }
14438
14439 select_next_state.done = true;
14440
14441 if new_selections.is_empty() {
14442 log::error!("bug: new_selections is empty in select_all_matches");
14443 return Ok(());
14444 }
14445
14446 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14447 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14448 selections.select_ranges(new_selections)
14449 });
14450
14451 Ok(())
14452 }
14453
14454 pub fn select_next(
14455 &mut self,
14456 action: &SelectNext,
14457 window: &mut Window,
14458 cx: &mut Context<Self>,
14459 ) -> Result<()> {
14460 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14461 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14462 self.select_next_match_internal(
14463 &display_map,
14464 action.replace_newest,
14465 Some(Autoscroll::newest()),
14466 window,
14467 cx,
14468 )?;
14469 Ok(())
14470 }
14471
14472 pub fn select_previous(
14473 &mut self,
14474 action: &SelectPrevious,
14475 window: &mut Window,
14476 cx: &mut Context<Self>,
14477 ) -> Result<()> {
14478 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14479 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14480 let buffer = &display_map.buffer_snapshot;
14481 let mut selections = self.selections.all::<usize>(cx);
14482 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14483 let query = &select_prev_state.query;
14484 if !select_prev_state.done {
14485 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14486 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14487 let mut next_selected_range = None;
14488 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14489 let bytes_before_last_selection =
14490 buffer.reversed_bytes_in_range(0..last_selection.start);
14491 let bytes_after_first_selection =
14492 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14493 let query_matches = query
14494 .stream_find_iter(bytes_before_last_selection)
14495 .map(|result| (last_selection.start, result))
14496 .chain(
14497 query
14498 .stream_find_iter(bytes_after_first_selection)
14499 .map(|result| (buffer.len(), result)),
14500 );
14501 for (end_offset, query_match) in query_matches {
14502 let query_match = query_match.unwrap(); // can only fail due to I/O
14503 let offset_range =
14504 end_offset - query_match.end()..end_offset - query_match.start();
14505
14506 if !select_prev_state.wordwise
14507 || (!buffer.is_inside_word(offset_range.start, None)
14508 && !buffer.is_inside_word(offset_range.end, None))
14509 {
14510 next_selected_range = Some(offset_range);
14511 break;
14512 }
14513 }
14514
14515 if let Some(next_selected_range) = next_selected_range {
14516 self.select_match_ranges(
14517 next_selected_range,
14518 last_selection.reversed,
14519 action.replace_newest,
14520 Some(Autoscroll::newest()),
14521 window,
14522 cx,
14523 );
14524 } else {
14525 select_prev_state.done = true;
14526 }
14527 }
14528
14529 self.select_prev_state = Some(select_prev_state);
14530 } else {
14531 let mut only_carets = true;
14532 let mut same_text_selected = true;
14533 let mut selected_text = None;
14534
14535 let mut selections_iter = selections.iter().peekable();
14536 while let Some(selection) = selections_iter.next() {
14537 if selection.start != selection.end {
14538 only_carets = false;
14539 }
14540
14541 if same_text_selected {
14542 if selected_text.is_none() {
14543 selected_text =
14544 Some(buffer.text_for_range(selection.range()).collect::<String>());
14545 }
14546
14547 if let Some(next_selection) = selections_iter.peek() {
14548 if next_selection.range().len() == selection.range().len() {
14549 let next_selected_text = buffer
14550 .text_for_range(next_selection.range())
14551 .collect::<String>();
14552 if Some(next_selected_text) != selected_text {
14553 same_text_selected = false;
14554 selected_text = None;
14555 }
14556 } else {
14557 same_text_selected = false;
14558 selected_text = None;
14559 }
14560 }
14561 }
14562 }
14563
14564 if only_carets {
14565 for selection in &mut selections {
14566 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14567 selection.start = word_range.start;
14568 selection.end = word_range.end;
14569 selection.goal = SelectionGoal::None;
14570 selection.reversed = false;
14571 self.select_match_ranges(
14572 selection.start..selection.end,
14573 selection.reversed,
14574 action.replace_newest,
14575 Some(Autoscroll::newest()),
14576 window,
14577 cx,
14578 );
14579 }
14580 if selections.len() == 1 {
14581 let selection = selections
14582 .last()
14583 .expect("ensured that there's only one selection");
14584 let query = buffer
14585 .text_for_range(selection.start..selection.end)
14586 .collect::<String>();
14587 let is_empty = query.is_empty();
14588 let select_state = SelectNextState {
14589 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14590 wordwise: true,
14591 done: is_empty,
14592 };
14593 self.select_prev_state = Some(select_state);
14594 } else {
14595 self.select_prev_state = None;
14596 }
14597 } else if let Some(selected_text) = selected_text {
14598 self.select_prev_state = Some(SelectNextState {
14599 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14600 wordwise: false,
14601 done: false,
14602 });
14603 self.select_previous(action, window, cx)?;
14604 }
14605 }
14606 Ok(())
14607 }
14608
14609 pub fn find_next_match(
14610 &mut self,
14611 _: &FindNextMatch,
14612 window: &mut Window,
14613 cx: &mut Context<Self>,
14614 ) -> Result<()> {
14615 let selections = self.selections.disjoint_anchors_arc();
14616 match selections.first() {
14617 Some(first) if selections.len() >= 2 => {
14618 self.change_selections(Default::default(), window, cx, |s| {
14619 s.select_ranges([first.range()]);
14620 });
14621 }
14622 _ => self.select_next(
14623 &SelectNext {
14624 replace_newest: true,
14625 },
14626 window,
14627 cx,
14628 )?,
14629 }
14630 Ok(())
14631 }
14632
14633 pub fn find_previous_match(
14634 &mut self,
14635 _: &FindPreviousMatch,
14636 window: &mut Window,
14637 cx: &mut Context<Self>,
14638 ) -> Result<()> {
14639 let selections = self.selections.disjoint_anchors_arc();
14640 match selections.last() {
14641 Some(last) if selections.len() >= 2 => {
14642 self.change_selections(Default::default(), window, cx, |s| {
14643 s.select_ranges([last.range()]);
14644 });
14645 }
14646 _ => self.select_previous(
14647 &SelectPrevious {
14648 replace_newest: true,
14649 },
14650 window,
14651 cx,
14652 )?,
14653 }
14654 Ok(())
14655 }
14656
14657 pub fn toggle_comments(
14658 &mut self,
14659 action: &ToggleComments,
14660 window: &mut Window,
14661 cx: &mut Context<Self>,
14662 ) {
14663 if self.read_only(cx) {
14664 return;
14665 }
14666 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14667 let text_layout_details = &self.text_layout_details(window);
14668 self.transact(window, cx, |this, window, cx| {
14669 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14670 let mut edits = Vec::new();
14671 let mut selection_edit_ranges = Vec::new();
14672 let mut last_toggled_row = None;
14673 let snapshot = this.buffer.read(cx).read(cx);
14674 let empty_str: Arc<str> = Arc::default();
14675 let mut suffixes_inserted = Vec::new();
14676 let ignore_indent = action.ignore_indent;
14677
14678 fn comment_prefix_range(
14679 snapshot: &MultiBufferSnapshot,
14680 row: MultiBufferRow,
14681 comment_prefix: &str,
14682 comment_prefix_whitespace: &str,
14683 ignore_indent: bool,
14684 ) -> Range<Point> {
14685 let indent_size = if ignore_indent {
14686 0
14687 } else {
14688 snapshot.indent_size_for_line(row).len
14689 };
14690
14691 let start = Point::new(row.0, indent_size);
14692
14693 let mut line_bytes = snapshot
14694 .bytes_in_range(start..snapshot.max_point())
14695 .flatten()
14696 .copied();
14697
14698 // If this line currently begins with the line comment prefix, then record
14699 // the range containing the prefix.
14700 if line_bytes
14701 .by_ref()
14702 .take(comment_prefix.len())
14703 .eq(comment_prefix.bytes())
14704 {
14705 // Include any whitespace that matches the comment prefix.
14706 let matching_whitespace_len = line_bytes
14707 .zip(comment_prefix_whitespace.bytes())
14708 .take_while(|(a, b)| a == b)
14709 .count() as u32;
14710 let end = Point::new(
14711 start.row,
14712 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14713 );
14714 start..end
14715 } else {
14716 start..start
14717 }
14718 }
14719
14720 fn comment_suffix_range(
14721 snapshot: &MultiBufferSnapshot,
14722 row: MultiBufferRow,
14723 comment_suffix: &str,
14724 comment_suffix_has_leading_space: bool,
14725 ) -> Range<Point> {
14726 let end = Point::new(row.0, snapshot.line_len(row));
14727 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14728
14729 let mut line_end_bytes = snapshot
14730 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14731 .flatten()
14732 .copied();
14733
14734 let leading_space_len = if suffix_start_column > 0
14735 && line_end_bytes.next() == Some(b' ')
14736 && comment_suffix_has_leading_space
14737 {
14738 1
14739 } else {
14740 0
14741 };
14742
14743 // If this line currently begins with the line comment prefix, then record
14744 // the range containing the prefix.
14745 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14746 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14747 start..end
14748 } else {
14749 end..end
14750 }
14751 }
14752
14753 // TODO: Handle selections that cross excerpts
14754 for selection in &mut selections {
14755 let start_column = snapshot
14756 .indent_size_for_line(MultiBufferRow(selection.start.row))
14757 .len;
14758 let language = if let Some(language) =
14759 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14760 {
14761 language
14762 } else {
14763 continue;
14764 };
14765
14766 selection_edit_ranges.clear();
14767
14768 // If multiple selections contain a given row, avoid processing that
14769 // row more than once.
14770 let mut start_row = MultiBufferRow(selection.start.row);
14771 if last_toggled_row == Some(start_row) {
14772 start_row = start_row.next_row();
14773 }
14774 let end_row =
14775 if selection.end.row > selection.start.row && selection.end.column == 0 {
14776 MultiBufferRow(selection.end.row - 1)
14777 } else {
14778 MultiBufferRow(selection.end.row)
14779 };
14780 last_toggled_row = Some(end_row);
14781
14782 if start_row > end_row {
14783 continue;
14784 }
14785
14786 // If the language has line comments, toggle those.
14787 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14788
14789 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14790 if ignore_indent {
14791 full_comment_prefixes = full_comment_prefixes
14792 .into_iter()
14793 .map(|s| Arc::from(s.trim_end()))
14794 .collect();
14795 }
14796
14797 if !full_comment_prefixes.is_empty() {
14798 let first_prefix = full_comment_prefixes
14799 .first()
14800 .expect("prefixes is non-empty");
14801 let prefix_trimmed_lengths = full_comment_prefixes
14802 .iter()
14803 .map(|p| p.trim_end_matches(' ').len())
14804 .collect::<SmallVec<[usize; 4]>>();
14805
14806 let mut all_selection_lines_are_comments = true;
14807
14808 for row in start_row.0..=end_row.0 {
14809 let row = MultiBufferRow(row);
14810 if start_row < end_row && snapshot.is_line_blank(row) {
14811 continue;
14812 }
14813
14814 let prefix_range = full_comment_prefixes
14815 .iter()
14816 .zip(prefix_trimmed_lengths.iter().copied())
14817 .map(|(prefix, trimmed_prefix_len)| {
14818 comment_prefix_range(
14819 snapshot.deref(),
14820 row,
14821 &prefix[..trimmed_prefix_len],
14822 &prefix[trimmed_prefix_len..],
14823 ignore_indent,
14824 )
14825 })
14826 .max_by_key(|range| range.end.column - range.start.column)
14827 .expect("prefixes is non-empty");
14828
14829 if prefix_range.is_empty() {
14830 all_selection_lines_are_comments = false;
14831 }
14832
14833 selection_edit_ranges.push(prefix_range);
14834 }
14835
14836 if all_selection_lines_are_comments {
14837 edits.extend(
14838 selection_edit_ranges
14839 .iter()
14840 .cloned()
14841 .map(|range| (range, empty_str.clone())),
14842 );
14843 } else {
14844 let min_column = selection_edit_ranges
14845 .iter()
14846 .map(|range| range.start.column)
14847 .min()
14848 .unwrap_or(0);
14849 edits.extend(selection_edit_ranges.iter().map(|range| {
14850 let position = Point::new(range.start.row, min_column);
14851 (position..position, first_prefix.clone())
14852 }));
14853 }
14854 } else if let Some(BlockCommentConfig {
14855 start: full_comment_prefix,
14856 end: comment_suffix,
14857 ..
14858 }) = language.block_comment()
14859 {
14860 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14861 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14862 let prefix_range = comment_prefix_range(
14863 snapshot.deref(),
14864 start_row,
14865 comment_prefix,
14866 comment_prefix_whitespace,
14867 ignore_indent,
14868 );
14869 let suffix_range = comment_suffix_range(
14870 snapshot.deref(),
14871 end_row,
14872 comment_suffix.trim_start_matches(' '),
14873 comment_suffix.starts_with(' '),
14874 );
14875
14876 if prefix_range.is_empty() || suffix_range.is_empty() {
14877 edits.push((
14878 prefix_range.start..prefix_range.start,
14879 full_comment_prefix.clone(),
14880 ));
14881 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14882 suffixes_inserted.push((end_row, comment_suffix.len()));
14883 } else {
14884 edits.push((prefix_range, empty_str.clone()));
14885 edits.push((suffix_range, empty_str.clone()));
14886 }
14887 } else {
14888 continue;
14889 }
14890 }
14891
14892 drop(snapshot);
14893 this.buffer.update(cx, |buffer, cx| {
14894 buffer.edit(edits, None, cx);
14895 });
14896
14897 // Adjust selections so that they end before any comment suffixes that
14898 // were inserted.
14899 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14900 let mut selections = this.selections.all::<Point>(cx);
14901 let snapshot = this.buffer.read(cx).read(cx);
14902 for selection in &mut selections {
14903 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14904 match row.cmp(&MultiBufferRow(selection.end.row)) {
14905 Ordering::Less => {
14906 suffixes_inserted.next();
14907 continue;
14908 }
14909 Ordering::Greater => break,
14910 Ordering::Equal => {
14911 if selection.end.column == snapshot.line_len(row) {
14912 if selection.is_empty() {
14913 selection.start.column -= suffix_len as u32;
14914 }
14915 selection.end.column -= suffix_len as u32;
14916 }
14917 break;
14918 }
14919 }
14920 }
14921 }
14922
14923 drop(snapshot);
14924 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14925
14926 let selections = this.selections.all::<Point>(cx);
14927 let selections_on_single_row = selections.windows(2).all(|selections| {
14928 selections[0].start.row == selections[1].start.row
14929 && selections[0].end.row == selections[1].end.row
14930 && selections[0].start.row == selections[0].end.row
14931 });
14932 let selections_selecting = selections
14933 .iter()
14934 .any(|selection| selection.start != selection.end);
14935 let advance_downwards = action.advance_downwards
14936 && selections_on_single_row
14937 && !selections_selecting
14938 && !matches!(this.mode, EditorMode::SingleLine);
14939
14940 if advance_downwards {
14941 let snapshot = this.buffer.read(cx).snapshot(cx);
14942
14943 this.change_selections(Default::default(), window, cx, |s| {
14944 s.move_cursors_with(|display_snapshot, display_point, _| {
14945 let mut point = display_point.to_point(display_snapshot);
14946 point.row += 1;
14947 point = snapshot.clip_point(point, Bias::Left);
14948 let display_point = point.to_display_point(display_snapshot);
14949 let goal = SelectionGoal::HorizontalPosition(
14950 display_snapshot
14951 .x_for_display_point(display_point, text_layout_details)
14952 .into(),
14953 );
14954 (display_point, goal)
14955 })
14956 });
14957 }
14958 });
14959 }
14960
14961 pub fn select_enclosing_symbol(
14962 &mut self,
14963 _: &SelectEnclosingSymbol,
14964 window: &mut Window,
14965 cx: &mut Context<Self>,
14966 ) {
14967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14968
14969 let buffer = self.buffer.read(cx).snapshot(cx);
14970 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14971
14972 fn update_selection(
14973 selection: &Selection<usize>,
14974 buffer_snap: &MultiBufferSnapshot,
14975 ) -> Option<Selection<usize>> {
14976 let cursor = selection.head();
14977 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14978 for symbol in symbols.iter().rev() {
14979 let start = symbol.range.start.to_offset(buffer_snap);
14980 let end = symbol.range.end.to_offset(buffer_snap);
14981 let new_range = start..end;
14982 if start < selection.start || end > selection.end {
14983 return Some(Selection {
14984 id: selection.id,
14985 start: new_range.start,
14986 end: new_range.end,
14987 goal: SelectionGoal::None,
14988 reversed: selection.reversed,
14989 });
14990 }
14991 }
14992 None
14993 }
14994
14995 let mut selected_larger_symbol = false;
14996 let new_selections = old_selections
14997 .iter()
14998 .map(|selection| match update_selection(selection, &buffer) {
14999 Some(new_selection) => {
15000 if new_selection.range() != selection.range() {
15001 selected_larger_symbol = true;
15002 }
15003 new_selection
15004 }
15005 None => selection.clone(),
15006 })
15007 .collect::<Vec<_>>();
15008
15009 if selected_larger_symbol {
15010 self.change_selections(Default::default(), window, cx, |s| {
15011 s.select(new_selections);
15012 });
15013 }
15014 }
15015
15016 pub fn select_larger_syntax_node(
15017 &mut self,
15018 _: &SelectLargerSyntaxNode,
15019 window: &mut Window,
15020 cx: &mut Context<Self>,
15021 ) {
15022 let Some(visible_row_count) = self.visible_row_count() else {
15023 return;
15024 };
15025 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15026 if old_selections.is_empty() {
15027 return;
15028 }
15029
15030 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15031
15032 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15033 let buffer = self.buffer.read(cx).snapshot(cx);
15034
15035 let mut selected_larger_node = false;
15036 let mut new_selections = old_selections
15037 .iter()
15038 .map(|selection| {
15039 let old_range = selection.start..selection.end;
15040
15041 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15042 // manually select word at selection
15043 if ["string_content", "inline"].contains(&node.kind()) {
15044 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15045 // ignore if word is already selected
15046 if !word_range.is_empty() && old_range != word_range {
15047 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15048 // only select word if start and end point belongs to same word
15049 if word_range == last_word_range {
15050 selected_larger_node = true;
15051 return Selection {
15052 id: selection.id,
15053 start: word_range.start,
15054 end: word_range.end,
15055 goal: SelectionGoal::None,
15056 reversed: selection.reversed,
15057 };
15058 }
15059 }
15060 }
15061 }
15062
15063 let mut new_range = old_range.clone();
15064 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
15065 {
15066 new_range = match containing_range {
15067 MultiOrSingleBufferOffsetRange::Single(_) => break,
15068 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15069 };
15070 if !node.is_named() {
15071 continue;
15072 }
15073 if !display_map.intersects_fold(new_range.start)
15074 && !display_map.intersects_fold(new_range.end)
15075 {
15076 break;
15077 }
15078 }
15079
15080 selected_larger_node |= new_range != old_range;
15081 Selection {
15082 id: selection.id,
15083 start: new_range.start,
15084 end: new_range.end,
15085 goal: SelectionGoal::None,
15086 reversed: selection.reversed,
15087 }
15088 })
15089 .collect::<Vec<_>>();
15090
15091 if !selected_larger_node {
15092 return; // don't put this call in the history
15093 }
15094
15095 // scroll based on transformation done to the last selection created by the user
15096 let (last_old, last_new) = old_selections
15097 .last()
15098 .zip(new_selections.last().cloned())
15099 .expect("old_selections isn't empty");
15100
15101 // revert selection
15102 let is_selection_reversed = {
15103 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15104 new_selections.last_mut().expect("checked above").reversed =
15105 should_newest_selection_be_reversed;
15106 should_newest_selection_be_reversed
15107 };
15108
15109 if selected_larger_node {
15110 self.select_syntax_node_history.disable_clearing = true;
15111 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15112 s.select(new_selections.clone());
15113 });
15114 self.select_syntax_node_history.disable_clearing = false;
15115 }
15116
15117 let start_row = last_new.start.to_display_point(&display_map).row().0;
15118 let end_row = last_new.end.to_display_point(&display_map).row().0;
15119 let selection_height = end_row - start_row + 1;
15120 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15121
15122 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15123 let scroll_behavior = if fits_on_the_screen {
15124 self.request_autoscroll(Autoscroll::fit(), cx);
15125 SelectSyntaxNodeScrollBehavior::FitSelection
15126 } else if is_selection_reversed {
15127 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15128 SelectSyntaxNodeScrollBehavior::CursorTop
15129 } else {
15130 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15131 SelectSyntaxNodeScrollBehavior::CursorBottom
15132 };
15133
15134 self.select_syntax_node_history.push((
15135 old_selections,
15136 scroll_behavior,
15137 is_selection_reversed,
15138 ));
15139 }
15140
15141 pub fn select_smaller_syntax_node(
15142 &mut self,
15143 _: &SelectSmallerSyntaxNode,
15144 window: &mut Window,
15145 cx: &mut Context<Self>,
15146 ) {
15147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15148
15149 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15150 self.select_syntax_node_history.pop()
15151 {
15152 if let Some(selection) = selections.last_mut() {
15153 selection.reversed = is_selection_reversed;
15154 }
15155
15156 self.select_syntax_node_history.disable_clearing = true;
15157 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15158 s.select(selections.to_vec());
15159 });
15160 self.select_syntax_node_history.disable_clearing = false;
15161
15162 match scroll_behavior {
15163 SelectSyntaxNodeScrollBehavior::CursorTop => {
15164 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15165 }
15166 SelectSyntaxNodeScrollBehavior::FitSelection => {
15167 self.request_autoscroll(Autoscroll::fit(), cx);
15168 }
15169 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15170 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15171 }
15172 }
15173 }
15174 }
15175
15176 pub fn unwrap_syntax_node(
15177 &mut self,
15178 _: &UnwrapSyntaxNode,
15179 window: &mut Window,
15180 cx: &mut Context<Self>,
15181 ) {
15182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15183
15184 let buffer = self.buffer.read(cx).snapshot(cx);
15185 let selections = self
15186 .selections
15187 .all::<usize>(cx)
15188 .into_iter()
15189 // subtracting the offset requires sorting
15190 .sorted_by_key(|i| i.start);
15191
15192 let full_edits = selections
15193 .into_iter()
15194 .filter_map(|selection| {
15195 let child = if selection.is_empty()
15196 && let Some((_, ancestor_range)) =
15197 buffer.syntax_ancestor(selection.start..selection.end)
15198 {
15199 match ancestor_range {
15200 MultiOrSingleBufferOffsetRange::Single(range) => range,
15201 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15202 }
15203 } else {
15204 selection.range()
15205 };
15206
15207 let mut parent = child.clone();
15208 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15209 parent = match ancestor_range {
15210 MultiOrSingleBufferOffsetRange::Single(range) => range,
15211 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15212 };
15213 if parent.start < child.start || parent.end > child.end {
15214 break;
15215 }
15216 }
15217
15218 if parent == child {
15219 return None;
15220 }
15221 let text = buffer.text_for_range(child).collect::<String>();
15222 Some((selection.id, parent, text))
15223 })
15224 .collect::<Vec<_>>();
15225 if full_edits.is_empty() {
15226 return;
15227 }
15228
15229 self.transact(window, cx, |this, window, cx| {
15230 this.buffer.update(cx, |buffer, cx| {
15231 buffer.edit(
15232 full_edits
15233 .iter()
15234 .map(|(_, p, t)| (p.clone(), t.clone()))
15235 .collect::<Vec<_>>(),
15236 None,
15237 cx,
15238 );
15239 });
15240 this.change_selections(Default::default(), window, cx, |s| {
15241 let mut offset = 0;
15242 let mut selections = vec![];
15243 for (id, parent, text) in full_edits {
15244 let start = parent.start - offset;
15245 offset += parent.len() - text.len();
15246 selections.push(Selection {
15247 id,
15248 start,
15249 end: start + text.len(),
15250 reversed: false,
15251 goal: Default::default(),
15252 });
15253 }
15254 s.select(selections);
15255 });
15256 });
15257 }
15258
15259 pub fn select_next_syntax_node(
15260 &mut self,
15261 _: &SelectNextSyntaxNode,
15262 window: &mut Window,
15263 cx: &mut Context<Self>,
15264 ) {
15265 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15266 if old_selections.is_empty() {
15267 return;
15268 }
15269
15270 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15271
15272 let buffer = self.buffer.read(cx).snapshot(cx);
15273 let mut selected_sibling = false;
15274
15275 let new_selections = old_selections
15276 .iter()
15277 .map(|selection| {
15278 let old_range = selection.start..selection.end;
15279
15280 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15281 let new_range = node.byte_range();
15282 selected_sibling = true;
15283 Selection {
15284 id: selection.id,
15285 start: new_range.start,
15286 end: new_range.end,
15287 goal: SelectionGoal::None,
15288 reversed: selection.reversed,
15289 }
15290 } else {
15291 selection.clone()
15292 }
15293 })
15294 .collect::<Vec<_>>();
15295
15296 if selected_sibling {
15297 self.change_selections(
15298 SelectionEffects::scroll(Autoscroll::fit()),
15299 window,
15300 cx,
15301 |s| {
15302 s.select(new_selections);
15303 },
15304 );
15305 }
15306 }
15307
15308 pub fn select_prev_syntax_node(
15309 &mut self,
15310 _: &SelectPreviousSyntaxNode,
15311 window: &mut Window,
15312 cx: &mut Context<Self>,
15313 ) {
15314 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15315 if old_selections.is_empty() {
15316 return;
15317 }
15318
15319 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15320
15321 let buffer = self.buffer.read(cx).snapshot(cx);
15322 let mut selected_sibling = false;
15323
15324 let new_selections = old_selections
15325 .iter()
15326 .map(|selection| {
15327 let old_range = selection.start..selection.end;
15328
15329 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15330 let new_range = node.byte_range();
15331 selected_sibling = true;
15332 Selection {
15333 id: selection.id,
15334 start: new_range.start,
15335 end: new_range.end,
15336 goal: SelectionGoal::None,
15337 reversed: selection.reversed,
15338 }
15339 } else {
15340 selection.clone()
15341 }
15342 })
15343 .collect::<Vec<_>>();
15344
15345 if selected_sibling {
15346 self.change_selections(
15347 SelectionEffects::scroll(Autoscroll::fit()),
15348 window,
15349 cx,
15350 |s| {
15351 s.select(new_selections);
15352 },
15353 );
15354 }
15355 }
15356
15357 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15358 if !EditorSettings::get_global(cx).gutter.runnables {
15359 self.clear_tasks();
15360 return Task::ready(());
15361 }
15362 let project = self.project().map(Entity::downgrade);
15363 let task_sources = self.lsp_task_sources(cx);
15364 let multi_buffer = self.buffer.downgrade();
15365 cx.spawn_in(window, async move |editor, cx| {
15366 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15367 let Some(project) = project.and_then(|p| p.upgrade()) else {
15368 return;
15369 };
15370 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15371 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15372 }) else {
15373 return;
15374 };
15375
15376 let hide_runnables = project
15377 .update(cx, |project, _| project.is_via_collab())
15378 .unwrap_or(true);
15379 if hide_runnables {
15380 return;
15381 }
15382 let new_rows =
15383 cx.background_spawn({
15384 let snapshot = display_snapshot.clone();
15385 async move {
15386 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15387 }
15388 })
15389 .await;
15390 let Ok(lsp_tasks) =
15391 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15392 else {
15393 return;
15394 };
15395 let lsp_tasks = lsp_tasks.await;
15396
15397 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15398 lsp_tasks
15399 .into_iter()
15400 .flat_map(|(kind, tasks)| {
15401 tasks.into_iter().filter_map(move |(location, task)| {
15402 Some((kind.clone(), location?, task))
15403 })
15404 })
15405 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15406 let buffer = location.target.buffer;
15407 let buffer_snapshot = buffer.read(cx).snapshot();
15408 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15409 |(excerpt_id, snapshot, _)| {
15410 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15411 display_snapshot
15412 .buffer_snapshot
15413 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15414 } else {
15415 None
15416 }
15417 },
15418 );
15419 if let Some(offset) = offset {
15420 let task_buffer_range =
15421 location.target.range.to_point(&buffer_snapshot);
15422 let context_buffer_range =
15423 task_buffer_range.to_offset(&buffer_snapshot);
15424 let context_range = BufferOffset(context_buffer_range.start)
15425 ..BufferOffset(context_buffer_range.end);
15426
15427 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15428 .or_insert_with(|| RunnableTasks {
15429 templates: Vec::new(),
15430 offset,
15431 column: task_buffer_range.start.column,
15432 extra_variables: HashMap::default(),
15433 context_range,
15434 })
15435 .templates
15436 .push((kind, task.original_task().clone()));
15437 }
15438
15439 acc
15440 })
15441 }) else {
15442 return;
15443 };
15444
15445 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15446 buffer.language_settings(cx).tasks.prefer_lsp
15447 }) else {
15448 return;
15449 };
15450
15451 let rows = Self::runnable_rows(
15452 project,
15453 display_snapshot,
15454 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15455 new_rows,
15456 cx.clone(),
15457 )
15458 .await;
15459 editor
15460 .update(cx, |editor, _| {
15461 editor.clear_tasks();
15462 for (key, mut value) in rows {
15463 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15464 value.templates.extend(lsp_tasks.templates);
15465 }
15466
15467 editor.insert_tasks(key, value);
15468 }
15469 for (key, value) in lsp_tasks_by_rows {
15470 editor.insert_tasks(key, value);
15471 }
15472 })
15473 .ok();
15474 })
15475 }
15476 fn fetch_runnable_ranges(
15477 snapshot: &DisplaySnapshot,
15478 range: Range<Anchor>,
15479 ) -> Vec<language::RunnableRange> {
15480 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15481 }
15482
15483 fn runnable_rows(
15484 project: Entity<Project>,
15485 snapshot: DisplaySnapshot,
15486 prefer_lsp: bool,
15487 runnable_ranges: Vec<RunnableRange>,
15488 cx: AsyncWindowContext,
15489 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15490 cx.spawn(async move |cx| {
15491 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15492 for mut runnable in runnable_ranges {
15493 let Some(tasks) = cx
15494 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15495 .ok()
15496 else {
15497 continue;
15498 };
15499 let mut tasks = tasks.await;
15500
15501 if prefer_lsp {
15502 tasks.retain(|(task_kind, _)| {
15503 !matches!(task_kind, TaskSourceKind::Language { .. })
15504 });
15505 }
15506 if tasks.is_empty() {
15507 continue;
15508 }
15509
15510 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15511 let Some(row) = snapshot
15512 .buffer_snapshot
15513 .buffer_line_for_row(MultiBufferRow(point.row))
15514 .map(|(_, range)| range.start.row)
15515 else {
15516 continue;
15517 };
15518
15519 let context_range =
15520 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15521 runnable_rows.push((
15522 (runnable.buffer_id, row),
15523 RunnableTasks {
15524 templates: tasks,
15525 offset: snapshot
15526 .buffer_snapshot
15527 .anchor_before(runnable.run_range.start),
15528 context_range,
15529 column: point.column,
15530 extra_variables: runnable.extra_captures,
15531 },
15532 ));
15533 }
15534 runnable_rows
15535 })
15536 }
15537
15538 fn templates_with_tags(
15539 project: &Entity<Project>,
15540 runnable: &mut Runnable,
15541 cx: &mut App,
15542 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15543 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15544 let (worktree_id, file) = project
15545 .buffer_for_id(runnable.buffer, cx)
15546 .and_then(|buffer| buffer.read(cx).file())
15547 .map(|file| (file.worktree_id(cx), file.clone()))
15548 .unzip();
15549
15550 (
15551 project.task_store().read(cx).task_inventory().cloned(),
15552 worktree_id,
15553 file,
15554 )
15555 });
15556
15557 let tags = mem::take(&mut runnable.tags);
15558 let language = runnable.language.clone();
15559 cx.spawn(async move |cx| {
15560 let mut templates_with_tags = Vec::new();
15561 if let Some(inventory) = inventory {
15562 for RunnableTag(tag) in tags {
15563 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15564 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15565 }) else {
15566 return templates_with_tags;
15567 };
15568 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15569 move |(_, template)| {
15570 template.tags.iter().any(|source_tag| source_tag == &tag)
15571 },
15572 ));
15573 }
15574 }
15575 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15576
15577 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15578 // Strongest source wins; if we have worktree tag binding, prefer that to
15579 // global and language bindings;
15580 // if we have a global binding, prefer that to language binding.
15581 let first_mismatch = templates_with_tags
15582 .iter()
15583 .position(|(tag_source, _)| tag_source != leading_tag_source);
15584 if let Some(index) = first_mismatch {
15585 templates_with_tags.truncate(index);
15586 }
15587 }
15588
15589 templates_with_tags
15590 })
15591 }
15592
15593 pub fn move_to_enclosing_bracket(
15594 &mut self,
15595 _: &MoveToEnclosingBracket,
15596 window: &mut Window,
15597 cx: &mut Context<Self>,
15598 ) {
15599 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15600 self.change_selections(Default::default(), window, cx, |s| {
15601 s.move_offsets_with(|snapshot, selection| {
15602 let Some(enclosing_bracket_ranges) =
15603 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15604 else {
15605 return;
15606 };
15607
15608 let mut best_length = usize::MAX;
15609 let mut best_inside = false;
15610 let mut best_in_bracket_range = false;
15611 let mut best_destination = None;
15612 for (open, close) in enclosing_bracket_ranges {
15613 let close = close.to_inclusive();
15614 let length = close.end() - open.start;
15615 let inside = selection.start >= open.end && selection.end <= *close.start();
15616 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15617 || close.contains(&selection.head());
15618
15619 // If best is next to a bracket and current isn't, skip
15620 if !in_bracket_range && best_in_bracket_range {
15621 continue;
15622 }
15623
15624 // Prefer smaller lengths unless best is inside and current isn't
15625 if length > best_length && (best_inside || !inside) {
15626 continue;
15627 }
15628
15629 best_length = length;
15630 best_inside = inside;
15631 best_in_bracket_range = in_bracket_range;
15632 best_destination = Some(
15633 if close.contains(&selection.start) && close.contains(&selection.end) {
15634 if inside { open.end } else { open.start }
15635 } else if inside {
15636 *close.start()
15637 } else {
15638 *close.end()
15639 },
15640 );
15641 }
15642
15643 if let Some(destination) = best_destination {
15644 selection.collapse_to(destination, SelectionGoal::None);
15645 }
15646 })
15647 });
15648 }
15649
15650 pub fn undo_selection(
15651 &mut self,
15652 _: &UndoSelection,
15653 window: &mut Window,
15654 cx: &mut Context<Self>,
15655 ) {
15656 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15657 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15658 self.selection_history.mode = SelectionHistoryMode::Undoing;
15659 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15660 this.end_selection(window, cx);
15661 this.change_selections(
15662 SelectionEffects::scroll(Autoscroll::newest()),
15663 window,
15664 cx,
15665 |s| s.select_anchors(entry.selections.to_vec()),
15666 );
15667 });
15668 self.selection_history.mode = SelectionHistoryMode::Normal;
15669
15670 self.select_next_state = entry.select_next_state;
15671 self.select_prev_state = entry.select_prev_state;
15672 self.add_selections_state = entry.add_selections_state;
15673 }
15674 }
15675
15676 pub fn redo_selection(
15677 &mut self,
15678 _: &RedoSelection,
15679 window: &mut Window,
15680 cx: &mut Context<Self>,
15681 ) {
15682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15683 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15684 self.selection_history.mode = SelectionHistoryMode::Redoing;
15685 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15686 this.end_selection(window, cx);
15687 this.change_selections(
15688 SelectionEffects::scroll(Autoscroll::newest()),
15689 window,
15690 cx,
15691 |s| s.select_anchors(entry.selections.to_vec()),
15692 );
15693 });
15694 self.selection_history.mode = SelectionHistoryMode::Normal;
15695
15696 self.select_next_state = entry.select_next_state;
15697 self.select_prev_state = entry.select_prev_state;
15698 self.add_selections_state = entry.add_selections_state;
15699 }
15700 }
15701
15702 pub fn expand_excerpts(
15703 &mut self,
15704 action: &ExpandExcerpts,
15705 _: &mut Window,
15706 cx: &mut Context<Self>,
15707 ) {
15708 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15709 }
15710
15711 pub fn expand_excerpts_down(
15712 &mut self,
15713 action: &ExpandExcerptsDown,
15714 _: &mut Window,
15715 cx: &mut Context<Self>,
15716 ) {
15717 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15718 }
15719
15720 pub fn expand_excerpts_up(
15721 &mut self,
15722 action: &ExpandExcerptsUp,
15723 _: &mut Window,
15724 cx: &mut Context<Self>,
15725 ) {
15726 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15727 }
15728
15729 pub fn expand_excerpts_for_direction(
15730 &mut self,
15731 lines: u32,
15732 direction: ExpandExcerptDirection,
15733
15734 cx: &mut Context<Self>,
15735 ) {
15736 let selections = self.selections.disjoint_anchors_arc();
15737
15738 let lines = if lines == 0 {
15739 EditorSettings::get_global(cx).expand_excerpt_lines
15740 } else {
15741 lines
15742 };
15743
15744 self.buffer.update(cx, |buffer, cx| {
15745 let snapshot = buffer.snapshot(cx);
15746 let mut excerpt_ids = selections
15747 .iter()
15748 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15749 .collect::<Vec<_>>();
15750 excerpt_ids.sort();
15751 excerpt_ids.dedup();
15752 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15753 })
15754 }
15755
15756 pub fn expand_excerpt(
15757 &mut self,
15758 excerpt: ExcerptId,
15759 direction: ExpandExcerptDirection,
15760 window: &mut Window,
15761 cx: &mut Context<Self>,
15762 ) {
15763 let current_scroll_position = self.scroll_position(cx);
15764 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15765 let mut should_scroll_up = false;
15766
15767 if direction == ExpandExcerptDirection::Down {
15768 let multi_buffer = self.buffer.read(cx);
15769 let snapshot = multi_buffer.snapshot(cx);
15770 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15771 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15772 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15773 {
15774 let buffer_snapshot = buffer.read(cx).snapshot();
15775 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15776 let last_row = buffer_snapshot.max_point().row;
15777 let lines_below = last_row.saturating_sub(excerpt_end_row);
15778 should_scroll_up = lines_below >= lines_to_expand;
15779 }
15780 }
15781
15782 self.buffer.update(cx, |buffer, cx| {
15783 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15784 });
15785
15786 if should_scroll_up {
15787 let new_scroll_position =
15788 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15789 self.set_scroll_position(new_scroll_position, window, cx);
15790 }
15791 }
15792
15793 pub fn go_to_singleton_buffer_point(
15794 &mut self,
15795 point: Point,
15796 window: &mut Window,
15797 cx: &mut Context<Self>,
15798 ) {
15799 self.go_to_singleton_buffer_range(point..point, window, cx);
15800 }
15801
15802 pub fn go_to_singleton_buffer_range(
15803 &mut self,
15804 range: Range<Point>,
15805 window: &mut Window,
15806 cx: &mut Context<Self>,
15807 ) {
15808 let multibuffer = self.buffer().read(cx);
15809 let Some(buffer) = multibuffer.as_singleton() else {
15810 return;
15811 };
15812 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15813 return;
15814 };
15815 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15816 return;
15817 };
15818 self.change_selections(
15819 SelectionEffects::default().nav_history(true),
15820 window,
15821 cx,
15822 |s| s.select_anchor_ranges([start..end]),
15823 );
15824 }
15825
15826 pub fn go_to_diagnostic(
15827 &mut self,
15828 action: &GoToDiagnostic,
15829 window: &mut Window,
15830 cx: &mut Context<Self>,
15831 ) {
15832 if !self.diagnostics_enabled() {
15833 return;
15834 }
15835 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15836 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15837 }
15838
15839 pub fn go_to_prev_diagnostic(
15840 &mut self,
15841 action: &GoToPreviousDiagnostic,
15842 window: &mut Window,
15843 cx: &mut Context<Self>,
15844 ) {
15845 if !self.diagnostics_enabled() {
15846 return;
15847 }
15848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15849 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15850 }
15851
15852 pub fn go_to_diagnostic_impl(
15853 &mut self,
15854 direction: Direction,
15855 severity: GoToDiagnosticSeverityFilter,
15856 window: &mut Window,
15857 cx: &mut Context<Self>,
15858 ) {
15859 let buffer = self.buffer.read(cx).snapshot(cx);
15860 let selection = self.selections.newest::<usize>(cx);
15861
15862 let mut active_group_id = None;
15863 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15864 && active_group.active_range.start.to_offset(&buffer) == selection.start
15865 {
15866 active_group_id = Some(active_group.group_id);
15867 }
15868
15869 fn filtered(
15870 snapshot: EditorSnapshot,
15871 severity: GoToDiagnosticSeverityFilter,
15872 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15873 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15874 diagnostics
15875 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15876 .filter(|entry| entry.range.start != entry.range.end)
15877 .filter(|entry| !entry.diagnostic.is_unnecessary)
15878 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15879 }
15880
15881 let snapshot = self.snapshot(window, cx);
15882 let before = filtered(
15883 snapshot.clone(),
15884 severity,
15885 buffer
15886 .diagnostics_in_range(0..selection.start)
15887 .filter(|entry| entry.range.start <= selection.start),
15888 );
15889 let after = filtered(
15890 snapshot,
15891 severity,
15892 buffer
15893 .diagnostics_in_range(selection.start..buffer.len())
15894 .filter(|entry| entry.range.start >= selection.start),
15895 );
15896
15897 let mut found: Option<DiagnosticEntry<usize>> = None;
15898 if direction == Direction::Prev {
15899 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15900 {
15901 for diagnostic in prev_diagnostics.into_iter().rev() {
15902 if diagnostic.range.start != selection.start
15903 || active_group_id
15904 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15905 {
15906 found = Some(diagnostic);
15907 break 'outer;
15908 }
15909 }
15910 }
15911 } else {
15912 for diagnostic in after.chain(before) {
15913 if diagnostic.range.start != selection.start
15914 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15915 {
15916 found = Some(diagnostic);
15917 break;
15918 }
15919 }
15920 }
15921 let Some(next_diagnostic) = found else {
15922 return;
15923 };
15924
15925 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15926 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15927 return;
15928 };
15929 self.change_selections(Default::default(), window, cx, |s| {
15930 s.select_ranges(vec![
15931 next_diagnostic.range.start..next_diagnostic.range.start,
15932 ])
15933 });
15934 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15935 self.refresh_edit_prediction(false, true, window, cx);
15936 }
15937
15938 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15939 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15940 let snapshot = self.snapshot(window, cx);
15941 let selection = self.selections.newest::<Point>(cx);
15942 self.go_to_hunk_before_or_after_position(
15943 &snapshot,
15944 selection.head(),
15945 Direction::Next,
15946 window,
15947 cx,
15948 );
15949 }
15950
15951 pub fn go_to_hunk_before_or_after_position(
15952 &mut self,
15953 snapshot: &EditorSnapshot,
15954 position: Point,
15955 direction: Direction,
15956 window: &mut Window,
15957 cx: &mut Context<Editor>,
15958 ) {
15959 let row = if direction == Direction::Next {
15960 self.hunk_after_position(snapshot, position)
15961 .map(|hunk| hunk.row_range.start)
15962 } else {
15963 self.hunk_before_position(snapshot, position)
15964 };
15965
15966 if let Some(row) = row {
15967 let destination = Point::new(row.0, 0);
15968 let autoscroll = Autoscroll::center();
15969
15970 self.unfold_ranges(&[destination..destination], false, false, cx);
15971 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15972 s.select_ranges([destination..destination]);
15973 });
15974 }
15975 }
15976
15977 fn hunk_after_position(
15978 &mut self,
15979 snapshot: &EditorSnapshot,
15980 position: Point,
15981 ) -> Option<MultiBufferDiffHunk> {
15982 snapshot
15983 .buffer_snapshot
15984 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15985 .find(|hunk| hunk.row_range.start.0 > position.row)
15986 .or_else(|| {
15987 snapshot
15988 .buffer_snapshot
15989 .diff_hunks_in_range(Point::zero()..position)
15990 .find(|hunk| hunk.row_range.end.0 < position.row)
15991 })
15992 }
15993
15994 fn go_to_prev_hunk(
15995 &mut self,
15996 _: &GoToPreviousHunk,
15997 window: &mut Window,
15998 cx: &mut Context<Self>,
15999 ) {
16000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16001 let snapshot = self.snapshot(window, cx);
16002 let selection = self.selections.newest::<Point>(cx);
16003 self.go_to_hunk_before_or_after_position(
16004 &snapshot,
16005 selection.head(),
16006 Direction::Prev,
16007 window,
16008 cx,
16009 );
16010 }
16011
16012 fn hunk_before_position(
16013 &mut self,
16014 snapshot: &EditorSnapshot,
16015 position: Point,
16016 ) -> Option<MultiBufferRow> {
16017 snapshot
16018 .buffer_snapshot
16019 .diff_hunk_before(position)
16020 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
16021 }
16022
16023 fn go_to_next_change(
16024 &mut self,
16025 _: &GoToNextChange,
16026 window: &mut Window,
16027 cx: &mut Context<Self>,
16028 ) {
16029 if let Some(selections) = self
16030 .change_list
16031 .next_change(1, Direction::Next)
16032 .map(|s| s.to_vec())
16033 {
16034 self.change_selections(Default::default(), window, cx, |s| {
16035 let map = s.display_map();
16036 s.select_display_ranges(selections.iter().map(|a| {
16037 let point = a.to_display_point(&map);
16038 point..point
16039 }))
16040 })
16041 }
16042 }
16043
16044 fn go_to_previous_change(
16045 &mut self,
16046 _: &GoToPreviousChange,
16047 window: &mut Window,
16048 cx: &mut Context<Self>,
16049 ) {
16050 if let Some(selections) = self
16051 .change_list
16052 .next_change(1, Direction::Prev)
16053 .map(|s| s.to_vec())
16054 {
16055 self.change_selections(Default::default(), window, cx, |s| {
16056 let map = s.display_map();
16057 s.select_display_ranges(selections.iter().map(|a| {
16058 let point = a.to_display_point(&map);
16059 point..point
16060 }))
16061 })
16062 }
16063 }
16064
16065 pub fn go_to_next_document_highlight(
16066 &mut self,
16067 _: &GoToNextDocumentHighlight,
16068 window: &mut Window,
16069 cx: &mut Context<Self>,
16070 ) {
16071 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16072 }
16073
16074 pub fn go_to_prev_document_highlight(
16075 &mut self,
16076 _: &GoToPreviousDocumentHighlight,
16077 window: &mut Window,
16078 cx: &mut Context<Self>,
16079 ) {
16080 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16081 }
16082
16083 pub fn go_to_document_highlight_before_or_after_position(
16084 &mut self,
16085 direction: Direction,
16086 window: &mut Window,
16087 cx: &mut Context<Editor>,
16088 ) {
16089 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16090 let snapshot = self.snapshot(window, cx);
16091 let buffer = &snapshot.buffer_snapshot;
16092 let position = self.selections.newest::<Point>(cx).head();
16093 let anchor_position = buffer.anchor_after(position);
16094
16095 // Get all document highlights (both read and write)
16096 let mut all_highlights = Vec::new();
16097
16098 if let Some((_, read_highlights)) = self
16099 .background_highlights
16100 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16101 {
16102 all_highlights.extend(read_highlights.iter());
16103 }
16104
16105 if let Some((_, write_highlights)) = self
16106 .background_highlights
16107 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16108 {
16109 all_highlights.extend(write_highlights.iter());
16110 }
16111
16112 if all_highlights.is_empty() {
16113 return;
16114 }
16115
16116 // Sort highlights by position
16117 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16118
16119 let target_highlight = match direction {
16120 Direction::Next => {
16121 // Find the first highlight after the current position
16122 all_highlights
16123 .iter()
16124 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16125 }
16126 Direction::Prev => {
16127 // Find the last highlight before the current position
16128 all_highlights
16129 .iter()
16130 .rev()
16131 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16132 }
16133 };
16134
16135 if let Some(highlight) = target_highlight {
16136 let destination = highlight.start.to_point(buffer);
16137 let autoscroll = Autoscroll::center();
16138
16139 self.unfold_ranges(&[destination..destination], false, false, cx);
16140 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16141 s.select_ranges([destination..destination]);
16142 });
16143 }
16144 }
16145
16146 fn go_to_line<T: 'static>(
16147 &mut self,
16148 position: Anchor,
16149 highlight_color: Option<Hsla>,
16150 window: &mut Window,
16151 cx: &mut Context<Self>,
16152 ) {
16153 let snapshot = self.snapshot(window, cx).display_snapshot;
16154 let position = position.to_point(&snapshot.buffer_snapshot);
16155 let start = snapshot
16156 .buffer_snapshot
16157 .clip_point(Point::new(position.row, 0), Bias::Left);
16158 let end = start + Point::new(1, 0);
16159 let start = snapshot.buffer_snapshot.anchor_before(start);
16160 let end = snapshot.buffer_snapshot.anchor_before(end);
16161
16162 self.highlight_rows::<T>(
16163 start..end,
16164 highlight_color
16165 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16166 Default::default(),
16167 cx,
16168 );
16169
16170 if self.buffer.read(cx).is_singleton() {
16171 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16172 }
16173 }
16174
16175 pub fn go_to_definition(
16176 &mut self,
16177 _: &GoToDefinition,
16178 window: &mut Window,
16179 cx: &mut Context<Self>,
16180 ) -> Task<Result<Navigated>> {
16181 let definition =
16182 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16183 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16184 cx.spawn_in(window, async move |editor, cx| {
16185 if definition.await? == Navigated::Yes {
16186 return Ok(Navigated::Yes);
16187 }
16188 match fallback_strategy {
16189 GoToDefinitionFallback::None => Ok(Navigated::No),
16190 GoToDefinitionFallback::FindAllReferences => {
16191 match editor.update_in(cx, |editor, window, cx| {
16192 editor.find_all_references(&FindAllReferences, window, cx)
16193 })? {
16194 Some(references) => references.await,
16195 None => Ok(Navigated::No),
16196 }
16197 }
16198 }
16199 })
16200 }
16201
16202 pub fn go_to_declaration(
16203 &mut self,
16204 _: &GoToDeclaration,
16205 window: &mut Window,
16206 cx: &mut Context<Self>,
16207 ) -> Task<Result<Navigated>> {
16208 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16209 }
16210
16211 pub fn go_to_declaration_split(
16212 &mut self,
16213 _: &GoToDeclaration,
16214 window: &mut Window,
16215 cx: &mut Context<Self>,
16216 ) -> Task<Result<Navigated>> {
16217 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16218 }
16219
16220 pub fn go_to_implementation(
16221 &mut self,
16222 _: &GoToImplementation,
16223 window: &mut Window,
16224 cx: &mut Context<Self>,
16225 ) -> Task<Result<Navigated>> {
16226 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16227 }
16228
16229 pub fn go_to_implementation_split(
16230 &mut self,
16231 _: &GoToImplementationSplit,
16232 window: &mut Window,
16233 cx: &mut Context<Self>,
16234 ) -> Task<Result<Navigated>> {
16235 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16236 }
16237
16238 pub fn go_to_type_definition(
16239 &mut self,
16240 _: &GoToTypeDefinition,
16241 window: &mut Window,
16242 cx: &mut Context<Self>,
16243 ) -> Task<Result<Navigated>> {
16244 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16245 }
16246
16247 pub fn go_to_definition_split(
16248 &mut self,
16249 _: &GoToDefinitionSplit,
16250 window: &mut Window,
16251 cx: &mut Context<Self>,
16252 ) -> Task<Result<Navigated>> {
16253 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16254 }
16255
16256 pub fn go_to_type_definition_split(
16257 &mut self,
16258 _: &GoToTypeDefinitionSplit,
16259 window: &mut Window,
16260 cx: &mut Context<Self>,
16261 ) -> Task<Result<Navigated>> {
16262 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16263 }
16264
16265 fn go_to_definition_of_kind(
16266 &mut self,
16267 kind: GotoDefinitionKind,
16268 split: bool,
16269 window: &mut Window,
16270 cx: &mut Context<Self>,
16271 ) -> Task<Result<Navigated>> {
16272 let Some(provider) = self.semantics_provider.clone() else {
16273 return Task::ready(Ok(Navigated::No));
16274 };
16275 let head = self.selections.newest::<usize>(cx).head();
16276 let buffer = self.buffer.read(cx);
16277 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16278 return Task::ready(Ok(Navigated::No));
16279 };
16280 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16281 return Task::ready(Ok(Navigated::No));
16282 };
16283
16284 cx.spawn_in(window, async move |editor, cx| {
16285 let Some(definitions) = definitions.await? else {
16286 return Ok(Navigated::No);
16287 };
16288 let navigated = editor
16289 .update_in(cx, |editor, window, cx| {
16290 editor.navigate_to_hover_links(
16291 Some(kind),
16292 definitions
16293 .into_iter()
16294 .filter(|location| {
16295 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16296 })
16297 .map(HoverLink::Text)
16298 .collect::<Vec<_>>(),
16299 split,
16300 window,
16301 cx,
16302 )
16303 })?
16304 .await?;
16305 anyhow::Ok(navigated)
16306 })
16307 }
16308
16309 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16310 let selection = self.selections.newest_anchor();
16311 let head = selection.head();
16312 let tail = selection.tail();
16313
16314 let Some((buffer, start_position)) =
16315 self.buffer.read(cx).text_anchor_for_position(head, cx)
16316 else {
16317 return;
16318 };
16319
16320 let end_position = if head != tail {
16321 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16322 return;
16323 };
16324 Some(pos)
16325 } else {
16326 None
16327 };
16328
16329 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16330 let url = if let Some(end_pos) = end_position {
16331 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16332 } else {
16333 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16334 };
16335
16336 if let Some(url) = url {
16337 cx.update(|window, cx| {
16338 if parse_zed_link(&url, cx).is_some() {
16339 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16340 } else {
16341 cx.open_url(&url);
16342 }
16343 })?;
16344 }
16345
16346 anyhow::Ok(())
16347 });
16348
16349 url_finder.detach();
16350 }
16351
16352 pub fn open_selected_filename(
16353 &mut self,
16354 _: &OpenSelectedFilename,
16355 window: &mut Window,
16356 cx: &mut Context<Self>,
16357 ) {
16358 let Some(workspace) = self.workspace() else {
16359 return;
16360 };
16361
16362 let position = self.selections.newest_anchor().head();
16363
16364 let Some((buffer, buffer_position)) =
16365 self.buffer.read(cx).text_anchor_for_position(position, cx)
16366 else {
16367 return;
16368 };
16369
16370 let project = self.project.clone();
16371
16372 cx.spawn_in(window, async move |_, cx| {
16373 let result = find_file(&buffer, project, buffer_position, cx).await;
16374
16375 if let Some((_, path)) = result {
16376 workspace
16377 .update_in(cx, |workspace, window, cx| {
16378 workspace.open_resolved_path(path, window, cx)
16379 })?
16380 .await?;
16381 }
16382 anyhow::Ok(())
16383 })
16384 .detach();
16385 }
16386
16387 pub(crate) fn navigate_to_hover_links(
16388 &mut self,
16389 kind: Option<GotoDefinitionKind>,
16390 definitions: Vec<HoverLink>,
16391 split: bool,
16392 window: &mut Window,
16393 cx: &mut Context<Editor>,
16394 ) -> Task<Result<Navigated>> {
16395 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16396 let mut first_url_or_file = None;
16397 let definitions: Vec<_> = definitions
16398 .into_iter()
16399 .filter_map(|def| match def {
16400 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16401 HoverLink::InlayHint(lsp_location, server_id) => {
16402 let computation =
16403 self.compute_target_location(lsp_location, server_id, window, cx);
16404 Some(cx.background_spawn(computation))
16405 }
16406 HoverLink::Url(url) => {
16407 first_url_or_file = Some(Either::Left(url));
16408 None
16409 }
16410 HoverLink::File(path) => {
16411 first_url_or_file = Some(Either::Right(path));
16412 None
16413 }
16414 })
16415 .collect();
16416
16417 let workspace = self.workspace();
16418
16419 cx.spawn_in(window, async move |editor, cx| {
16420 let locations: Vec<Location> = future::join_all(definitions)
16421 .await
16422 .into_iter()
16423 .filter_map(|location| location.transpose())
16424 .collect::<Result<_>>()
16425 .context("location tasks")?;
16426 let mut locations = cx.update(|_, cx| {
16427 locations
16428 .into_iter()
16429 .map(|location| {
16430 let buffer = location.buffer.read(cx);
16431 (location.buffer, location.range.to_point(buffer))
16432 })
16433 .into_group_map()
16434 })?;
16435 let mut num_locations = 0;
16436 for ranges in locations.values_mut() {
16437 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16438 ranges.dedup();
16439 num_locations += ranges.len();
16440 }
16441
16442 if num_locations > 1 {
16443 let Some(workspace) = workspace else {
16444 return Ok(Navigated::No);
16445 };
16446
16447 let tab_kind = match kind {
16448 Some(GotoDefinitionKind::Implementation) => "Implementations",
16449 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16450 Some(GotoDefinitionKind::Declaration) => "Declarations",
16451 Some(GotoDefinitionKind::Type) => "Types",
16452 };
16453 let title = editor
16454 .update_in(cx, |_, _, cx| {
16455 let target = locations
16456 .iter()
16457 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16458 .map(|(buffer, location)| {
16459 buffer
16460 .read(cx)
16461 .text_for_range(location.clone())
16462 .collect::<String>()
16463 })
16464 .filter(|text| !text.contains('\n'))
16465 .unique()
16466 .take(3)
16467 .join(", ");
16468 if target.is_empty() {
16469 tab_kind.to_owned()
16470 } else {
16471 format!("{tab_kind} for {target}")
16472 }
16473 })
16474 .context("buffer title")?;
16475
16476 let opened = workspace
16477 .update_in(cx, |workspace, window, cx| {
16478 Self::open_locations_in_multibuffer(
16479 workspace,
16480 locations,
16481 title,
16482 split,
16483 MultibufferSelectionMode::First,
16484 window,
16485 cx,
16486 )
16487 })
16488 .is_ok();
16489
16490 anyhow::Ok(Navigated::from_bool(opened))
16491 } else if num_locations == 0 {
16492 // If there is one url or file, open it directly
16493 match first_url_or_file {
16494 Some(Either::Left(url)) => {
16495 cx.update(|_, cx| cx.open_url(&url))?;
16496 Ok(Navigated::Yes)
16497 }
16498 Some(Either::Right(path)) => {
16499 let Some(workspace) = workspace else {
16500 return Ok(Navigated::No);
16501 };
16502
16503 workspace
16504 .update_in(cx, |workspace, window, cx| {
16505 workspace.open_resolved_path(path, window, cx)
16506 })?
16507 .await?;
16508 Ok(Navigated::Yes)
16509 }
16510 None => Ok(Navigated::No),
16511 }
16512 } else {
16513 let Some(workspace) = workspace else {
16514 return Ok(Navigated::No);
16515 };
16516
16517 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16518 let target_range = target_ranges.first().unwrap().clone();
16519
16520 editor.update_in(cx, |editor, window, cx| {
16521 let range = target_range.to_point(target_buffer.read(cx));
16522 let range = editor.range_for_match(&range);
16523 let range = collapse_multiline_range(range);
16524
16525 if !split
16526 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16527 {
16528 editor.go_to_singleton_buffer_range(range, window, cx);
16529 } else {
16530 let pane = workspace.read(cx).active_pane().clone();
16531 window.defer(cx, move |window, cx| {
16532 let target_editor: Entity<Self> =
16533 workspace.update(cx, |workspace, cx| {
16534 let pane = if split {
16535 workspace.adjacent_pane(window, cx)
16536 } else {
16537 workspace.active_pane().clone()
16538 };
16539
16540 workspace.open_project_item(
16541 pane,
16542 target_buffer.clone(),
16543 true,
16544 true,
16545 window,
16546 cx,
16547 )
16548 });
16549 target_editor.update(cx, |target_editor, cx| {
16550 // When selecting a definition in a different buffer, disable the nav history
16551 // to avoid creating a history entry at the previous cursor location.
16552 pane.update(cx, |pane, _| pane.disable_history());
16553 target_editor.go_to_singleton_buffer_range(range, window, cx);
16554 pane.update(cx, |pane, _| pane.enable_history());
16555 });
16556 });
16557 }
16558 Navigated::Yes
16559 })
16560 }
16561 })
16562 }
16563
16564 fn compute_target_location(
16565 &self,
16566 lsp_location: lsp::Location,
16567 server_id: LanguageServerId,
16568 window: &mut Window,
16569 cx: &mut Context<Self>,
16570 ) -> Task<anyhow::Result<Option<Location>>> {
16571 let Some(project) = self.project.clone() else {
16572 return Task::ready(Ok(None));
16573 };
16574
16575 cx.spawn_in(window, async move |editor, cx| {
16576 let location_task = editor.update(cx, |_, cx| {
16577 project.update(cx, |project, cx| {
16578 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16579 })
16580 })?;
16581 let location = Some({
16582 let target_buffer_handle = location_task.await.context("open local buffer")?;
16583 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16584 let target_start = target_buffer
16585 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16586 let target_end = target_buffer
16587 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16588 target_buffer.anchor_after(target_start)
16589 ..target_buffer.anchor_before(target_end)
16590 })?;
16591 Location {
16592 buffer: target_buffer_handle,
16593 range,
16594 }
16595 });
16596 Ok(location)
16597 })
16598 }
16599
16600 pub fn find_all_references(
16601 &mut self,
16602 _: &FindAllReferences,
16603 window: &mut Window,
16604 cx: &mut Context<Self>,
16605 ) -> Option<Task<Result<Navigated>>> {
16606 let selection = self.selections.newest::<usize>(cx);
16607 let multi_buffer = self.buffer.read(cx);
16608 let head = selection.head();
16609
16610 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16611 let head_anchor = multi_buffer_snapshot.anchor_at(
16612 head,
16613 if head < selection.tail() {
16614 Bias::Right
16615 } else {
16616 Bias::Left
16617 },
16618 );
16619
16620 match self
16621 .find_all_references_task_sources
16622 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16623 {
16624 Ok(_) => {
16625 log::info!(
16626 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16627 );
16628 return None;
16629 }
16630 Err(i) => {
16631 self.find_all_references_task_sources.insert(i, head_anchor);
16632 }
16633 }
16634
16635 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16636 let workspace = self.workspace()?;
16637 let project = workspace.read(cx).project().clone();
16638 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16639 Some(cx.spawn_in(window, async move |editor, cx| {
16640 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16641 if let Ok(i) = editor
16642 .find_all_references_task_sources
16643 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16644 {
16645 editor.find_all_references_task_sources.remove(i);
16646 }
16647 });
16648
16649 let Some(locations) = references.await? else {
16650 return anyhow::Ok(Navigated::No);
16651 };
16652 let mut locations = cx.update(|_, cx| {
16653 locations
16654 .into_iter()
16655 .map(|location| {
16656 let buffer = location.buffer.read(cx);
16657 (location.buffer, location.range.to_point(buffer))
16658 })
16659 .into_group_map()
16660 })?;
16661 if locations.is_empty() {
16662 return anyhow::Ok(Navigated::No);
16663 }
16664 for ranges in locations.values_mut() {
16665 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16666 ranges.dedup();
16667 }
16668
16669 workspace.update_in(cx, |workspace, window, cx| {
16670 let target = locations
16671 .iter()
16672 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16673 .map(|(buffer, location)| {
16674 buffer
16675 .read(cx)
16676 .text_for_range(location.clone())
16677 .collect::<String>()
16678 })
16679 .filter(|text| !text.contains('\n'))
16680 .unique()
16681 .take(3)
16682 .join(", ");
16683 let title = if target.is_empty() {
16684 "References".to_owned()
16685 } else {
16686 format!("References to {target}")
16687 };
16688 Self::open_locations_in_multibuffer(
16689 workspace,
16690 locations,
16691 title,
16692 false,
16693 MultibufferSelectionMode::First,
16694 window,
16695 cx,
16696 );
16697 Navigated::Yes
16698 })
16699 }))
16700 }
16701
16702 /// Opens a multibuffer with the given project locations in it
16703 pub fn open_locations_in_multibuffer(
16704 workspace: &mut Workspace,
16705 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16706 title: String,
16707 split: bool,
16708 multibuffer_selection_mode: MultibufferSelectionMode,
16709 window: &mut Window,
16710 cx: &mut Context<Workspace>,
16711 ) {
16712 if locations.is_empty() {
16713 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16714 return;
16715 }
16716
16717 let capability = workspace.project().read(cx).capability();
16718 let mut ranges = <Vec<Range<Anchor>>>::new();
16719
16720 // a key to find existing multibuffer editors with the same set of locations
16721 // to prevent us from opening more and more multibuffer tabs for searches and the like
16722 let mut key = (title.clone(), vec![]);
16723 let excerpt_buffer = cx.new(|cx| {
16724 let key = &mut key.1;
16725 let mut multibuffer = MultiBuffer::new(capability);
16726 for (buffer, mut ranges_for_buffer) in locations {
16727 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16728 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16729 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16730 PathKey::for_buffer(&buffer, cx),
16731 buffer.clone(),
16732 ranges_for_buffer,
16733 multibuffer_context_lines(cx),
16734 cx,
16735 );
16736 ranges.extend(new_ranges)
16737 }
16738
16739 multibuffer.with_title(title)
16740 });
16741 let existing = workspace.active_pane().update(cx, |pane, cx| {
16742 pane.items()
16743 .filter_map(|item| item.downcast::<Editor>())
16744 .find(|editor| {
16745 editor
16746 .read(cx)
16747 .lookup_key
16748 .as_ref()
16749 .and_then(|it| {
16750 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16751 })
16752 .is_some_and(|it| *it == key)
16753 })
16754 });
16755 let editor = existing.unwrap_or_else(|| {
16756 cx.new(|cx| {
16757 let mut editor = Editor::for_multibuffer(
16758 excerpt_buffer,
16759 Some(workspace.project().clone()),
16760 window,
16761 cx,
16762 );
16763 editor.lookup_key = Some(Box::new(key));
16764 editor
16765 })
16766 });
16767 editor.update(cx, |editor, cx| {
16768 match multibuffer_selection_mode {
16769 MultibufferSelectionMode::First => {
16770 if let Some(first_range) = ranges.first() {
16771 editor.change_selections(
16772 SelectionEffects::no_scroll(),
16773 window,
16774 cx,
16775 |selections| {
16776 selections.clear_disjoint();
16777 selections
16778 .select_anchor_ranges(std::iter::once(first_range.clone()));
16779 },
16780 );
16781 }
16782 editor.highlight_background::<Self>(
16783 &ranges,
16784 |theme| theme.colors().editor_highlighted_line_background,
16785 cx,
16786 );
16787 }
16788 MultibufferSelectionMode::All => {
16789 editor.change_selections(
16790 SelectionEffects::no_scroll(),
16791 window,
16792 cx,
16793 |selections| {
16794 selections.clear_disjoint();
16795 selections.select_anchor_ranges(ranges);
16796 },
16797 );
16798 }
16799 }
16800 editor.register_buffers_with_language_servers(cx);
16801 });
16802
16803 let item = Box::new(editor);
16804 let item_id = item.item_id();
16805
16806 if split {
16807 workspace.split_item(SplitDirection::Right, item, window, cx);
16808 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16809 let (preview_item_id, preview_item_idx) =
16810 workspace.active_pane().read_with(cx, |pane, _| {
16811 (pane.preview_item_id(), pane.preview_item_idx())
16812 });
16813
16814 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16815
16816 if let Some(preview_item_id) = preview_item_id {
16817 workspace.active_pane().update(cx, |pane, cx| {
16818 pane.remove_item(preview_item_id, false, false, window, cx);
16819 });
16820 }
16821 } else {
16822 workspace.add_item_to_active_pane(item, None, true, window, cx);
16823 }
16824 workspace.active_pane().update(cx, |pane, cx| {
16825 pane.set_preview_item_id(Some(item_id), cx);
16826 });
16827 }
16828
16829 pub fn rename(
16830 &mut self,
16831 _: &Rename,
16832 window: &mut Window,
16833 cx: &mut Context<Self>,
16834 ) -> Option<Task<Result<()>>> {
16835 use language::ToOffset as _;
16836
16837 let provider = self.semantics_provider.clone()?;
16838 let selection = self.selections.newest_anchor().clone();
16839 let (cursor_buffer, cursor_buffer_position) = self
16840 .buffer
16841 .read(cx)
16842 .text_anchor_for_position(selection.head(), cx)?;
16843 let (tail_buffer, cursor_buffer_position_end) = self
16844 .buffer
16845 .read(cx)
16846 .text_anchor_for_position(selection.tail(), cx)?;
16847 if tail_buffer != cursor_buffer {
16848 return None;
16849 }
16850
16851 let snapshot = cursor_buffer.read(cx).snapshot();
16852 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16853 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16854 let prepare_rename = provider
16855 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16856 .unwrap_or_else(|| Task::ready(Ok(None)));
16857 drop(snapshot);
16858
16859 Some(cx.spawn_in(window, async move |this, cx| {
16860 let rename_range = if let Some(range) = prepare_rename.await? {
16861 Some(range)
16862 } else {
16863 this.update(cx, |this, cx| {
16864 let buffer = this.buffer.read(cx).snapshot(cx);
16865 let mut buffer_highlights = this
16866 .document_highlights_for_position(selection.head(), &buffer)
16867 .filter(|highlight| {
16868 highlight.start.excerpt_id == selection.head().excerpt_id
16869 && highlight.end.excerpt_id == selection.head().excerpt_id
16870 });
16871 buffer_highlights
16872 .next()
16873 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16874 })?
16875 };
16876 if let Some(rename_range) = rename_range {
16877 this.update_in(cx, |this, window, cx| {
16878 let snapshot = cursor_buffer.read(cx).snapshot();
16879 let rename_buffer_range = rename_range.to_offset(&snapshot);
16880 let cursor_offset_in_rename_range =
16881 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16882 let cursor_offset_in_rename_range_end =
16883 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16884
16885 this.take_rename(false, window, cx);
16886 let buffer = this.buffer.read(cx).read(cx);
16887 let cursor_offset = selection.head().to_offset(&buffer);
16888 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16889 let rename_end = rename_start + rename_buffer_range.len();
16890 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16891 let mut old_highlight_id = None;
16892 let old_name: Arc<str> = buffer
16893 .chunks(rename_start..rename_end, true)
16894 .map(|chunk| {
16895 if old_highlight_id.is_none() {
16896 old_highlight_id = chunk.syntax_highlight_id;
16897 }
16898 chunk.text
16899 })
16900 .collect::<String>()
16901 .into();
16902
16903 drop(buffer);
16904
16905 // Position the selection in the rename editor so that it matches the current selection.
16906 this.show_local_selections = false;
16907 let rename_editor = cx.new(|cx| {
16908 let mut editor = Editor::single_line(window, cx);
16909 editor.buffer.update(cx, |buffer, cx| {
16910 buffer.edit([(0..0, old_name.clone())], None, cx)
16911 });
16912 let rename_selection_range = match cursor_offset_in_rename_range
16913 .cmp(&cursor_offset_in_rename_range_end)
16914 {
16915 Ordering::Equal => {
16916 editor.select_all(&SelectAll, window, cx);
16917 return editor;
16918 }
16919 Ordering::Less => {
16920 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16921 }
16922 Ordering::Greater => {
16923 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16924 }
16925 };
16926 if rename_selection_range.end > old_name.len() {
16927 editor.select_all(&SelectAll, window, cx);
16928 } else {
16929 editor.change_selections(Default::default(), window, cx, |s| {
16930 s.select_ranges([rename_selection_range]);
16931 });
16932 }
16933 editor
16934 });
16935 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16936 if e == &EditorEvent::Focused {
16937 cx.emit(EditorEvent::FocusedIn)
16938 }
16939 })
16940 .detach();
16941
16942 let write_highlights =
16943 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16944 let read_highlights =
16945 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16946 let ranges = write_highlights
16947 .iter()
16948 .flat_map(|(_, ranges)| ranges.iter())
16949 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16950 .cloned()
16951 .collect();
16952
16953 this.highlight_text::<Rename>(
16954 ranges,
16955 HighlightStyle {
16956 fade_out: Some(0.6),
16957 ..Default::default()
16958 },
16959 cx,
16960 );
16961 let rename_focus_handle = rename_editor.focus_handle(cx);
16962 window.focus(&rename_focus_handle);
16963 let block_id = this.insert_blocks(
16964 [BlockProperties {
16965 style: BlockStyle::Flex,
16966 placement: BlockPlacement::Below(range.start),
16967 height: Some(1),
16968 render: Arc::new({
16969 let rename_editor = rename_editor.clone();
16970 move |cx: &mut BlockContext| {
16971 let mut text_style = cx.editor_style.text.clone();
16972 if let Some(highlight_style) = old_highlight_id
16973 .and_then(|h| h.style(&cx.editor_style.syntax))
16974 {
16975 text_style = text_style.highlight(highlight_style);
16976 }
16977 div()
16978 .block_mouse_except_scroll()
16979 .pl(cx.anchor_x)
16980 .child(EditorElement::new(
16981 &rename_editor,
16982 EditorStyle {
16983 background: cx.theme().system().transparent,
16984 local_player: cx.editor_style.local_player,
16985 text: text_style,
16986 scrollbar_width: cx.editor_style.scrollbar_width,
16987 syntax: cx.editor_style.syntax.clone(),
16988 status: cx.editor_style.status.clone(),
16989 inlay_hints_style: HighlightStyle {
16990 font_weight: Some(FontWeight::BOLD),
16991 ..make_inlay_hints_style(cx.app)
16992 },
16993 edit_prediction_styles: make_suggestion_styles(
16994 cx.app,
16995 ),
16996 ..EditorStyle::default()
16997 },
16998 ))
16999 .into_any_element()
17000 }
17001 }),
17002 priority: 0,
17003 }],
17004 Some(Autoscroll::fit()),
17005 cx,
17006 )[0];
17007 this.pending_rename = Some(RenameState {
17008 range,
17009 old_name,
17010 editor: rename_editor,
17011 block_id,
17012 });
17013 })?;
17014 }
17015
17016 Ok(())
17017 }))
17018 }
17019
17020 pub fn confirm_rename(
17021 &mut self,
17022 _: &ConfirmRename,
17023 window: &mut Window,
17024 cx: &mut Context<Self>,
17025 ) -> Option<Task<Result<()>>> {
17026 let rename = self.take_rename(false, window, cx)?;
17027 let workspace = self.workspace()?.downgrade();
17028 let (buffer, start) = self
17029 .buffer
17030 .read(cx)
17031 .text_anchor_for_position(rename.range.start, cx)?;
17032 let (end_buffer, _) = self
17033 .buffer
17034 .read(cx)
17035 .text_anchor_for_position(rename.range.end, cx)?;
17036 if buffer != end_buffer {
17037 return None;
17038 }
17039
17040 let old_name = rename.old_name;
17041 let new_name = rename.editor.read(cx).text(cx);
17042
17043 let rename = self.semantics_provider.as_ref()?.perform_rename(
17044 &buffer,
17045 start,
17046 new_name.clone(),
17047 cx,
17048 )?;
17049
17050 Some(cx.spawn_in(window, async move |editor, cx| {
17051 let project_transaction = rename.await?;
17052 Self::open_project_transaction(
17053 &editor,
17054 workspace,
17055 project_transaction,
17056 format!("Rename: {} → {}", old_name, new_name),
17057 cx,
17058 )
17059 .await?;
17060
17061 editor.update(cx, |editor, cx| {
17062 editor.refresh_document_highlights(cx);
17063 })?;
17064 Ok(())
17065 }))
17066 }
17067
17068 fn take_rename(
17069 &mut self,
17070 moving_cursor: bool,
17071 window: &mut Window,
17072 cx: &mut Context<Self>,
17073 ) -> Option<RenameState> {
17074 let rename = self.pending_rename.take()?;
17075 if rename.editor.focus_handle(cx).is_focused(window) {
17076 window.focus(&self.focus_handle);
17077 }
17078
17079 self.remove_blocks(
17080 [rename.block_id].into_iter().collect(),
17081 Some(Autoscroll::fit()),
17082 cx,
17083 );
17084 self.clear_highlights::<Rename>(cx);
17085 self.show_local_selections = true;
17086
17087 if moving_cursor {
17088 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17089 editor.selections.newest::<usize>(cx).head()
17090 });
17091
17092 // Update the selection to match the position of the selection inside
17093 // the rename editor.
17094 let snapshot = self.buffer.read(cx).read(cx);
17095 let rename_range = rename.range.to_offset(&snapshot);
17096 let cursor_in_editor = snapshot
17097 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17098 .min(rename_range.end);
17099 drop(snapshot);
17100
17101 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17102 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17103 });
17104 } else {
17105 self.refresh_document_highlights(cx);
17106 }
17107
17108 Some(rename)
17109 }
17110
17111 pub fn pending_rename(&self) -> Option<&RenameState> {
17112 self.pending_rename.as_ref()
17113 }
17114
17115 fn format(
17116 &mut self,
17117 _: &Format,
17118 window: &mut Window,
17119 cx: &mut Context<Self>,
17120 ) -> Option<Task<Result<()>>> {
17121 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17122
17123 let project = match &self.project {
17124 Some(project) => project.clone(),
17125 None => return None,
17126 };
17127
17128 Some(self.perform_format(
17129 project,
17130 FormatTrigger::Manual,
17131 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17132 window,
17133 cx,
17134 ))
17135 }
17136
17137 fn format_selections(
17138 &mut self,
17139 _: &FormatSelections,
17140 window: &mut Window,
17141 cx: &mut Context<Self>,
17142 ) -> Option<Task<Result<()>>> {
17143 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17144
17145 let project = match &self.project {
17146 Some(project) => project.clone(),
17147 None => return None,
17148 };
17149
17150 let ranges = self
17151 .selections
17152 .all_adjusted(cx)
17153 .into_iter()
17154 .map(|selection| selection.range())
17155 .collect_vec();
17156
17157 Some(self.perform_format(
17158 project,
17159 FormatTrigger::Manual,
17160 FormatTarget::Ranges(ranges),
17161 window,
17162 cx,
17163 ))
17164 }
17165
17166 fn perform_format(
17167 &mut self,
17168 project: Entity<Project>,
17169 trigger: FormatTrigger,
17170 target: FormatTarget,
17171 window: &mut Window,
17172 cx: &mut Context<Self>,
17173 ) -> Task<Result<()>> {
17174 let buffer = self.buffer.clone();
17175 let (buffers, target) = match target {
17176 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17177 FormatTarget::Ranges(selection_ranges) => {
17178 let multi_buffer = buffer.read(cx);
17179 let snapshot = multi_buffer.read(cx);
17180 let mut buffers = HashSet::default();
17181 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17182 BTreeMap::new();
17183 for selection_range in selection_ranges {
17184 for (buffer, buffer_range, _) in
17185 snapshot.range_to_buffer_ranges(selection_range)
17186 {
17187 let buffer_id = buffer.remote_id();
17188 let start = buffer.anchor_before(buffer_range.start);
17189 let end = buffer.anchor_after(buffer_range.end);
17190 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17191 buffer_id_to_ranges
17192 .entry(buffer_id)
17193 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17194 .or_insert_with(|| vec![start..end]);
17195 }
17196 }
17197 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17198 }
17199 };
17200
17201 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17202 let selections_prev = transaction_id_prev
17203 .and_then(|transaction_id_prev| {
17204 // default to selections as they were after the last edit, if we have them,
17205 // instead of how they are now.
17206 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17207 // will take you back to where you made the last edit, instead of staying where you scrolled
17208 self.selection_history
17209 .transaction(transaction_id_prev)
17210 .map(|t| t.0.clone())
17211 })
17212 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17213
17214 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17215 let format = project.update(cx, |project, cx| {
17216 project.format(buffers, target, true, trigger, cx)
17217 });
17218
17219 cx.spawn_in(window, async move |editor, cx| {
17220 let transaction = futures::select_biased! {
17221 transaction = format.log_err().fuse() => transaction,
17222 () = timeout => {
17223 log::warn!("timed out waiting for formatting");
17224 None
17225 }
17226 };
17227
17228 buffer
17229 .update(cx, |buffer, cx| {
17230 if let Some(transaction) = transaction
17231 && !buffer.is_singleton()
17232 {
17233 buffer.push_transaction(&transaction.0, cx);
17234 }
17235 cx.notify();
17236 })
17237 .ok();
17238
17239 if let Some(transaction_id_now) =
17240 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17241 {
17242 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17243 if has_new_transaction {
17244 _ = editor.update(cx, |editor, _| {
17245 editor
17246 .selection_history
17247 .insert_transaction(transaction_id_now, selections_prev);
17248 });
17249 }
17250 }
17251
17252 Ok(())
17253 })
17254 }
17255
17256 fn organize_imports(
17257 &mut self,
17258 _: &OrganizeImports,
17259 window: &mut Window,
17260 cx: &mut Context<Self>,
17261 ) -> Option<Task<Result<()>>> {
17262 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17263 let project = match &self.project {
17264 Some(project) => project.clone(),
17265 None => return None,
17266 };
17267 Some(self.perform_code_action_kind(
17268 project,
17269 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17270 window,
17271 cx,
17272 ))
17273 }
17274
17275 fn perform_code_action_kind(
17276 &mut self,
17277 project: Entity<Project>,
17278 kind: CodeActionKind,
17279 window: &mut Window,
17280 cx: &mut Context<Self>,
17281 ) -> Task<Result<()>> {
17282 let buffer = self.buffer.clone();
17283 let buffers = buffer.read(cx).all_buffers();
17284 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17285 let apply_action = project.update(cx, |project, cx| {
17286 project.apply_code_action_kind(buffers, kind, true, cx)
17287 });
17288 cx.spawn_in(window, async move |_, cx| {
17289 let transaction = futures::select_biased! {
17290 () = timeout => {
17291 log::warn!("timed out waiting for executing code action");
17292 None
17293 }
17294 transaction = apply_action.log_err().fuse() => transaction,
17295 };
17296 buffer
17297 .update(cx, |buffer, cx| {
17298 // check if we need this
17299 if let Some(transaction) = transaction
17300 && !buffer.is_singleton()
17301 {
17302 buffer.push_transaction(&transaction.0, cx);
17303 }
17304 cx.notify();
17305 })
17306 .ok();
17307 Ok(())
17308 })
17309 }
17310
17311 pub fn restart_language_server(
17312 &mut self,
17313 _: &RestartLanguageServer,
17314 _: &mut Window,
17315 cx: &mut Context<Self>,
17316 ) {
17317 if let Some(project) = self.project.clone() {
17318 self.buffer.update(cx, |multi_buffer, cx| {
17319 project.update(cx, |project, cx| {
17320 project.restart_language_servers_for_buffers(
17321 multi_buffer.all_buffers().into_iter().collect(),
17322 HashSet::default(),
17323 cx,
17324 );
17325 });
17326 })
17327 }
17328 }
17329
17330 pub fn stop_language_server(
17331 &mut self,
17332 _: &StopLanguageServer,
17333 _: &mut Window,
17334 cx: &mut Context<Self>,
17335 ) {
17336 if let Some(project) = self.project.clone() {
17337 self.buffer.update(cx, |multi_buffer, cx| {
17338 project.update(cx, |project, cx| {
17339 project.stop_language_servers_for_buffers(
17340 multi_buffer.all_buffers().into_iter().collect(),
17341 HashSet::default(),
17342 cx,
17343 );
17344 cx.emit(project::Event::RefreshInlayHints);
17345 });
17346 });
17347 }
17348 }
17349
17350 fn cancel_language_server_work(
17351 workspace: &mut Workspace,
17352 _: &actions::CancelLanguageServerWork,
17353 _: &mut Window,
17354 cx: &mut Context<Workspace>,
17355 ) {
17356 let project = workspace.project();
17357 let buffers = workspace
17358 .active_item(cx)
17359 .and_then(|item| item.act_as::<Editor>(cx))
17360 .map_or(HashSet::default(), |editor| {
17361 editor.read(cx).buffer.read(cx).all_buffers()
17362 });
17363 project.update(cx, |project, cx| {
17364 project.cancel_language_server_work_for_buffers(buffers, cx);
17365 });
17366 }
17367
17368 fn show_character_palette(
17369 &mut self,
17370 _: &ShowCharacterPalette,
17371 window: &mut Window,
17372 _: &mut Context<Self>,
17373 ) {
17374 window.show_character_palette();
17375 }
17376
17377 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17378 if !self.diagnostics_enabled() {
17379 return;
17380 }
17381
17382 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17383 let buffer = self.buffer.read(cx).snapshot(cx);
17384 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17385 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17386 let is_valid = buffer
17387 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17388 .any(|entry| {
17389 entry.diagnostic.is_primary
17390 && !entry.range.is_empty()
17391 && entry.range.start == primary_range_start
17392 && entry.diagnostic.message == active_diagnostics.active_message
17393 });
17394
17395 if !is_valid {
17396 self.dismiss_diagnostics(cx);
17397 }
17398 }
17399 }
17400
17401 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17402 match &self.active_diagnostics {
17403 ActiveDiagnostic::Group(group) => Some(group),
17404 _ => None,
17405 }
17406 }
17407
17408 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17409 if !self.diagnostics_enabled() {
17410 return;
17411 }
17412 self.dismiss_diagnostics(cx);
17413 self.active_diagnostics = ActiveDiagnostic::All;
17414 }
17415
17416 fn activate_diagnostics(
17417 &mut self,
17418 buffer_id: BufferId,
17419 diagnostic: DiagnosticEntry<usize>,
17420 window: &mut Window,
17421 cx: &mut Context<Self>,
17422 ) {
17423 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17424 return;
17425 }
17426 self.dismiss_diagnostics(cx);
17427 let snapshot = self.snapshot(window, cx);
17428 let buffer = self.buffer.read(cx).snapshot(cx);
17429 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17430 return;
17431 };
17432
17433 let diagnostic_group = buffer
17434 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17435 .collect::<Vec<_>>();
17436
17437 let blocks =
17438 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17439
17440 let blocks = self.display_map.update(cx, |display_map, cx| {
17441 display_map.insert_blocks(blocks, cx).into_iter().collect()
17442 });
17443 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17444 active_range: buffer.anchor_before(diagnostic.range.start)
17445 ..buffer.anchor_after(diagnostic.range.end),
17446 active_message: diagnostic.diagnostic.message.clone(),
17447 group_id: diagnostic.diagnostic.group_id,
17448 blocks,
17449 });
17450 cx.notify();
17451 }
17452
17453 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17454 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17455 return;
17456 };
17457
17458 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17459 if let ActiveDiagnostic::Group(group) = prev {
17460 self.display_map.update(cx, |display_map, cx| {
17461 display_map.remove_blocks(group.blocks, cx);
17462 });
17463 cx.notify();
17464 }
17465 }
17466
17467 /// Disable inline diagnostics rendering for this editor.
17468 pub fn disable_inline_diagnostics(&mut self) {
17469 self.inline_diagnostics_enabled = false;
17470 self.inline_diagnostics_update = Task::ready(());
17471 self.inline_diagnostics.clear();
17472 }
17473
17474 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17475 self.diagnostics_enabled = false;
17476 self.dismiss_diagnostics(cx);
17477 self.inline_diagnostics_update = Task::ready(());
17478 self.inline_diagnostics.clear();
17479 }
17480
17481 pub fn disable_word_completions(&mut self) {
17482 self.word_completions_enabled = false;
17483 }
17484
17485 pub fn diagnostics_enabled(&self) -> bool {
17486 self.diagnostics_enabled && self.mode.is_full()
17487 }
17488
17489 pub fn inline_diagnostics_enabled(&self) -> bool {
17490 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17491 }
17492
17493 pub fn show_inline_diagnostics(&self) -> bool {
17494 self.show_inline_diagnostics
17495 }
17496
17497 pub fn toggle_inline_diagnostics(
17498 &mut self,
17499 _: &ToggleInlineDiagnostics,
17500 window: &mut Window,
17501 cx: &mut Context<Editor>,
17502 ) {
17503 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17504 self.refresh_inline_diagnostics(false, window, cx);
17505 }
17506
17507 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17508 self.diagnostics_max_severity = severity;
17509 self.display_map.update(cx, |display_map, _| {
17510 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17511 });
17512 }
17513
17514 pub fn toggle_diagnostics(
17515 &mut self,
17516 _: &ToggleDiagnostics,
17517 window: &mut Window,
17518 cx: &mut Context<Editor>,
17519 ) {
17520 if !self.diagnostics_enabled() {
17521 return;
17522 }
17523
17524 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17525 EditorSettings::get_global(cx)
17526 .diagnostics_max_severity
17527 .filter(|severity| severity != &DiagnosticSeverity::Off)
17528 .unwrap_or(DiagnosticSeverity::Hint)
17529 } else {
17530 DiagnosticSeverity::Off
17531 };
17532 self.set_max_diagnostics_severity(new_severity, cx);
17533 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17534 self.active_diagnostics = ActiveDiagnostic::None;
17535 self.inline_diagnostics_update = Task::ready(());
17536 self.inline_diagnostics.clear();
17537 } else {
17538 self.refresh_inline_diagnostics(false, window, cx);
17539 }
17540
17541 cx.notify();
17542 }
17543
17544 pub fn toggle_minimap(
17545 &mut self,
17546 _: &ToggleMinimap,
17547 window: &mut Window,
17548 cx: &mut Context<Editor>,
17549 ) {
17550 if self.supports_minimap(cx) {
17551 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17552 }
17553 }
17554
17555 fn refresh_inline_diagnostics(
17556 &mut self,
17557 debounce: bool,
17558 window: &mut Window,
17559 cx: &mut Context<Self>,
17560 ) {
17561 let max_severity = ProjectSettings::get_global(cx)
17562 .diagnostics
17563 .inline
17564 .max_severity
17565 .unwrap_or(self.diagnostics_max_severity);
17566
17567 if !self.inline_diagnostics_enabled()
17568 || !self.show_inline_diagnostics
17569 || max_severity == DiagnosticSeverity::Off
17570 {
17571 self.inline_diagnostics_update = Task::ready(());
17572 self.inline_diagnostics.clear();
17573 return;
17574 }
17575
17576 let debounce_ms = ProjectSettings::get_global(cx)
17577 .diagnostics
17578 .inline
17579 .update_debounce_ms;
17580 let debounce = if debounce && debounce_ms > 0 {
17581 Some(Duration::from_millis(debounce_ms))
17582 } else {
17583 None
17584 };
17585 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17586 if let Some(debounce) = debounce {
17587 cx.background_executor().timer(debounce).await;
17588 }
17589 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17590 editor
17591 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17592 .ok()
17593 }) else {
17594 return;
17595 };
17596
17597 let new_inline_diagnostics = cx
17598 .background_spawn(async move {
17599 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17600 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17601 let message = diagnostic_entry
17602 .diagnostic
17603 .message
17604 .split_once('\n')
17605 .map(|(line, _)| line)
17606 .map(SharedString::new)
17607 .unwrap_or_else(|| {
17608 SharedString::from(diagnostic_entry.diagnostic.message)
17609 });
17610 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17611 let (Ok(i) | Err(i)) = inline_diagnostics
17612 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17613 inline_diagnostics.insert(
17614 i,
17615 (
17616 start_anchor,
17617 InlineDiagnostic {
17618 message,
17619 group_id: diagnostic_entry.diagnostic.group_id,
17620 start: diagnostic_entry.range.start.to_point(&snapshot),
17621 is_primary: diagnostic_entry.diagnostic.is_primary,
17622 severity: diagnostic_entry.diagnostic.severity,
17623 },
17624 ),
17625 );
17626 }
17627 inline_diagnostics
17628 })
17629 .await;
17630
17631 editor
17632 .update(cx, |editor, cx| {
17633 editor.inline_diagnostics = new_inline_diagnostics;
17634 cx.notify();
17635 })
17636 .ok();
17637 });
17638 }
17639
17640 fn pull_diagnostics(
17641 &mut self,
17642 buffer_id: Option<BufferId>,
17643 window: &Window,
17644 cx: &mut Context<Self>,
17645 ) -> Option<()> {
17646 if !self.mode().is_full() {
17647 return None;
17648 }
17649 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17650 .diagnostics
17651 .lsp_pull_diagnostics;
17652 if !pull_diagnostics_settings.enabled {
17653 return None;
17654 }
17655 let project = self.project()?.downgrade();
17656 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17657 let mut buffers = self.buffer.read(cx).all_buffers();
17658 if let Some(buffer_id) = buffer_id {
17659 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17660 }
17661
17662 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17663 cx.background_executor().timer(debounce).await;
17664
17665 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17666 buffers
17667 .into_iter()
17668 .filter_map(|buffer| {
17669 project
17670 .update(cx, |project, cx| {
17671 project.lsp_store().update(cx, |lsp_store, cx| {
17672 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17673 })
17674 })
17675 .ok()
17676 })
17677 .collect::<FuturesUnordered<_>>()
17678 }) else {
17679 return;
17680 };
17681
17682 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17683 match pull_task {
17684 Ok(()) => {
17685 if editor
17686 .update_in(cx, |editor, window, cx| {
17687 editor.update_diagnostics_state(window, cx);
17688 })
17689 .is_err()
17690 {
17691 return;
17692 }
17693 }
17694 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17695 }
17696 }
17697 });
17698
17699 Some(())
17700 }
17701
17702 pub fn set_selections_from_remote(
17703 &mut self,
17704 selections: Vec<Selection<Anchor>>,
17705 pending_selection: Option<Selection<Anchor>>,
17706 window: &mut Window,
17707 cx: &mut Context<Self>,
17708 ) {
17709 let old_cursor_position = self.selections.newest_anchor().head();
17710 self.selections.change_with(cx, |s| {
17711 s.select_anchors(selections);
17712 if let Some(pending_selection) = pending_selection {
17713 s.set_pending(pending_selection, SelectMode::Character);
17714 } else {
17715 s.clear_pending();
17716 }
17717 });
17718 self.selections_did_change(
17719 false,
17720 &old_cursor_position,
17721 SelectionEffects::default(),
17722 window,
17723 cx,
17724 );
17725 }
17726
17727 pub fn transact(
17728 &mut self,
17729 window: &mut Window,
17730 cx: &mut Context<Self>,
17731 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17732 ) -> Option<TransactionId> {
17733 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17734 this.start_transaction_at(Instant::now(), window, cx);
17735 update(this, window, cx);
17736 this.end_transaction_at(Instant::now(), cx)
17737 })
17738 }
17739
17740 pub fn start_transaction_at(
17741 &mut self,
17742 now: Instant,
17743 window: &mut Window,
17744 cx: &mut Context<Self>,
17745 ) -> Option<TransactionId> {
17746 self.end_selection(window, cx);
17747 if let Some(tx_id) = self
17748 .buffer
17749 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17750 {
17751 self.selection_history
17752 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17753 cx.emit(EditorEvent::TransactionBegun {
17754 transaction_id: tx_id,
17755 });
17756 Some(tx_id)
17757 } else {
17758 None
17759 }
17760 }
17761
17762 pub fn end_transaction_at(
17763 &mut self,
17764 now: Instant,
17765 cx: &mut Context<Self>,
17766 ) -> Option<TransactionId> {
17767 if let Some(transaction_id) = self
17768 .buffer
17769 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17770 {
17771 if let Some((_, end_selections)) =
17772 self.selection_history.transaction_mut(transaction_id)
17773 {
17774 *end_selections = Some(self.selections.disjoint_anchors_arc());
17775 } else {
17776 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17777 }
17778
17779 cx.emit(EditorEvent::Edited { transaction_id });
17780 Some(transaction_id)
17781 } else {
17782 None
17783 }
17784 }
17785
17786 pub fn modify_transaction_selection_history(
17787 &mut self,
17788 transaction_id: TransactionId,
17789 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17790 ) -> bool {
17791 self.selection_history
17792 .transaction_mut(transaction_id)
17793 .map(modify)
17794 .is_some()
17795 }
17796
17797 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17798 if self.selection_mark_mode {
17799 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17800 s.move_with(|_, sel| {
17801 sel.collapse_to(sel.head(), SelectionGoal::None);
17802 });
17803 })
17804 }
17805 self.selection_mark_mode = true;
17806 cx.notify();
17807 }
17808
17809 pub fn swap_selection_ends(
17810 &mut self,
17811 _: &actions::SwapSelectionEnds,
17812 window: &mut Window,
17813 cx: &mut Context<Self>,
17814 ) {
17815 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17816 s.move_with(|_, sel| {
17817 if sel.start != sel.end {
17818 sel.reversed = !sel.reversed
17819 }
17820 });
17821 });
17822 self.request_autoscroll(Autoscroll::newest(), cx);
17823 cx.notify();
17824 }
17825
17826 pub fn toggle_focus(
17827 workspace: &mut Workspace,
17828 _: &actions::ToggleFocus,
17829 window: &mut Window,
17830 cx: &mut Context<Workspace>,
17831 ) {
17832 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17833 return;
17834 };
17835 workspace.activate_item(&item, true, true, window, cx);
17836 }
17837
17838 pub fn toggle_fold(
17839 &mut self,
17840 _: &actions::ToggleFold,
17841 window: &mut Window,
17842 cx: &mut Context<Self>,
17843 ) {
17844 if self.is_singleton(cx) {
17845 let selection = self.selections.newest::<Point>(cx);
17846
17847 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17848 let range = if selection.is_empty() {
17849 let point = selection.head().to_display_point(&display_map);
17850 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17851 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17852 .to_point(&display_map);
17853 start..end
17854 } else {
17855 selection.range()
17856 };
17857 if display_map.folds_in_range(range).next().is_some() {
17858 self.unfold_lines(&Default::default(), window, cx)
17859 } else {
17860 self.fold(&Default::default(), window, cx)
17861 }
17862 } else {
17863 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17864 let buffer_ids: HashSet<_> = self
17865 .selections
17866 .disjoint_anchor_ranges()
17867 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17868 .collect();
17869
17870 let should_unfold = buffer_ids
17871 .iter()
17872 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17873
17874 for buffer_id in buffer_ids {
17875 if should_unfold {
17876 self.unfold_buffer(buffer_id, cx);
17877 } else {
17878 self.fold_buffer(buffer_id, cx);
17879 }
17880 }
17881 }
17882 }
17883
17884 pub fn toggle_fold_recursive(
17885 &mut self,
17886 _: &actions::ToggleFoldRecursive,
17887 window: &mut Window,
17888 cx: &mut Context<Self>,
17889 ) {
17890 let selection = self.selections.newest::<Point>(cx);
17891
17892 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17893 let range = if selection.is_empty() {
17894 let point = selection.head().to_display_point(&display_map);
17895 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17896 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17897 .to_point(&display_map);
17898 start..end
17899 } else {
17900 selection.range()
17901 };
17902 if display_map.folds_in_range(range).next().is_some() {
17903 self.unfold_recursive(&Default::default(), window, cx)
17904 } else {
17905 self.fold_recursive(&Default::default(), window, cx)
17906 }
17907 }
17908
17909 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17910 if self.is_singleton(cx) {
17911 let mut to_fold = Vec::new();
17912 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17913 let selections = self.selections.all_adjusted(cx);
17914
17915 for selection in selections {
17916 let range = selection.range().sorted();
17917 let buffer_start_row = range.start.row;
17918
17919 if range.start.row != range.end.row {
17920 let mut found = false;
17921 let mut row = range.start.row;
17922 while row <= range.end.row {
17923 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17924 {
17925 found = true;
17926 row = crease.range().end.row + 1;
17927 to_fold.push(crease);
17928 } else {
17929 row += 1
17930 }
17931 }
17932 if found {
17933 continue;
17934 }
17935 }
17936
17937 for row in (0..=range.start.row).rev() {
17938 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17939 && crease.range().end.row >= buffer_start_row
17940 {
17941 to_fold.push(crease);
17942 if row <= range.start.row {
17943 break;
17944 }
17945 }
17946 }
17947 }
17948
17949 self.fold_creases(to_fold, true, window, cx);
17950 } else {
17951 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17952 let buffer_ids = self
17953 .selections
17954 .disjoint_anchor_ranges()
17955 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17956 .collect::<HashSet<_>>();
17957 for buffer_id in buffer_ids {
17958 self.fold_buffer(buffer_id, cx);
17959 }
17960 }
17961 }
17962
17963 pub fn toggle_fold_all(
17964 &mut self,
17965 _: &actions::ToggleFoldAll,
17966 window: &mut Window,
17967 cx: &mut Context<Self>,
17968 ) {
17969 if self.buffer.read(cx).is_singleton() {
17970 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17971 let has_folds = display_map
17972 .folds_in_range(0..display_map.buffer_snapshot.len())
17973 .next()
17974 .is_some();
17975
17976 if has_folds {
17977 self.unfold_all(&actions::UnfoldAll, window, cx);
17978 } else {
17979 self.fold_all(&actions::FoldAll, window, cx);
17980 }
17981 } else {
17982 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17983 let should_unfold = buffer_ids
17984 .iter()
17985 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17986
17987 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17988 editor
17989 .update_in(cx, |editor, _, cx| {
17990 for buffer_id in buffer_ids {
17991 if should_unfold {
17992 editor.unfold_buffer(buffer_id, cx);
17993 } else {
17994 editor.fold_buffer(buffer_id, cx);
17995 }
17996 }
17997 })
17998 .ok();
17999 });
18000 }
18001 }
18002
18003 fn fold_at_level(
18004 &mut self,
18005 fold_at: &FoldAtLevel,
18006 window: &mut Window,
18007 cx: &mut Context<Self>,
18008 ) {
18009 if !self.buffer.read(cx).is_singleton() {
18010 return;
18011 }
18012
18013 let fold_at_level = fold_at.0;
18014 let snapshot = self.buffer.read(cx).snapshot(cx);
18015 let mut to_fold = Vec::new();
18016 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18017
18018 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18019 while start_row < end_row {
18020 match self
18021 .snapshot(window, cx)
18022 .crease_for_buffer_row(MultiBufferRow(start_row))
18023 {
18024 Some(crease) => {
18025 let nested_start_row = crease.range().start.row + 1;
18026 let nested_end_row = crease.range().end.row;
18027
18028 if current_level < fold_at_level {
18029 stack.push((nested_start_row, nested_end_row, current_level + 1));
18030 } else if current_level == fold_at_level {
18031 to_fold.push(crease);
18032 }
18033
18034 start_row = nested_end_row + 1;
18035 }
18036 None => start_row += 1,
18037 }
18038 }
18039 }
18040
18041 self.fold_creases(to_fold, true, window, cx);
18042 }
18043
18044 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18045 if self.buffer.read(cx).is_singleton() {
18046 let mut fold_ranges = Vec::new();
18047 let snapshot = self.buffer.read(cx).snapshot(cx);
18048
18049 for row in 0..snapshot.max_row().0 {
18050 if let Some(foldable_range) = self
18051 .snapshot(window, cx)
18052 .crease_for_buffer_row(MultiBufferRow(row))
18053 {
18054 fold_ranges.push(foldable_range);
18055 }
18056 }
18057
18058 self.fold_creases(fold_ranges, true, window, cx);
18059 } else {
18060 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18061 editor
18062 .update_in(cx, |editor, _, cx| {
18063 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18064 editor.fold_buffer(buffer_id, cx);
18065 }
18066 })
18067 .ok();
18068 });
18069 }
18070 }
18071
18072 pub fn fold_function_bodies(
18073 &mut self,
18074 _: &actions::FoldFunctionBodies,
18075 window: &mut Window,
18076 cx: &mut Context<Self>,
18077 ) {
18078 let snapshot = self.buffer.read(cx).snapshot(cx);
18079
18080 let ranges = snapshot
18081 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18082 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18083 .collect::<Vec<_>>();
18084
18085 let creases = ranges
18086 .into_iter()
18087 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18088 .collect();
18089
18090 self.fold_creases(creases, true, window, cx);
18091 }
18092
18093 pub fn fold_recursive(
18094 &mut self,
18095 _: &actions::FoldRecursive,
18096 window: &mut Window,
18097 cx: &mut Context<Self>,
18098 ) {
18099 let mut to_fold = Vec::new();
18100 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18101 let selections = self.selections.all_adjusted(cx);
18102
18103 for selection in selections {
18104 let range = selection.range().sorted();
18105 let buffer_start_row = range.start.row;
18106
18107 if range.start.row != range.end.row {
18108 let mut found = false;
18109 for row in range.start.row..=range.end.row {
18110 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18111 found = true;
18112 to_fold.push(crease);
18113 }
18114 }
18115 if found {
18116 continue;
18117 }
18118 }
18119
18120 for row in (0..=range.start.row).rev() {
18121 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18122 if crease.range().end.row >= buffer_start_row {
18123 to_fold.push(crease);
18124 } else {
18125 break;
18126 }
18127 }
18128 }
18129 }
18130
18131 self.fold_creases(to_fold, true, window, cx);
18132 }
18133
18134 pub fn fold_at(
18135 &mut self,
18136 buffer_row: MultiBufferRow,
18137 window: &mut Window,
18138 cx: &mut Context<Self>,
18139 ) {
18140 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18141
18142 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18143 let autoscroll = self
18144 .selections
18145 .all::<Point>(cx)
18146 .iter()
18147 .any(|selection| crease.range().overlaps(&selection.range()));
18148
18149 self.fold_creases(vec![crease], autoscroll, window, cx);
18150 }
18151 }
18152
18153 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18154 if self.is_singleton(cx) {
18155 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18156 let buffer = &display_map.buffer_snapshot;
18157 let selections = self.selections.all::<Point>(cx);
18158 let ranges = selections
18159 .iter()
18160 .map(|s| {
18161 let range = s.display_range(&display_map).sorted();
18162 let mut start = range.start.to_point(&display_map);
18163 let mut end = range.end.to_point(&display_map);
18164 start.column = 0;
18165 end.column = buffer.line_len(MultiBufferRow(end.row));
18166 start..end
18167 })
18168 .collect::<Vec<_>>();
18169
18170 self.unfold_ranges(&ranges, true, true, cx);
18171 } else {
18172 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18173 let buffer_ids = self
18174 .selections
18175 .disjoint_anchor_ranges()
18176 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18177 .collect::<HashSet<_>>();
18178 for buffer_id in buffer_ids {
18179 self.unfold_buffer(buffer_id, cx);
18180 }
18181 }
18182 }
18183
18184 pub fn unfold_recursive(
18185 &mut self,
18186 _: &UnfoldRecursive,
18187 _window: &mut Window,
18188 cx: &mut Context<Self>,
18189 ) {
18190 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18191 let selections = self.selections.all::<Point>(cx);
18192 let ranges = selections
18193 .iter()
18194 .map(|s| {
18195 let mut range = s.display_range(&display_map).sorted();
18196 *range.start.column_mut() = 0;
18197 *range.end.column_mut() = display_map.line_len(range.end.row());
18198 let start = range.start.to_point(&display_map);
18199 let end = range.end.to_point(&display_map);
18200 start..end
18201 })
18202 .collect::<Vec<_>>();
18203
18204 self.unfold_ranges(&ranges, true, true, cx);
18205 }
18206
18207 pub fn unfold_at(
18208 &mut self,
18209 buffer_row: MultiBufferRow,
18210 _window: &mut Window,
18211 cx: &mut Context<Self>,
18212 ) {
18213 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18214
18215 let intersection_range = Point::new(buffer_row.0, 0)
18216 ..Point::new(
18217 buffer_row.0,
18218 display_map.buffer_snapshot.line_len(buffer_row),
18219 );
18220
18221 let autoscroll = self
18222 .selections
18223 .all::<Point>(cx)
18224 .iter()
18225 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18226
18227 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18228 }
18229
18230 pub fn unfold_all(
18231 &mut self,
18232 _: &actions::UnfoldAll,
18233 _window: &mut Window,
18234 cx: &mut Context<Self>,
18235 ) {
18236 if self.buffer.read(cx).is_singleton() {
18237 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18238 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18239 } else {
18240 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18241 editor
18242 .update(cx, |editor, cx| {
18243 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18244 editor.unfold_buffer(buffer_id, cx);
18245 }
18246 })
18247 .ok();
18248 });
18249 }
18250 }
18251
18252 pub fn fold_selected_ranges(
18253 &mut self,
18254 _: &FoldSelectedRanges,
18255 window: &mut Window,
18256 cx: &mut Context<Self>,
18257 ) {
18258 let selections = self.selections.all_adjusted(cx);
18259 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18260 let ranges = selections
18261 .into_iter()
18262 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18263 .collect::<Vec<_>>();
18264 self.fold_creases(ranges, true, window, cx);
18265 }
18266
18267 pub fn fold_ranges<T: ToOffset + Clone>(
18268 &mut self,
18269 ranges: Vec<Range<T>>,
18270 auto_scroll: bool,
18271 window: &mut Window,
18272 cx: &mut Context<Self>,
18273 ) {
18274 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18275 let ranges = ranges
18276 .into_iter()
18277 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18278 .collect::<Vec<_>>();
18279 self.fold_creases(ranges, auto_scroll, window, cx);
18280 }
18281
18282 pub fn fold_creases<T: ToOffset + Clone>(
18283 &mut self,
18284 creases: Vec<Crease<T>>,
18285 auto_scroll: bool,
18286 _window: &mut Window,
18287 cx: &mut Context<Self>,
18288 ) {
18289 if creases.is_empty() {
18290 return;
18291 }
18292
18293 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18294
18295 if auto_scroll {
18296 self.request_autoscroll(Autoscroll::fit(), cx);
18297 }
18298
18299 cx.notify();
18300
18301 self.scrollbar_marker_state.dirty = true;
18302 self.folds_did_change(cx);
18303 }
18304
18305 /// Removes any folds whose ranges intersect any of the given ranges.
18306 pub fn unfold_ranges<T: ToOffset + Clone>(
18307 &mut self,
18308 ranges: &[Range<T>],
18309 inclusive: bool,
18310 auto_scroll: bool,
18311 cx: &mut Context<Self>,
18312 ) {
18313 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18314 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18315 });
18316 self.folds_did_change(cx);
18317 }
18318
18319 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18320 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18321 return;
18322 }
18323 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18324 self.display_map.update(cx, |display_map, cx| {
18325 display_map.fold_buffers([buffer_id], cx)
18326 });
18327 cx.emit(EditorEvent::BufferFoldToggled {
18328 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18329 folded: true,
18330 });
18331 cx.notify();
18332 }
18333
18334 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18335 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18336 return;
18337 }
18338 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18339 self.display_map.update(cx, |display_map, cx| {
18340 display_map.unfold_buffers([buffer_id], cx);
18341 });
18342 cx.emit(EditorEvent::BufferFoldToggled {
18343 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18344 folded: false,
18345 });
18346 cx.notify();
18347 }
18348
18349 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18350 self.display_map.read(cx).is_buffer_folded(buffer)
18351 }
18352
18353 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18354 self.display_map.read(cx).folded_buffers()
18355 }
18356
18357 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18358 self.display_map.update(cx, |display_map, cx| {
18359 display_map.disable_header_for_buffer(buffer_id, cx);
18360 });
18361 cx.notify();
18362 }
18363
18364 /// Removes any folds with the given ranges.
18365 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18366 &mut self,
18367 ranges: &[Range<T>],
18368 type_id: TypeId,
18369 auto_scroll: bool,
18370 cx: &mut Context<Self>,
18371 ) {
18372 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18373 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18374 });
18375 self.folds_did_change(cx);
18376 }
18377
18378 fn remove_folds_with<T: ToOffset + Clone>(
18379 &mut self,
18380 ranges: &[Range<T>],
18381 auto_scroll: bool,
18382 cx: &mut Context<Self>,
18383 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18384 ) {
18385 if ranges.is_empty() {
18386 return;
18387 }
18388
18389 let mut buffers_affected = HashSet::default();
18390 let multi_buffer = self.buffer().read(cx);
18391 for range in ranges {
18392 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18393 buffers_affected.insert(buffer.read(cx).remote_id());
18394 };
18395 }
18396
18397 self.display_map.update(cx, update);
18398
18399 if auto_scroll {
18400 self.request_autoscroll(Autoscroll::fit(), cx);
18401 }
18402
18403 cx.notify();
18404 self.scrollbar_marker_state.dirty = true;
18405 self.active_indent_guides_state.dirty = true;
18406 }
18407
18408 pub fn update_renderer_widths(
18409 &mut self,
18410 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18411 cx: &mut Context<Self>,
18412 ) -> bool {
18413 self.display_map
18414 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18415 }
18416
18417 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18418 self.display_map.read(cx).fold_placeholder.clone()
18419 }
18420
18421 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18422 self.buffer.update(cx, |buffer, cx| {
18423 buffer.set_all_diff_hunks_expanded(cx);
18424 });
18425 }
18426
18427 pub fn expand_all_diff_hunks(
18428 &mut self,
18429 _: &ExpandAllDiffHunks,
18430 _window: &mut Window,
18431 cx: &mut Context<Self>,
18432 ) {
18433 self.buffer.update(cx, |buffer, cx| {
18434 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18435 });
18436 }
18437
18438 pub fn toggle_selected_diff_hunks(
18439 &mut self,
18440 _: &ToggleSelectedDiffHunks,
18441 _window: &mut Window,
18442 cx: &mut Context<Self>,
18443 ) {
18444 let ranges: Vec<_> = self
18445 .selections
18446 .disjoint_anchors()
18447 .iter()
18448 .map(|s| s.range())
18449 .collect();
18450 self.toggle_diff_hunks_in_ranges(ranges, cx);
18451 }
18452
18453 pub fn diff_hunks_in_ranges<'a>(
18454 &'a self,
18455 ranges: &'a [Range<Anchor>],
18456 buffer: &'a MultiBufferSnapshot,
18457 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18458 ranges.iter().flat_map(move |range| {
18459 let end_excerpt_id = range.end.excerpt_id;
18460 let range = range.to_point(buffer);
18461 let mut peek_end = range.end;
18462 if range.end.row < buffer.max_row().0 {
18463 peek_end = Point::new(range.end.row + 1, 0);
18464 }
18465 buffer
18466 .diff_hunks_in_range(range.start..peek_end)
18467 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18468 })
18469 }
18470
18471 pub fn has_stageable_diff_hunks_in_ranges(
18472 &self,
18473 ranges: &[Range<Anchor>],
18474 snapshot: &MultiBufferSnapshot,
18475 ) -> bool {
18476 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18477 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18478 }
18479
18480 pub fn toggle_staged_selected_diff_hunks(
18481 &mut self,
18482 _: &::git::ToggleStaged,
18483 _: &mut Window,
18484 cx: &mut Context<Self>,
18485 ) {
18486 let snapshot = self.buffer.read(cx).snapshot(cx);
18487 let ranges: Vec<_> = self
18488 .selections
18489 .disjoint_anchors()
18490 .iter()
18491 .map(|s| s.range())
18492 .collect();
18493 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18494 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18495 }
18496
18497 pub fn set_render_diff_hunk_controls(
18498 &mut self,
18499 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18500 cx: &mut Context<Self>,
18501 ) {
18502 self.render_diff_hunk_controls = render_diff_hunk_controls;
18503 cx.notify();
18504 }
18505
18506 pub fn stage_and_next(
18507 &mut self,
18508 _: &::git::StageAndNext,
18509 window: &mut Window,
18510 cx: &mut Context<Self>,
18511 ) {
18512 self.do_stage_or_unstage_and_next(true, window, cx);
18513 }
18514
18515 pub fn unstage_and_next(
18516 &mut self,
18517 _: &::git::UnstageAndNext,
18518 window: &mut Window,
18519 cx: &mut Context<Self>,
18520 ) {
18521 self.do_stage_or_unstage_and_next(false, window, cx);
18522 }
18523
18524 pub fn stage_or_unstage_diff_hunks(
18525 &mut self,
18526 stage: bool,
18527 ranges: Vec<Range<Anchor>>,
18528 cx: &mut Context<Self>,
18529 ) {
18530 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18531 cx.spawn(async move |this, cx| {
18532 task.await?;
18533 this.update(cx, |this, cx| {
18534 let snapshot = this.buffer.read(cx).snapshot(cx);
18535 let chunk_by = this
18536 .diff_hunks_in_ranges(&ranges, &snapshot)
18537 .chunk_by(|hunk| hunk.buffer_id);
18538 for (buffer_id, hunks) in &chunk_by {
18539 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18540 }
18541 })
18542 })
18543 .detach_and_log_err(cx);
18544 }
18545
18546 fn save_buffers_for_ranges_if_needed(
18547 &mut self,
18548 ranges: &[Range<Anchor>],
18549 cx: &mut Context<Editor>,
18550 ) -> Task<Result<()>> {
18551 let multibuffer = self.buffer.read(cx);
18552 let snapshot = multibuffer.read(cx);
18553 let buffer_ids: HashSet<_> = ranges
18554 .iter()
18555 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18556 .collect();
18557 drop(snapshot);
18558
18559 let mut buffers = HashSet::default();
18560 for buffer_id in buffer_ids {
18561 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18562 let buffer = buffer_entity.read(cx);
18563 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18564 {
18565 buffers.insert(buffer_entity);
18566 }
18567 }
18568 }
18569
18570 if let Some(project) = &self.project {
18571 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18572 } else {
18573 Task::ready(Ok(()))
18574 }
18575 }
18576
18577 fn do_stage_or_unstage_and_next(
18578 &mut self,
18579 stage: bool,
18580 window: &mut Window,
18581 cx: &mut Context<Self>,
18582 ) {
18583 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18584
18585 if ranges.iter().any(|range| range.start != range.end) {
18586 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18587 return;
18588 }
18589
18590 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18591 let snapshot = self.snapshot(window, cx);
18592 let position = self.selections.newest::<Point>(cx).head();
18593 let mut row = snapshot
18594 .buffer_snapshot
18595 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18596 .find(|hunk| hunk.row_range.start.0 > position.row)
18597 .map(|hunk| hunk.row_range.start);
18598
18599 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18600 // Outside of the project diff editor, wrap around to the beginning.
18601 if !all_diff_hunks_expanded {
18602 row = row.or_else(|| {
18603 snapshot
18604 .buffer_snapshot
18605 .diff_hunks_in_range(Point::zero()..position)
18606 .find(|hunk| hunk.row_range.end.0 < position.row)
18607 .map(|hunk| hunk.row_range.start)
18608 });
18609 }
18610
18611 if let Some(row) = row {
18612 let destination = Point::new(row.0, 0);
18613 let autoscroll = Autoscroll::center();
18614
18615 self.unfold_ranges(&[destination..destination], false, false, cx);
18616 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18617 s.select_ranges([destination..destination]);
18618 });
18619 }
18620 }
18621
18622 fn do_stage_or_unstage(
18623 &self,
18624 stage: bool,
18625 buffer_id: BufferId,
18626 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18627 cx: &mut App,
18628 ) -> Option<()> {
18629 let project = self.project()?;
18630 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18631 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18632 let buffer_snapshot = buffer.read(cx).snapshot();
18633 let file_exists = buffer_snapshot
18634 .file()
18635 .is_some_and(|file| file.disk_state().exists());
18636 diff.update(cx, |diff, cx| {
18637 diff.stage_or_unstage_hunks(
18638 stage,
18639 &hunks
18640 .map(|hunk| buffer_diff::DiffHunk {
18641 buffer_range: hunk.buffer_range,
18642 diff_base_byte_range: hunk.diff_base_byte_range,
18643 secondary_status: hunk.secondary_status,
18644 range: Point::zero()..Point::zero(), // unused
18645 })
18646 .collect::<Vec<_>>(),
18647 &buffer_snapshot,
18648 file_exists,
18649 cx,
18650 )
18651 });
18652 None
18653 }
18654
18655 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18656 let ranges: Vec<_> = self
18657 .selections
18658 .disjoint_anchors()
18659 .iter()
18660 .map(|s| s.range())
18661 .collect();
18662 self.buffer
18663 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18664 }
18665
18666 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18667 self.buffer.update(cx, |buffer, cx| {
18668 let ranges = vec![Anchor::min()..Anchor::max()];
18669 if !buffer.all_diff_hunks_expanded()
18670 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18671 {
18672 buffer.collapse_diff_hunks(ranges, cx);
18673 true
18674 } else {
18675 false
18676 }
18677 })
18678 }
18679
18680 fn toggle_diff_hunks_in_ranges(
18681 &mut self,
18682 ranges: Vec<Range<Anchor>>,
18683 cx: &mut Context<Editor>,
18684 ) {
18685 self.buffer.update(cx, |buffer, cx| {
18686 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18687 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18688 })
18689 }
18690
18691 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18692 self.buffer.update(cx, |buffer, cx| {
18693 let snapshot = buffer.snapshot(cx);
18694 let excerpt_id = range.end.excerpt_id;
18695 let point_range = range.to_point(&snapshot);
18696 let expand = !buffer.single_hunk_is_expanded(range, cx);
18697 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18698 })
18699 }
18700
18701 pub(crate) fn apply_all_diff_hunks(
18702 &mut self,
18703 _: &ApplyAllDiffHunks,
18704 window: &mut Window,
18705 cx: &mut Context<Self>,
18706 ) {
18707 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18708
18709 let buffers = self.buffer.read(cx).all_buffers();
18710 for branch_buffer in buffers {
18711 branch_buffer.update(cx, |branch_buffer, cx| {
18712 branch_buffer.merge_into_base(Vec::new(), cx);
18713 });
18714 }
18715
18716 if let Some(project) = self.project.clone() {
18717 self.save(
18718 SaveOptions {
18719 format: true,
18720 autosave: false,
18721 },
18722 project,
18723 window,
18724 cx,
18725 )
18726 .detach_and_log_err(cx);
18727 }
18728 }
18729
18730 pub(crate) fn apply_selected_diff_hunks(
18731 &mut self,
18732 _: &ApplyDiffHunk,
18733 window: &mut Window,
18734 cx: &mut Context<Self>,
18735 ) {
18736 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18737 let snapshot = self.snapshot(window, cx);
18738 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18739 let mut ranges_by_buffer = HashMap::default();
18740 self.transact(window, cx, |editor, _window, cx| {
18741 for hunk in hunks {
18742 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18743 ranges_by_buffer
18744 .entry(buffer.clone())
18745 .or_insert_with(Vec::new)
18746 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18747 }
18748 }
18749
18750 for (buffer, ranges) in ranges_by_buffer {
18751 buffer.update(cx, |buffer, cx| {
18752 buffer.merge_into_base(ranges, cx);
18753 });
18754 }
18755 });
18756
18757 if let Some(project) = self.project.clone() {
18758 self.save(
18759 SaveOptions {
18760 format: true,
18761 autosave: false,
18762 },
18763 project,
18764 window,
18765 cx,
18766 )
18767 .detach_and_log_err(cx);
18768 }
18769 }
18770
18771 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18772 if hovered != self.gutter_hovered {
18773 self.gutter_hovered = hovered;
18774 cx.notify();
18775 }
18776 }
18777
18778 pub fn insert_blocks(
18779 &mut self,
18780 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18781 autoscroll: Option<Autoscroll>,
18782 cx: &mut Context<Self>,
18783 ) -> Vec<CustomBlockId> {
18784 let blocks = self
18785 .display_map
18786 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18787 if let Some(autoscroll) = autoscroll {
18788 self.request_autoscroll(autoscroll, cx);
18789 }
18790 cx.notify();
18791 blocks
18792 }
18793
18794 pub fn resize_blocks(
18795 &mut self,
18796 heights: HashMap<CustomBlockId, u32>,
18797 autoscroll: Option<Autoscroll>,
18798 cx: &mut Context<Self>,
18799 ) {
18800 self.display_map
18801 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18802 if let Some(autoscroll) = autoscroll {
18803 self.request_autoscroll(autoscroll, cx);
18804 }
18805 cx.notify();
18806 }
18807
18808 pub fn replace_blocks(
18809 &mut self,
18810 renderers: HashMap<CustomBlockId, RenderBlock>,
18811 autoscroll: Option<Autoscroll>,
18812 cx: &mut Context<Self>,
18813 ) {
18814 self.display_map
18815 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18816 if let Some(autoscroll) = autoscroll {
18817 self.request_autoscroll(autoscroll, cx);
18818 }
18819 cx.notify();
18820 }
18821
18822 pub fn remove_blocks(
18823 &mut self,
18824 block_ids: HashSet<CustomBlockId>,
18825 autoscroll: Option<Autoscroll>,
18826 cx: &mut Context<Self>,
18827 ) {
18828 self.display_map.update(cx, |display_map, cx| {
18829 display_map.remove_blocks(block_ids, cx)
18830 });
18831 if let Some(autoscroll) = autoscroll {
18832 self.request_autoscroll(autoscroll, cx);
18833 }
18834 cx.notify();
18835 }
18836
18837 pub fn row_for_block(
18838 &self,
18839 block_id: CustomBlockId,
18840 cx: &mut Context<Self>,
18841 ) -> Option<DisplayRow> {
18842 self.display_map
18843 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18844 }
18845
18846 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18847 self.focused_block = Some(focused_block);
18848 }
18849
18850 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18851 self.focused_block.take()
18852 }
18853
18854 pub fn insert_creases(
18855 &mut self,
18856 creases: impl IntoIterator<Item = Crease<Anchor>>,
18857 cx: &mut Context<Self>,
18858 ) -> Vec<CreaseId> {
18859 self.display_map
18860 .update(cx, |map, cx| map.insert_creases(creases, cx))
18861 }
18862
18863 pub fn remove_creases(
18864 &mut self,
18865 ids: impl IntoIterator<Item = CreaseId>,
18866 cx: &mut Context<Self>,
18867 ) -> Vec<(CreaseId, Range<Anchor>)> {
18868 self.display_map
18869 .update(cx, |map, cx| map.remove_creases(ids, cx))
18870 }
18871
18872 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18873 self.display_map
18874 .update(cx, |map, cx| map.snapshot(cx))
18875 .longest_row()
18876 }
18877
18878 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18879 self.display_map
18880 .update(cx, |map, cx| map.snapshot(cx))
18881 .max_point()
18882 }
18883
18884 pub fn text(&self, cx: &App) -> String {
18885 self.buffer.read(cx).read(cx).text()
18886 }
18887
18888 pub fn is_empty(&self, cx: &App) -> bool {
18889 self.buffer.read(cx).read(cx).is_empty()
18890 }
18891
18892 pub fn text_option(&self, cx: &App) -> Option<String> {
18893 let text = self.text(cx);
18894 let text = text.trim();
18895
18896 if text.is_empty() {
18897 return None;
18898 }
18899
18900 Some(text.to_string())
18901 }
18902
18903 pub fn set_text(
18904 &mut self,
18905 text: impl Into<Arc<str>>,
18906 window: &mut Window,
18907 cx: &mut Context<Self>,
18908 ) {
18909 self.transact(window, cx, |this, _, cx| {
18910 this.buffer
18911 .read(cx)
18912 .as_singleton()
18913 .expect("you can only call set_text on editors for singleton buffers")
18914 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18915 });
18916 }
18917
18918 pub fn display_text(&self, cx: &mut App) -> String {
18919 self.display_map
18920 .update(cx, |map, cx| map.snapshot(cx))
18921 .text()
18922 }
18923
18924 fn create_minimap(
18925 &self,
18926 minimap_settings: MinimapSettings,
18927 window: &mut Window,
18928 cx: &mut Context<Self>,
18929 ) -> Option<Entity<Self>> {
18930 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18931 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18932 }
18933
18934 fn initialize_new_minimap(
18935 &self,
18936 minimap_settings: MinimapSettings,
18937 window: &mut Window,
18938 cx: &mut Context<Self>,
18939 ) -> Entity<Self> {
18940 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18941
18942 let mut minimap = Editor::new_internal(
18943 EditorMode::Minimap {
18944 parent: cx.weak_entity(),
18945 },
18946 self.buffer.clone(),
18947 None,
18948 Some(self.display_map.clone()),
18949 window,
18950 cx,
18951 );
18952 minimap.scroll_manager.clone_state(&self.scroll_manager);
18953 minimap.set_text_style_refinement(TextStyleRefinement {
18954 font_size: Some(MINIMAP_FONT_SIZE),
18955 font_weight: Some(MINIMAP_FONT_WEIGHT),
18956 ..Default::default()
18957 });
18958 minimap.update_minimap_configuration(minimap_settings, cx);
18959 cx.new(|_| minimap)
18960 }
18961
18962 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18963 let current_line_highlight = minimap_settings
18964 .current_line_highlight
18965 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18966 self.set_current_line_highlight(Some(current_line_highlight));
18967 }
18968
18969 pub fn minimap(&self) -> Option<&Entity<Self>> {
18970 self.minimap
18971 .as_ref()
18972 .filter(|_| self.minimap_visibility.visible())
18973 }
18974
18975 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18976 let mut wrap_guides = smallvec![];
18977
18978 if self.show_wrap_guides == Some(false) {
18979 return wrap_guides;
18980 }
18981
18982 let settings = self.buffer.read(cx).language_settings(cx);
18983 if settings.show_wrap_guides {
18984 match self.soft_wrap_mode(cx) {
18985 SoftWrap::Column(soft_wrap) => {
18986 wrap_guides.push((soft_wrap as usize, true));
18987 }
18988 SoftWrap::Bounded(soft_wrap) => {
18989 wrap_guides.push((soft_wrap as usize, true));
18990 }
18991 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18992 }
18993 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18994 }
18995
18996 wrap_guides
18997 }
18998
18999 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19000 let settings = self.buffer.read(cx).language_settings(cx);
19001 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19002 match mode {
19003 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19004 SoftWrap::None
19005 }
19006 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19007 language_settings::SoftWrap::PreferredLineLength => {
19008 SoftWrap::Column(settings.preferred_line_length)
19009 }
19010 language_settings::SoftWrap::Bounded => {
19011 SoftWrap::Bounded(settings.preferred_line_length)
19012 }
19013 }
19014 }
19015
19016 pub fn set_soft_wrap_mode(
19017 &mut self,
19018 mode: language_settings::SoftWrap,
19019
19020 cx: &mut Context<Self>,
19021 ) {
19022 self.soft_wrap_mode_override = Some(mode);
19023 cx.notify();
19024 }
19025
19026 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19027 self.hard_wrap = hard_wrap;
19028 cx.notify();
19029 }
19030
19031 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19032 self.text_style_refinement = Some(style);
19033 }
19034
19035 /// called by the Element so we know what style we were most recently rendered with.
19036 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19037 // We intentionally do not inform the display map about the minimap style
19038 // so that wrapping is not recalculated and stays consistent for the editor
19039 // and its linked minimap.
19040 if !self.mode.is_minimap() {
19041 let font = style.text.font();
19042 let font_size = style.text.font_size.to_pixels(window.rem_size());
19043 let display_map = self
19044 .placeholder_display_map
19045 .as_ref()
19046 .filter(|_| self.is_empty(cx))
19047 .unwrap_or(&self.display_map);
19048
19049 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19050 }
19051 self.style = Some(style);
19052 }
19053
19054 pub fn style(&self) -> Option<&EditorStyle> {
19055 self.style.as_ref()
19056 }
19057
19058 // Called by the element. This method is not designed to be called outside of the editor
19059 // element's layout code because it does not notify when rewrapping is computed synchronously.
19060 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19061 if self.is_empty(cx) {
19062 self.placeholder_display_map
19063 .as_ref()
19064 .map_or(false, |display_map| {
19065 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19066 })
19067 } else {
19068 self.display_map
19069 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19070 }
19071 }
19072
19073 pub fn set_soft_wrap(&mut self) {
19074 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19075 }
19076
19077 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19078 if self.soft_wrap_mode_override.is_some() {
19079 self.soft_wrap_mode_override.take();
19080 } else {
19081 let soft_wrap = match self.soft_wrap_mode(cx) {
19082 SoftWrap::GitDiff => return,
19083 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19084 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19085 language_settings::SoftWrap::None
19086 }
19087 };
19088 self.soft_wrap_mode_override = Some(soft_wrap);
19089 }
19090 cx.notify();
19091 }
19092
19093 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19094 let Some(workspace) = self.workspace() else {
19095 return;
19096 };
19097 let fs = workspace.read(cx).app_state().fs.clone();
19098 let current_show = TabBarSettings::get_global(cx).show;
19099 update_settings_file(fs, cx, move |setting, _| {
19100 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19101 });
19102 }
19103
19104 pub fn toggle_indent_guides(
19105 &mut self,
19106 _: &ToggleIndentGuides,
19107 _: &mut Window,
19108 cx: &mut Context<Self>,
19109 ) {
19110 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19111 self.buffer
19112 .read(cx)
19113 .language_settings(cx)
19114 .indent_guides
19115 .enabled
19116 });
19117 self.show_indent_guides = Some(!currently_enabled);
19118 cx.notify();
19119 }
19120
19121 fn should_show_indent_guides(&self) -> Option<bool> {
19122 self.show_indent_guides
19123 }
19124
19125 pub fn toggle_line_numbers(
19126 &mut self,
19127 _: &ToggleLineNumbers,
19128 _: &mut Window,
19129 cx: &mut Context<Self>,
19130 ) {
19131 let mut editor_settings = EditorSettings::get_global(cx).clone();
19132 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19133 EditorSettings::override_global(editor_settings, cx);
19134 }
19135
19136 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19137 if let Some(show_line_numbers) = self.show_line_numbers {
19138 return show_line_numbers;
19139 }
19140 EditorSettings::get_global(cx).gutter.line_numbers
19141 }
19142
19143 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19144 self.use_relative_line_numbers
19145 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19146 }
19147
19148 pub fn toggle_relative_line_numbers(
19149 &mut self,
19150 _: &ToggleRelativeLineNumbers,
19151 _: &mut Window,
19152 cx: &mut Context<Self>,
19153 ) {
19154 let is_relative = self.should_use_relative_line_numbers(cx);
19155 self.set_relative_line_number(Some(!is_relative), cx)
19156 }
19157
19158 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19159 self.use_relative_line_numbers = is_relative;
19160 cx.notify();
19161 }
19162
19163 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19164 self.show_gutter = show_gutter;
19165 cx.notify();
19166 }
19167
19168 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19169 self.show_scrollbars = ScrollbarAxes {
19170 horizontal: show,
19171 vertical: show,
19172 };
19173 cx.notify();
19174 }
19175
19176 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19177 self.show_scrollbars.vertical = show;
19178 cx.notify();
19179 }
19180
19181 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19182 self.show_scrollbars.horizontal = show;
19183 cx.notify();
19184 }
19185
19186 pub fn set_minimap_visibility(
19187 &mut self,
19188 minimap_visibility: MinimapVisibility,
19189 window: &mut Window,
19190 cx: &mut Context<Self>,
19191 ) {
19192 if self.minimap_visibility != minimap_visibility {
19193 if minimap_visibility.visible() && self.minimap.is_none() {
19194 let minimap_settings = EditorSettings::get_global(cx).minimap;
19195 self.minimap =
19196 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19197 }
19198 self.minimap_visibility = minimap_visibility;
19199 cx.notify();
19200 }
19201 }
19202
19203 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19204 self.set_show_scrollbars(false, cx);
19205 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19206 }
19207
19208 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19209 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19210 }
19211
19212 /// Normally the text in full mode and auto height editors is padded on the
19213 /// left side by roughly half a character width for improved hit testing.
19214 ///
19215 /// Use this method to disable this for cases where this is not wanted (e.g.
19216 /// if you want to align the editor text with some other text above or below)
19217 /// or if you want to add this padding to single-line editors.
19218 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19219 self.offset_content = offset_content;
19220 cx.notify();
19221 }
19222
19223 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19224 self.show_line_numbers = Some(show_line_numbers);
19225 cx.notify();
19226 }
19227
19228 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19229 self.disable_expand_excerpt_buttons = true;
19230 cx.notify();
19231 }
19232
19233 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19234 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19235 cx.notify();
19236 }
19237
19238 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19239 self.show_code_actions = Some(show_code_actions);
19240 cx.notify();
19241 }
19242
19243 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19244 self.show_runnables = Some(show_runnables);
19245 cx.notify();
19246 }
19247
19248 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19249 self.show_breakpoints = Some(show_breakpoints);
19250 cx.notify();
19251 }
19252
19253 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19254 if self.display_map.read(cx).masked != masked {
19255 self.display_map.update(cx, |map, _| map.masked = masked);
19256 }
19257 cx.notify()
19258 }
19259
19260 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19261 self.show_wrap_guides = Some(show_wrap_guides);
19262 cx.notify();
19263 }
19264
19265 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19266 self.show_indent_guides = Some(show_indent_guides);
19267 cx.notify();
19268 }
19269
19270 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19271 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19272 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19273 && let Some(dir) = file.abs_path(cx).parent()
19274 {
19275 return Some(dir.to_owned());
19276 }
19277 }
19278
19279 None
19280 }
19281
19282 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19283 self.active_excerpt(cx)?
19284 .1
19285 .read(cx)
19286 .file()
19287 .and_then(|f| f.as_local())
19288 }
19289
19290 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19291 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19292 let buffer = buffer.read(cx);
19293 if let Some(project_path) = buffer.project_path(cx) {
19294 let project = self.project()?.read(cx);
19295 project.absolute_path(&project_path, cx)
19296 } else {
19297 buffer
19298 .file()
19299 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19300 }
19301 })
19302 }
19303
19304 pub fn reveal_in_finder(
19305 &mut self,
19306 _: &RevealInFileManager,
19307 _window: &mut Window,
19308 cx: &mut Context<Self>,
19309 ) {
19310 if let Some(target) = self.target_file(cx) {
19311 cx.reveal_path(&target.abs_path(cx));
19312 }
19313 }
19314
19315 pub fn copy_path(
19316 &mut self,
19317 _: &zed_actions::workspace::CopyPath,
19318 _window: &mut Window,
19319 cx: &mut Context<Self>,
19320 ) {
19321 if let Some(path) = self.target_file_abs_path(cx)
19322 && let Some(path) = path.to_str()
19323 {
19324 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19325 } else {
19326 cx.propagate();
19327 }
19328 }
19329
19330 pub fn copy_relative_path(
19331 &mut self,
19332 _: &zed_actions::workspace::CopyRelativePath,
19333 _window: &mut Window,
19334 cx: &mut Context<Self>,
19335 ) {
19336 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19337 let project = self.project()?.read(cx);
19338 let path = buffer.read(cx).file()?.path();
19339 let path = path.display(project.path_style(cx));
19340 Some(path)
19341 }) {
19342 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19343 } else {
19344 cx.propagate();
19345 }
19346 }
19347
19348 /// Returns the project path for the editor's buffer, if any buffer is
19349 /// opened in the editor.
19350 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19351 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19352 buffer.read(cx).project_path(cx)
19353 } else {
19354 None
19355 }
19356 }
19357
19358 // Returns true if the editor handled a go-to-line request
19359 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19360 maybe!({
19361 let breakpoint_store = self.breakpoint_store.as_ref()?;
19362
19363 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19364 else {
19365 self.clear_row_highlights::<ActiveDebugLine>();
19366 return None;
19367 };
19368
19369 let position = active_stack_frame.position;
19370 let buffer_id = position.buffer_id?;
19371 let snapshot = self
19372 .project
19373 .as_ref()?
19374 .read(cx)
19375 .buffer_for_id(buffer_id, cx)?
19376 .read(cx)
19377 .snapshot();
19378
19379 let mut handled = false;
19380 for (id, ExcerptRange { context, .. }) in
19381 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19382 {
19383 if context.start.cmp(&position, &snapshot).is_ge()
19384 || context.end.cmp(&position, &snapshot).is_lt()
19385 {
19386 continue;
19387 }
19388 let snapshot = self.buffer.read(cx).snapshot(cx);
19389 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19390
19391 handled = true;
19392 self.clear_row_highlights::<ActiveDebugLine>();
19393
19394 self.go_to_line::<ActiveDebugLine>(
19395 multibuffer_anchor,
19396 Some(cx.theme().colors().editor_debugger_active_line_background),
19397 window,
19398 cx,
19399 );
19400
19401 cx.notify();
19402 }
19403
19404 handled.then_some(())
19405 })
19406 .is_some()
19407 }
19408
19409 pub fn copy_file_name_without_extension(
19410 &mut self,
19411 _: &CopyFileNameWithoutExtension,
19412 _: &mut Window,
19413 cx: &mut Context<Self>,
19414 ) {
19415 if let Some(file) = self.target_file(cx)
19416 && let Some(file_stem) = file.path().file_stem()
19417 {
19418 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19419 }
19420 }
19421
19422 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19423 if let Some(file) = self.target_file(cx)
19424 && let Some(name) = file.path().file_name()
19425 {
19426 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19427 }
19428 }
19429
19430 pub fn toggle_git_blame(
19431 &mut self,
19432 _: &::git::Blame,
19433 window: &mut Window,
19434 cx: &mut Context<Self>,
19435 ) {
19436 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19437
19438 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19439 self.start_git_blame(true, window, cx);
19440 }
19441
19442 cx.notify();
19443 }
19444
19445 pub fn toggle_git_blame_inline(
19446 &mut self,
19447 _: &ToggleGitBlameInline,
19448 window: &mut Window,
19449 cx: &mut Context<Self>,
19450 ) {
19451 self.toggle_git_blame_inline_internal(true, window, cx);
19452 cx.notify();
19453 }
19454
19455 pub fn open_git_blame_commit(
19456 &mut self,
19457 _: &OpenGitBlameCommit,
19458 window: &mut Window,
19459 cx: &mut Context<Self>,
19460 ) {
19461 self.open_git_blame_commit_internal(window, cx);
19462 }
19463
19464 fn open_git_blame_commit_internal(
19465 &mut self,
19466 window: &mut Window,
19467 cx: &mut Context<Self>,
19468 ) -> Option<()> {
19469 let blame = self.blame.as_ref()?;
19470 let snapshot = self.snapshot(window, cx);
19471 let cursor = self.selections.newest::<Point>(cx).head();
19472 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19473 let (_, blame_entry) = blame
19474 .update(cx, |blame, cx| {
19475 blame
19476 .blame_for_rows(
19477 &[RowInfo {
19478 buffer_id: Some(buffer.remote_id()),
19479 buffer_row: Some(point.row),
19480 ..Default::default()
19481 }],
19482 cx,
19483 )
19484 .next()
19485 })
19486 .flatten()?;
19487 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19488 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19489 let workspace = self.workspace()?.downgrade();
19490 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19491 None
19492 }
19493
19494 pub fn git_blame_inline_enabled(&self) -> bool {
19495 self.git_blame_inline_enabled
19496 }
19497
19498 pub fn toggle_selection_menu(
19499 &mut self,
19500 _: &ToggleSelectionMenu,
19501 _: &mut Window,
19502 cx: &mut Context<Self>,
19503 ) {
19504 self.show_selection_menu = self
19505 .show_selection_menu
19506 .map(|show_selections_menu| !show_selections_menu)
19507 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19508
19509 cx.notify();
19510 }
19511
19512 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19513 self.show_selection_menu
19514 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19515 }
19516
19517 fn start_git_blame(
19518 &mut self,
19519 user_triggered: bool,
19520 window: &mut Window,
19521 cx: &mut Context<Self>,
19522 ) {
19523 if let Some(project) = self.project() {
19524 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19525 && buffer.read(cx).file().is_none()
19526 {
19527 return;
19528 }
19529
19530 let focused = self.focus_handle(cx).contains_focused(window, cx);
19531
19532 let project = project.clone();
19533 let blame = cx
19534 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19535 self.blame_subscription =
19536 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19537 self.blame = Some(blame);
19538 }
19539 }
19540
19541 fn toggle_git_blame_inline_internal(
19542 &mut self,
19543 user_triggered: bool,
19544 window: &mut Window,
19545 cx: &mut Context<Self>,
19546 ) {
19547 if self.git_blame_inline_enabled {
19548 self.git_blame_inline_enabled = false;
19549 self.show_git_blame_inline = false;
19550 self.show_git_blame_inline_delay_task.take();
19551 } else {
19552 self.git_blame_inline_enabled = true;
19553 self.start_git_blame_inline(user_triggered, window, cx);
19554 }
19555
19556 cx.notify();
19557 }
19558
19559 fn start_git_blame_inline(
19560 &mut self,
19561 user_triggered: bool,
19562 window: &mut Window,
19563 cx: &mut Context<Self>,
19564 ) {
19565 self.start_git_blame(user_triggered, window, cx);
19566
19567 if ProjectSettings::get_global(cx)
19568 .git
19569 .inline_blame_delay()
19570 .is_some()
19571 {
19572 self.start_inline_blame_timer(window, cx);
19573 } else {
19574 self.show_git_blame_inline = true
19575 }
19576 }
19577
19578 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19579 self.blame.as_ref()
19580 }
19581
19582 pub fn show_git_blame_gutter(&self) -> bool {
19583 self.show_git_blame_gutter
19584 }
19585
19586 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19587 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19588 }
19589
19590 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19591 self.show_git_blame_inline
19592 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19593 && !self.newest_selection_head_on_empty_line(cx)
19594 && self.has_blame_entries(cx)
19595 }
19596
19597 fn has_blame_entries(&self, cx: &App) -> bool {
19598 self.blame()
19599 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19600 }
19601
19602 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19603 let cursor_anchor = self.selections.newest_anchor().head();
19604
19605 let snapshot = self.buffer.read(cx).snapshot(cx);
19606 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19607
19608 snapshot.line_len(buffer_row) == 0
19609 }
19610
19611 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19612 let buffer_and_selection = maybe!({
19613 let selection = self.selections.newest::<Point>(cx);
19614 let selection_range = selection.range();
19615
19616 let multi_buffer = self.buffer().read(cx);
19617 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19618 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19619
19620 let (buffer, range, _) = if selection.reversed {
19621 buffer_ranges.first()
19622 } else {
19623 buffer_ranges.last()
19624 }?;
19625
19626 let selection = text::ToPoint::to_point(&range.start, buffer).row
19627 ..text::ToPoint::to_point(&range.end, buffer).row;
19628 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19629 });
19630
19631 let Some((buffer, selection)) = buffer_and_selection else {
19632 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19633 };
19634
19635 let Some(project) = self.project() else {
19636 return Task::ready(Err(anyhow!("editor does not have project")));
19637 };
19638
19639 project.update(cx, |project, cx| {
19640 project.get_permalink_to_line(&buffer, selection, cx)
19641 })
19642 }
19643
19644 pub fn copy_permalink_to_line(
19645 &mut self,
19646 _: &CopyPermalinkToLine,
19647 window: &mut Window,
19648 cx: &mut Context<Self>,
19649 ) {
19650 let permalink_task = self.get_permalink_to_line(cx);
19651 let workspace = self.workspace();
19652
19653 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19654 Ok(permalink) => {
19655 cx.update(|_, cx| {
19656 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19657 })
19658 .ok();
19659 }
19660 Err(err) => {
19661 let message = format!("Failed to copy permalink: {err}");
19662
19663 anyhow::Result::<()>::Err(err).log_err();
19664
19665 if let Some(workspace) = workspace {
19666 workspace
19667 .update_in(cx, |workspace, _, cx| {
19668 struct CopyPermalinkToLine;
19669
19670 workspace.show_toast(
19671 Toast::new(
19672 NotificationId::unique::<CopyPermalinkToLine>(),
19673 message,
19674 ),
19675 cx,
19676 )
19677 })
19678 .ok();
19679 }
19680 }
19681 })
19682 .detach();
19683 }
19684
19685 pub fn copy_file_location(
19686 &mut self,
19687 _: &CopyFileLocation,
19688 _: &mut Window,
19689 cx: &mut Context<Self>,
19690 ) {
19691 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19692 if let Some(file) = self.target_file(cx) {
19693 let path = file.path().display(file.path_style(cx));
19694 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19695 }
19696 }
19697
19698 pub fn open_permalink_to_line(
19699 &mut self,
19700 _: &OpenPermalinkToLine,
19701 window: &mut Window,
19702 cx: &mut Context<Self>,
19703 ) {
19704 let permalink_task = self.get_permalink_to_line(cx);
19705 let workspace = self.workspace();
19706
19707 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19708 Ok(permalink) => {
19709 cx.update(|_, cx| {
19710 cx.open_url(permalink.as_ref());
19711 })
19712 .ok();
19713 }
19714 Err(err) => {
19715 let message = format!("Failed to open permalink: {err}");
19716
19717 anyhow::Result::<()>::Err(err).log_err();
19718
19719 if let Some(workspace) = workspace {
19720 workspace
19721 .update(cx, |workspace, cx| {
19722 struct OpenPermalinkToLine;
19723
19724 workspace.show_toast(
19725 Toast::new(
19726 NotificationId::unique::<OpenPermalinkToLine>(),
19727 message,
19728 ),
19729 cx,
19730 )
19731 })
19732 .ok();
19733 }
19734 }
19735 })
19736 .detach();
19737 }
19738
19739 pub fn insert_uuid_v4(
19740 &mut self,
19741 _: &InsertUuidV4,
19742 window: &mut Window,
19743 cx: &mut Context<Self>,
19744 ) {
19745 self.insert_uuid(UuidVersion::V4, window, cx);
19746 }
19747
19748 pub fn insert_uuid_v7(
19749 &mut self,
19750 _: &InsertUuidV7,
19751 window: &mut Window,
19752 cx: &mut Context<Self>,
19753 ) {
19754 self.insert_uuid(UuidVersion::V7, window, cx);
19755 }
19756
19757 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19759 self.transact(window, cx, |this, window, cx| {
19760 let edits = this
19761 .selections
19762 .all::<Point>(cx)
19763 .into_iter()
19764 .map(|selection| {
19765 let uuid = match version {
19766 UuidVersion::V4 => uuid::Uuid::new_v4(),
19767 UuidVersion::V7 => uuid::Uuid::now_v7(),
19768 };
19769
19770 (selection.range(), uuid.to_string())
19771 });
19772 this.edit(edits, cx);
19773 this.refresh_edit_prediction(true, false, window, cx);
19774 });
19775 }
19776
19777 pub fn open_selections_in_multibuffer(
19778 &mut self,
19779 _: &OpenSelectionsInMultibuffer,
19780 window: &mut Window,
19781 cx: &mut Context<Self>,
19782 ) {
19783 let multibuffer = self.buffer.read(cx);
19784
19785 let Some(buffer) = multibuffer.as_singleton() else {
19786 return;
19787 };
19788
19789 let Some(workspace) = self.workspace() else {
19790 return;
19791 };
19792
19793 let title = multibuffer.title(cx).to_string();
19794
19795 let locations = self
19796 .selections
19797 .all_anchors(cx)
19798 .iter()
19799 .map(|selection| {
19800 (
19801 buffer.clone(),
19802 (selection.start.text_anchor..selection.end.text_anchor)
19803 .to_point(buffer.read(cx)),
19804 )
19805 })
19806 .into_group_map();
19807
19808 cx.spawn_in(window, async move |_, cx| {
19809 workspace.update_in(cx, |workspace, window, cx| {
19810 Self::open_locations_in_multibuffer(
19811 workspace,
19812 locations,
19813 format!("Selections for '{title}'"),
19814 false,
19815 MultibufferSelectionMode::All,
19816 window,
19817 cx,
19818 );
19819 })
19820 })
19821 .detach();
19822 }
19823
19824 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19825 /// last highlight added will be used.
19826 ///
19827 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19828 pub fn highlight_rows<T: 'static>(
19829 &mut self,
19830 range: Range<Anchor>,
19831 color: Hsla,
19832 options: RowHighlightOptions,
19833 cx: &mut Context<Self>,
19834 ) {
19835 let snapshot = self.buffer().read(cx).snapshot(cx);
19836 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19837 let ix = row_highlights.binary_search_by(|highlight| {
19838 Ordering::Equal
19839 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19840 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19841 });
19842
19843 if let Err(mut ix) = ix {
19844 let index = post_inc(&mut self.highlight_order);
19845
19846 // If this range intersects with the preceding highlight, then merge it with
19847 // the preceding highlight. Otherwise insert a new highlight.
19848 let mut merged = false;
19849 if ix > 0 {
19850 let prev_highlight = &mut row_highlights[ix - 1];
19851 if prev_highlight
19852 .range
19853 .end
19854 .cmp(&range.start, &snapshot)
19855 .is_ge()
19856 {
19857 ix -= 1;
19858 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19859 prev_highlight.range.end = range.end;
19860 }
19861 merged = true;
19862 prev_highlight.index = index;
19863 prev_highlight.color = color;
19864 prev_highlight.options = options;
19865 }
19866 }
19867
19868 if !merged {
19869 row_highlights.insert(
19870 ix,
19871 RowHighlight {
19872 range,
19873 index,
19874 color,
19875 options,
19876 type_id: TypeId::of::<T>(),
19877 },
19878 );
19879 }
19880
19881 // If any of the following highlights intersect with this one, merge them.
19882 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19883 let highlight = &row_highlights[ix];
19884 if next_highlight
19885 .range
19886 .start
19887 .cmp(&highlight.range.end, &snapshot)
19888 .is_le()
19889 {
19890 if next_highlight
19891 .range
19892 .end
19893 .cmp(&highlight.range.end, &snapshot)
19894 .is_gt()
19895 {
19896 row_highlights[ix].range.end = next_highlight.range.end;
19897 }
19898 row_highlights.remove(ix + 1);
19899 } else {
19900 break;
19901 }
19902 }
19903 }
19904 }
19905
19906 /// Remove any highlighted row ranges of the given type that intersect the
19907 /// given ranges.
19908 pub fn remove_highlighted_rows<T: 'static>(
19909 &mut self,
19910 ranges_to_remove: Vec<Range<Anchor>>,
19911 cx: &mut Context<Self>,
19912 ) {
19913 let snapshot = self.buffer().read(cx).snapshot(cx);
19914 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19915 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19916 row_highlights.retain(|highlight| {
19917 while let Some(range_to_remove) = ranges_to_remove.peek() {
19918 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19919 Ordering::Less | Ordering::Equal => {
19920 ranges_to_remove.next();
19921 }
19922 Ordering::Greater => {
19923 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19924 Ordering::Less | Ordering::Equal => {
19925 return false;
19926 }
19927 Ordering::Greater => break,
19928 }
19929 }
19930 }
19931 }
19932
19933 true
19934 })
19935 }
19936
19937 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19938 pub fn clear_row_highlights<T: 'static>(&mut self) {
19939 self.highlighted_rows.remove(&TypeId::of::<T>());
19940 }
19941
19942 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19943 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19944 self.highlighted_rows
19945 .get(&TypeId::of::<T>())
19946 .map_or(&[] as &[_], |vec| vec.as_slice())
19947 .iter()
19948 .map(|highlight| (highlight.range.clone(), highlight.color))
19949 }
19950
19951 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19952 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19953 /// Allows to ignore certain kinds of highlights.
19954 pub fn highlighted_display_rows(
19955 &self,
19956 window: &mut Window,
19957 cx: &mut App,
19958 ) -> BTreeMap<DisplayRow, LineHighlight> {
19959 let snapshot = self.snapshot(window, cx);
19960 let mut used_highlight_orders = HashMap::default();
19961 self.highlighted_rows
19962 .iter()
19963 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19964 .fold(
19965 BTreeMap::<DisplayRow, LineHighlight>::new(),
19966 |mut unique_rows, highlight| {
19967 let start = highlight.range.start.to_display_point(&snapshot);
19968 let end = highlight.range.end.to_display_point(&snapshot);
19969 let start_row = start.row().0;
19970 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19971 && end.column() == 0
19972 {
19973 end.row().0.saturating_sub(1)
19974 } else {
19975 end.row().0
19976 };
19977 for row in start_row..=end_row {
19978 let used_index =
19979 used_highlight_orders.entry(row).or_insert(highlight.index);
19980 if highlight.index >= *used_index {
19981 *used_index = highlight.index;
19982 unique_rows.insert(
19983 DisplayRow(row),
19984 LineHighlight {
19985 include_gutter: highlight.options.include_gutter,
19986 border: None,
19987 background: highlight.color.into(),
19988 type_id: Some(highlight.type_id),
19989 },
19990 );
19991 }
19992 }
19993 unique_rows
19994 },
19995 )
19996 }
19997
19998 pub fn highlighted_display_row_for_autoscroll(
19999 &self,
20000 snapshot: &DisplaySnapshot,
20001 ) -> Option<DisplayRow> {
20002 self.highlighted_rows
20003 .values()
20004 .flat_map(|highlighted_rows| highlighted_rows.iter())
20005 .filter_map(|highlight| {
20006 if highlight.options.autoscroll {
20007 Some(highlight.range.start.to_display_point(snapshot).row())
20008 } else {
20009 None
20010 }
20011 })
20012 .min()
20013 }
20014
20015 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20016 self.highlight_background::<SearchWithinRange>(
20017 ranges,
20018 |colors| colors.colors().editor_document_highlight_read_background,
20019 cx,
20020 )
20021 }
20022
20023 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20024 self.breadcrumb_header = Some(new_header);
20025 }
20026
20027 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20028 self.clear_background_highlights::<SearchWithinRange>(cx);
20029 }
20030
20031 pub fn highlight_background<T: 'static>(
20032 &mut self,
20033 ranges: &[Range<Anchor>],
20034 color_fetcher: fn(&Theme) -> Hsla,
20035 cx: &mut Context<Self>,
20036 ) {
20037 self.background_highlights.insert(
20038 HighlightKey::Type(TypeId::of::<T>()),
20039 (color_fetcher, Arc::from(ranges)),
20040 );
20041 self.scrollbar_marker_state.dirty = true;
20042 cx.notify();
20043 }
20044
20045 pub fn highlight_background_key<T: 'static>(
20046 &mut self,
20047 key: usize,
20048 ranges: &[Range<Anchor>],
20049 color_fetcher: fn(&Theme) -> Hsla,
20050 cx: &mut Context<Self>,
20051 ) {
20052 self.background_highlights.insert(
20053 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20054 (color_fetcher, Arc::from(ranges)),
20055 );
20056 self.scrollbar_marker_state.dirty = true;
20057 cx.notify();
20058 }
20059
20060 pub fn clear_background_highlights<T: 'static>(
20061 &mut self,
20062 cx: &mut Context<Self>,
20063 ) -> Option<BackgroundHighlight> {
20064 let text_highlights = self
20065 .background_highlights
20066 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20067 if !text_highlights.1.is_empty() {
20068 self.scrollbar_marker_state.dirty = true;
20069 cx.notify();
20070 }
20071 Some(text_highlights)
20072 }
20073
20074 pub fn highlight_gutter<T: 'static>(
20075 &mut self,
20076 ranges: impl Into<Vec<Range<Anchor>>>,
20077 color_fetcher: fn(&App) -> Hsla,
20078 cx: &mut Context<Self>,
20079 ) {
20080 self.gutter_highlights
20081 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20082 cx.notify();
20083 }
20084
20085 pub fn clear_gutter_highlights<T: 'static>(
20086 &mut self,
20087 cx: &mut Context<Self>,
20088 ) -> Option<GutterHighlight> {
20089 cx.notify();
20090 self.gutter_highlights.remove(&TypeId::of::<T>())
20091 }
20092
20093 pub fn insert_gutter_highlight<T: 'static>(
20094 &mut self,
20095 range: Range<Anchor>,
20096 color_fetcher: fn(&App) -> Hsla,
20097 cx: &mut Context<Self>,
20098 ) {
20099 let snapshot = self.buffer().read(cx).snapshot(cx);
20100 let mut highlights = self
20101 .gutter_highlights
20102 .remove(&TypeId::of::<T>())
20103 .map(|(_, highlights)| highlights)
20104 .unwrap_or_default();
20105 let ix = highlights.binary_search_by(|highlight| {
20106 Ordering::Equal
20107 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20108 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20109 });
20110 if let Err(ix) = ix {
20111 highlights.insert(ix, range);
20112 }
20113 self.gutter_highlights
20114 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20115 }
20116
20117 pub fn remove_gutter_highlights<T: 'static>(
20118 &mut self,
20119 ranges_to_remove: Vec<Range<Anchor>>,
20120 cx: &mut Context<Self>,
20121 ) {
20122 let snapshot = self.buffer().read(cx).snapshot(cx);
20123 let Some((color_fetcher, mut gutter_highlights)) =
20124 self.gutter_highlights.remove(&TypeId::of::<T>())
20125 else {
20126 return;
20127 };
20128 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20129 gutter_highlights.retain(|highlight| {
20130 while let Some(range_to_remove) = ranges_to_remove.peek() {
20131 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20132 Ordering::Less | Ordering::Equal => {
20133 ranges_to_remove.next();
20134 }
20135 Ordering::Greater => {
20136 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20137 Ordering::Less | Ordering::Equal => {
20138 return false;
20139 }
20140 Ordering::Greater => break,
20141 }
20142 }
20143 }
20144 }
20145
20146 true
20147 });
20148 self.gutter_highlights
20149 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20150 }
20151
20152 #[cfg(feature = "test-support")]
20153 pub fn all_text_highlights(
20154 &self,
20155 window: &mut Window,
20156 cx: &mut Context<Self>,
20157 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20158 let snapshot = self.snapshot(window, cx);
20159 self.display_map.update(cx, |display_map, _| {
20160 display_map
20161 .all_text_highlights()
20162 .map(|highlight| {
20163 let (style, ranges) = highlight.as_ref();
20164 (
20165 *style,
20166 ranges
20167 .iter()
20168 .map(|range| range.clone().to_display_points(&snapshot))
20169 .collect(),
20170 )
20171 })
20172 .collect()
20173 })
20174 }
20175
20176 #[cfg(feature = "test-support")]
20177 pub fn all_text_background_highlights(
20178 &self,
20179 window: &mut Window,
20180 cx: &mut Context<Self>,
20181 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20182 let snapshot = self.snapshot(window, cx);
20183 let buffer = &snapshot.buffer_snapshot;
20184 let start = buffer.anchor_before(0);
20185 let end = buffer.anchor_after(buffer.len());
20186 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20187 }
20188
20189 #[cfg(any(test, feature = "test-support"))]
20190 pub fn sorted_background_highlights_in_range(
20191 &self,
20192 search_range: Range<Anchor>,
20193 display_snapshot: &DisplaySnapshot,
20194 theme: &Theme,
20195 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20196 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20197 res.sort_by(|a, b| {
20198 a.0.start
20199 .cmp(&b.0.start)
20200 .then_with(|| a.0.end.cmp(&b.0.end))
20201 .then_with(|| a.1.cmp(&b.1))
20202 });
20203 res
20204 }
20205
20206 #[cfg(feature = "test-support")]
20207 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20208 let snapshot = self.buffer().read(cx).snapshot(cx);
20209
20210 let highlights = self
20211 .background_highlights
20212 .get(&HighlightKey::Type(TypeId::of::<
20213 items::BufferSearchHighlights,
20214 >()));
20215
20216 if let Some((_color, ranges)) = highlights {
20217 ranges
20218 .iter()
20219 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20220 .collect_vec()
20221 } else {
20222 vec![]
20223 }
20224 }
20225
20226 fn document_highlights_for_position<'a>(
20227 &'a self,
20228 position: Anchor,
20229 buffer: &'a MultiBufferSnapshot,
20230 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20231 let read_highlights = self
20232 .background_highlights
20233 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20234 .map(|h| &h.1);
20235 let write_highlights = self
20236 .background_highlights
20237 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20238 .map(|h| &h.1);
20239 let left_position = position.bias_left(buffer);
20240 let right_position = position.bias_right(buffer);
20241 read_highlights
20242 .into_iter()
20243 .chain(write_highlights)
20244 .flat_map(move |ranges| {
20245 let start_ix = match ranges.binary_search_by(|probe| {
20246 let cmp = probe.end.cmp(&left_position, buffer);
20247 if cmp.is_ge() {
20248 Ordering::Greater
20249 } else {
20250 Ordering::Less
20251 }
20252 }) {
20253 Ok(i) | Err(i) => i,
20254 };
20255
20256 ranges[start_ix..]
20257 .iter()
20258 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20259 })
20260 }
20261
20262 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20263 self.background_highlights
20264 .get(&HighlightKey::Type(TypeId::of::<T>()))
20265 .is_some_and(|(_, highlights)| !highlights.is_empty())
20266 }
20267
20268 /// Returns all background highlights for a given range.
20269 ///
20270 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20271 pub fn background_highlights_in_range(
20272 &self,
20273 search_range: Range<Anchor>,
20274 display_snapshot: &DisplaySnapshot,
20275 theme: &Theme,
20276 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20277 let mut results = Vec::new();
20278 for (color_fetcher, ranges) in self.background_highlights.values() {
20279 let color = color_fetcher(theme);
20280 let start_ix = match ranges.binary_search_by(|probe| {
20281 let cmp = probe
20282 .end
20283 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20284 if cmp.is_gt() {
20285 Ordering::Greater
20286 } else {
20287 Ordering::Less
20288 }
20289 }) {
20290 Ok(i) | Err(i) => i,
20291 };
20292 for range in &ranges[start_ix..] {
20293 if range
20294 .start
20295 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20296 .is_ge()
20297 {
20298 break;
20299 }
20300
20301 let start = range.start.to_display_point(display_snapshot);
20302 let end = range.end.to_display_point(display_snapshot);
20303 results.push((start..end, color))
20304 }
20305 }
20306 results
20307 }
20308
20309 pub fn gutter_highlights_in_range(
20310 &self,
20311 search_range: Range<Anchor>,
20312 display_snapshot: &DisplaySnapshot,
20313 cx: &App,
20314 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20315 let mut results = Vec::new();
20316 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20317 let color = color_fetcher(cx);
20318 let start_ix = match ranges.binary_search_by(|probe| {
20319 let cmp = probe
20320 .end
20321 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20322 if cmp.is_gt() {
20323 Ordering::Greater
20324 } else {
20325 Ordering::Less
20326 }
20327 }) {
20328 Ok(i) | Err(i) => i,
20329 };
20330 for range in &ranges[start_ix..] {
20331 if range
20332 .start
20333 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20334 .is_ge()
20335 {
20336 break;
20337 }
20338
20339 let start = range.start.to_display_point(display_snapshot);
20340 let end = range.end.to_display_point(display_snapshot);
20341 results.push((start..end, color))
20342 }
20343 }
20344 results
20345 }
20346
20347 /// Get the text ranges corresponding to the redaction query
20348 pub fn redacted_ranges(
20349 &self,
20350 search_range: Range<Anchor>,
20351 display_snapshot: &DisplaySnapshot,
20352 cx: &App,
20353 ) -> Vec<Range<DisplayPoint>> {
20354 display_snapshot
20355 .buffer_snapshot
20356 .redacted_ranges(search_range, |file| {
20357 if let Some(file) = file {
20358 file.is_private()
20359 && EditorSettings::get(
20360 Some(SettingsLocation {
20361 worktree_id: file.worktree_id(cx),
20362 path: file.path().as_ref(),
20363 }),
20364 cx,
20365 )
20366 .redact_private_values
20367 } else {
20368 false
20369 }
20370 })
20371 .map(|range| {
20372 range.start.to_display_point(display_snapshot)
20373 ..range.end.to_display_point(display_snapshot)
20374 })
20375 .collect()
20376 }
20377
20378 pub fn highlight_text_key<T: 'static>(
20379 &mut self,
20380 key: usize,
20381 ranges: Vec<Range<Anchor>>,
20382 style: HighlightStyle,
20383 cx: &mut Context<Self>,
20384 ) {
20385 self.display_map.update(cx, |map, _| {
20386 map.highlight_text(
20387 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20388 ranges,
20389 style,
20390 );
20391 });
20392 cx.notify();
20393 }
20394
20395 pub fn highlight_text<T: 'static>(
20396 &mut self,
20397 ranges: Vec<Range<Anchor>>,
20398 style: HighlightStyle,
20399 cx: &mut Context<Self>,
20400 ) {
20401 self.display_map.update(cx, |map, _| {
20402 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20403 });
20404 cx.notify();
20405 }
20406
20407 pub(crate) fn highlight_inlays<T: 'static>(
20408 &mut self,
20409 highlights: Vec<InlayHighlight>,
20410 style: HighlightStyle,
20411 cx: &mut Context<Self>,
20412 ) {
20413 self.display_map.update(cx, |map, _| {
20414 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20415 });
20416 cx.notify();
20417 }
20418
20419 pub fn text_highlights<'a, T: 'static>(
20420 &'a self,
20421 cx: &'a App,
20422 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20423 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20424 }
20425
20426 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20427 let cleared = self
20428 .display_map
20429 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20430 if cleared {
20431 cx.notify();
20432 }
20433 }
20434
20435 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20436 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20437 && self.focus_handle.is_focused(window)
20438 }
20439
20440 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20441 self.show_cursor_when_unfocused = is_enabled;
20442 cx.notify();
20443 }
20444
20445 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20446 cx.notify();
20447 }
20448
20449 fn on_debug_session_event(
20450 &mut self,
20451 _session: Entity<Session>,
20452 event: &SessionEvent,
20453 cx: &mut Context<Self>,
20454 ) {
20455 if let SessionEvent::InvalidateInlineValue = event {
20456 self.refresh_inline_values(cx);
20457 }
20458 }
20459
20460 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20461 let Some(project) = self.project.clone() else {
20462 return;
20463 };
20464
20465 if !self.inline_value_cache.enabled {
20466 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20467 self.splice_inlays(&inlays, Vec::new(), cx);
20468 return;
20469 }
20470
20471 let current_execution_position = self
20472 .highlighted_rows
20473 .get(&TypeId::of::<ActiveDebugLine>())
20474 .and_then(|lines| lines.last().map(|line| line.range.end));
20475
20476 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20477 let inline_values = editor
20478 .update(cx, |editor, cx| {
20479 let Some(current_execution_position) = current_execution_position else {
20480 return Some(Task::ready(Ok(Vec::new())));
20481 };
20482
20483 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20484 let snapshot = buffer.snapshot(cx);
20485
20486 let excerpt = snapshot.excerpt_containing(
20487 current_execution_position..current_execution_position,
20488 )?;
20489
20490 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20491 })?;
20492
20493 let range =
20494 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20495
20496 project.inline_values(buffer, range, cx)
20497 })
20498 .ok()
20499 .flatten()?
20500 .await
20501 .context("refreshing debugger inlays")
20502 .log_err()?;
20503
20504 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20505
20506 for (buffer_id, inline_value) in inline_values
20507 .into_iter()
20508 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20509 {
20510 buffer_inline_values
20511 .entry(buffer_id)
20512 .or_default()
20513 .push(inline_value);
20514 }
20515
20516 editor
20517 .update(cx, |editor, cx| {
20518 let snapshot = editor.buffer.read(cx).snapshot(cx);
20519 let mut new_inlays = Vec::default();
20520
20521 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20522 let buffer_id = buffer_snapshot.remote_id();
20523 buffer_inline_values
20524 .get(&buffer_id)
20525 .into_iter()
20526 .flatten()
20527 .for_each(|hint| {
20528 let inlay = Inlay::debugger(
20529 post_inc(&mut editor.next_inlay_id),
20530 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20531 hint.text(),
20532 );
20533 if !inlay.text.chars().contains(&'\n') {
20534 new_inlays.push(inlay);
20535 }
20536 });
20537 }
20538
20539 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20540 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20541
20542 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20543 })
20544 .ok()?;
20545 Some(())
20546 });
20547 }
20548
20549 fn on_buffer_event(
20550 &mut self,
20551 multibuffer: &Entity<MultiBuffer>,
20552 event: &multi_buffer::Event,
20553 window: &mut Window,
20554 cx: &mut Context<Self>,
20555 ) {
20556 match event {
20557 multi_buffer::Event::Edited {
20558 singleton_buffer_edited,
20559 edited_buffer,
20560 } => {
20561 self.scrollbar_marker_state.dirty = true;
20562 self.active_indent_guides_state.dirty = true;
20563 self.refresh_active_diagnostics(cx);
20564 self.refresh_code_actions(window, cx);
20565 self.refresh_selected_text_highlights(true, window, cx);
20566 self.refresh_single_line_folds(window, cx);
20567 refresh_matching_bracket_highlights(self, window, cx);
20568 if self.has_active_edit_prediction() {
20569 self.update_visible_edit_prediction(window, cx);
20570 }
20571 if let Some(project) = self.project.as_ref()
20572 && let Some(edited_buffer) = edited_buffer
20573 {
20574 project.update(cx, |project, cx| {
20575 self.registered_buffers
20576 .entry(edited_buffer.read(cx).remote_id())
20577 .or_insert_with(|| {
20578 project.register_buffer_with_language_servers(edited_buffer, cx)
20579 });
20580 });
20581 }
20582 cx.emit(EditorEvent::BufferEdited);
20583 cx.emit(SearchEvent::MatchesInvalidated);
20584
20585 if let Some(buffer) = edited_buffer {
20586 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20587 }
20588
20589 if *singleton_buffer_edited {
20590 if let Some(buffer) = edited_buffer
20591 && buffer.read(cx).file().is_none()
20592 {
20593 cx.emit(EditorEvent::TitleChanged);
20594 }
20595 if let Some(project) = &self.project {
20596 #[allow(clippy::mutable_key_type)]
20597 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20598 multibuffer
20599 .all_buffers()
20600 .into_iter()
20601 .filter_map(|buffer| {
20602 buffer.update(cx, |buffer, cx| {
20603 let language = buffer.language()?;
20604 let should_discard = project.update(cx, |project, cx| {
20605 project.is_local()
20606 && !project.has_language_servers_for(buffer, cx)
20607 });
20608 should_discard.not().then_some(language.clone())
20609 })
20610 })
20611 .collect::<HashSet<_>>()
20612 });
20613 if !languages_affected.is_empty() {
20614 self.refresh_inlay_hints(
20615 InlayHintRefreshReason::BufferEdited(languages_affected),
20616 cx,
20617 );
20618 }
20619 }
20620 }
20621
20622 let Some(project) = &self.project else { return };
20623 let (telemetry, is_via_ssh) = {
20624 let project = project.read(cx);
20625 let telemetry = project.client().telemetry().clone();
20626 let is_via_ssh = project.is_via_remote_server();
20627 (telemetry, is_via_ssh)
20628 };
20629 refresh_linked_ranges(self, window, cx);
20630 telemetry.log_edit_event("editor", is_via_ssh);
20631 }
20632 multi_buffer::Event::ExcerptsAdded {
20633 buffer,
20634 predecessor,
20635 excerpts,
20636 } => {
20637 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20638 let buffer_id = buffer.read(cx).remote_id();
20639 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20640 && let Some(project) = &self.project
20641 {
20642 update_uncommitted_diff_for_buffer(
20643 cx.entity(),
20644 project,
20645 [buffer.clone()],
20646 self.buffer.clone(),
20647 cx,
20648 )
20649 .detach();
20650 }
20651 if self.active_diagnostics != ActiveDiagnostic::All {
20652 self.update_lsp_data(false, Some(buffer_id), window, cx);
20653 }
20654 cx.emit(EditorEvent::ExcerptsAdded {
20655 buffer: buffer.clone(),
20656 predecessor: *predecessor,
20657 excerpts: excerpts.clone(),
20658 });
20659 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20660 }
20661 multi_buffer::Event::ExcerptsRemoved {
20662 ids,
20663 removed_buffer_ids,
20664 } => {
20665 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20666 let buffer = self.buffer.read(cx);
20667 self.registered_buffers
20668 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20669 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20670 cx.emit(EditorEvent::ExcerptsRemoved {
20671 ids: ids.clone(),
20672 removed_buffer_ids: removed_buffer_ids.clone(),
20673 });
20674 }
20675 multi_buffer::Event::ExcerptsEdited {
20676 excerpt_ids,
20677 buffer_ids,
20678 } => {
20679 self.display_map.update(cx, |map, cx| {
20680 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20681 });
20682 cx.emit(EditorEvent::ExcerptsEdited {
20683 ids: excerpt_ids.clone(),
20684 });
20685 }
20686 multi_buffer::Event::ExcerptsExpanded { ids } => {
20687 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20688 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20689 }
20690 multi_buffer::Event::Reparsed(buffer_id) => {
20691 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20692 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20693
20694 cx.emit(EditorEvent::Reparsed(*buffer_id));
20695 }
20696 multi_buffer::Event::DiffHunksToggled => {
20697 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20698 }
20699 multi_buffer::Event::LanguageChanged(buffer_id) => {
20700 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20701 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20702 cx.emit(EditorEvent::Reparsed(*buffer_id));
20703 cx.notify();
20704 }
20705 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20706 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20707 multi_buffer::Event::FileHandleChanged
20708 | multi_buffer::Event::Reloaded
20709 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20710 multi_buffer::Event::DiagnosticsUpdated => {
20711 self.update_diagnostics_state(window, cx);
20712 }
20713 _ => {}
20714 };
20715 }
20716
20717 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20718 if !self.diagnostics_enabled() {
20719 return;
20720 }
20721 self.refresh_active_diagnostics(cx);
20722 self.refresh_inline_diagnostics(true, window, cx);
20723 self.scrollbar_marker_state.dirty = true;
20724 cx.notify();
20725 }
20726
20727 pub fn start_temporary_diff_override(&mut self) {
20728 self.load_diff_task.take();
20729 self.temporary_diff_override = true;
20730 }
20731
20732 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20733 self.temporary_diff_override = false;
20734 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20735 self.buffer.update(cx, |buffer, cx| {
20736 buffer.set_all_diff_hunks_collapsed(cx);
20737 });
20738
20739 if let Some(project) = self.project.clone() {
20740 self.load_diff_task = Some(
20741 update_uncommitted_diff_for_buffer(
20742 cx.entity(),
20743 &project,
20744 self.buffer.read(cx).all_buffers(),
20745 self.buffer.clone(),
20746 cx,
20747 )
20748 .shared(),
20749 );
20750 }
20751 }
20752
20753 fn on_display_map_changed(
20754 &mut self,
20755 _: Entity<DisplayMap>,
20756 _: &mut Window,
20757 cx: &mut Context<Self>,
20758 ) {
20759 cx.notify();
20760 }
20761
20762 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20763 if self.diagnostics_enabled() {
20764 let new_severity = EditorSettings::get_global(cx)
20765 .diagnostics_max_severity
20766 .unwrap_or(DiagnosticSeverity::Hint);
20767 self.set_max_diagnostics_severity(new_severity, cx);
20768 }
20769 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20770 self.update_edit_prediction_settings(cx);
20771 self.refresh_edit_prediction(true, false, window, cx);
20772 self.refresh_inline_values(cx);
20773 self.refresh_inlay_hints(
20774 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20775 self.selections.newest_anchor().head(),
20776 &self.buffer.read(cx).snapshot(cx),
20777 cx,
20778 )),
20779 cx,
20780 );
20781
20782 let old_cursor_shape = self.cursor_shape;
20783 let old_show_breadcrumbs = self.show_breadcrumbs;
20784
20785 {
20786 let editor_settings = EditorSettings::get_global(cx);
20787 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20788 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20789 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20790 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20791 }
20792
20793 if old_cursor_shape != self.cursor_shape {
20794 cx.emit(EditorEvent::CursorShapeChanged);
20795 }
20796
20797 if old_show_breadcrumbs != self.show_breadcrumbs {
20798 cx.emit(EditorEvent::BreadcrumbsChanged);
20799 }
20800
20801 let project_settings = ProjectSettings::get_global(cx);
20802 self.serialize_dirty_buffers =
20803 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20804
20805 if self.mode.is_full() {
20806 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20807 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20808 if self.show_inline_diagnostics != show_inline_diagnostics {
20809 self.show_inline_diagnostics = show_inline_diagnostics;
20810 self.refresh_inline_diagnostics(false, window, cx);
20811 }
20812
20813 if self.git_blame_inline_enabled != inline_blame_enabled {
20814 self.toggle_git_blame_inline_internal(false, window, cx);
20815 }
20816
20817 let minimap_settings = EditorSettings::get_global(cx).minimap;
20818 if self.minimap_visibility != MinimapVisibility::Disabled {
20819 if self.minimap_visibility.settings_visibility()
20820 != minimap_settings.minimap_enabled()
20821 {
20822 self.set_minimap_visibility(
20823 MinimapVisibility::for_mode(self.mode(), cx),
20824 window,
20825 cx,
20826 );
20827 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20828 minimap_entity.update(cx, |minimap_editor, cx| {
20829 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20830 })
20831 }
20832 }
20833 }
20834
20835 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20836 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20837 }) {
20838 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20839 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20840 }
20841 self.refresh_colors(false, None, window, cx);
20842 }
20843
20844 cx.notify();
20845 }
20846
20847 pub fn set_searchable(&mut self, searchable: bool) {
20848 self.searchable = searchable;
20849 }
20850
20851 pub fn searchable(&self) -> bool {
20852 self.searchable
20853 }
20854
20855 fn open_proposed_changes_editor(
20856 &mut self,
20857 _: &OpenProposedChangesEditor,
20858 window: &mut Window,
20859 cx: &mut Context<Self>,
20860 ) {
20861 let Some(workspace) = self.workspace() else {
20862 cx.propagate();
20863 return;
20864 };
20865
20866 let selections = self.selections.all::<usize>(cx);
20867 let multi_buffer = self.buffer.read(cx);
20868 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20869 let mut new_selections_by_buffer = HashMap::default();
20870 for selection in selections {
20871 for (buffer, range, _) in
20872 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20873 {
20874 let mut range = range.to_point(buffer);
20875 range.start.column = 0;
20876 range.end.column = buffer.line_len(range.end.row);
20877 new_selections_by_buffer
20878 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20879 .or_insert(Vec::new())
20880 .push(range)
20881 }
20882 }
20883
20884 let proposed_changes_buffers = new_selections_by_buffer
20885 .into_iter()
20886 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20887 .collect::<Vec<_>>();
20888 let proposed_changes_editor = cx.new(|cx| {
20889 ProposedChangesEditor::new(
20890 "Proposed changes",
20891 proposed_changes_buffers,
20892 self.project.clone(),
20893 window,
20894 cx,
20895 )
20896 });
20897
20898 window.defer(cx, move |window, cx| {
20899 workspace.update(cx, |workspace, cx| {
20900 workspace.active_pane().update(cx, |pane, cx| {
20901 pane.add_item(
20902 Box::new(proposed_changes_editor),
20903 true,
20904 true,
20905 None,
20906 window,
20907 cx,
20908 );
20909 });
20910 });
20911 });
20912 }
20913
20914 pub fn open_excerpts_in_split(
20915 &mut self,
20916 _: &OpenExcerptsSplit,
20917 window: &mut Window,
20918 cx: &mut Context<Self>,
20919 ) {
20920 self.open_excerpts_common(None, true, window, cx)
20921 }
20922
20923 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20924 self.open_excerpts_common(None, false, window, cx)
20925 }
20926
20927 fn open_excerpts_common(
20928 &mut self,
20929 jump_data: Option<JumpData>,
20930 split: bool,
20931 window: &mut Window,
20932 cx: &mut Context<Self>,
20933 ) {
20934 let Some(workspace) = self.workspace() else {
20935 cx.propagate();
20936 return;
20937 };
20938
20939 if self.buffer.read(cx).is_singleton() {
20940 cx.propagate();
20941 return;
20942 }
20943
20944 let mut new_selections_by_buffer = HashMap::default();
20945 match &jump_data {
20946 Some(JumpData::MultiBufferPoint {
20947 excerpt_id,
20948 position,
20949 anchor,
20950 line_offset_from_top,
20951 }) => {
20952 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20953 if let Some(buffer) = multi_buffer_snapshot
20954 .buffer_id_for_excerpt(*excerpt_id)
20955 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20956 {
20957 let buffer_snapshot = buffer.read(cx).snapshot();
20958 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20959 language::ToPoint::to_point(anchor, &buffer_snapshot)
20960 } else {
20961 buffer_snapshot.clip_point(*position, Bias::Left)
20962 };
20963 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20964 new_selections_by_buffer.insert(
20965 buffer,
20966 (
20967 vec![jump_to_offset..jump_to_offset],
20968 Some(*line_offset_from_top),
20969 ),
20970 );
20971 }
20972 }
20973 Some(JumpData::MultiBufferRow {
20974 row,
20975 line_offset_from_top,
20976 }) => {
20977 let point = MultiBufferPoint::new(row.0, 0);
20978 if let Some((buffer, buffer_point, _)) =
20979 self.buffer.read(cx).point_to_buffer_point(point, cx)
20980 {
20981 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20982 new_selections_by_buffer
20983 .entry(buffer)
20984 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20985 .0
20986 .push(buffer_offset..buffer_offset)
20987 }
20988 }
20989 None => {
20990 let selections = self.selections.all::<usize>(cx);
20991 let multi_buffer = self.buffer.read(cx);
20992 for selection in selections {
20993 for (snapshot, range, _, anchor) in multi_buffer
20994 .snapshot(cx)
20995 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20996 {
20997 if let Some(anchor) = anchor {
20998 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20999 else {
21000 continue;
21001 };
21002 let offset = text::ToOffset::to_offset(
21003 &anchor.text_anchor,
21004 &buffer_handle.read(cx).snapshot(),
21005 );
21006 let range = offset..offset;
21007 new_selections_by_buffer
21008 .entry(buffer_handle)
21009 .or_insert((Vec::new(), None))
21010 .0
21011 .push(range)
21012 } else {
21013 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21014 else {
21015 continue;
21016 };
21017 new_selections_by_buffer
21018 .entry(buffer_handle)
21019 .or_insert((Vec::new(), None))
21020 .0
21021 .push(range)
21022 }
21023 }
21024 }
21025 }
21026 }
21027
21028 new_selections_by_buffer
21029 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21030
21031 if new_selections_by_buffer.is_empty() {
21032 return;
21033 }
21034
21035 // We defer the pane interaction because we ourselves are a workspace item
21036 // and activating a new item causes the pane to call a method on us reentrantly,
21037 // which panics if we're on the stack.
21038 window.defer(cx, move |window, cx| {
21039 workspace.update(cx, |workspace, cx| {
21040 let pane = if split {
21041 workspace.adjacent_pane(window, cx)
21042 } else {
21043 workspace.active_pane().clone()
21044 };
21045
21046 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21047 let editor = buffer
21048 .read(cx)
21049 .file()
21050 .is_none()
21051 .then(|| {
21052 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21053 // so `workspace.open_project_item` will never find them, always opening a new editor.
21054 // Instead, we try to activate the existing editor in the pane first.
21055 let (editor, pane_item_index) =
21056 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21057 let editor = item.downcast::<Editor>()?;
21058 let singleton_buffer =
21059 editor.read(cx).buffer().read(cx).as_singleton()?;
21060 if singleton_buffer == buffer {
21061 Some((editor, i))
21062 } else {
21063 None
21064 }
21065 })?;
21066 pane.update(cx, |pane, cx| {
21067 pane.activate_item(pane_item_index, true, true, window, cx)
21068 });
21069 Some(editor)
21070 })
21071 .flatten()
21072 .unwrap_or_else(|| {
21073 workspace.open_project_item::<Self>(
21074 pane.clone(),
21075 buffer,
21076 true,
21077 true,
21078 window,
21079 cx,
21080 )
21081 });
21082
21083 editor.update(cx, |editor, cx| {
21084 let autoscroll = match scroll_offset {
21085 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21086 None => Autoscroll::newest(),
21087 };
21088 let nav_history = editor.nav_history.take();
21089 editor.change_selections(
21090 SelectionEffects::scroll(autoscroll),
21091 window,
21092 cx,
21093 |s| {
21094 s.select_ranges(ranges);
21095 },
21096 );
21097 editor.nav_history = nav_history;
21098 });
21099 }
21100 })
21101 });
21102 }
21103
21104 // For now, don't allow opening excerpts in buffers that aren't backed by
21105 // regular project files.
21106 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21107 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21108 }
21109
21110 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21111 let snapshot = self.buffer.read(cx).read(cx);
21112 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21113 Some(
21114 ranges
21115 .iter()
21116 .map(move |range| {
21117 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21118 })
21119 .collect(),
21120 )
21121 }
21122
21123 fn selection_replacement_ranges(
21124 &self,
21125 range: Range<OffsetUtf16>,
21126 cx: &mut App,
21127 ) -> Vec<Range<OffsetUtf16>> {
21128 let selections = self.selections.all::<OffsetUtf16>(cx);
21129 let newest_selection = selections
21130 .iter()
21131 .max_by_key(|selection| selection.id)
21132 .unwrap();
21133 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21134 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21135 let snapshot = self.buffer.read(cx).read(cx);
21136 selections
21137 .into_iter()
21138 .map(|mut selection| {
21139 selection.start.0 =
21140 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21141 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21142 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21143 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21144 })
21145 .collect()
21146 }
21147
21148 fn report_editor_event(
21149 &self,
21150 reported_event: ReportEditorEvent,
21151 file_extension: Option<String>,
21152 cx: &App,
21153 ) {
21154 if cfg!(any(test, feature = "test-support")) {
21155 return;
21156 }
21157
21158 let Some(project) = &self.project else { return };
21159
21160 // If None, we are in a file without an extension
21161 let file = self
21162 .buffer
21163 .read(cx)
21164 .as_singleton()
21165 .and_then(|b| b.read(cx).file());
21166 let file_extension = file_extension.or(file
21167 .as_ref()
21168 .and_then(|file| Path::new(file.file_name(cx)).extension())
21169 .and_then(|e| e.to_str())
21170 .map(|a| a.to_string()));
21171
21172 let vim_mode = vim_enabled(cx);
21173
21174 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21175 let copilot_enabled = edit_predictions_provider
21176 == language::language_settings::EditPredictionProvider::Copilot;
21177 let copilot_enabled_for_language = self
21178 .buffer
21179 .read(cx)
21180 .language_settings(cx)
21181 .show_edit_predictions;
21182
21183 let project = project.read(cx);
21184 let event_type = reported_event.event_type();
21185
21186 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21187 telemetry::event!(
21188 event_type,
21189 type = if auto_saved {"autosave"} else {"manual"},
21190 file_extension,
21191 vim_mode,
21192 copilot_enabled,
21193 copilot_enabled_for_language,
21194 edit_predictions_provider,
21195 is_via_ssh = project.is_via_remote_server(),
21196 );
21197 } else {
21198 telemetry::event!(
21199 event_type,
21200 file_extension,
21201 vim_mode,
21202 copilot_enabled,
21203 copilot_enabled_for_language,
21204 edit_predictions_provider,
21205 is_via_ssh = project.is_via_remote_server(),
21206 );
21207 };
21208 }
21209
21210 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21211 /// with each line being an array of {text, highlight} objects.
21212 fn copy_highlight_json(
21213 &mut self,
21214 _: &CopyHighlightJson,
21215 window: &mut Window,
21216 cx: &mut Context<Self>,
21217 ) {
21218 #[derive(Serialize)]
21219 struct Chunk<'a> {
21220 text: String,
21221 highlight: Option<&'a str>,
21222 }
21223
21224 let snapshot = self.buffer.read(cx).snapshot(cx);
21225 let range = self
21226 .selected_text_range(false, window, cx)
21227 .and_then(|selection| {
21228 if selection.range.is_empty() {
21229 None
21230 } else {
21231 Some(selection.range)
21232 }
21233 })
21234 .unwrap_or_else(|| 0..snapshot.len());
21235
21236 let chunks = snapshot.chunks(range, true);
21237 let mut lines = Vec::new();
21238 let mut line: VecDeque<Chunk> = VecDeque::new();
21239
21240 let Some(style) = self.style.as_ref() else {
21241 return;
21242 };
21243
21244 for chunk in chunks {
21245 let highlight = chunk
21246 .syntax_highlight_id
21247 .and_then(|id| id.name(&style.syntax));
21248 let mut chunk_lines = chunk.text.split('\n').peekable();
21249 while let Some(text) = chunk_lines.next() {
21250 let mut merged_with_last_token = false;
21251 if let Some(last_token) = line.back_mut()
21252 && last_token.highlight == highlight
21253 {
21254 last_token.text.push_str(text);
21255 merged_with_last_token = true;
21256 }
21257
21258 if !merged_with_last_token {
21259 line.push_back(Chunk {
21260 text: text.into(),
21261 highlight,
21262 });
21263 }
21264
21265 if chunk_lines.peek().is_some() {
21266 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21267 line.pop_front();
21268 }
21269 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21270 line.pop_back();
21271 }
21272
21273 lines.push(mem::take(&mut line));
21274 }
21275 }
21276 }
21277
21278 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21279 return;
21280 };
21281 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21282 }
21283
21284 pub fn open_context_menu(
21285 &mut self,
21286 _: &OpenContextMenu,
21287 window: &mut Window,
21288 cx: &mut Context<Self>,
21289 ) {
21290 self.request_autoscroll(Autoscroll::newest(), cx);
21291 let position = self.selections.newest_display(cx).start;
21292 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21293 }
21294
21295 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21296 &self.inlay_hint_cache
21297 }
21298
21299 pub fn replay_insert_event(
21300 &mut self,
21301 text: &str,
21302 relative_utf16_range: Option<Range<isize>>,
21303 window: &mut Window,
21304 cx: &mut Context<Self>,
21305 ) {
21306 if !self.input_enabled {
21307 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21308 return;
21309 }
21310 if let Some(relative_utf16_range) = relative_utf16_range {
21311 let selections = self.selections.all::<OffsetUtf16>(cx);
21312 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21313 let new_ranges = selections.into_iter().map(|range| {
21314 let start = OffsetUtf16(
21315 range
21316 .head()
21317 .0
21318 .saturating_add_signed(relative_utf16_range.start),
21319 );
21320 let end = OffsetUtf16(
21321 range
21322 .head()
21323 .0
21324 .saturating_add_signed(relative_utf16_range.end),
21325 );
21326 start..end
21327 });
21328 s.select_ranges(new_ranges);
21329 });
21330 }
21331
21332 self.handle_input(text, window, cx);
21333 }
21334
21335 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21336 let Some(provider) = self.semantics_provider.as_ref() else {
21337 return false;
21338 };
21339
21340 let mut supports = false;
21341 self.buffer().update(cx, |this, cx| {
21342 this.for_each_buffer(|buffer| {
21343 supports |= provider.supports_inlay_hints(buffer, cx);
21344 });
21345 });
21346
21347 supports
21348 }
21349
21350 pub fn is_focused(&self, window: &Window) -> bool {
21351 self.focus_handle.is_focused(window)
21352 }
21353
21354 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21355 cx.emit(EditorEvent::Focused);
21356
21357 if let Some(descendant) = self
21358 .last_focused_descendant
21359 .take()
21360 .and_then(|descendant| descendant.upgrade())
21361 {
21362 window.focus(&descendant);
21363 } else {
21364 if let Some(blame) = self.blame.as_ref() {
21365 blame.update(cx, GitBlame::focus)
21366 }
21367
21368 self.blink_manager.update(cx, BlinkManager::enable);
21369 self.show_cursor_names(window, cx);
21370 self.buffer.update(cx, |buffer, cx| {
21371 buffer.finalize_last_transaction(cx);
21372 if self.leader_id.is_none() {
21373 buffer.set_active_selections(
21374 &self.selections.disjoint_anchors_arc(),
21375 self.selections.line_mode(),
21376 self.cursor_shape,
21377 cx,
21378 );
21379 }
21380 });
21381 }
21382 }
21383
21384 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21385 cx.emit(EditorEvent::FocusedIn)
21386 }
21387
21388 fn handle_focus_out(
21389 &mut self,
21390 event: FocusOutEvent,
21391 _window: &mut Window,
21392 cx: &mut Context<Self>,
21393 ) {
21394 if event.blurred != self.focus_handle {
21395 self.last_focused_descendant = Some(event.blurred);
21396 }
21397 self.selection_drag_state = SelectionDragState::None;
21398 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21399 }
21400
21401 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21402 self.blink_manager.update(cx, BlinkManager::disable);
21403 self.buffer
21404 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21405
21406 if let Some(blame) = self.blame.as_ref() {
21407 blame.update(cx, GitBlame::blur)
21408 }
21409 if !self.hover_state.focused(window, cx) {
21410 hide_hover(self, cx);
21411 }
21412 if !self
21413 .context_menu
21414 .borrow()
21415 .as_ref()
21416 .is_some_and(|context_menu| context_menu.focused(window, cx))
21417 {
21418 self.hide_context_menu(window, cx);
21419 }
21420 self.discard_edit_prediction(false, cx);
21421 cx.emit(EditorEvent::Blurred);
21422 cx.notify();
21423 }
21424
21425 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21426 let mut pending: String = window
21427 .pending_input_keystrokes()
21428 .into_iter()
21429 .flatten()
21430 .filter_map(|keystroke| {
21431 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21432 keystroke.key_char.clone()
21433 } else {
21434 None
21435 }
21436 })
21437 .collect();
21438
21439 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21440 pending = "".to_string();
21441 }
21442
21443 let existing_pending = self
21444 .text_highlights::<PendingInput>(cx)
21445 .map(|(_, ranges)| ranges.to_vec());
21446 if existing_pending.is_none() && pending.is_empty() {
21447 return;
21448 }
21449 let transaction =
21450 self.transact(window, cx, |this, window, cx| {
21451 let selections = this.selections.all::<usize>(cx);
21452 let edits = selections
21453 .iter()
21454 .map(|selection| (selection.end..selection.end, pending.clone()));
21455 this.edit(edits, cx);
21456 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21457 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21458 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21459 }));
21460 });
21461 if let Some(existing_ranges) = existing_pending {
21462 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21463 this.edit(edits, cx);
21464 }
21465 });
21466
21467 let snapshot = self.snapshot(window, cx);
21468 let ranges = self
21469 .selections
21470 .all::<usize>(cx)
21471 .into_iter()
21472 .map(|selection| {
21473 snapshot.buffer_snapshot.anchor_after(selection.end)
21474 ..snapshot
21475 .buffer_snapshot
21476 .anchor_before(selection.end + pending.len())
21477 })
21478 .collect();
21479
21480 if pending.is_empty() {
21481 self.clear_highlights::<PendingInput>(cx);
21482 } else {
21483 self.highlight_text::<PendingInput>(
21484 ranges,
21485 HighlightStyle {
21486 underline: Some(UnderlineStyle {
21487 thickness: px(1.),
21488 color: None,
21489 wavy: false,
21490 }),
21491 ..Default::default()
21492 },
21493 cx,
21494 );
21495 }
21496
21497 self.ime_transaction = self.ime_transaction.or(transaction);
21498 if let Some(transaction) = self.ime_transaction {
21499 self.buffer.update(cx, |buffer, cx| {
21500 buffer.group_until_transaction(transaction, cx);
21501 });
21502 }
21503
21504 if self.text_highlights::<PendingInput>(cx).is_none() {
21505 self.ime_transaction.take();
21506 }
21507 }
21508
21509 pub fn register_action_renderer(
21510 &mut self,
21511 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21512 ) -> Subscription {
21513 let id = self.next_editor_action_id.post_inc();
21514 self.editor_actions
21515 .borrow_mut()
21516 .insert(id, Box::new(listener));
21517
21518 let editor_actions = self.editor_actions.clone();
21519 Subscription::new(move || {
21520 editor_actions.borrow_mut().remove(&id);
21521 })
21522 }
21523
21524 pub fn register_action<A: Action>(
21525 &mut self,
21526 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21527 ) -> Subscription {
21528 let id = self.next_editor_action_id.post_inc();
21529 let listener = Arc::new(listener);
21530 self.editor_actions.borrow_mut().insert(
21531 id,
21532 Box::new(move |_, window, _| {
21533 let listener = listener.clone();
21534 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21535 let action = action.downcast_ref().unwrap();
21536 if phase == DispatchPhase::Bubble {
21537 listener(action, window, cx)
21538 }
21539 })
21540 }),
21541 );
21542
21543 let editor_actions = self.editor_actions.clone();
21544 Subscription::new(move || {
21545 editor_actions.borrow_mut().remove(&id);
21546 })
21547 }
21548
21549 pub fn file_header_size(&self) -> u32 {
21550 FILE_HEADER_HEIGHT
21551 }
21552
21553 pub fn restore(
21554 &mut self,
21555 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21556 window: &mut Window,
21557 cx: &mut Context<Self>,
21558 ) {
21559 let workspace = self.workspace();
21560 let project = self.project();
21561 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21562 let mut tasks = Vec::new();
21563 for (buffer_id, changes) in revert_changes {
21564 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21565 buffer.update(cx, |buffer, cx| {
21566 buffer.edit(
21567 changes
21568 .into_iter()
21569 .map(|(range, text)| (range, text.to_string())),
21570 None,
21571 cx,
21572 );
21573 });
21574
21575 if let Some(project) =
21576 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21577 {
21578 project.update(cx, |project, cx| {
21579 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21580 })
21581 }
21582 }
21583 }
21584 tasks
21585 });
21586 cx.spawn_in(window, async move |_, cx| {
21587 for (buffer, task) in save_tasks {
21588 let result = task.await;
21589 if result.is_err() {
21590 let Some(path) = buffer
21591 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21592 .ok()
21593 else {
21594 continue;
21595 };
21596 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21597 let Some(task) = cx
21598 .update_window_entity(workspace, |workspace, window, cx| {
21599 workspace
21600 .open_path_preview(path, None, false, false, false, window, cx)
21601 })
21602 .ok()
21603 else {
21604 continue;
21605 };
21606 task.await.log_err();
21607 }
21608 }
21609 }
21610 })
21611 .detach();
21612 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21613 selections.refresh()
21614 });
21615 }
21616
21617 pub fn to_pixel_point(
21618 &self,
21619 source: multi_buffer::Anchor,
21620 editor_snapshot: &EditorSnapshot,
21621 window: &mut Window,
21622 ) -> Option<gpui::Point<Pixels>> {
21623 let source_point = source.to_display_point(editor_snapshot);
21624 self.display_to_pixel_point(source_point, editor_snapshot, window)
21625 }
21626
21627 pub fn display_to_pixel_point(
21628 &self,
21629 source: DisplayPoint,
21630 editor_snapshot: &EditorSnapshot,
21631 window: &mut Window,
21632 ) -> Option<gpui::Point<Pixels>> {
21633 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21634 let text_layout_details = self.text_layout_details(window);
21635 let scroll_top = text_layout_details
21636 .scroll_anchor
21637 .scroll_position(editor_snapshot)
21638 .y;
21639
21640 if source.row().as_f32() < scroll_top.floor() {
21641 return None;
21642 }
21643 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21644 let source_y = line_height * (source.row().as_f32() - scroll_top);
21645 Some(gpui::Point::new(source_x, source_y))
21646 }
21647
21648 pub fn has_visible_completions_menu(&self) -> bool {
21649 !self.edit_prediction_preview_is_active()
21650 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21651 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21652 })
21653 }
21654
21655 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21656 if self.mode.is_minimap() {
21657 return;
21658 }
21659 self.addons
21660 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21661 }
21662
21663 pub fn unregister_addon<T: Addon>(&mut self) {
21664 self.addons.remove(&std::any::TypeId::of::<T>());
21665 }
21666
21667 pub fn addon<T: Addon>(&self) -> Option<&T> {
21668 let type_id = std::any::TypeId::of::<T>();
21669 self.addons
21670 .get(&type_id)
21671 .and_then(|item| item.to_any().downcast_ref::<T>())
21672 }
21673
21674 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21675 let type_id = std::any::TypeId::of::<T>();
21676 self.addons
21677 .get_mut(&type_id)
21678 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21679 }
21680
21681 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21682 let text_layout_details = self.text_layout_details(window);
21683 let style = &text_layout_details.editor_style;
21684 let font_id = window.text_system().resolve_font(&style.text.font());
21685 let font_size = style.text.font_size.to_pixels(window.rem_size());
21686 let line_height = style.text.line_height_in_pixels(window.rem_size());
21687 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21688 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21689
21690 CharacterDimensions {
21691 em_width,
21692 em_advance,
21693 line_height,
21694 }
21695 }
21696
21697 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21698 self.load_diff_task.clone()
21699 }
21700
21701 fn read_metadata_from_db(
21702 &mut self,
21703 item_id: u64,
21704 workspace_id: WorkspaceId,
21705 window: &mut Window,
21706 cx: &mut Context<Editor>,
21707 ) {
21708 if self.is_singleton(cx)
21709 && !self.mode.is_minimap()
21710 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21711 {
21712 let buffer_snapshot = OnceCell::new();
21713
21714 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21715 && !folds.is_empty()
21716 {
21717 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21718 self.fold_ranges(
21719 folds
21720 .into_iter()
21721 .map(|(start, end)| {
21722 snapshot.clip_offset(start, Bias::Left)
21723 ..snapshot.clip_offset(end, Bias::Right)
21724 })
21725 .collect(),
21726 false,
21727 window,
21728 cx,
21729 );
21730 }
21731
21732 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21733 && !selections.is_empty()
21734 {
21735 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21736 // skip adding the initial selection to selection history
21737 self.selection_history.mode = SelectionHistoryMode::Skipping;
21738 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21739 s.select_ranges(selections.into_iter().map(|(start, end)| {
21740 snapshot.clip_offset(start, Bias::Left)
21741 ..snapshot.clip_offset(end, Bias::Right)
21742 }));
21743 });
21744 self.selection_history.mode = SelectionHistoryMode::Normal;
21745 };
21746 }
21747
21748 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21749 }
21750
21751 fn update_lsp_data(
21752 &mut self,
21753 ignore_cache: bool,
21754 for_buffer: Option<BufferId>,
21755 window: &mut Window,
21756 cx: &mut Context<'_, Self>,
21757 ) {
21758 self.pull_diagnostics(for_buffer, window, cx);
21759 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21760 }
21761}
21762
21763fn edit_for_markdown_paste<'a>(
21764 buffer: &MultiBufferSnapshot,
21765 range: Range<usize>,
21766 to_insert: &'a str,
21767 url: Option<url::Url>,
21768) -> (Range<usize>, Cow<'a, str>) {
21769 if url.is_none() {
21770 return (range, Cow::Borrowed(to_insert));
21771 };
21772
21773 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21774
21775 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21776 Cow::Borrowed(to_insert)
21777 } else {
21778 Cow::Owned(format!("[{old_text}]({to_insert})"))
21779 };
21780 (range, new_text)
21781}
21782
21783fn vim_enabled(cx: &App) -> bool {
21784 vim_mode_setting::VimModeSetting::try_get(cx)
21785 .map(|vim_mode| vim_mode.0)
21786 .unwrap_or(false)
21787}
21788
21789fn process_completion_for_edit(
21790 completion: &Completion,
21791 intent: CompletionIntent,
21792 buffer: &Entity<Buffer>,
21793 cursor_position: &text::Anchor,
21794 cx: &mut Context<Editor>,
21795) -> CompletionEdit {
21796 let buffer = buffer.read(cx);
21797 let buffer_snapshot = buffer.snapshot();
21798 let (snippet, new_text) = if completion.is_snippet() {
21799 // Workaround for typescript language server issues so that methods don't expand within
21800 // strings and functions with type expressions. The previous point is used because the query
21801 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21802 let mut snippet_source = completion.new_text.clone();
21803 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21804 previous_point.column = previous_point.column.saturating_sub(1);
21805 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21806 && scope.prefers_label_for_snippet_in_completion()
21807 && let Some(label) = completion.label()
21808 && matches!(
21809 completion.kind(),
21810 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21811 )
21812 {
21813 snippet_source = label;
21814 }
21815 match Snippet::parse(&snippet_source).log_err() {
21816 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21817 None => (None, completion.new_text.clone()),
21818 }
21819 } else {
21820 (None, completion.new_text.clone())
21821 };
21822
21823 let mut range_to_replace = {
21824 let replace_range = &completion.replace_range;
21825 if let CompletionSource::Lsp {
21826 insert_range: Some(insert_range),
21827 ..
21828 } = &completion.source
21829 {
21830 debug_assert_eq!(
21831 insert_range.start, replace_range.start,
21832 "insert_range and replace_range should start at the same position"
21833 );
21834 debug_assert!(
21835 insert_range
21836 .start
21837 .cmp(cursor_position, &buffer_snapshot)
21838 .is_le(),
21839 "insert_range should start before or at cursor position"
21840 );
21841 debug_assert!(
21842 replace_range
21843 .start
21844 .cmp(cursor_position, &buffer_snapshot)
21845 .is_le(),
21846 "replace_range should start before or at cursor position"
21847 );
21848
21849 let should_replace = match intent {
21850 CompletionIntent::CompleteWithInsert => false,
21851 CompletionIntent::CompleteWithReplace => true,
21852 CompletionIntent::Complete | CompletionIntent::Compose => {
21853 let insert_mode =
21854 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21855 .completions
21856 .lsp_insert_mode;
21857 match insert_mode {
21858 LspInsertMode::Insert => false,
21859 LspInsertMode::Replace => true,
21860 LspInsertMode::ReplaceSubsequence => {
21861 let mut text_to_replace = buffer.chars_for_range(
21862 buffer.anchor_before(replace_range.start)
21863 ..buffer.anchor_after(replace_range.end),
21864 );
21865 let mut current_needle = text_to_replace.next();
21866 for haystack_ch in completion.label.text.chars() {
21867 if let Some(needle_ch) = current_needle
21868 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21869 {
21870 current_needle = text_to_replace.next();
21871 }
21872 }
21873 current_needle.is_none()
21874 }
21875 LspInsertMode::ReplaceSuffix => {
21876 if replace_range
21877 .end
21878 .cmp(cursor_position, &buffer_snapshot)
21879 .is_gt()
21880 {
21881 let range_after_cursor = *cursor_position..replace_range.end;
21882 let text_after_cursor = buffer
21883 .text_for_range(
21884 buffer.anchor_before(range_after_cursor.start)
21885 ..buffer.anchor_after(range_after_cursor.end),
21886 )
21887 .collect::<String>()
21888 .to_ascii_lowercase();
21889 completion
21890 .label
21891 .text
21892 .to_ascii_lowercase()
21893 .ends_with(&text_after_cursor)
21894 } else {
21895 true
21896 }
21897 }
21898 }
21899 }
21900 };
21901
21902 if should_replace {
21903 replace_range.clone()
21904 } else {
21905 insert_range.clone()
21906 }
21907 } else {
21908 replace_range.clone()
21909 }
21910 };
21911
21912 if range_to_replace
21913 .end
21914 .cmp(cursor_position, &buffer_snapshot)
21915 .is_lt()
21916 {
21917 range_to_replace.end = *cursor_position;
21918 }
21919
21920 CompletionEdit {
21921 new_text,
21922 replace_range: range_to_replace.to_offset(buffer),
21923 snippet,
21924 }
21925}
21926
21927struct CompletionEdit {
21928 new_text: String,
21929 replace_range: Range<usize>,
21930 snippet: Option<Snippet>,
21931}
21932
21933fn insert_extra_newline_brackets(
21934 buffer: &MultiBufferSnapshot,
21935 range: Range<usize>,
21936 language: &language::LanguageScope,
21937) -> bool {
21938 let leading_whitespace_len = buffer
21939 .reversed_chars_at(range.start)
21940 .take_while(|c| c.is_whitespace() && *c != '\n')
21941 .map(|c| c.len_utf8())
21942 .sum::<usize>();
21943 let trailing_whitespace_len = buffer
21944 .chars_at(range.end)
21945 .take_while(|c| c.is_whitespace() && *c != '\n')
21946 .map(|c| c.len_utf8())
21947 .sum::<usize>();
21948 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21949
21950 language.brackets().any(|(pair, enabled)| {
21951 let pair_start = pair.start.trim_end();
21952 let pair_end = pair.end.trim_start();
21953
21954 enabled
21955 && pair.newline
21956 && buffer.contains_str_at(range.end, pair_end)
21957 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21958 })
21959}
21960
21961fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21962 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21963 [(buffer, range, _)] => (*buffer, range.clone()),
21964 _ => return false,
21965 };
21966 let pair = {
21967 let mut result: Option<BracketMatch> = None;
21968
21969 for pair in buffer
21970 .all_bracket_ranges(range.clone())
21971 .filter(move |pair| {
21972 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21973 })
21974 {
21975 let len = pair.close_range.end - pair.open_range.start;
21976
21977 if let Some(existing) = &result {
21978 let existing_len = existing.close_range.end - existing.open_range.start;
21979 if len > existing_len {
21980 continue;
21981 }
21982 }
21983
21984 result = Some(pair);
21985 }
21986
21987 result
21988 };
21989 let Some(pair) = pair else {
21990 return false;
21991 };
21992 pair.newline_only
21993 && buffer
21994 .chars_for_range(pair.open_range.end..range.start)
21995 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21996 .all(|c| c.is_whitespace() && c != '\n')
21997}
21998
21999fn update_uncommitted_diff_for_buffer(
22000 editor: Entity<Editor>,
22001 project: &Entity<Project>,
22002 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22003 buffer: Entity<MultiBuffer>,
22004 cx: &mut App,
22005) -> Task<()> {
22006 let mut tasks = Vec::new();
22007 project.update(cx, |project, cx| {
22008 for buffer in buffers {
22009 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22010 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22011 }
22012 }
22013 });
22014 cx.spawn(async move |cx| {
22015 let diffs = future::join_all(tasks).await;
22016 if editor
22017 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22018 .unwrap_or(false)
22019 {
22020 return;
22021 }
22022
22023 buffer
22024 .update(cx, |buffer, cx| {
22025 for diff in diffs.into_iter().flatten() {
22026 buffer.add_diff(diff, cx);
22027 }
22028 })
22029 .ok();
22030 })
22031}
22032
22033fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22034 let tab_size = tab_size.get() as usize;
22035 let mut width = offset;
22036
22037 for ch in text.chars() {
22038 width += if ch == '\t' {
22039 tab_size - (width % tab_size)
22040 } else {
22041 1
22042 };
22043 }
22044
22045 width - offset
22046}
22047
22048#[cfg(test)]
22049mod tests {
22050 use super::*;
22051
22052 #[test]
22053 fn test_string_size_with_expanded_tabs() {
22054 let nz = |val| NonZeroU32::new(val).unwrap();
22055 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22056 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22057 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22058 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22059 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22060 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22061 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22062 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22063 }
22064}
22065
22066/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22067struct WordBreakingTokenizer<'a> {
22068 input: &'a str,
22069}
22070
22071impl<'a> WordBreakingTokenizer<'a> {
22072 fn new(input: &'a str) -> Self {
22073 Self { input }
22074 }
22075}
22076
22077fn is_char_ideographic(ch: char) -> bool {
22078 use unicode_script::Script::*;
22079 use unicode_script::UnicodeScript;
22080 matches!(ch.script(), Han | Tangut | Yi)
22081}
22082
22083fn is_grapheme_ideographic(text: &str) -> bool {
22084 text.chars().any(is_char_ideographic)
22085}
22086
22087fn is_grapheme_whitespace(text: &str) -> bool {
22088 text.chars().any(|x| x.is_whitespace())
22089}
22090
22091fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22092 text.chars()
22093 .next()
22094 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22095}
22096
22097#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22098enum WordBreakToken<'a> {
22099 Word { token: &'a str, grapheme_len: usize },
22100 InlineWhitespace { token: &'a str, grapheme_len: usize },
22101 Newline,
22102}
22103
22104impl<'a> Iterator for WordBreakingTokenizer<'a> {
22105 /// Yields a span, the count of graphemes in the token, and whether it was
22106 /// whitespace. Note that it also breaks at word boundaries.
22107 type Item = WordBreakToken<'a>;
22108
22109 fn next(&mut self) -> Option<Self::Item> {
22110 use unicode_segmentation::UnicodeSegmentation;
22111 if self.input.is_empty() {
22112 return None;
22113 }
22114
22115 let mut iter = self.input.graphemes(true).peekable();
22116 let mut offset = 0;
22117 let mut grapheme_len = 0;
22118 if let Some(first_grapheme) = iter.next() {
22119 let is_newline = first_grapheme == "\n";
22120 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22121 offset += first_grapheme.len();
22122 grapheme_len += 1;
22123 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22124 if let Some(grapheme) = iter.peek().copied()
22125 && should_stay_with_preceding_ideograph(grapheme)
22126 {
22127 offset += grapheme.len();
22128 grapheme_len += 1;
22129 }
22130 } else {
22131 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22132 let mut next_word_bound = words.peek().copied();
22133 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22134 next_word_bound = words.next();
22135 }
22136 while let Some(grapheme) = iter.peek().copied() {
22137 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22138 break;
22139 };
22140 if is_grapheme_whitespace(grapheme) != is_whitespace
22141 || (grapheme == "\n") != is_newline
22142 {
22143 break;
22144 };
22145 offset += grapheme.len();
22146 grapheme_len += 1;
22147 iter.next();
22148 }
22149 }
22150 let token = &self.input[..offset];
22151 self.input = &self.input[offset..];
22152 if token == "\n" {
22153 Some(WordBreakToken::Newline)
22154 } else if is_whitespace {
22155 Some(WordBreakToken::InlineWhitespace {
22156 token,
22157 grapheme_len,
22158 })
22159 } else {
22160 Some(WordBreakToken::Word {
22161 token,
22162 grapheme_len,
22163 })
22164 }
22165 } else {
22166 None
22167 }
22168 }
22169}
22170
22171#[test]
22172fn test_word_breaking_tokenizer() {
22173 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22174 ("", &[]),
22175 (" ", &[whitespace(" ", 2)]),
22176 ("Ʒ", &[word("Ʒ", 1)]),
22177 ("Ǽ", &[word("Ǽ", 1)]),
22178 ("⋑", &[word("⋑", 1)]),
22179 ("⋑⋑", &[word("⋑⋑", 2)]),
22180 (
22181 "原理,进而",
22182 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22183 ),
22184 (
22185 "hello world",
22186 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22187 ),
22188 (
22189 "hello, world",
22190 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22191 ),
22192 (
22193 " hello world",
22194 &[
22195 whitespace(" ", 2),
22196 word("hello", 5),
22197 whitespace(" ", 1),
22198 word("world", 5),
22199 ],
22200 ),
22201 (
22202 "这是什么 \n 钢笔",
22203 &[
22204 word("这", 1),
22205 word("是", 1),
22206 word("什", 1),
22207 word("么", 1),
22208 whitespace(" ", 1),
22209 newline(),
22210 whitespace(" ", 1),
22211 word("钢", 1),
22212 word("笔", 1),
22213 ],
22214 ),
22215 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22216 ];
22217
22218 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22219 WordBreakToken::Word {
22220 token,
22221 grapheme_len,
22222 }
22223 }
22224
22225 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22226 WordBreakToken::InlineWhitespace {
22227 token,
22228 grapheme_len,
22229 }
22230 }
22231
22232 fn newline() -> WordBreakToken<'static> {
22233 WordBreakToken::Newline
22234 }
22235
22236 for (input, result) in tests {
22237 assert_eq!(
22238 WordBreakingTokenizer::new(input)
22239 .collect::<Vec<_>>()
22240 .as_slice(),
22241 *result,
22242 );
22243 }
22244}
22245
22246fn wrap_with_prefix(
22247 first_line_prefix: String,
22248 subsequent_lines_prefix: String,
22249 unwrapped_text: String,
22250 wrap_column: usize,
22251 tab_size: NonZeroU32,
22252 preserve_existing_whitespace: bool,
22253) -> String {
22254 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22255 let subsequent_lines_prefix_len =
22256 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22257 let mut wrapped_text = String::new();
22258 let mut current_line = first_line_prefix;
22259 let mut is_first_line = true;
22260
22261 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22262 let mut current_line_len = first_line_prefix_len;
22263 let mut in_whitespace = false;
22264 for token in tokenizer {
22265 let have_preceding_whitespace = in_whitespace;
22266 match token {
22267 WordBreakToken::Word {
22268 token,
22269 grapheme_len,
22270 } => {
22271 in_whitespace = false;
22272 let current_prefix_len = if is_first_line {
22273 first_line_prefix_len
22274 } else {
22275 subsequent_lines_prefix_len
22276 };
22277 if current_line_len + grapheme_len > wrap_column
22278 && current_line_len != current_prefix_len
22279 {
22280 wrapped_text.push_str(current_line.trim_end());
22281 wrapped_text.push('\n');
22282 is_first_line = false;
22283 current_line = subsequent_lines_prefix.clone();
22284 current_line_len = subsequent_lines_prefix_len;
22285 }
22286 current_line.push_str(token);
22287 current_line_len += grapheme_len;
22288 }
22289 WordBreakToken::InlineWhitespace {
22290 mut token,
22291 mut grapheme_len,
22292 } => {
22293 in_whitespace = true;
22294 if have_preceding_whitespace && !preserve_existing_whitespace {
22295 continue;
22296 }
22297 if !preserve_existing_whitespace {
22298 token = " ";
22299 grapheme_len = 1;
22300 }
22301 let current_prefix_len = if is_first_line {
22302 first_line_prefix_len
22303 } else {
22304 subsequent_lines_prefix_len
22305 };
22306 if current_line_len + grapheme_len > wrap_column {
22307 wrapped_text.push_str(current_line.trim_end());
22308 wrapped_text.push('\n');
22309 is_first_line = false;
22310 current_line = subsequent_lines_prefix.clone();
22311 current_line_len = subsequent_lines_prefix_len;
22312 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22313 current_line.push_str(token);
22314 current_line_len += grapheme_len;
22315 }
22316 }
22317 WordBreakToken::Newline => {
22318 in_whitespace = true;
22319 let current_prefix_len = if is_first_line {
22320 first_line_prefix_len
22321 } else {
22322 subsequent_lines_prefix_len
22323 };
22324 if preserve_existing_whitespace {
22325 wrapped_text.push_str(current_line.trim_end());
22326 wrapped_text.push('\n');
22327 is_first_line = false;
22328 current_line = subsequent_lines_prefix.clone();
22329 current_line_len = subsequent_lines_prefix_len;
22330 } else if have_preceding_whitespace {
22331 continue;
22332 } else if current_line_len + 1 > wrap_column
22333 && current_line_len != current_prefix_len
22334 {
22335 wrapped_text.push_str(current_line.trim_end());
22336 wrapped_text.push('\n');
22337 is_first_line = false;
22338 current_line = subsequent_lines_prefix.clone();
22339 current_line_len = subsequent_lines_prefix_len;
22340 } else if current_line_len != current_prefix_len {
22341 current_line.push(' ');
22342 current_line_len += 1;
22343 }
22344 }
22345 }
22346 }
22347
22348 if !current_line.is_empty() {
22349 wrapped_text.push_str(¤t_line);
22350 }
22351 wrapped_text
22352}
22353
22354#[test]
22355fn test_wrap_with_prefix() {
22356 assert_eq!(
22357 wrap_with_prefix(
22358 "# ".to_string(),
22359 "# ".to_string(),
22360 "abcdefg".to_string(),
22361 4,
22362 NonZeroU32::new(4).unwrap(),
22363 false,
22364 ),
22365 "# abcdefg"
22366 );
22367 assert_eq!(
22368 wrap_with_prefix(
22369 "".to_string(),
22370 "".to_string(),
22371 "\thello world".to_string(),
22372 8,
22373 NonZeroU32::new(4).unwrap(),
22374 false,
22375 ),
22376 "hello\nworld"
22377 );
22378 assert_eq!(
22379 wrap_with_prefix(
22380 "// ".to_string(),
22381 "// ".to_string(),
22382 "xx \nyy zz aa bb cc".to_string(),
22383 12,
22384 NonZeroU32::new(4).unwrap(),
22385 false,
22386 ),
22387 "// xx yy zz\n// aa bb cc"
22388 );
22389 assert_eq!(
22390 wrap_with_prefix(
22391 String::new(),
22392 String::new(),
22393 "这是什么 \n 钢笔".to_string(),
22394 3,
22395 NonZeroU32::new(4).unwrap(),
22396 false,
22397 ),
22398 "这是什\n么 钢\n笔"
22399 );
22400}
22401
22402pub trait CollaborationHub {
22403 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22404 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22405 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22406}
22407
22408impl CollaborationHub for Entity<Project> {
22409 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22410 self.read(cx).collaborators()
22411 }
22412
22413 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22414 self.read(cx).user_store().read(cx).participant_indices()
22415 }
22416
22417 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22418 let this = self.read(cx);
22419 let user_ids = this.collaborators().values().map(|c| c.user_id);
22420 this.user_store().read(cx).participant_names(user_ids, cx)
22421 }
22422}
22423
22424pub trait SemanticsProvider {
22425 fn hover(
22426 &self,
22427 buffer: &Entity<Buffer>,
22428 position: text::Anchor,
22429 cx: &mut App,
22430 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22431
22432 fn inline_values(
22433 &self,
22434 buffer_handle: Entity<Buffer>,
22435 range: Range<text::Anchor>,
22436 cx: &mut App,
22437 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22438
22439 fn inlay_hints(
22440 &self,
22441 buffer_handle: Entity<Buffer>,
22442 range: Range<text::Anchor>,
22443 cx: &mut App,
22444 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22445
22446 fn resolve_inlay_hint(
22447 &self,
22448 hint: InlayHint,
22449 buffer_handle: Entity<Buffer>,
22450 server_id: LanguageServerId,
22451 cx: &mut App,
22452 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22453
22454 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22455
22456 fn document_highlights(
22457 &self,
22458 buffer: &Entity<Buffer>,
22459 position: text::Anchor,
22460 cx: &mut App,
22461 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22462
22463 fn definitions(
22464 &self,
22465 buffer: &Entity<Buffer>,
22466 position: text::Anchor,
22467 kind: GotoDefinitionKind,
22468 cx: &mut App,
22469 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22470
22471 fn range_for_rename(
22472 &self,
22473 buffer: &Entity<Buffer>,
22474 position: text::Anchor,
22475 cx: &mut App,
22476 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22477
22478 fn perform_rename(
22479 &self,
22480 buffer: &Entity<Buffer>,
22481 position: text::Anchor,
22482 new_name: String,
22483 cx: &mut App,
22484 ) -> Option<Task<Result<ProjectTransaction>>>;
22485}
22486
22487pub trait CompletionProvider {
22488 fn completions(
22489 &self,
22490 excerpt_id: ExcerptId,
22491 buffer: &Entity<Buffer>,
22492 buffer_position: text::Anchor,
22493 trigger: CompletionContext,
22494 window: &mut Window,
22495 cx: &mut Context<Editor>,
22496 ) -> Task<Result<Vec<CompletionResponse>>>;
22497
22498 fn resolve_completions(
22499 &self,
22500 _buffer: Entity<Buffer>,
22501 _completion_indices: Vec<usize>,
22502 _completions: Rc<RefCell<Box<[Completion]>>>,
22503 _cx: &mut Context<Editor>,
22504 ) -> Task<Result<bool>> {
22505 Task::ready(Ok(false))
22506 }
22507
22508 fn apply_additional_edits_for_completion(
22509 &self,
22510 _buffer: Entity<Buffer>,
22511 _completions: Rc<RefCell<Box<[Completion]>>>,
22512 _completion_index: usize,
22513 _push_to_history: bool,
22514 _cx: &mut Context<Editor>,
22515 ) -> Task<Result<Option<language::Transaction>>> {
22516 Task::ready(Ok(None))
22517 }
22518
22519 fn is_completion_trigger(
22520 &self,
22521 buffer: &Entity<Buffer>,
22522 position: language::Anchor,
22523 text: &str,
22524 trigger_in_words: bool,
22525 menu_is_open: bool,
22526 cx: &mut Context<Editor>,
22527 ) -> bool;
22528
22529 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22530
22531 fn sort_completions(&self) -> bool {
22532 true
22533 }
22534
22535 fn filter_completions(&self) -> bool {
22536 true
22537 }
22538}
22539
22540pub trait CodeActionProvider {
22541 fn id(&self) -> Arc<str>;
22542
22543 fn code_actions(
22544 &self,
22545 buffer: &Entity<Buffer>,
22546 range: Range<text::Anchor>,
22547 window: &mut Window,
22548 cx: &mut App,
22549 ) -> Task<Result<Vec<CodeAction>>>;
22550
22551 fn apply_code_action(
22552 &self,
22553 buffer_handle: Entity<Buffer>,
22554 action: CodeAction,
22555 excerpt_id: ExcerptId,
22556 push_to_history: bool,
22557 window: &mut Window,
22558 cx: &mut App,
22559 ) -> Task<Result<ProjectTransaction>>;
22560}
22561
22562impl CodeActionProvider for Entity<Project> {
22563 fn id(&self) -> Arc<str> {
22564 "project".into()
22565 }
22566
22567 fn code_actions(
22568 &self,
22569 buffer: &Entity<Buffer>,
22570 range: Range<text::Anchor>,
22571 _window: &mut Window,
22572 cx: &mut App,
22573 ) -> Task<Result<Vec<CodeAction>>> {
22574 self.update(cx, |project, cx| {
22575 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22576 let code_actions = project.code_actions(buffer, range, None, cx);
22577 cx.background_spawn(async move {
22578 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22579 Ok(code_lens_actions
22580 .context("code lens fetch")?
22581 .into_iter()
22582 .flatten()
22583 .chain(
22584 code_actions
22585 .context("code action fetch")?
22586 .into_iter()
22587 .flatten(),
22588 )
22589 .collect())
22590 })
22591 })
22592 }
22593
22594 fn apply_code_action(
22595 &self,
22596 buffer_handle: Entity<Buffer>,
22597 action: CodeAction,
22598 _excerpt_id: ExcerptId,
22599 push_to_history: bool,
22600 _window: &mut Window,
22601 cx: &mut App,
22602 ) -> Task<Result<ProjectTransaction>> {
22603 self.update(cx, |project, cx| {
22604 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22605 })
22606 }
22607}
22608
22609fn snippet_completions(
22610 project: &Project,
22611 buffer: &Entity<Buffer>,
22612 buffer_position: text::Anchor,
22613 cx: &mut App,
22614) -> Task<Result<CompletionResponse>> {
22615 let languages = buffer.read(cx).languages_at(buffer_position);
22616 let snippet_store = project.snippets().read(cx);
22617
22618 let scopes: Vec<_> = languages
22619 .iter()
22620 .filter_map(|language| {
22621 let language_name = language.lsp_id();
22622 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22623
22624 if snippets.is_empty() {
22625 None
22626 } else {
22627 Some((language.default_scope(), snippets))
22628 }
22629 })
22630 .collect();
22631
22632 if scopes.is_empty() {
22633 return Task::ready(Ok(CompletionResponse {
22634 completions: vec![],
22635 display_options: CompletionDisplayOptions::default(),
22636 is_incomplete: false,
22637 }));
22638 }
22639
22640 let snapshot = buffer.read(cx).text_snapshot();
22641 let chars: String = snapshot
22642 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22643 .collect();
22644 let executor = cx.background_executor().clone();
22645
22646 cx.background_spawn(async move {
22647 let mut is_incomplete = false;
22648 let mut completions: Vec<Completion> = Vec::new();
22649 for (scope, snippets) in scopes.into_iter() {
22650 let classifier =
22651 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22652 let mut last_word = chars
22653 .chars()
22654 .take_while(|c| classifier.is_word(*c))
22655 .collect::<String>();
22656 last_word = last_word.chars().rev().collect();
22657
22658 if last_word.is_empty() {
22659 return Ok(CompletionResponse {
22660 completions: vec![],
22661 display_options: CompletionDisplayOptions::default(),
22662 is_incomplete: true,
22663 });
22664 }
22665
22666 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22667 let to_lsp = |point: &text::Anchor| {
22668 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22669 point_to_lsp(end)
22670 };
22671 let lsp_end = to_lsp(&buffer_position);
22672
22673 let candidates = snippets
22674 .iter()
22675 .enumerate()
22676 .flat_map(|(ix, snippet)| {
22677 snippet
22678 .prefix
22679 .iter()
22680 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22681 })
22682 .collect::<Vec<StringMatchCandidate>>();
22683
22684 const MAX_RESULTS: usize = 100;
22685 let mut matches = fuzzy::match_strings(
22686 &candidates,
22687 &last_word,
22688 last_word.chars().any(|c| c.is_uppercase()),
22689 true,
22690 MAX_RESULTS,
22691 &Default::default(),
22692 executor.clone(),
22693 )
22694 .await;
22695
22696 if matches.len() >= MAX_RESULTS {
22697 is_incomplete = true;
22698 }
22699
22700 // Remove all candidates where the query's start does not match the start of any word in the candidate
22701 if let Some(query_start) = last_word.chars().next() {
22702 matches.retain(|string_match| {
22703 split_words(&string_match.string).any(|word| {
22704 // Check that the first codepoint of the word as lowercase matches the first
22705 // codepoint of the query as lowercase
22706 word.chars()
22707 .flat_map(|codepoint| codepoint.to_lowercase())
22708 .zip(query_start.to_lowercase())
22709 .all(|(word_cp, query_cp)| word_cp == query_cp)
22710 })
22711 });
22712 }
22713
22714 let matched_strings = matches
22715 .into_iter()
22716 .map(|m| m.string)
22717 .collect::<HashSet<_>>();
22718
22719 completions.extend(snippets.iter().filter_map(|snippet| {
22720 let matching_prefix = snippet
22721 .prefix
22722 .iter()
22723 .find(|prefix| matched_strings.contains(*prefix))?;
22724 let start = as_offset - last_word.len();
22725 let start = snapshot.anchor_before(start);
22726 let range = start..buffer_position;
22727 let lsp_start = to_lsp(&start);
22728 let lsp_range = lsp::Range {
22729 start: lsp_start,
22730 end: lsp_end,
22731 };
22732 Some(Completion {
22733 replace_range: range,
22734 new_text: snippet.body.clone(),
22735 source: CompletionSource::Lsp {
22736 insert_range: None,
22737 server_id: LanguageServerId(usize::MAX),
22738 resolved: true,
22739 lsp_completion: Box::new(lsp::CompletionItem {
22740 label: snippet.prefix.first().unwrap().clone(),
22741 kind: Some(CompletionItemKind::SNIPPET),
22742 label_details: snippet.description.as_ref().map(|description| {
22743 lsp::CompletionItemLabelDetails {
22744 detail: Some(description.clone()),
22745 description: None,
22746 }
22747 }),
22748 insert_text_format: Some(InsertTextFormat::SNIPPET),
22749 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22750 lsp::InsertReplaceEdit {
22751 new_text: snippet.body.clone(),
22752 insert: lsp_range,
22753 replace: lsp_range,
22754 },
22755 )),
22756 filter_text: Some(snippet.body.clone()),
22757 sort_text: Some(char::MAX.to_string()),
22758 ..lsp::CompletionItem::default()
22759 }),
22760 lsp_defaults: None,
22761 },
22762 label: CodeLabel {
22763 text: matching_prefix.clone(),
22764 runs: Vec::new(),
22765 filter_range: 0..matching_prefix.len(),
22766 },
22767 icon_path: None,
22768 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22769 single_line: snippet.name.clone().into(),
22770 plain_text: snippet
22771 .description
22772 .clone()
22773 .map(|description| description.into()),
22774 }),
22775 insert_text_mode: None,
22776 confirm: None,
22777 })
22778 }))
22779 }
22780
22781 Ok(CompletionResponse {
22782 completions,
22783 display_options: CompletionDisplayOptions::default(),
22784 is_incomplete,
22785 })
22786 })
22787}
22788
22789impl CompletionProvider for Entity<Project> {
22790 fn completions(
22791 &self,
22792 _excerpt_id: ExcerptId,
22793 buffer: &Entity<Buffer>,
22794 buffer_position: text::Anchor,
22795 options: CompletionContext,
22796 _window: &mut Window,
22797 cx: &mut Context<Editor>,
22798 ) -> Task<Result<Vec<CompletionResponse>>> {
22799 self.update(cx, |project, cx| {
22800 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22801 let project_completions = project.completions(buffer, buffer_position, options, cx);
22802 cx.background_spawn(async move {
22803 let mut responses = project_completions.await?;
22804 let snippets = snippets.await?;
22805 if !snippets.completions.is_empty() {
22806 responses.push(snippets);
22807 }
22808 Ok(responses)
22809 })
22810 })
22811 }
22812
22813 fn resolve_completions(
22814 &self,
22815 buffer: Entity<Buffer>,
22816 completion_indices: Vec<usize>,
22817 completions: Rc<RefCell<Box<[Completion]>>>,
22818 cx: &mut Context<Editor>,
22819 ) -> Task<Result<bool>> {
22820 self.update(cx, |project, cx| {
22821 project.lsp_store().update(cx, |lsp_store, cx| {
22822 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22823 })
22824 })
22825 }
22826
22827 fn apply_additional_edits_for_completion(
22828 &self,
22829 buffer: Entity<Buffer>,
22830 completions: Rc<RefCell<Box<[Completion]>>>,
22831 completion_index: usize,
22832 push_to_history: bool,
22833 cx: &mut Context<Editor>,
22834 ) -> Task<Result<Option<language::Transaction>>> {
22835 self.update(cx, |project, cx| {
22836 project.lsp_store().update(cx, |lsp_store, cx| {
22837 lsp_store.apply_additional_edits_for_completion(
22838 buffer,
22839 completions,
22840 completion_index,
22841 push_to_history,
22842 cx,
22843 )
22844 })
22845 })
22846 }
22847
22848 fn is_completion_trigger(
22849 &self,
22850 buffer: &Entity<Buffer>,
22851 position: language::Anchor,
22852 text: &str,
22853 trigger_in_words: bool,
22854 menu_is_open: bool,
22855 cx: &mut Context<Editor>,
22856 ) -> bool {
22857 let mut chars = text.chars();
22858 let char = if let Some(char) = chars.next() {
22859 char
22860 } else {
22861 return false;
22862 };
22863 if chars.next().is_some() {
22864 return false;
22865 }
22866
22867 let buffer = buffer.read(cx);
22868 let snapshot = buffer.snapshot();
22869 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22870 return false;
22871 }
22872 let classifier = snapshot
22873 .char_classifier_at(position)
22874 .scope_context(Some(CharScopeContext::Completion));
22875 if trigger_in_words && classifier.is_word(char) {
22876 return true;
22877 }
22878
22879 buffer.completion_triggers().contains(text)
22880 }
22881}
22882
22883impl SemanticsProvider for Entity<Project> {
22884 fn hover(
22885 &self,
22886 buffer: &Entity<Buffer>,
22887 position: text::Anchor,
22888 cx: &mut App,
22889 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22890 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22891 }
22892
22893 fn document_highlights(
22894 &self,
22895 buffer: &Entity<Buffer>,
22896 position: text::Anchor,
22897 cx: &mut App,
22898 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22899 Some(self.update(cx, |project, cx| {
22900 project.document_highlights(buffer, position, cx)
22901 }))
22902 }
22903
22904 fn definitions(
22905 &self,
22906 buffer: &Entity<Buffer>,
22907 position: text::Anchor,
22908 kind: GotoDefinitionKind,
22909 cx: &mut App,
22910 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22911 Some(self.update(cx, |project, cx| match kind {
22912 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22913 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22914 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22915 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22916 }))
22917 }
22918
22919 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22920 self.update(cx, |project, cx| {
22921 if project
22922 .active_debug_session(cx)
22923 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22924 {
22925 return true;
22926 }
22927
22928 buffer.update(cx, |buffer, cx| {
22929 project.any_language_server_supports_inlay_hints(buffer, cx)
22930 })
22931 })
22932 }
22933
22934 fn inline_values(
22935 &self,
22936 buffer_handle: Entity<Buffer>,
22937 range: Range<text::Anchor>,
22938 cx: &mut App,
22939 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22940 self.update(cx, |project, cx| {
22941 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22942
22943 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22944 })
22945 }
22946
22947 fn inlay_hints(
22948 &self,
22949 buffer_handle: Entity<Buffer>,
22950 range: Range<text::Anchor>,
22951 cx: &mut App,
22952 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22953 Some(self.update(cx, |project, cx| {
22954 project.inlay_hints(buffer_handle, range, cx)
22955 }))
22956 }
22957
22958 fn resolve_inlay_hint(
22959 &self,
22960 hint: InlayHint,
22961 buffer_handle: Entity<Buffer>,
22962 server_id: LanguageServerId,
22963 cx: &mut App,
22964 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22965 Some(self.update(cx, |project, cx| {
22966 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22967 }))
22968 }
22969
22970 fn range_for_rename(
22971 &self,
22972 buffer: &Entity<Buffer>,
22973 position: text::Anchor,
22974 cx: &mut App,
22975 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22976 Some(self.update(cx, |project, cx| {
22977 let buffer = buffer.clone();
22978 let task = project.prepare_rename(buffer.clone(), position, cx);
22979 cx.spawn(async move |_, cx| {
22980 Ok(match task.await? {
22981 PrepareRenameResponse::Success(range) => Some(range),
22982 PrepareRenameResponse::InvalidPosition => None,
22983 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22984 // Fallback on using TreeSitter info to determine identifier range
22985 buffer.read_with(cx, |buffer, _| {
22986 let snapshot = buffer.snapshot();
22987 let (range, kind) = snapshot.surrounding_word(position, None);
22988 if kind != Some(CharKind::Word) {
22989 return None;
22990 }
22991 Some(
22992 snapshot.anchor_before(range.start)
22993 ..snapshot.anchor_after(range.end),
22994 )
22995 })?
22996 }
22997 })
22998 })
22999 }))
23000 }
23001
23002 fn perform_rename(
23003 &self,
23004 buffer: &Entity<Buffer>,
23005 position: text::Anchor,
23006 new_name: String,
23007 cx: &mut App,
23008 ) -> Option<Task<Result<ProjectTransaction>>> {
23009 Some(self.update(cx, |project, cx| {
23010 project.perform_rename(buffer.clone(), position, new_name, cx)
23011 }))
23012 }
23013}
23014
23015fn inlay_hint_settings(
23016 location: Anchor,
23017 snapshot: &MultiBufferSnapshot,
23018 cx: &mut Context<Editor>,
23019) -> InlayHintSettings {
23020 let file = snapshot.file_at(location);
23021 let language = snapshot.language_at(location).map(|l| l.name());
23022 language_settings(language, file, cx).inlay_hints
23023}
23024
23025fn consume_contiguous_rows(
23026 contiguous_row_selections: &mut Vec<Selection<Point>>,
23027 selection: &Selection<Point>,
23028 display_map: &DisplaySnapshot,
23029 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23030) -> (MultiBufferRow, MultiBufferRow) {
23031 contiguous_row_selections.push(selection.clone());
23032 let start_row = starting_row(selection, display_map);
23033 let mut end_row = ending_row(selection, display_map);
23034
23035 while let Some(next_selection) = selections.peek() {
23036 if next_selection.start.row <= end_row.0 {
23037 end_row = ending_row(next_selection, display_map);
23038 contiguous_row_selections.push(selections.next().unwrap().clone());
23039 } else {
23040 break;
23041 }
23042 }
23043 (start_row, end_row)
23044}
23045
23046fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23047 if selection.start.column > 0 {
23048 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23049 } else {
23050 MultiBufferRow(selection.start.row)
23051 }
23052}
23053
23054fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23055 if next_selection.end.column > 0 || next_selection.is_empty() {
23056 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23057 } else {
23058 MultiBufferRow(next_selection.end.row)
23059 }
23060}
23061
23062impl EditorSnapshot {
23063 pub fn remote_selections_in_range<'a>(
23064 &'a self,
23065 range: &'a Range<Anchor>,
23066 collaboration_hub: &dyn CollaborationHub,
23067 cx: &'a App,
23068 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23069 let participant_names = collaboration_hub.user_names(cx);
23070 let participant_indices = collaboration_hub.user_participant_indices(cx);
23071 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23072 let collaborators_by_replica_id = collaborators_by_peer_id
23073 .values()
23074 .map(|collaborator| (collaborator.replica_id, collaborator))
23075 .collect::<HashMap<_, _>>();
23076 self.buffer_snapshot
23077 .selections_in_range(range, false)
23078 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23079 if replica_id == AGENT_REPLICA_ID {
23080 Some(RemoteSelection {
23081 replica_id,
23082 selection,
23083 cursor_shape,
23084 line_mode,
23085 collaborator_id: CollaboratorId::Agent,
23086 user_name: Some("Agent".into()),
23087 color: cx.theme().players().agent(),
23088 })
23089 } else {
23090 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23091 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23092 let user_name = participant_names.get(&collaborator.user_id).cloned();
23093 Some(RemoteSelection {
23094 replica_id,
23095 selection,
23096 cursor_shape,
23097 line_mode,
23098 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23099 user_name,
23100 color: if let Some(index) = participant_index {
23101 cx.theme().players().color_for_participant(index.0)
23102 } else {
23103 cx.theme().players().absent()
23104 },
23105 })
23106 }
23107 })
23108 }
23109
23110 pub fn hunks_for_ranges(
23111 &self,
23112 ranges: impl IntoIterator<Item = Range<Point>>,
23113 ) -> Vec<MultiBufferDiffHunk> {
23114 let mut hunks = Vec::new();
23115 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23116 HashMap::default();
23117 for query_range in ranges {
23118 let query_rows =
23119 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23120 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23121 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23122 ) {
23123 // Include deleted hunks that are adjacent to the query range, because
23124 // otherwise they would be missed.
23125 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23126 if hunk.status().is_deleted() {
23127 intersects_range |= hunk.row_range.start == query_rows.end;
23128 intersects_range |= hunk.row_range.end == query_rows.start;
23129 }
23130 if intersects_range {
23131 if !processed_buffer_rows
23132 .entry(hunk.buffer_id)
23133 .or_default()
23134 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23135 {
23136 continue;
23137 }
23138 hunks.push(hunk);
23139 }
23140 }
23141 }
23142
23143 hunks
23144 }
23145
23146 fn display_diff_hunks_for_rows<'a>(
23147 &'a self,
23148 display_rows: Range<DisplayRow>,
23149 folded_buffers: &'a HashSet<BufferId>,
23150 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23151 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23152 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23153
23154 self.buffer_snapshot
23155 .diff_hunks_in_range(buffer_start..buffer_end)
23156 .filter_map(|hunk| {
23157 if folded_buffers.contains(&hunk.buffer_id) {
23158 return None;
23159 }
23160
23161 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23162 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23163
23164 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23165 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23166
23167 let display_hunk = if hunk_display_start.column() != 0 {
23168 DisplayDiffHunk::Folded {
23169 display_row: hunk_display_start.row(),
23170 }
23171 } else {
23172 let mut end_row = hunk_display_end.row();
23173 if hunk_display_end.column() > 0 {
23174 end_row.0 += 1;
23175 }
23176 let is_created_file = hunk.is_created_file();
23177 DisplayDiffHunk::Unfolded {
23178 status: hunk.status(),
23179 diff_base_byte_range: hunk.diff_base_byte_range,
23180 display_row_range: hunk_display_start.row()..end_row,
23181 multi_buffer_range: Anchor::range_in_buffer(
23182 hunk.excerpt_id,
23183 hunk.buffer_id,
23184 hunk.buffer_range,
23185 ),
23186 is_created_file,
23187 }
23188 };
23189
23190 Some(display_hunk)
23191 })
23192 }
23193
23194 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23195 self.display_snapshot.buffer_snapshot.language_at(position)
23196 }
23197
23198 pub fn is_focused(&self) -> bool {
23199 self.is_focused
23200 }
23201
23202 pub fn placeholder_text(&self) -> Option<String> {
23203 self.placeholder_display_snapshot
23204 .as_ref()
23205 .map(|display_map| display_map.text())
23206 }
23207
23208 pub fn scroll_position(&self) -> gpui::Point<f32> {
23209 self.scroll_anchor.scroll_position(&self.display_snapshot)
23210 }
23211
23212 fn gutter_dimensions(
23213 &self,
23214 font_id: FontId,
23215 font_size: Pixels,
23216 max_line_number_width: Pixels,
23217 cx: &App,
23218 ) -> Option<GutterDimensions> {
23219 if !self.show_gutter {
23220 return None;
23221 }
23222
23223 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23224 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23225
23226 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23227 matches!(
23228 ProjectSettings::get_global(cx).git.git_gutter,
23229 GitGutterSetting::TrackedFiles
23230 )
23231 });
23232 let gutter_settings = EditorSettings::get_global(cx).gutter;
23233 let show_line_numbers = self
23234 .show_line_numbers
23235 .unwrap_or(gutter_settings.line_numbers);
23236 let line_gutter_width = if show_line_numbers {
23237 // Avoid flicker-like gutter resizes when the line number gains another digit by
23238 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23239 let min_width_for_number_on_gutter =
23240 ch_advance * gutter_settings.min_line_number_digits as f32;
23241 max_line_number_width.max(min_width_for_number_on_gutter)
23242 } else {
23243 0.0.into()
23244 };
23245
23246 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23247 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23248
23249 let git_blame_entries_width =
23250 self.git_blame_gutter_max_author_length
23251 .map(|max_author_length| {
23252 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23253 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23254
23255 /// The number of characters to dedicate to gaps and margins.
23256 const SPACING_WIDTH: usize = 4;
23257
23258 let max_char_count = max_author_length.min(renderer.max_author_length())
23259 + ::git::SHORT_SHA_LENGTH
23260 + MAX_RELATIVE_TIMESTAMP.len()
23261 + SPACING_WIDTH;
23262
23263 ch_advance * max_char_count
23264 });
23265
23266 let is_singleton = self.buffer_snapshot.is_singleton();
23267
23268 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23269 left_padding += if !is_singleton {
23270 ch_width * 4.0
23271 } else if show_runnables || show_breakpoints {
23272 ch_width * 3.0
23273 } else if show_git_gutter && show_line_numbers {
23274 ch_width * 2.0
23275 } else if show_git_gutter || show_line_numbers {
23276 ch_width
23277 } else {
23278 px(0.)
23279 };
23280
23281 let shows_folds = is_singleton && gutter_settings.folds;
23282
23283 let right_padding = if shows_folds && show_line_numbers {
23284 ch_width * 4.0
23285 } else if shows_folds || (!is_singleton && show_line_numbers) {
23286 ch_width * 3.0
23287 } else if show_line_numbers {
23288 ch_width
23289 } else {
23290 px(0.)
23291 };
23292
23293 Some(GutterDimensions {
23294 left_padding,
23295 right_padding,
23296 width: line_gutter_width + left_padding + right_padding,
23297 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23298 git_blame_entries_width,
23299 })
23300 }
23301
23302 pub fn render_crease_toggle(
23303 &self,
23304 buffer_row: MultiBufferRow,
23305 row_contains_cursor: bool,
23306 editor: Entity<Editor>,
23307 window: &mut Window,
23308 cx: &mut App,
23309 ) -> Option<AnyElement> {
23310 let folded = self.is_line_folded(buffer_row);
23311 let mut is_foldable = false;
23312
23313 if let Some(crease) = self
23314 .crease_snapshot
23315 .query_row(buffer_row, &self.buffer_snapshot)
23316 {
23317 is_foldable = true;
23318 match crease {
23319 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23320 if let Some(render_toggle) = render_toggle {
23321 let toggle_callback =
23322 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23323 if folded {
23324 editor.update(cx, |editor, cx| {
23325 editor.fold_at(buffer_row, window, cx)
23326 });
23327 } else {
23328 editor.update(cx, |editor, cx| {
23329 editor.unfold_at(buffer_row, window, cx)
23330 });
23331 }
23332 });
23333 return Some((render_toggle)(
23334 buffer_row,
23335 folded,
23336 toggle_callback,
23337 window,
23338 cx,
23339 ));
23340 }
23341 }
23342 }
23343 }
23344
23345 is_foldable |= self.starts_indent(buffer_row);
23346
23347 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23348 Some(
23349 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23350 .toggle_state(folded)
23351 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23352 if folded {
23353 this.unfold_at(buffer_row, window, cx);
23354 } else {
23355 this.fold_at(buffer_row, window, cx);
23356 }
23357 }))
23358 .into_any_element(),
23359 )
23360 } else {
23361 None
23362 }
23363 }
23364
23365 pub fn render_crease_trailer(
23366 &self,
23367 buffer_row: MultiBufferRow,
23368 window: &mut Window,
23369 cx: &mut App,
23370 ) -> Option<AnyElement> {
23371 let folded = self.is_line_folded(buffer_row);
23372 if let Crease::Inline { render_trailer, .. } = self
23373 .crease_snapshot
23374 .query_row(buffer_row, &self.buffer_snapshot)?
23375 {
23376 let render_trailer = render_trailer.as_ref()?;
23377 Some(render_trailer(buffer_row, folded, window, cx))
23378 } else {
23379 None
23380 }
23381 }
23382}
23383
23384impl Deref for EditorSnapshot {
23385 type Target = DisplaySnapshot;
23386
23387 fn deref(&self) -> &Self::Target {
23388 &self.display_snapshot
23389 }
23390}
23391
23392#[derive(Clone, Debug, PartialEq, Eq)]
23393pub enum EditorEvent {
23394 InputIgnored {
23395 text: Arc<str>,
23396 },
23397 InputHandled {
23398 utf16_range_to_replace: Option<Range<isize>>,
23399 text: Arc<str>,
23400 },
23401 ExcerptsAdded {
23402 buffer: Entity<Buffer>,
23403 predecessor: ExcerptId,
23404 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23405 },
23406 ExcerptsRemoved {
23407 ids: Vec<ExcerptId>,
23408 removed_buffer_ids: Vec<BufferId>,
23409 },
23410 BufferFoldToggled {
23411 ids: Vec<ExcerptId>,
23412 folded: bool,
23413 },
23414 ExcerptsEdited {
23415 ids: Vec<ExcerptId>,
23416 },
23417 ExcerptsExpanded {
23418 ids: Vec<ExcerptId>,
23419 },
23420 BufferEdited,
23421 Edited {
23422 transaction_id: clock::Lamport,
23423 },
23424 Reparsed(BufferId),
23425 Focused,
23426 FocusedIn,
23427 Blurred,
23428 DirtyChanged,
23429 Saved,
23430 TitleChanged,
23431 SelectionsChanged {
23432 local: bool,
23433 },
23434 ScrollPositionChanged {
23435 local: bool,
23436 autoscroll: bool,
23437 },
23438 TransactionUndone {
23439 transaction_id: clock::Lamport,
23440 },
23441 TransactionBegun {
23442 transaction_id: clock::Lamport,
23443 },
23444 CursorShapeChanged,
23445 BreadcrumbsChanged,
23446 PushedToNavHistory {
23447 anchor: Anchor,
23448 is_deactivate: bool,
23449 },
23450}
23451
23452impl EventEmitter<EditorEvent> for Editor {}
23453
23454impl Focusable for Editor {
23455 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23456 self.focus_handle.clone()
23457 }
23458}
23459
23460impl Render for Editor {
23461 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23462 let settings = ThemeSettings::get_global(cx);
23463
23464 let mut text_style = match self.mode {
23465 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23466 color: cx.theme().colors().editor_foreground,
23467 font_family: settings.ui_font.family.clone(),
23468 font_features: settings.ui_font.features.clone(),
23469 font_fallbacks: settings.ui_font.fallbacks.clone(),
23470 font_size: rems(0.875).into(),
23471 font_weight: settings.ui_font.weight,
23472 line_height: relative(settings.buffer_line_height.value()),
23473 ..Default::default()
23474 },
23475 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23476 color: cx.theme().colors().editor_foreground,
23477 font_family: settings.buffer_font.family.clone(),
23478 font_features: settings.buffer_font.features.clone(),
23479 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23480 font_size: settings.buffer_font_size(cx).into(),
23481 font_weight: settings.buffer_font.weight,
23482 line_height: relative(settings.buffer_line_height.value()),
23483 ..Default::default()
23484 },
23485 };
23486 if let Some(text_style_refinement) = &self.text_style_refinement {
23487 text_style.refine(text_style_refinement)
23488 }
23489
23490 let background = match self.mode {
23491 EditorMode::SingleLine => cx.theme().system().transparent,
23492 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23493 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23494 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23495 };
23496
23497 EditorElement::new(
23498 &cx.entity(),
23499 EditorStyle {
23500 background,
23501 border: cx.theme().colors().border,
23502 local_player: cx.theme().players().local(),
23503 text: text_style,
23504 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23505 syntax: cx.theme().syntax().clone(),
23506 status: cx.theme().status().clone(),
23507 inlay_hints_style: make_inlay_hints_style(cx),
23508 edit_prediction_styles: make_suggestion_styles(cx),
23509 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23510 show_underlines: self.diagnostics_enabled(),
23511 },
23512 )
23513 }
23514}
23515
23516impl EntityInputHandler for Editor {
23517 fn text_for_range(
23518 &mut self,
23519 range_utf16: Range<usize>,
23520 adjusted_range: &mut Option<Range<usize>>,
23521 _: &mut Window,
23522 cx: &mut Context<Self>,
23523 ) -> Option<String> {
23524 let snapshot = self.buffer.read(cx).read(cx);
23525 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23526 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23527 if (start.0..end.0) != range_utf16 {
23528 adjusted_range.replace(start.0..end.0);
23529 }
23530 Some(snapshot.text_for_range(start..end).collect())
23531 }
23532
23533 fn selected_text_range(
23534 &mut self,
23535 ignore_disabled_input: bool,
23536 _: &mut Window,
23537 cx: &mut Context<Self>,
23538 ) -> Option<UTF16Selection> {
23539 // Prevent the IME menu from appearing when holding down an alphabetic key
23540 // while input is disabled.
23541 if !ignore_disabled_input && !self.input_enabled {
23542 return None;
23543 }
23544
23545 let selection = self.selections.newest::<OffsetUtf16>(cx);
23546 let range = selection.range();
23547
23548 Some(UTF16Selection {
23549 range: range.start.0..range.end.0,
23550 reversed: selection.reversed,
23551 })
23552 }
23553
23554 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23555 let snapshot = self.buffer.read(cx).read(cx);
23556 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23557 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23558 }
23559
23560 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23561 self.clear_highlights::<InputComposition>(cx);
23562 self.ime_transaction.take();
23563 }
23564
23565 fn replace_text_in_range(
23566 &mut self,
23567 range_utf16: Option<Range<usize>>,
23568 text: &str,
23569 window: &mut Window,
23570 cx: &mut Context<Self>,
23571 ) {
23572 if !self.input_enabled {
23573 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23574 return;
23575 }
23576
23577 self.transact(window, cx, |this, window, cx| {
23578 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23579 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23580 Some(this.selection_replacement_ranges(range_utf16, cx))
23581 } else {
23582 this.marked_text_ranges(cx)
23583 };
23584
23585 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23586 let newest_selection_id = this.selections.newest_anchor().id;
23587 this.selections
23588 .all::<OffsetUtf16>(cx)
23589 .iter()
23590 .zip(ranges_to_replace.iter())
23591 .find_map(|(selection, range)| {
23592 if selection.id == newest_selection_id {
23593 Some(
23594 (range.start.0 as isize - selection.head().0 as isize)
23595 ..(range.end.0 as isize - selection.head().0 as isize),
23596 )
23597 } else {
23598 None
23599 }
23600 })
23601 });
23602
23603 cx.emit(EditorEvent::InputHandled {
23604 utf16_range_to_replace: range_to_replace,
23605 text: text.into(),
23606 });
23607
23608 if let Some(new_selected_ranges) = new_selected_ranges {
23609 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23610 selections.select_ranges(new_selected_ranges)
23611 });
23612 this.backspace(&Default::default(), window, cx);
23613 }
23614
23615 this.handle_input(text, window, cx);
23616 });
23617
23618 if let Some(transaction) = self.ime_transaction {
23619 self.buffer.update(cx, |buffer, cx| {
23620 buffer.group_until_transaction(transaction, cx);
23621 });
23622 }
23623
23624 self.unmark_text(window, cx);
23625 }
23626
23627 fn replace_and_mark_text_in_range(
23628 &mut self,
23629 range_utf16: Option<Range<usize>>,
23630 text: &str,
23631 new_selected_range_utf16: Option<Range<usize>>,
23632 window: &mut Window,
23633 cx: &mut Context<Self>,
23634 ) {
23635 if !self.input_enabled {
23636 return;
23637 }
23638
23639 let transaction = self.transact(window, cx, |this, window, cx| {
23640 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23641 let snapshot = this.buffer.read(cx).read(cx);
23642 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23643 for marked_range in &mut marked_ranges {
23644 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23645 marked_range.start.0 += relative_range_utf16.start;
23646 marked_range.start =
23647 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23648 marked_range.end =
23649 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23650 }
23651 }
23652 Some(marked_ranges)
23653 } else if let Some(range_utf16) = range_utf16 {
23654 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23655 Some(this.selection_replacement_ranges(range_utf16, cx))
23656 } else {
23657 None
23658 };
23659
23660 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23661 let newest_selection_id = this.selections.newest_anchor().id;
23662 this.selections
23663 .all::<OffsetUtf16>(cx)
23664 .iter()
23665 .zip(ranges_to_replace.iter())
23666 .find_map(|(selection, range)| {
23667 if selection.id == newest_selection_id {
23668 Some(
23669 (range.start.0 as isize - selection.head().0 as isize)
23670 ..(range.end.0 as isize - selection.head().0 as isize),
23671 )
23672 } else {
23673 None
23674 }
23675 })
23676 });
23677
23678 cx.emit(EditorEvent::InputHandled {
23679 utf16_range_to_replace: range_to_replace,
23680 text: text.into(),
23681 });
23682
23683 if let Some(ranges) = ranges_to_replace {
23684 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23685 s.select_ranges(ranges)
23686 });
23687 }
23688
23689 let marked_ranges = {
23690 let snapshot = this.buffer.read(cx).read(cx);
23691 this.selections
23692 .disjoint_anchors_arc()
23693 .iter()
23694 .map(|selection| {
23695 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23696 })
23697 .collect::<Vec<_>>()
23698 };
23699
23700 if text.is_empty() {
23701 this.unmark_text(window, cx);
23702 } else {
23703 this.highlight_text::<InputComposition>(
23704 marked_ranges.clone(),
23705 HighlightStyle {
23706 underline: Some(UnderlineStyle {
23707 thickness: px(1.),
23708 color: None,
23709 wavy: false,
23710 }),
23711 ..Default::default()
23712 },
23713 cx,
23714 );
23715 }
23716
23717 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23718 let use_autoclose = this.use_autoclose;
23719 let use_auto_surround = this.use_auto_surround;
23720 this.set_use_autoclose(false);
23721 this.set_use_auto_surround(false);
23722 this.handle_input(text, window, cx);
23723 this.set_use_autoclose(use_autoclose);
23724 this.set_use_auto_surround(use_auto_surround);
23725
23726 if let Some(new_selected_range) = new_selected_range_utf16 {
23727 let snapshot = this.buffer.read(cx).read(cx);
23728 let new_selected_ranges = marked_ranges
23729 .into_iter()
23730 .map(|marked_range| {
23731 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23732 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23733 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23734 snapshot.clip_offset_utf16(new_start, Bias::Left)
23735 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23736 })
23737 .collect::<Vec<_>>();
23738
23739 drop(snapshot);
23740 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23741 selections.select_ranges(new_selected_ranges)
23742 });
23743 }
23744 });
23745
23746 self.ime_transaction = self.ime_transaction.or(transaction);
23747 if let Some(transaction) = self.ime_transaction {
23748 self.buffer.update(cx, |buffer, cx| {
23749 buffer.group_until_transaction(transaction, cx);
23750 });
23751 }
23752
23753 if self.text_highlights::<InputComposition>(cx).is_none() {
23754 self.ime_transaction.take();
23755 }
23756 }
23757
23758 fn bounds_for_range(
23759 &mut self,
23760 range_utf16: Range<usize>,
23761 element_bounds: gpui::Bounds<Pixels>,
23762 window: &mut Window,
23763 cx: &mut Context<Self>,
23764 ) -> Option<gpui::Bounds<Pixels>> {
23765 let text_layout_details = self.text_layout_details(window);
23766 let CharacterDimensions {
23767 em_width,
23768 em_advance,
23769 line_height,
23770 } = self.character_dimensions(window);
23771
23772 let snapshot = self.snapshot(window, cx);
23773 let scroll_position = snapshot.scroll_position();
23774 let scroll_left = scroll_position.x * em_advance;
23775
23776 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23777 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23778 + self.gutter_dimensions.full_width();
23779 let y = line_height * (start.row().as_f32() - scroll_position.y);
23780
23781 Some(Bounds {
23782 origin: element_bounds.origin + point(x, y),
23783 size: size(em_width, line_height),
23784 })
23785 }
23786
23787 fn character_index_for_point(
23788 &mut self,
23789 point: gpui::Point<Pixels>,
23790 _window: &mut Window,
23791 _cx: &mut Context<Self>,
23792 ) -> Option<usize> {
23793 let position_map = self.last_position_map.as_ref()?;
23794 if !position_map.text_hitbox.contains(&point) {
23795 return None;
23796 }
23797 let display_point = position_map.point_for_position(point).previous_valid;
23798 let anchor = position_map
23799 .snapshot
23800 .display_point_to_anchor(display_point, Bias::Left);
23801 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23802 Some(utf16_offset.0)
23803 }
23804}
23805
23806trait SelectionExt {
23807 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23808 fn spanned_rows(
23809 &self,
23810 include_end_if_at_line_start: bool,
23811 map: &DisplaySnapshot,
23812 ) -> Range<MultiBufferRow>;
23813}
23814
23815impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23816 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23817 let start = self
23818 .start
23819 .to_point(&map.buffer_snapshot)
23820 .to_display_point(map);
23821 let end = self
23822 .end
23823 .to_point(&map.buffer_snapshot)
23824 .to_display_point(map);
23825 if self.reversed {
23826 end..start
23827 } else {
23828 start..end
23829 }
23830 }
23831
23832 fn spanned_rows(
23833 &self,
23834 include_end_if_at_line_start: bool,
23835 map: &DisplaySnapshot,
23836 ) -> Range<MultiBufferRow> {
23837 let start = self.start.to_point(&map.buffer_snapshot);
23838 let mut end = self.end.to_point(&map.buffer_snapshot);
23839 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23840 end.row -= 1;
23841 }
23842
23843 let buffer_start = map.prev_line_boundary(start).0;
23844 let buffer_end = map.next_line_boundary(end).0;
23845 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23846 }
23847}
23848
23849impl<T: InvalidationRegion> InvalidationStack<T> {
23850 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23851 where
23852 S: Clone + ToOffset,
23853 {
23854 while let Some(region) = self.last() {
23855 let all_selections_inside_invalidation_ranges =
23856 if selections.len() == region.ranges().len() {
23857 selections
23858 .iter()
23859 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23860 .all(|(selection, invalidation_range)| {
23861 let head = selection.head().to_offset(buffer);
23862 invalidation_range.start <= head && invalidation_range.end >= head
23863 })
23864 } else {
23865 false
23866 };
23867
23868 if all_selections_inside_invalidation_ranges {
23869 break;
23870 } else {
23871 self.pop();
23872 }
23873 }
23874 }
23875}
23876
23877impl<T> Default for InvalidationStack<T> {
23878 fn default() -> Self {
23879 Self(Default::default())
23880 }
23881}
23882
23883impl<T> Deref for InvalidationStack<T> {
23884 type Target = Vec<T>;
23885
23886 fn deref(&self) -> &Self::Target {
23887 &self.0
23888 }
23889}
23890
23891impl<T> DerefMut for InvalidationStack<T> {
23892 fn deref_mut(&mut self) -> &mut Self::Target {
23893 &mut self.0
23894 }
23895}
23896
23897impl InvalidationRegion for SnippetState {
23898 fn ranges(&self) -> &[Range<Anchor>] {
23899 &self.ranges[self.active_index]
23900 }
23901}
23902
23903fn edit_prediction_edit_text(
23904 current_snapshot: &BufferSnapshot,
23905 edits: &[(Range<Anchor>, String)],
23906 edit_preview: &EditPreview,
23907 include_deletions: bool,
23908 cx: &App,
23909) -> HighlightedText {
23910 let edits = edits
23911 .iter()
23912 .map(|(anchor, text)| {
23913 (
23914 anchor.start.text_anchor..anchor.end.text_anchor,
23915 text.clone(),
23916 )
23917 })
23918 .collect::<Vec<_>>();
23919
23920 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23921}
23922
23923fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23924 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23925 // Just show the raw edit text with basic styling
23926 let mut text = String::new();
23927 let mut highlights = Vec::new();
23928
23929 let insertion_highlight_style = HighlightStyle {
23930 color: Some(cx.theme().colors().text),
23931 ..Default::default()
23932 };
23933
23934 for (_, edit_text) in edits {
23935 let start_offset = text.len();
23936 text.push_str(edit_text);
23937 let end_offset = text.len();
23938
23939 if start_offset < end_offset {
23940 highlights.push((start_offset..end_offset, insertion_highlight_style));
23941 }
23942 }
23943
23944 HighlightedText {
23945 text: text.into(),
23946 highlights,
23947 }
23948}
23949
23950pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23951 match severity {
23952 lsp::DiagnosticSeverity::ERROR => colors.error,
23953 lsp::DiagnosticSeverity::WARNING => colors.warning,
23954 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23955 lsp::DiagnosticSeverity::HINT => colors.info,
23956 _ => colors.ignored,
23957 }
23958}
23959
23960pub fn styled_runs_for_code_label<'a>(
23961 label: &'a CodeLabel,
23962 syntax_theme: &'a theme::SyntaxTheme,
23963) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23964 let fade_out = HighlightStyle {
23965 fade_out: Some(0.35),
23966 ..Default::default()
23967 };
23968
23969 let mut prev_end = label.filter_range.end;
23970 label
23971 .runs
23972 .iter()
23973 .enumerate()
23974 .flat_map(move |(ix, (range, highlight_id))| {
23975 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23976 style
23977 } else {
23978 return Default::default();
23979 };
23980 let muted_style = style.highlight(fade_out);
23981
23982 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23983 if range.start >= label.filter_range.end {
23984 if range.start > prev_end {
23985 runs.push((prev_end..range.start, fade_out));
23986 }
23987 runs.push((range.clone(), muted_style));
23988 } else if range.end <= label.filter_range.end {
23989 runs.push((range.clone(), style));
23990 } else {
23991 runs.push((range.start..label.filter_range.end, style));
23992 runs.push((label.filter_range.end..range.end, muted_style));
23993 }
23994 prev_end = cmp::max(prev_end, range.end);
23995
23996 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23997 runs.push((prev_end..label.text.len(), fade_out));
23998 }
23999
24000 runs
24001 })
24002}
24003
24004pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24005 let mut prev_index = 0;
24006 let mut prev_codepoint: Option<char> = None;
24007 text.char_indices()
24008 .chain([(text.len(), '\0')])
24009 .filter_map(move |(index, codepoint)| {
24010 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24011 let is_boundary = index == text.len()
24012 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24013 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24014 if is_boundary {
24015 let chunk = &text[prev_index..index];
24016 prev_index = index;
24017 Some(chunk)
24018 } else {
24019 None
24020 }
24021 })
24022}
24023
24024pub trait RangeToAnchorExt: Sized {
24025 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24026
24027 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24028 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
24029 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24030 }
24031}
24032
24033impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24034 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24035 let start_offset = self.start.to_offset(snapshot);
24036 let end_offset = self.end.to_offset(snapshot);
24037 if start_offset == end_offset {
24038 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24039 } else {
24040 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24041 }
24042 }
24043}
24044
24045pub trait RowExt {
24046 fn as_f32(&self) -> f32;
24047
24048 fn next_row(&self) -> Self;
24049
24050 fn previous_row(&self) -> Self;
24051
24052 fn minus(&self, other: Self) -> u32;
24053}
24054
24055impl RowExt for DisplayRow {
24056 fn as_f32(&self) -> f32 {
24057 self.0 as f32
24058 }
24059
24060 fn next_row(&self) -> Self {
24061 Self(self.0 + 1)
24062 }
24063
24064 fn previous_row(&self) -> Self {
24065 Self(self.0.saturating_sub(1))
24066 }
24067
24068 fn minus(&self, other: Self) -> u32 {
24069 self.0 - other.0
24070 }
24071}
24072
24073impl RowExt for MultiBufferRow {
24074 fn as_f32(&self) -> f32 {
24075 self.0 as f32
24076 }
24077
24078 fn next_row(&self) -> Self {
24079 Self(self.0 + 1)
24080 }
24081
24082 fn previous_row(&self) -> Self {
24083 Self(self.0.saturating_sub(1))
24084 }
24085
24086 fn minus(&self, other: Self) -> u32 {
24087 self.0 - other.0
24088 }
24089}
24090
24091trait RowRangeExt {
24092 type Row;
24093
24094 fn len(&self) -> usize;
24095
24096 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24097}
24098
24099impl RowRangeExt for Range<MultiBufferRow> {
24100 type Row = MultiBufferRow;
24101
24102 fn len(&self) -> usize {
24103 (self.end.0 - self.start.0) as usize
24104 }
24105
24106 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24107 (self.start.0..self.end.0).map(MultiBufferRow)
24108 }
24109}
24110
24111impl RowRangeExt for Range<DisplayRow> {
24112 type Row = DisplayRow;
24113
24114 fn len(&self) -> usize {
24115 (self.end.0 - self.start.0) as usize
24116 }
24117
24118 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24119 (self.start.0..self.end.0).map(DisplayRow)
24120 }
24121}
24122
24123/// If select range has more than one line, we
24124/// just point the cursor to range.start.
24125fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24126 if range.start.row == range.end.row {
24127 range
24128 } else {
24129 range.start..range.start
24130 }
24131}
24132pub struct KillRing(ClipboardItem);
24133impl Global for KillRing {}
24134
24135const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24136
24137enum BreakpointPromptEditAction {
24138 Log,
24139 Condition,
24140 HitCondition,
24141}
24142
24143struct BreakpointPromptEditor {
24144 pub(crate) prompt: Entity<Editor>,
24145 editor: WeakEntity<Editor>,
24146 breakpoint_anchor: Anchor,
24147 breakpoint: Breakpoint,
24148 edit_action: BreakpointPromptEditAction,
24149 block_ids: HashSet<CustomBlockId>,
24150 editor_margins: Arc<Mutex<EditorMargins>>,
24151 _subscriptions: Vec<Subscription>,
24152}
24153
24154impl BreakpointPromptEditor {
24155 const MAX_LINES: u8 = 4;
24156
24157 fn new(
24158 editor: WeakEntity<Editor>,
24159 breakpoint_anchor: Anchor,
24160 breakpoint: Breakpoint,
24161 edit_action: BreakpointPromptEditAction,
24162 window: &mut Window,
24163 cx: &mut Context<Self>,
24164 ) -> Self {
24165 let base_text = match edit_action {
24166 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24167 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24168 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24169 }
24170 .map(|msg| msg.to_string())
24171 .unwrap_or_default();
24172
24173 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24174 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24175
24176 let prompt = cx.new(|cx| {
24177 let mut prompt = Editor::new(
24178 EditorMode::AutoHeight {
24179 min_lines: 1,
24180 max_lines: Some(Self::MAX_LINES as usize),
24181 },
24182 buffer,
24183 None,
24184 window,
24185 cx,
24186 );
24187 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24188 prompt.set_show_cursor_when_unfocused(false, cx);
24189 prompt.set_placeholder_text(
24190 match edit_action {
24191 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24192 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24193 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24194 },
24195 window,
24196 cx,
24197 );
24198
24199 prompt
24200 });
24201
24202 Self {
24203 prompt,
24204 editor,
24205 breakpoint_anchor,
24206 breakpoint,
24207 edit_action,
24208 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24209 block_ids: Default::default(),
24210 _subscriptions: vec![],
24211 }
24212 }
24213
24214 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24215 self.block_ids.extend(block_ids)
24216 }
24217
24218 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24219 if let Some(editor) = self.editor.upgrade() {
24220 let message = self
24221 .prompt
24222 .read(cx)
24223 .buffer
24224 .read(cx)
24225 .as_singleton()
24226 .expect("A multi buffer in breakpoint prompt isn't possible")
24227 .read(cx)
24228 .as_rope()
24229 .to_string();
24230
24231 editor.update(cx, |editor, cx| {
24232 editor.edit_breakpoint_at_anchor(
24233 self.breakpoint_anchor,
24234 self.breakpoint.clone(),
24235 match self.edit_action {
24236 BreakpointPromptEditAction::Log => {
24237 BreakpointEditAction::EditLogMessage(message.into())
24238 }
24239 BreakpointPromptEditAction::Condition => {
24240 BreakpointEditAction::EditCondition(message.into())
24241 }
24242 BreakpointPromptEditAction::HitCondition => {
24243 BreakpointEditAction::EditHitCondition(message.into())
24244 }
24245 },
24246 cx,
24247 );
24248
24249 editor.remove_blocks(self.block_ids.clone(), None, cx);
24250 cx.focus_self(window);
24251 });
24252 }
24253 }
24254
24255 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24256 self.editor
24257 .update(cx, |editor, cx| {
24258 editor.remove_blocks(self.block_ids.clone(), None, cx);
24259 window.focus(&editor.focus_handle);
24260 })
24261 .log_err();
24262 }
24263
24264 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24265 let settings = ThemeSettings::get_global(cx);
24266 let text_style = TextStyle {
24267 color: if self.prompt.read(cx).read_only(cx) {
24268 cx.theme().colors().text_disabled
24269 } else {
24270 cx.theme().colors().text
24271 },
24272 font_family: settings.buffer_font.family.clone(),
24273 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24274 font_size: settings.buffer_font_size(cx).into(),
24275 font_weight: settings.buffer_font.weight,
24276 line_height: relative(settings.buffer_line_height.value()),
24277 ..Default::default()
24278 };
24279 EditorElement::new(
24280 &self.prompt,
24281 EditorStyle {
24282 background: cx.theme().colors().editor_background,
24283 local_player: cx.theme().players().local(),
24284 text: text_style,
24285 ..Default::default()
24286 },
24287 )
24288 }
24289}
24290
24291impl Render for BreakpointPromptEditor {
24292 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24293 let editor_margins = *self.editor_margins.lock();
24294 let gutter_dimensions = editor_margins.gutter;
24295 h_flex()
24296 .key_context("Editor")
24297 .bg(cx.theme().colors().editor_background)
24298 .border_y_1()
24299 .border_color(cx.theme().status().info_border)
24300 .size_full()
24301 .py(window.line_height() / 2.5)
24302 .on_action(cx.listener(Self::confirm))
24303 .on_action(cx.listener(Self::cancel))
24304 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24305 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24306 }
24307}
24308
24309impl Focusable for BreakpointPromptEditor {
24310 fn focus_handle(&self, cx: &App) -> FocusHandle {
24311 self.prompt.focus_handle(cx)
24312 }
24313}
24314
24315fn all_edits_insertions_or_deletions(
24316 edits: &Vec<(Range<Anchor>, String)>,
24317 snapshot: &MultiBufferSnapshot,
24318) -> bool {
24319 let mut all_insertions = true;
24320 let mut all_deletions = true;
24321
24322 for (range, new_text) in edits.iter() {
24323 let range_is_empty = range.to_offset(snapshot).is_empty();
24324 let text_is_empty = new_text.is_empty();
24325
24326 if range_is_empty != text_is_empty {
24327 if range_is_empty {
24328 all_deletions = false;
24329 } else {
24330 all_insertions = false;
24331 }
24332 } else {
24333 return false;
24334 }
24335
24336 if !all_insertions && !all_deletions {
24337 return false;
24338 }
24339 }
24340 all_insertions || all_deletions
24341}
24342
24343struct MissingEditPredictionKeybindingTooltip;
24344
24345impl Render for MissingEditPredictionKeybindingTooltip {
24346 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24347 ui::tooltip_container(window, cx, |container, _, cx| {
24348 container
24349 .flex_shrink_0()
24350 .max_w_80()
24351 .min_h(rems_from_px(124.))
24352 .justify_between()
24353 .child(
24354 v_flex()
24355 .flex_1()
24356 .text_ui_sm(cx)
24357 .child(Label::new("Conflict with Accept Keybinding"))
24358 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24359 )
24360 .child(
24361 h_flex()
24362 .pb_1()
24363 .gap_1()
24364 .items_end()
24365 .w_full()
24366 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24367 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24368 }))
24369 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24370 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24371 })),
24372 )
24373 })
24374 }
24375}
24376
24377#[derive(Debug, Clone, Copy, PartialEq)]
24378pub struct LineHighlight {
24379 pub background: Background,
24380 pub border: Option<gpui::Hsla>,
24381 pub include_gutter: bool,
24382 pub type_id: Option<TypeId>,
24383}
24384
24385struct LineManipulationResult {
24386 pub new_text: String,
24387 pub line_count_before: usize,
24388 pub line_count_after: usize,
24389}
24390
24391fn render_diff_hunk_controls(
24392 row: u32,
24393 status: &DiffHunkStatus,
24394 hunk_range: Range<Anchor>,
24395 is_created_file: bool,
24396 line_height: Pixels,
24397 editor: &Entity<Editor>,
24398 _window: &mut Window,
24399 cx: &mut App,
24400) -> AnyElement {
24401 h_flex()
24402 .h(line_height)
24403 .mr_1()
24404 .gap_1()
24405 .px_0p5()
24406 .pb_1()
24407 .border_x_1()
24408 .border_b_1()
24409 .border_color(cx.theme().colors().border_variant)
24410 .rounded_b_lg()
24411 .bg(cx.theme().colors().editor_background)
24412 .gap_1()
24413 .block_mouse_except_scroll()
24414 .shadow_md()
24415 .child(if status.has_secondary_hunk() {
24416 Button::new(("stage", row as u64), "Stage")
24417 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24418 .tooltip({
24419 let focus_handle = editor.focus_handle(cx);
24420 move |window, cx| {
24421 Tooltip::for_action_in(
24422 "Stage Hunk",
24423 &::git::ToggleStaged,
24424 &focus_handle,
24425 window,
24426 cx,
24427 )
24428 }
24429 })
24430 .on_click({
24431 let editor = editor.clone();
24432 move |_event, _window, cx| {
24433 editor.update(cx, |editor, cx| {
24434 editor.stage_or_unstage_diff_hunks(
24435 true,
24436 vec![hunk_range.start..hunk_range.start],
24437 cx,
24438 );
24439 });
24440 }
24441 })
24442 } else {
24443 Button::new(("unstage", row as u64), "Unstage")
24444 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24445 .tooltip({
24446 let focus_handle = editor.focus_handle(cx);
24447 move |window, cx| {
24448 Tooltip::for_action_in(
24449 "Unstage Hunk",
24450 &::git::ToggleStaged,
24451 &focus_handle,
24452 window,
24453 cx,
24454 )
24455 }
24456 })
24457 .on_click({
24458 let editor = editor.clone();
24459 move |_event, _window, cx| {
24460 editor.update(cx, |editor, cx| {
24461 editor.stage_or_unstage_diff_hunks(
24462 false,
24463 vec![hunk_range.start..hunk_range.start],
24464 cx,
24465 );
24466 });
24467 }
24468 })
24469 })
24470 .child(
24471 Button::new(("restore", row as u64), "Restore")
24472 .tooltip({
24473 let focus_handle = editor.focus_handle(cx);
24474 move |window, cx| {
24475 Tooltip::for_action_in(
24476 "Restore Hunk",
24477 &::git::Restore,
24478 &focus_handle,
24479 window,
24480 cx,
24481 )
24482 }
24483 })
24484 .on_click({
24485 let editor = editor.clone();
24486 move |_event, window, cx| {
24487 editor.update(cx, |editor, cx| {
24488 let snapshot = editor.snapshot(window, cx);
24489 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24490 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24491 });
24492 }
24493 })
24494 .disabled(is_created_file),
24495 )
24496 .when(
24497 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24498 |el| {
24499 el.child(
24500 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24501 .shape(IconButtonShape::Square)
24502 .icon_size(IconSize::Small)
24503 // .disabled(!has_multiple_hunks)
24504 .tooltip({
24505 let focus_handle = editor.focus_handle(cx);
24506 move |window, cx| {
24507 Tooltip::for_action_in(
24508 "Next Hunk",
24509 &GoToHunk,
24510 &focus_handle,
24511 window,
24512 cx,
24513 )
24514 }
24515 })
24516 .on_click({
24517 let editor = editor.clone();
24518 move |_event, window, cx| {
24519 editor.update(cx, |editor, cx| {
24520 let snapshot = editor.snapshot(window, cx);
24521 let position =
24522 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24523 editor.go_to_hunk_before_or_after_position(
24524 &snapshot,
24525 position,
24526 Direction::Next,
24527 window,
24528 cx,
24529 );
24530 editor.expand_selected_diff_hunks(cx);
24531 });
24532 }
24533 }),
24534 )
24535 .child(
24536 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24537 .shape(IconButtonShape::Square)
24538 .icon_size(IconSize::Small)
24539 // .disabled(!has_multiple_hunks)
24540 .tooltip({
24541 let focus_handle = editor.focus_handle(cx);
24542 move |window, cx| {
24543 Tooltip::for_action_in(
24544 "Previous Hunk",
24545 &GoToPreviousHunk,
24546 &focus_handle,
24547 window,
24548 cx,
24549 )
24550 }
24551 })
24552 .on_click({
24553 let editor = editor.clone();
24554 move |_event, window, cx| {
24555 editor.update(cx, |editor, cx| {
24556 let snapshot = editor.snapshot(window, cx);
24557 let point =
24558 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24559 editor.go_to_hunk_before_or_after_position(
24560 &snapshot,
24561 point,
24562 Direction::Prev,
24563 window,
24564 cx,
24565 );
24566 editor.expand_selected_diff_hunks(cx);
24567 });
24568 }
24569 }),
24570 )
24571 },
24572 )
24573 .into_any_element()
24574}
24575
24576pub fn multibuffer_context_lines(cx: &App) -> u32 {
24577 EditorSettings::try_get(cx)
24578 .map(|settings| settings.excerpt_context_lines)
24579 .unwrap_or(2)
24580 .min(32)
24581}