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(u32),
283 DebuggerValue(u32),
284 // LSP
285 Hint(u32),
286 Color(u32),
287}
288
289impl InlayId {
290 fn id(&self) -> u32 {
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: u32,
1122 next_color_inlay_id: u32,
1123 _subscriptions: Vec<Subscription>,
1124 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1125 gutter_dimensions: GutterDimensions,
1126 style: Option<EditorStyle>,
1127 text_style_refinement: Option<TextStyleRefinement>,
1128 next_editor_action_id: EditorActionId,
1129 editor_actions: Rc<
1130 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1131 >,
1132 use_autoclose: bool,
1133 use_auto_surround: bool,
1134 auto_replace_emoji_shortcode: bool,
1135 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1136 show_git_blame_gutter: bool,
1137 show_git_blame_inline: bool,
1138 show_git_blame_inline_delay_task: Option<Task<()>>,
1139 git_blame_inline_enabled: bool,
1140 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1141 serialize_dirty_buffers: bool,
1142 show_selection_menu: Option<bool>,
1143 blame: Option<Entity<GitBlame>>,
1144 blame_subscription: Option<Subscription>,
1145 custom_context_menu: Option<
1146 Box<
1147 dyn 'static
1148 + Fn(
1149 &mut Self,
1150 DisplayPoint,
1151 &mut Window,
1152 &mut Context<Self>,
1153 ) -> Option<Entity<ui::ContextMenu>>,
1154 >,
1155 >,
1156 last_bounds: Option<Bounds<Pixels>>,
1157 last_position_map: Option<Rc<PositionMap>>,
1158 expect_bounds_change: Option<Bounds<Pixels>>,
1159 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1160 tasks_update_task: Option<Task<()>>,
1161 breakpoint_store: Option<Entity<BreakpointStore>>,
1162 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1163 hovered_diff_hunk_row: Option<DisplayRow>,
1164 pull_diagnostics_task: Task<()>,
1165 in_project_search: bool,
1166 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1167 breadcrumb_header: Option<String>,
1168 focused_block: Option<FocusedBlock>,
1169 next_scroll_position: NextScrollCursorCenterTopBottom,
1170 addons: HashMap<TypeId, Box<dyn Addon>>,
1171 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1172 load_diff_task: Option<Shared<Task<()>>>,
1173 /// Whether we are temporarily displaying a diff other than git's
1174 temporary_diff_override: bool,
1175 selection_mark_mode: bool,
1176 toggle_fold_multiple_buffers: Task<()>,
1177 _scroll_cursor_center_top_bottom_task: Task<()>,
1178 serialize_selections: Task<()>,
1179 serialize_folds: Task<()>,
1180 mouse_cursor_hidden: bool,
1181 minimap: Option<Entity<Self>>,
1182 hide_mouse_mode: HideMouseMode,
1183 pub change_list: ChangeList,
1184 inline_value_cache: InlineValueCache,
1185 selection_drag_state: SelectionDragState,
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 if !other_selections.is_empty() {
3297 self.selections.change_with(cx, |selections| {
3298 selections.select_anchors(other_selections);
3299 });
3300 }
3301
3302 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3303 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3304 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3305 if other_selections.is_empty() {
3306 return;
3307 }
3308 this.selections.change_with(cx, |selections| {
3309 selections.select_anchors(other_selections);
3310 });
3311 }
3312 });
3313
3314 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3315 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3316 let these_selections = this.selections.disjoint_anchors().to_vec();
3317 if these_selections.is_empty() {
3318 return;
3319 }
3320 other.update(cx, |other_editor, cx| {
3321 other_editor.selections.change_with(cx, |selections| {
3322 selections.select_anchors(these_selections);
3323 })
3324 });
3325 }
3326 });
3327
3328 Subscription::join(other_subscription, this_subscription)
3329 }
3330
3331 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3332 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3333 /// effects of selection change occur at the end of the transaction.
3334 pub fn change_selections<R>(
3335 &mut self,
3336 effects: SelectionEffects,
3337 window: &mut Window,
3338 cx: &mut Context<Self>,
3339 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3340 ) -> R {
3341 if let Some(state) = &mut self.deferred_selection_effects_state {
3342 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3343 state.effects.completions = effects.completions;
3344 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3345 let (changed, result) = self.selections.change_with(cx, change);
3346 state.changed |= changed;
3347 return result;
3348 }
3349 let mut state = DeferredSelectionEffectsState {
3350 changed: false,
3351 effects,
3352 old_cursor_position: self.selections.newest_anchor().head(),
3353 history_entry: SelectionHistoryEntry {
3354 selections: self.selections.disjoint_anchors_arc(),
3355 select_next_state: self.select_next_state.clone(),
3356 select_prev_state: self.select_prev_state.clone(),
3357 add_selections_state: self.add_selections_state.clone(),
3358 },
3359 };
3360 let (changed, result) = self.selections.change_with(cx, change);
3361 state.changed = state.changed || changed;
3362 if self.defer_selection_effects {
3363 self.deferred_selection_effects_state = Some(state);
3364 } else {
3365 self.apply_selection_effects(state, window, cx);
3366 }
3367 result
3368 }
3369
3370 /// Defers the effects of selection change, so that the effects of multiple calls to
3371 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3372 /// to selection history and the state of popovers based on selection position aren't
3373 /// erroneously updated.
3374 pub fn with_selection_effects_deferred<R>(
3375 &mut self,
3376 window: &mut Window,
3377 cx: &mut Context<Self>,
3378 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3379 ) -> R {
3380 let already_deferred = self.defer_selection_effects;
3381 self.defer_selection_effects = true;
3382 let result = update(self, window, cx);
3383 if !already_deferred {
3384 self.defer_selection_effects = false;
3385 if let Some(state) = self.deferred_selection_effects_state.take() {
3386 self.apply_selection_effects(state, window, cx);
3387 }
3388 }
3389 result
3390 }
3391
3392 fn apply_selection_effects(
3393 &mut self,
3394 state: DeferredSelectionEffectsState,
3395 window: &mut Window,
3396 cx: &mut Context<Self>,
3397 ) {
3398 if state.changed {
3399 self.selection_history.push(state.history_entry);
3400
3401 if let Some(autoscroll) = state.effects.scroll {
3402 self.request_autoscroll(autoscroll, cx);
3403 }
3404
3405 let old_cursor_position = &state.old_cursor_position;
3406
3407 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3408
3409 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3410 self.show_signature_help(&ShowSignatureHelp, window, cx);
3411 }
3412 }
3413 }
3414
3415 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3416 where
3417 I: IntoIterator<Item = (Range<S>, T)>,
3418 S: ToOffset,
3419 T: Into<Arc<str>>,
3420 {
3421 if self.read_only(cx) {
3422 return;
3423 }
3424
3425 self.buffer
3426 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3427 }
3428
3429 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3430 where
3431 I: IntoIterator<Item = (Range<S>, T)>,
3432 S: ToOffset,
3433 T: Into<Arc<str>>,
3434 {
3435 if self.read_only(cx) {
3436 return;
3437 }
3438
3439 self.buffer.update(cx, |buffer, cx| {
3440 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3441 });
3442 }
3443
3444 pub fn edit_with_block_indent<I, S, T>(
3445 &mut self,
3446 edits: I,
3447 original_indent_columns: Vec<Option<u32>>,
3448 cx: &mut Context<Self>,
3449 ) where
3450 I: IntoIterator<Item = (Range<S>, T)>,
3451 S: ToOffset,
3452 T: Into<Arc<str>>,
3453 {
3454 if self.read_only(cx) {
3455 return;
3456 }
3457
3458 self.buffer.update(cx, |buffer, cx| {
3459 buffer.edit(
3460 edits,
3461 Some(AutoindentMode::Block {
3462 original_indent_columns,
3463 }),
3464 cx,
3465 )
3466 });
3467 }
3468
3469 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3470 self.hide_context_menu(window, cx);
3471
3472 match phase {
3473 SelectPhase::Begin {
3474 position,
3475 add,
3476 click_count,
3477 } => self.begin_selection(position, add, click_count, window, cx),
3478 SelectPhase::BeginColumnar {
3479 position,
3480 goal_column,
3481 reset,
3482 mode,
3483 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3484 SelectPhase::Extend {
3485 position,
3486 click_count,
3487 } => self.extend_selection(position, click_count, window, cx),
3488 SelectPhase::Update {
3489 position,
3490 goal_column,
3491 scroll_delta,
3492 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3493 SelectPhase::End => self.end_selection(window, cx),
3494 }
3495 }
3496
3497 fn extend_selection(
3498 &mut self,
3499 position: DisplayPoint,
3500 click_count: usize,
3501 window: &mut Window,
3502 cx: &mut Context<Self>,
3503 ) {
3504 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3505 let tail = self.selections.newest::<usize>(cx).tail();
3506 self.begin_selection(position, false, click_count, window, cx);
3507
3508 let position = position.to_offset(&display_map, Bias::Left);
3509 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3510
3511 let mut pending_selection = self
3512 .selections
3513 .pending_anchor()
3514 .cloned()
3515 .expect("extend_selection not called with pending selection");
3516 if position >= tail {
3517 pending_selection.start = tail_anchor;
3518 } else {
3519 pending_selection.end = tail_anchor;
3520 pending_selection.reversed = true;
3521 }
3522
3523 let mut pending_mode = self.selections.pending_mode().unwrap();
3524 match &mut pending_mode {
3525 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3526 _ => {}
3527 }
3528
3529 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3530 SelectionEffects::scroll(Autoscroll::fit())
3531 } else {
3532 SelectionEffects::no_scroll()
3533 };
3534
3535 self.change_selections(effects, window, cx, |s| {
3536 s.set_pending(pending_selection.clone(), pending_mode)
3537 });
3538 }
3539
3540 fn begin_selection(
3541 &mut self,
3542 position: DisplayPoint,
3543 add: bool,
3544 click_count: usize,
3545 window: &mut Window,
3546 cx: &mut Context<Self>,
3547 ) {
3548 if !self.focus_handle.is_focused(window) {
3549 self.last_focused_descendant = None;
3550 window.focus(&self.focus_handle);
3551 }
3552
3553 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3554 let buffer = &display_map.buffer_snapshot;
3555 let position = display_map.clip_point(position, Bias::Left);
3556
3557 let start;
3558 let end;
3559 let mode;
3560 let mut auto_scroll;
3561 match click_count {
3562 1 => {
3563 start = buffer.anchor_before(position.to_point(&display_map));
3564 end = start;
3565 mode = SelectMode::Character;
3566 auto_scroll = true;
3567 }
3568 2 => {
3569 let position = display_map
3570 .clip_point(position, Bias::Left)
3571 .to_offset(&display_map, Bias::Left);
3572 let (range, _) = buffer.surrounding_word(position, None);
3573 start = buffer.anchor_before(range.start);
3574 end = buffer.anchor_before(range.end);
3575 mode = SelectMode::Word(start..end);
3576 auto_scroll = true;
3577 }
3578 3 => {
3579 let position = display_map
3580 .clip_point(position, Bias::Left)
3581 .to_point(&display_map);
3582 let line_start = display_map.prev_line_boundary(position).0;
3583 let next_line_start = buffer.clip_point(
3584 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3585 Bias::Left,
3586 );
3587 start = buffer.anchor_before(line_start);
3588 end = buffer.anchor_before(next_line_start);
3589 mode = SelectMode::Line(start..end);
3590 auto_scroll = true;
3591 }
3592 _ => {
3593 start = buffer.anchor_before(0);
3594 end = buffer.anchor_before(buffer.len());
3595 mode = SelectMode::All;
3596 auto_scroll = false;
3597 }
3598 }
3599 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3600
3601 let point_to_delete: Option<usize> = {
3602 let selected_points: Vec<Selection<Point>> =
3603 self.selections.disjoint_in_range(start..end, cx);
3604
3605 if !add || click_count > 1 {
3606 None
3607 } else if !selected_points.is_empty() {
3608 Some(selected_points[0].id)
3609 } else {
3610 let clicked_point_already_selected =
3611 self.selections.disjoint_anchors().iter().find(|selection| {
3612 selection.start.to_point(buffer) == start.to_point(buffer)
3613 || selection.end.to_point(buffer) == end.to_point(buffer)
3614 });
3615
3616 clicked_point_already_selected.map(|selection| selection.id)
3617 }
3618 };
3619
3620 let selections_count = self.selections.count();
3621 let effects = if auto_scroll {
3622 SelectionEffects::default()
3623 } else {
3624 SelectionEffects::no_scroll()
3625 };
3626
3627 self.change_selections(effects, window, cx, |s| {
3628 if let Some(point_to_delete) = point_to_delete {
3629 s.delete(point_to_delete);
3630
3631 if selections_count == 1 {
3632 s.set_pending_anchor_range(start..end, mode);
3633 }
3634 } else {
3635 if !add {
3636 s.clear_disjoint();
3637 }
3638
3639 s.set_pending_anchor_range(start..end, mode);
3640 }
3641 });
3642 }
3643
3644 fn begin_columnar_selection(
3645 &mut self,
3646 position: DisplayPoint,
3647 goal_column: u32,
3648 reset: bool,
3649 mode: ColumnarMode,
3650 window: &mut Window,
3651 cx: &mut Context<Self>,
3652 ) {
3653 if !self.focus_handle.is_focused(window) {
3654 self.last_focused_descendant = None;
3655 window.focus(&self.focus_handle);
3656 }
3657
3658 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3659
3660 if reset {
3661 let pointer_position = display_map
3662 .buffer_snapshot
3663 .anchor_before(position.to_point(&display_map));
3664
3665 self.change_selections(
3666 SelectionEffects::scroll(Autoscroll::newest()),
3667 window,
3668 cx,
3669 |s| {
3670 s.clear_disjoint();
3671 s.set_pending_anchor_range(
3672 pointer_position..pointer_position,
3673 SelectMode::Character,
3674 );
3675 },
3676 );
3677 };
3678
3679 let tail = self.selections.newest::<Point>(cx).tail();
3680 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3681 self.columnar_selection_state = match mode {
3682 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3683 selection_tail: selection_anchor,
3684 display_point: if reset {
3685 if position.column() != goal_column {
3686 Some(DisplayPoint::new(position.row(), goal_column))
3687 } else {
3688 None
3689 }
3690 } else {
3691 None
3692 },
3693 }),
3694 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3695 selection_tail: selection_anchor,
3696 }),
3697 };
3698
3699 if !reset {
3700 self.select_columns(position, goal_column, &display_map, window, cx);
3701 }
3702 }
3703
3704 fn update_selection(
3705 &mut self,
3706 position: DisplayPoint,
3707 goal_column: u32,
3708 scroll_delta: gpui::Point<f32>,
3709 window: &mut Window,
3710 cx: &mut Context<Self>,
3711 ) {
3712 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3713
3714 if self.columnar_selection_state.is_some() {
3715 self.select_columns(position, goal_column, &display_map, window, cx);
3716 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3717 let buffer = &display_map.buffer_snapshot;
3718 let head;
3719 let tail;
3720 let mode = self.selections.pending_mode().unwrap();
3721 match &mode {
3722 SelectMode::Character => {
3723 head = position.to_point(&display_map);
3724 tail = pending.tail().to_point(buffer);
3725 }
3726 SelectMode::Word(original_range) => {
3727 let offset = display_map
3728 .clip_point(position, Bias::Left)
3729 .to_offset(&display_map, Bias::Left);
3730 let original_range = original_range.to_offset(buffer);
3731
3732 let head_offset = if buffer.is_inside_word(offset, None)
3733 || original_range.contains(&offset)
3734 {
3735 let (word_range, _) = buffer.surrounding_word(offset, None);
3736 if word_range.start < original_range.start {
3737 word_range.start
3738 } else {
3739 word_range.end
3740 }
3741 } else {
3742 offset
3743 };
3744
3745 head = head_offset.to_point(buffer);
3746 if head_offset <= original_range.start {
3747 tail = original_range.end.to_point(buffer);
3748 } else {
3749 tail = original_range.start.to_point(buffer);
3750 }
3751 }
3752 SelectMode::Line(original_range) => {
3753 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3754
3755 let position = display_map
3756 .clip_point(position, Bias::Left)
3757 .to_point(&display_map);
3758 let line_start = display_map.prev_line_boundary(position).0;
3759 let next_line_start = buffer.clip_point(
3760 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3761 Bias::Left,
3762 );
3763
3764 if line_start < original_range.start {
3765 head = line_start
3766 } else {
3767 head = next_line_start
3768 }
3769
3770 if head <= original_range.start {
3771 tail = original_range.end;
3772 } else {
3773 tail = original_range.start;
3774 }
3775 }
3776 SelectMode::All => {
3777 return;
3778 }
3779 };
3780
3781 if head < tail {
3782 pending.start = buffer.anchor_before(head);
3783 pending.end = buffer.anchor_before(tail);
3784 pending.reversed = true;
3785 } else {
3786 pending.start = buffer.anchor_before(tail);
3787 pending.end = buffer.anchor_before(head);
3788 pending.reversed = false;
3789 }
3790
3791 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3792 s.set_pending(pending.clone(), mode);
3793 });
3794 } else {
3795 log::error!("update_selection dispatched with no pending selection");
3796 return;
3797 }
3798
3799 self.apply_scroll_delta(scroll_delta, window, cx);
3800 cx.notify();
3801 }
3802
3803 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3804 self.columnar_selection_state.take();
3805 if self.selections.pending_anchor().is_some() {
3806 let selections = self.selections.all::<usize>(cx);
3807 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3808 s.select(selections);
3809 s.clear_pending();
3810 });
3811 }
3812 }
3813
3814 fn select_columns(
3815 &mut self,
3816 head: DisplayPoint,
3817 goal_column: u32,
3818 display_map: &DisplaySnapshot,
3819 window: &mut Window,
3820 cx: &mut Context<Self>,
3821 ) {
3822 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3823 return;
3824 };
3825
3826 let tail = match columnar_state {
3827 ColumnarSelectionState::FromMouse {
3828 selection_tail,
3829 display_point,
3830 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3831 ColumnarSelectionState::FromSelection { selection_tail } => {
3832 selection_tail.to_display_point(display_map)
3833 }
3834 };
3835
3836 let start_row = cmp::min(tail.row(), head.row());
3837 let end_row = cmp::max(tail.row(), head.row());
3838 let start_column = cmp::min(tail.column(), goal_column);
3839 let end_column = cmp::max(tail.column(), goal_column);
3840 let reversed = start_column < tail.column();
3841
3842 let selection_ranges = (start_row.0..=end_row.0)
3843 .map(DisplayRow)
3844 .filter_map(|row| {
3845 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3846 || start_column <= display_map.line_len(row))
3847 && !display_map.is_block_line(row)
3848 {
3849 let start = display_map
3850 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3851 .to_point(display_map);
3852 let end = display_map
3853 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3854 .to_point(display_map);
3855 if reversed {
3856 Some(end..start)
3857 } else {
3858 Some(start..end)
3859 }
3860 } else {
3861 None
3862 }
3863 })
3864 .collect::<Vec<_>>();
3865
3866 let ranges = match columnar_state {
3867 ColumnarSelectionState::FromMouse { .. } => {
3868 let mut non_empty_ranges = selection_ranges
3869 .iter()
3870 .filter(|selection_range| selection_range.start != selection_range.end)
3871 .peekable();
3872 if non_empty_ranges.peek().is_some() {
3873 non_empty_ranges.cloned().collect()
3874 } else {
3875 selection_ranges
3876 }
3877 }
3878 _ => selection_ranges,
3879 };
3880
3881 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3882 s.select_ranges(ranges);
3883 });
3884 cx.notify();
3885 }
3886
3887 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3888 self.selections
3889 .all_adjusted(cx)
3890 .iter()
3891 .any(|selection| !selection.is_empty())
3892 }
3893
3894 pub fn has_pending_nonempty_selection(&self) -> bool {
3895 let pending_nonempty_selection = match self.selections.pending_anchor() {
3896 Some(Selection { start, end, .. }) => start != end,
3897 None => false,
3898 };
3899
3900 pending_nonempty_selection
3901 || (self.columnar_selection_state.is_some()
3902 && self.selections.disjoint_anchors().len() > 1)
3903 }
3904
3905 pub fn has_pending_selection(&self) -> bool {
3906 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3907 }
3908
3909 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3910 self.selection_mark_mode = false;
3911 self.selection_drag_state = SelectionDragState::None;
3912
3913 if self.clear_expanded_diff_hunks(cx) {
3914 cx.notify();
3915 return;
3916 }
3917 if self.dismiss_menus_and_popups(true, window, cx) {
3918 return;
3919 }
3920
3921 if self.mode.is_full()
3922 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3923 {
3924 return;
3925 }
3926
3927 cx.propagate();
3928 }
3929
3930 pub fn dismiss_menus_and_popups(
3931 &mut self,
3932 is_user_requested: bool,
3933 window: &mut Window,
3934 cx: &mut Context<Self>,
3935 ) -> bool {
3936 if self.take_rename(false, window, cx).is_some() {
3937 return true;
3938 }
3939
3940 if hide_hover(self, cx) {
3941 return true;
3942 }
3943
3944 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3945 return true;
3946 }
3947
3948 if self.hide_context_menu(window, cx).is_some() {
3949 return true;
3950 }
3951
3952 if self.mouse_context_menu.take().is_some() {
3953 return true;
3954 }
3955
3956 if is_user_requested && self.discard_edit_prediction(true, cx) {
3957 return true;
3958 }
3959
3960 if self.snippet_stack.pop().is_some() {
3961 return true;
3962 }
3963
3964 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3965 self.dismiss_diagnostics(cx);
3966 return true;
3967 }
3968
3969 false
3970 }
3971
3972 fn linked_editing_ranges_for(
3973 &self,
3974 selection: Range<text::Anchor>,
3975 cx: &App,
3976 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3977 if self.linked_edit_ranges.is_empty() {
3978 return None;
3979 }
3980 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3981 selection.end.buffer_id.and_then(|end_buffer_id| {
3982 if selection.start.buffer_id != Some(end_buffer_id) {
3983 return None;
3984 }
3985 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3986 let snapshot = buffer.read(cx).snapshot();
3987 self.linked_edit_ranges
3988 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3989 .map(|ranges| (ranges, snapshot, buffer))
3990 })?;
3991 use text::ToOffset as TO;
3992 // find offset from the start of current range to current cursor position
3993 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3994
3995 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3996 let start_difference = start_offset - start_byte_offset;
3997 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3998 let end_difference = end_offset - start_byte_offset;
3999 // Current range has associated linked ranges.
4000 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4001 for range in linked_ranges.iter() {
4002 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4003 let end_offset = start_offset + end_difference;
4004 let start_offset = start_offset + start_difference;
4005 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4006 continue;
4007 }
4008 if self.selections.disjoint_anchor_ranges().any(|s| {
4009 if s.start.buffer_id != selection.start.buffer_id
4010 || s.end.buffer_id != selection.end.buffer_id
4011 {
4012 return false;
4013 }
4014 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4015 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4016 }) {
4017 continue;
4018 }
4019 let start = buffer_snapshot.anchor_after(start_offset);
4020 let end = buffer_snapshot.anchor_after(end_offset);
4021 linked_edits
4022 .entry(buffer.clone())
4023 .or_default()
4024 .push(start..end);
4025 }
4026 Some(linked_edits)
4027 }
4028
4029 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4030 let text: Arc<str> = text.into();
4031
4032 if self.read_only(cx) {
4033 return;
4034 }
4035
4036 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4037
4038 let selections = self.selections.all_adjusted(cx);
4039 let mut bracket_inserted = false;
4040 let mut edits = Vec::new();
4041 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4042 let mut new_selections = Vec::with_capacity(selections.len());
4043 let mut new_autoclose_regions = Vec::new();
4044 let snapshot = self.buffer.read(cx).read(cx);
4045 let mut clear_linked_edit_ranges = false;
4046
4047 for (selection, autoclose_region) in
4048 self.selections_with_autoclose_regions(selections, &snapshot)
4049 {
4050 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4051 // Determine if the inserted text matches the opening or closing
4052 // bracket of any of this language's bracket pairs.
4053 let mut bracket_pair = None;
4054 let mut is_bracket_pair_start = false;
4055 let mut is_bracket_pair_end = false;
4056 if !text.is_empty() {
4057 let mut bracket_pair_matching_end = None;
4058 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4059 // and they are removing the character that triggered IME popup.
4060 for (pair, enabled) in scope.brackets() {
4061 if !pair.close && !pair.surround {
4062 continue;
4063 }
4064
4065 if enabled && pair.start.ends_with(text.as_ref()) {
4066 let prefix_len = pair.start.len() - text.len();
4067 let preceding_text_matches_prefix = prefix_len == 0
4068 || (selection.start.column >= (prefix_len as u32)
4069 && snapshot.contains_str_at(
4070 Point::new(
4071 selection.start.row,
4072 selection.start.column - (prefix_len as u32),
4073 ),
4074 &pair.start[..prefix_len],
4075 ));
4076 if preceding_text_matches_prefix {
4077 bracket_pair = Some(pair.clone());
4078 is_bracket_pair_start = true;
4079 break;
4080 }
4081 }
4082 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4083 {
4084 // take first bracket pair matching end, but don't break in case a later bracket
4085 // pair matches start
4086 bracket_pair_matching_end = Some(pair.clone());
4087 }
4088 }
4089 if let Some(end) = bracket_pair_matching_end
4090 && bracket_pair.is_none()
4091 {
4092 bracket_pair = Some(end);
4093 is_bracket_pair_end = true;
4094 }
4095 }
4096
4097 if let Some(bracket_pair) = bracket_pair {
4098 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4099 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4100 let auto_surround =
4101 self.use_auto_surround && snapshot_settings.use_auto_surround;
4102 if selection.is_empty() {
4103 if is_bracket_pair_start {
4104 // If the inserted text is a suffix of an opening bracket and the
4105 // selection is preceded by the rest of the opening bracket, then
4106 // insert the closing bracket.
4107 let following_text_allows_autoclose = snapshot
4108 .chars_at(selection.start)
4109 .next()
4110 .is_none_or(|c| scope.should_autoclose_before(c));
4111
4112 let preceding_text_allows_autoclose = selection.start.column == 0
4113 || snapshot
4114 .reversed_chars_at(selection.start)
4115 .next()
4116 .is_none_or(|c| {
4117 bracket_pair.start != bracket_pair.end
4118 || !snapshot
4119 .char_classifier_at(selection.start)
4120 .is_word(c)
4121 });
4122
4123 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4124 && bracket_pair.start.len() == 1
4125 {
4126 let target = bracket_pair.start.chars().next().unwrap();
4127 let current_line_count = snapshot
4128 .reversed_chars_at(selection.start)
4129 .take_while(|&c| c != '\n')
4130 .filter(|&c| c == target)
4131 .count();
4132 current_line_count % 2 == 1
4133 } else {
4134 false
4135 };
4136
4137 if autoclose
4138 && bracket_pair.close
4139 && following_text_allows_autoclose
4140 && preceding_text_allows_autoclose
4141 && !is_closing_quote
4142 {
4143 let anchor = snapshot.anchor_before(selection.end);
4144 new_selections.push((selection.map(|_| anchor), text.len()));
4145 new_autoclose_regions.push((
4146 anchor,
4147 text.len(),
4148 selection.id,
4149 bracket_pair.clone(),
4150 ));
4151 edits.push((
4152 selection.range(),
4153 format!("{}{}", text, bracket_pair.end).into(),
4154 ));
4155 bracket_inserted = true;
4156 continue;
4157 }
4158 }
4159
4160 if let Some(region) = autoclose_region {
4161 // If the selection is followed by an auto-inserted closing bracket,
4162 // then don't insert that closing bracket again; just move the selection
4163 // past the closing bracket.
4164 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4165 && text.as_ref() == region.pair.end.as_str()
4166 && snapshot.contains_str_at(region.range.end, text.as_ref());
4167 if should_skip {
4168 let anchor = snapshot.anchor_after(selection.end);
4169 new_selections
4170 .push((selection.map(|_| anchor), region.pair.end.len()));
4171 continue;
4172 }
4173 }
4174
4175 let always_treat_brackets_as_autoclosed = snapshot
4176 .language_settings_at(selection.start, cx)
4177 .always_treat_brackets_as_autoclosed;
4178 if always_treat_brackets_as_autoclosed
4179 && is_bracket_pair_end
4180 && snapshot.contains_str_at(selection.end, text.as_ref())
4181 {
4182 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4183 // and the inserted text is a closing bracket and the selection is followed
4184 // by the closing bracket then move the selection past the closing bracket.
4185 let anchor = snapshot.anchor_after(selection.end);
4186 new_selections.push((selection.map(|_| anchor), text.len()));
4187 continue;
4188 }
4189 }
4190 // If an opening bracket is 1 character long and is typed while
4191 // text is selected, then surround that text with the bracket pair.
4192 else if auto_surround
4193 && bracket_pair.surround
4194 && is_bracket_pair_start
4195 && bracket_pair.start.chars().count() == 1
4196 {
4197 edits.push((selection.start..selection.start, text.clone()));
4198 edits.push((
4199 selection.end..selection.end,
4200 bracket_pair.end.as_str().into(),
4201 ));
4202 bracket_inserted = true;
4203 new_selections.push((
4204 Selection {
4205 id: selection.id,
4206 start: snapshot.anchor_after(selection.start),
4207 end: snapshot.anchor_before(selection.end),
4208 reversed: selection.reversed,
4209 goal: selection.goal,
4210 },
4211 0,
4212 ));
4213 continue;
4214 }
4215 }
4216 }
4217
4218 if self.auto_replace_emoji_shortcode
4219 && selection.is_empty()
4220 && text.as_ref().ends_with(':')
4221 && let Some(possible_emoji_short_code) =
4222 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4223 && !possible_emoji_short_code.is_empty()
4224 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4225 {
4226 let emoji_shortcode_start = Point::new(
4227 selection.start.row,
4228 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4229 );
4230
4231 // Remove shortcode from buffer
4232 edits.push((
4233 emoji_shortcode_start..selection.start,
4234 "".to_string().into(),
4235 ));
4236 new_selections.push((
4237 Selection {
4238 id: selection.id,
4239 start: snapshot.anchor_after(emoji_shortcode_start),
4240 end: snapshot.anchor_before(selection.start),
4241 reversed: selection.reversed,
4242 goal: selection.goal,
4243 },
4244 0,
4245 ));
4246
4247 // Insert emoji
4248 let selection_start_anchor = snapshot.anchor_after(selection.start);
4249 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4250 edits.push((selection.start..selection.end, emoji.to_string().into()));
4251
4252 continue;
4253 }
4254
4255 // If not handling any auto-close operation, then just replace the selected
4256 // text with the given input and move the selection to the end of the
4257 // newly inserted text.
4258 let anchor = snapshot.anchor_after(selection.end);
4259 if !self.linked_edit_ranges.is_empty() {
4260 let start_anchor = snapshot.anchor_before(selection.start);
4261
4262 let is_word_char = text.chars().next().is_none_or(|char| {
4263 let classifier = snapshot
4264 .char_classifier_at(start_anchor.to_offset(&snapshot))
4265 .scope_context(Some(CharScopeContext::LinkedEdit));
4266 classifier.is_word(char)
4267 });
4268
4269 if is_word_char {
4270 if let Some(ranges) = self
4271 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4272 {
4273 for (buffer, edits) in ranges {
4274 linked_edits
4275 .entry(buffer.clone())
4276 .or_default()
4277 .extend(edits.into_iter().map(|range| (range, text.clone())));
4278 }
4279 }
4280 } else {
4281 clear_linked_edit_ranges = true;
4282 }
4283 }
4284
4285 new_selections.push((selection.map(|_| anchor), 0));
4286 edits.push((selection.start..selection.end, text.clone()));
4287 }
4288
4289 drop(snapshot);
4290
4291 self.transact(window, cx, |this, window, cx| {
4292 if clear_linked_edit_ranges {
4293 this.linked_edit_ranges.clear();
4294 }
4295 let initial_buffer_versions =
4296 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4297
4298 this.buffer.update(cx, |buffer, cx| {
4299 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4300 });
4301 for (buffer, edits) in linked_edits {
4302 buffer.update(cx, |buffer, cx| {
4303 let snapshot = buffer.snapshot();
4304 let edits = edits
4305 .into_iter()
4306 .map(|(range, text)| {
4307 use text::ToPoint as TP;
4308 let end_point = TP::to_point(&range.end, &snapshot);
4309 let start_point = TP::to_point(&range.start, &snapshot);
4310 (start_point..end_point, text)
4311 })
4312 .sorted_by_key(|(range, _)| range.start);
4313 buffer.edit(edits, None, cx);
4314 })
4315 }
4316 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4317 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4318 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4319 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4320 .zip(new_selection_deltas)
4321 .map(|(selection, delta)| Selection {
4322 id: selection.id,
4323 start: selection.start + delta,
4324 end: selection.end + delta,
4325 reversed: selection.reversed,
4326 goal: SelectionGoal::None,
4327 })
4328 .collect::<Vec<_>>();
4329
4330 let mut i = 0;
4331 for (position, delta, selection_id, pair) in new_autoclose_regions {
4332 let position = position.to_offset(&map.buffer_snapshot) + delta;
4333 let start = map.buffer_snapshot.anchor_before(position);
4334 let end = map.buffer_snapshot.anchor_after(position);
4335 while let Some(existing_state) = this.autoclose_regions.get(i) {
4336 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4337 Ordering::Less => i += 1,
4338 Ordering::Greater => break,
4339 Ordering::Equal => {
4340 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4341 Ordering::Less => i += 1,
4342 Ordering::Equal => break,
4343 Ordering::Greater => break,
4344 }
4345 }
4346 }
4347 }
4348 this.autoclose_regions.insert(
4349 i,
4350 AutocloseRegion {
4351 selection_id,
4352 range: start..end,
4353 pair,
4354 },
4355 );
4356 }
4357
4358 let had_active_edit_prediction = this.has_active_edit_prediction();
4359 this.change_selections(
4360 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4361 window,
4362 cx,
4363 |s| s.select(new_selections),
4364 );
4365
4366 if !bracket_inserted
4367 && let Some(on_type_format_task) =
4368 this.trigger_on_type_formatting(text.to_string(), window, cx)
4369 {
4370 on_type_format_task.detach_and_log_err(cx);
4371 }
4372
4373 let editor_settings = EditorSettings::get_global(cx);
4374 if bracket_inserted
4375 && (editor_settings.auto_signature_help
4376 || editor_settings.show_signature_help_after_edits)
4377 {
4378 this.show_signature_help(&ShowSignatureHelp, window, cx);
4379 }
4380
4381 let trigger_in_words =
4382 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4383 if this.hard_wrap.is_some() {
4384 let latest: Range<Point> = this.selections.newest(cx).range();
4385 if latest.is_empty()
4386 && this
4387 .buffer()
4388 .read(cx)
4389 .snapshot(cx)
4390 .line_len(MultiBufferRow(latest.start.row))
4391 == latest.start.column
4392 {
4393 this.rewrap_impl(
4394 RewrapOptions {
4395 override_language_settings: true,
4396 preserve_existing_whitespace: true,
4397 },
4398 cx,
4399 )
4400 }
4401 }
4402 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4403 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4404 this.refresh_edit_prediction(true, false, window, cx);
4405 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4406 });
4407 }
4408
4409 fn find_possible_emoji_shortcode_at_position(
4410 snapshot: &MultiBufferSnapshot,
4411 position: Point,
4412 ) -> Option<String> {
4413 let mut chars = Vec::new();
4414 let mut found_colon = false;
4415 for char in snapshot.reversed_chars_at(position).take(100) {
4416 // Found a possible emoji shortcode in the middle of the buffer
4417 if found_colon {
4418 if char.is_whitespace() {
4419 chars.reverse();
4420 return Some(chars.iter().collect());
4421 }
4422 // If the previous character is not a whitespace, we are in the middle of a word
4423 // and we only want to complete the shortcode if the word is made up of other emojis
4424 let mut containing_word = String::new();
4425 for ch in snapshot
4426 .reversed_chars_at(position)
4427 .skip(chars.len() + 1)
4428 .take(100)
4429 {
4430 if ch.is_whitespace() {
4431 break;
4432 }
4433 containing_word.push(ch);
4434 }
4435 let containing_word = containing_word.chars().rev().collect::<String>();
4436 if util::word_consists_of_emojis(containing_word.as_str()) {
4437 chars.reverse();
4438 return Some(chars.iter().collect());
4439 }
4440 }
4441
4442 if char.is_whitespace() || !char.is_ascii() {
4443 return None;
4444 }
4445 if char == ':' {
4446 found_colon = true;
4447 } else {
4448 chars.push(char);
4449 }
4450 }
4451 // Found a possible emoji shortcode at the beginning of the buffer
4452 chars.reverse();
4453 Some(chars.iter().collect())
4454 }
4455
4456 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4458 self.transact(window, cx, |this, window, cx| {
4459 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4460 let selections = this.selections.all::<usize>(cx);
4461 let multi_buffer = this.buffer.read(cx);
4462 let buffer = multi_buffer.snapshot(cx);
4463 selections
4464 .iter()
4465 .map(|selection| {
4466 let start_point = selection.start.to_point(&buffer);
4467 let mut existing_indent =
4468 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4469 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4470 let start = selection.start;
4471 let end = selection.end;
4472 let selection_is_empty = start == end;
4473 let language_scope = buffer.language_scope_at(start);
4474 let (
4475 comment_delimiter,
4476 doc_delimiter,
4477 insert_extra_newline,
4478 indent_on_newline,
4479 indent_on_extra_newline,
4480 ) = if let Some(language) = &language_scope {
4481 let mut insert_extra_newline =
4482 insert_extra_newline_brackets(&buffer, start..end, language)
4483 || insert_extra_newline_tree_sitter(&buffer, start..end);
4484
4485 // Comment extension on newline is allowed only for cursor selections
4486 let comment_delimiter = maybe!({
4487 if !selection_is_empty {
4488 return None;
4489 }
4490
4491 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4492 return None;
4493 }
4494
4495 let delimiters = language.line_comment_prefixes();
4496 let max_len_of_delimiter =
4497 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4498 let (snapshot, range) =
4499 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4500
4501 let num_of_whitespaces = snapshot
4502 .chars_for_range(range.clone())
4503 .take_while(|c| c.is_whitespace())
4504 .count();
4505 let comment_candidate = snapshot
4506 .chars_for_range(range.clone())
4507 .skip(num_of_whitespaces)
4508 .take(max_len_of_delimiter)
4509 .collect::<String>();
4510 let (delimiter, trimmed_len) = delimiters
4511 .iter()
4512 .filter_map(|delimiter| {
4513 let prefix = delimiter.trim_end();
4514 if comment_candidate.starts_with(prefix) {
4515 Some((delimiter, prefix.len()))
4516 } else {
4517 None
4518 }
4519 })
4520 .max_by_key(|(_, len)| *len)?;
4521
4522 if let Some(BlockCommentConfig {
4523 start: block_start, ..
4524 }) = language.block_comment()
4525 {
4526 let block_start_trimmed = block_start.trim_end();
4527 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4528 let line_content = snapshot
4529 .chars_for_range(range)
4530 .skip(num_of_whitespaces)
4531 .take(block_start_trimmed.len())
4532 .collect::<String>();
4533
4534 if line_content.starts_with(block_start_trimmed) {
4535 return None;
4536 }
4537 }
4538 }
4539
4540 let cursor_is_placed_after_comment_marker =
4541 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4542 if cursor_is_placed_after_comment_marker {
4543 Some(delimiter.clone())
4544 } else {
4545 None
4546 }
4547 });
4548
4549 let mut indent_on_newline = IndentSize::spaces(0);
4550 let mut indent_on_extra_newline = IndentSize::spaces(0);
4551
4552 let doc_delimiter = maybe!({
4553 if !selection_is_empty {
4554 return None;
4555 }
4556
4557 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4558 return None;
4559 }
4560
4561 let BlockCommentConfig {
4562 start: start_tag,
4563 end: end_tag,
4564 prefix: delimiter,
4565 tab_size: len,
4566 } = language.documentation_comment()?;
4567 let is_within_block_comment = buffer
4568 .language_scope_at(start_point)
4569 .is_some_and(|scope| scope.override_name() == Some("comment"));
4570 if !is_within_block_comment {
4571 return None;
4572 }
4573
4574 let (snapshot, range) =
4575 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4576
4577 let num_of_whitespaces = snapshot
4578 .chars_for_range(range.clone())
4579 .take_while(|c| c.is_whitespace())
4580 .count();
4581
4582 // 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.
4583 let column = start_point.column;
4584 let cursor_is_after_start_tag = {
4585 let start_tag_len = start_tag.len();
4586 let start_tag_line = snapshot
4587 .chars_for_range(range.clone())
4588 .skip(num_of_whitespaces)
4589 .take(start_tag_len)
4590 .collect::<String>();
4591 if start_tag_line.starts_with(start_tag.as_ref()) {
4592 num_of_whitespaces + start_tag_len <= column as usize
4593 } else {
4594 false
4595 }
4596 };
4597
4598 let cursor_is_after_delimiter = {
4599 let delimiter_trim = delimiter.trim_end();
4600 let delimiter_line = snapshot
4601 .chars_for_range(range.clone())
4602 .skip(num_of_whitespaces)
4603 .take(delimiter_trim.len())
4604 .collect::<String>();
4605 if delimiter_line.starts_with(delimiter_trim) {
4606 num_of_whitespaces + delimiter_trim.len() <= column as usize
4607 } else {
4608 false
4609 }
4610 };
4611
4612 let cursor_is_before_end_tag_if_exists = {
4613 let mut char_position = 0u32;
4614 let mut end_tag_offset = None;
4615
4616 'outer: for chunk in snapshot.text_for_range(range) {
4617 if let Some(byte_pos) = chunk.find(&**end_tag) {
4618 let chars_before_match =
4619 chunk[..byte_pos].chars().count() as u32;
4620 end_tag_offset =
4621 Some(char_position + chars_before_match);
4622 break 'outer;
4623 }
4624 char_position += chunk.chars().count() as u32;
4625 }
4626
4627 if let Some(end_tag_offset) = end_tag_offset {
4628 let cursor_is_before_end_tag = column <= end_tag_offset;
4629 if cursor_is_after_start_tag {
4630 if cursor_is_before_end_tag {
4631 insert_extra_newline = true;
4632 }
4633 let cursor_is_at_start_of_end_tag =
4634 column == end_tag_offset;
4635 if cursor_is_at_start_of_end_tag {
4636 indent_on_extra_newline.len = *len;
4637 }
4638 }
4639 cursor_is_before_end_tag
4640 } else {
4641 true
4642 }
4643 };
4644
4645 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4646 && cursor_is_before_end_tag_if_exists
4647 {
4648 if cursor_is_after_start_tag {
4649 indent_on_newline.len = *len;
4650 }
4651 Some(delimiter.clone())
4652 } else {
4653 None
4654 }
4655 });
4656
4657 (
4658 comment_delimiter,
4659 doc_delimiter,
4660 insert_extra_newline,
4661 indent_on_newline,
4662 indent_on_extra_newline,
4663 )
4664 } else {
4665 (
4666 None,
4667 None,
4668 false,
4669 IndentSize::default(),
4670 IndentSize::default(),
4671 )
4672 };
4673
4674 let prevent_auto_indent = doc_delimiter.is_some();
4675 let delimiter = comment_delimiter.or(doc_delimiter);
4676
4677 let capacity_for_delimiter =
4678 delimiter.as_deref().map(str::len).unwrap_or_default();
4679 let mut new_text = String::with_capacity(
4680 1 + capacity_for_delimiter
4681 + existing_indent.len as usize
4682 + indent_on_newline.len as usize
4683 + indent_on_extra_newline.len as usize,
4684 );
4685 new_text.push('\n');
4686 new_text.extend(existing_indent.chars());
4687 new_text.extend(indent_on_newline.chars());
4688
4689 if let Some(delimiter) = &delimiter {
4690 new_text.push_str(delimiter);
4691 }
4692
4693 if insert_extra_newline {
4694 new_text.push('\n');
4695 new_text.extend(existing_indent.chars());
4696 new_text.extend(indent_on_extra_newline.chars());
4697 }
4698
4699 let anchor = buffer.anchor_after(end);
4700 let new_selection = selection.map(|_| anchor);
4701 (
4702 ((start..end, new_text), prevent_auto_indent),
4703 (insert_extra_newline, new_selection),
4704 )
4705 })
4706 .unzip()
4707 };
4708
4709 let mut auto_indent_edits = Vec::new();
4710 let mut edits = Vec::new();
4711 for (edit, prevent_auto_indent) in edits_with_flags {
4712 if prevent_auto_indent {
4713 edits.push(edit);
4714 } else {
4715 auto_indent_edits.push(edit);
4716 }
4717 }
4718 if !edits.is_empty() {
4719 this.edit(edits, cx);
4720 }
4721 if !auto_indent_edits.is_empty() {
4722 this.edit_with_autoindent(auto_indent_edits, cx);
4723 }
4724
4725 let buffer = this.buffer.read(cx).snapshot(cx);
4726 let new_selections = selection_info
4727 .into_iter()
4728 .map(|(extra_newline_inserted, new_selection)| {
4729 let mut cursor = new_selection.end.to_point(&buffer);
4730 if extra_newline_inserted {
4731 cursor.row -= 1;
4732 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4733 }
4734 new_selection.map(|_| cursor)
4735 })
4736 .collect();
4737
4738 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4739 this.refresh_edit_prediction(true, false, window, cx);
4740 });
4741 }
4742
4743 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4744 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4745
4746 let buffer = self.buffer.read(cx);
4747 let snapshot = buffer.snapshot(cx);
4748
4749 let mut edits = Vec::new();
4750 let mut rows = Vec::new();
4751
4752 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4753 let cursor = selection.head();
4754 let row = cursor.row;
4755
4756 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4757
4758 let newline = "\n".to_string();
4759 edits.push((start_of_line..start_of_line, newline));
4760
4761 rows.push(row + rows_inserted as u32);
4762 }
4763
4764 self.transact(window, cx, |editor, window, cx| {
4765 editor.edit(edits, cx);
4766
4767 editor.change_selections(Default::default(), window, cx, |s| {
4768 let mut index = 0;
4769 s.move_cursors_with(|map, _, _| {
4770 let row = rows[index];
4771 index += 1;
4772
4773 let point = Point::new(row, 0);
4774 let boundary = map.next_line_boundary(point).1;
4775 let clipped = map.clip_point(boundary, Bias::Left);
4776
4777 (clipped, SelectionGoal::None)
4778 });
4779 });
4780
4781 let mut indent_edits = Vec::new();
4782 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4783 for row in rows {
4784 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4785 for (row, indent) in indents {
4786 if indent.len == 0 {
4787 continue;
4788 }
4789
4790 let text = match indent.kind {
4791 IndentKind::Space => " ".repeat(indent.len as usize),
4792 IndentKind::Tab => "\t".repeat(indent.len as usize),
4793 };
4794 let point = Point::new(row.0, 0);
4795 indent_edits.push((point..point, text));
4796 }
4797 }
4798 editor.edit(indent_edits, cx);
4799 });
4800 }
4801
4802 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4803 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4804
4805 let buffer = self.buffer.read(cx);
4806 let snapshot = buffer.snapshot(cx);
4807
4808 let mut edits = Vec::new();
4809 let mut rows = Vec::new();
4810 let mut rows_inserted = 0;
4811
4812 for selection in self.selections.all_adjusted(cx) {
4813 let cursor = selection.head();
4814 let row = cursor.row;
4815
4816 let point = Point::new(row + 1, 0);
4817 let start_of_line = snapshot.clip_point(point, Bias::Left);
4818
4819 let newline = "\n".to_string();
4820 edits.push((start_of_line..start_of_line, newline));
4821
4822 rows_inserted += 1;
4823 rows.push(row + rows_inserted);
4824 }
4825
4826 self.transact(window, cx, |editor, window, cx| {
4827 editor.edit(edits, cx);
4828
4829 editor.change_selections(Default::default(), window, cx, |s| {
4830 let mut index = 0;
4831 s.move_cursors_with(|map, _, _| {
4832 let row = rows[index];
4833 index += 1;
4834
4835 let point = Point::new(row, 0);
4836 let boundary = map.next_line_boundary(point).1;
4837 let clipped = map.clip_point(boundary, Bias::Left);
4838
4839 (clipped, SelectionGoal::None)
4840 });
4841 });
4842
4843 let mut indent_edits = Vec::new();
4844 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4845 for row in rows {
4846 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4847 for (row, indent) in indents {
4848 if indent.len == 0 {
4849 continue;
4850 }
4851
4852 let text = match indent.kind {
4853 IndentKind::Space => " ".repeat(indent.len as usize),
4854 IndentKind::Tab => "\t".repeat(indent.len as usize),
4855 };
4856 let point = Point::new(row.0, 0);
4857 indent_edits.push((point..point, text));
4858 }
4859 }
4860 editor.edit(indent_edits, cx);
4861 });
4862 }
4863
4864 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4865 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4866 original_indent_columns: Vec::new(),
4867 });
4868 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4869 }
4870
4871 fn insert_with_autoindent_mode(
4872 &mut self,
4873 text: &str,
4874 autoindent_mode: Option<AutoindentMode>,
4875 window: &mut Window,
4876 cx: &mut Context<Self>,
4877 ) {
4878 if self.read_only(cx) {
4879 return;
4880 }
4881
4882 let text: Arc<str> = text.into();
4883 self.transact(window, cx, |this, window, cx| {
4884 let old_selections = this.selections.all_adjusted(cx);
4885 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4886 let anchors = {
4887 let snapshot = buffer.read(cx);
4888 old_selections
4889 .iter()
4890 .map(|s| {
4891 let anchor = snapshot.anchor_after(s.head());
4892 s.map(|_| anchor)
4893 })
4894 .collect::<Vec<_>>()
4895 };
4896 buffer.edit(
4897 old_selections
4898 .iter()
4899 .map(|s| (s.start..s.end, text.clone())),
4900 autoindent_mode,
4901 cx,
4902 );
4903 anchors
4904 });
4905
4906 this.change_selections(Default::default(), window, cx, |s| {
4907 s.select_anchors(selection_anchors);
4908 });
4909
4910 cx.notify();
4911 });
4912 }
4913
4914 fn trigger_completion_on_input(
4915 &mut self,
4916 text: &str,
4917 trigger_in_words: bool,
4918 window: &mut Window,
4919 cx: &mut Context<Self>,
4920 ) {
4921 let completions_source = self
4922 .context_menu
4923 .borrow()
4924 .as_ref()
4925 .and_then(|menu| match menu {
4926 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4927 CodeContextMenu::CodeActions(_) => None,
4928 });
4929
4930 match completions_source {
4931 Some(CompletionsMenuSource::Words { .. }) => {
4932 self.open_or_update_completions_menu(
4933 Some(CompletionsMenuSource::Words {
4934 ignore_threshold: false,
4935 }),
4936 None,
4937 window,
4938 cx,
4939 );
4940 }
4941 Some(CompletionsMenuSource::Normal)
4942 | Some(CompletionsMenuSource::SnippetChoices)
4943 | None
4944 if self.is_completion_trigger(
4945 text,
4946 trigger_in_words,
4947 completions_source.is_some(),
4948 cx,
4949 ) =>
4950 {
4951 self.show_completions(
4952 &ShowCompletions {
4953 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4954 },
4955 window,
4956 cx,
4957 )
4958 }
4959 _ => {
4960 self.hide_context_menu(window, cx);
4961 }
4962 }
4963 }
4964
4965 fn is_completion_trigger(
4966 &self,
4967 text: &str,
4968 trigger_in_words: bool,
4969 menu_is_open: bool,
4970 cx: &mut Context<Self>,
4971 ) -> bool {
4972 let position = self.selections.newest_anchor().head();
4973 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4974 return false;
4975 };
4976
4977 if let Some(completion_provider) = &self.completion_provider {
4978 completion_provider.is_completion_trigger(
4979 &buffer,
4980 position.text_anchor,
4981 text,
4982 trigger_in_words,
4983 menu_is_open,
4984 cx,
4985 )
4986 } else {
4987 false
4988 }
4989 }
4990
4991 /// If any empty selections is touching the start of its innermost containing autoclose
4992 /// region, expand it to select the brackets.
4993 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4994 let selections = self.selections.all::<usize>(cx);
4995 let buffer = self.buffer.read(cx).read(cx);
4996 let new_selections = self
4997 .selections_with_autoclose_regions(selections, &buffer)
4998 .map(|(mut selection, region)| {
4999 if !selection.is_empty() {
5000 return selection;
5001 }
5002
5003 if let Some(region) = region {
5004 let mut range = region.range.to_offset(&buffer);
5005 if selection.start == range.start && range.start >= region.pair.start.len() {
5006 range.start -= region.pair.start.len();
5007 if buffer.contains_str_at(range.start, ®ion.pair.start)
5008 && buffer.contains_str_at(range.end, ®ion.pair.end)
5009 {
5010 range.end += region.pair.end.len();
5011 selection.start = range.start;
5012 selection.end = range.end;
5013
5014 return selection;
5015 }
5016 }
5017 }
5018
5019 let always_treat_brackets_as_autoclosed = buffer
5020 .language_settings_at(selection.start, cx)
5021 .always_treat_brackets_as_autoclosed;
5022
5023 if !always_treat_brackets_as_autoclosed {
5024 return selection;
5025 }
5026
5027 if let Some(scope) = buffer.language_scope_at(selection.start) {
5028 for (pair, enabled) in scope.brackets() {
5029 if !enabled || !pair.close {
5030 continue;
5031 }
5032
5033 if buffer.contains_str_at(selection.start, &pair.end) {
5034 let pair_start_len = pair.start.len();
5035 if buffer.contains_str_at(
5036 selection.start.saturating_sub(pair_start_len),
5037 &pair.start,
5038 ) {
5039 selection.start -= pair_start_len;
5040 selection.end += pair.end.len();
5041
5042 return selection;
5043 }
5044 }
5045 }
5046 }
5047
5048 selection
5049 })
5050 .collect();
5051
5052 drop(buffer);
5053 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5054 selections.select(new_selections)
5055 });
5056 }
5057
5058 /// Iterate the given selections, and for each one, find the smallest surrounding
5059 /// autoclose region. This uses the ordering of the selections and the autoclose
5060 /// regions to avoid repeated comparisons.
5061 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5062 &'a self,
5063 selections: impl IntoIterator<Item = Selection<D>>,
5064 buffer: &'a MultiBufferSnapshot,
5065 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5066 let mut i = 0;
5067 let mut regions = self.autoclose_regions.as_slice();
5068 selections.into_iter().map(move |selection| {
5069 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5070
5071 let mut enclosing = None;
5072 while let Some(pair_state) = regions.get(i) {
5073 if pair_state.range.end.to_offset(buffer) < range.start {
5074 regions = ®ions[i + 1..];
5075 i = 0;
5076 } else if pair_state.range.start.to_offset(buffer) > range.end {
5077 break;
5078 } else {
5079 if pair_state.selection_id == selection.id {
5080 enclosing = Some(pair_state);
5081 }
5082 i += 1;
5083 }
5084 }
5085
5086 (selection, enclosing)
5087 })
5088 }
5089
5090 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5091 fn invalidate_autoclose_regions(
5092 &mut self,
5093 mut selections: &[Selection<Anchor>],
5094 buffer: &MultiBufferSnapshot,
5095 ) {
5096 self.autoclose_regions.retain(|state| {
5097 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5098 return false;
5099 }
5100
5101 let mut i = 0;
5102 while let Some(selection) = selections.get(i) {
5103 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5104 selections = &selections[1..];
5105 continue;
5106 }
5107 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5108 break;
5109 }
5110 if selection.id == state.selection_id {
5111 return true;
5112 } else {
5113 i += 1;
5114 }
5115 }
5116 false
5117 });
5118 }
5119
5120 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5121 let offset = position.to_offset(buffer);
5122 let (word_range, kind) =
5123 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5124 if offset > word_range.start && kind == Some(CharKind::Word) {
5125 Some(
5126 buffer
5127 .text_for_range(word_range.start..offset)
5128 .collect::<String>(),
5129 )
5130 } else {
5131 None
5132 }
5133 }
5134
5135 pub fn toggle_inline_values(
5136 &mut self,
5137 _: &ToggleInlineValues,
5138 _: &mut Window,
5139 cx: &mut Context<Self>,
5140 ) {
5141 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5142
5143 self.refresh_inline_values(cx);
5144 }
5145
5146 pub fn toggle_inlay_hints(
5147 &mut self,
5148 _: &ToggleInlayHints,
5149 _: &mut Window,
5150 cx: &mut Context<Self>,
5151 ) {
5152 self.refresh_inlay_hints(
5153 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5154 cx,
5155 );
5156 }
5157
5158 pub fn inlay_hints_enabled(&self) -> bool {
5159 self.inlay_hint_cache.enabled
5160 }
5161
5162 pub fn inline_values_enabled(&self) -> bool {
5163 self.inline_value_cache.enabled
5164 }
5165
5166 #[cfg(any(test, feature = "test-support"))]
5167 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5168 self.display_map
5169 .read(cx)
5170 .current_inlays()
5171 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5172 .cloned()
5173 .collect()
5174 }
5175
5176 #[cfg(any(test, feature = "test-support"))]
5177 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5178 self.display_map
5179 .read(cx)
5180 .current_inlays()
5181 .cloned()
5182 .collect()
5183 }
5184
5185 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5186 if self.semantics_provider.is_none() || !self.mode.is_full() {
5187 return;
5188 }
5189
5190 let reason_description = reason.description();
5191 let ignore_debounce = matches!(
5192 reason,
5193 InlayHintRefreshReason::SettingsChange(_)
5194 | InlayHintRefreshReason::Toggle(_)
5195 | InlayHintRefreshReason::ExcerptsRemoved(_)
5196 | InlayHintRefreshReason::ModifiersChanged(_)
5197 );
5198 let (invalidate_cache, required_languages) = match reason {
5199 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5200 match self.inlay_hint_cache.modifiers_override(enabled) {
5201 Some(enabled) => {
5202 if enabled {
5203 (InvalidationStrategy::RefreshRequested, None)
5204 } else {
5205 self.splice_inlays(
5206 &self
5207 .visible_inlay_hints(cx)
5208 .iter()
5209 .map(|inlay| inlay.id)
5210 .collect::<Vec<InlayId>>(),
5211 Vec::new(),
5212 cx,
5213 );
5214 return;
5215 }
5216 }
5217 None => return,
5218 }
5219 }
5220 InlayHintRefreshReason::Toggle(enabled) => {
5221 if self.inlay_hint_cache.toggle(enabled) {
5222 if enabled {
5223 (InvalidationStrategy::RefreshRequested, None)
5224 } else {
5225 self.splice_inlays(
5226 &self
5227 .visible_inlay_hints(cx)
5228 .iter()
5229 .map(|inlay| inlay.id)
5230 .collect::<Vec<InlayId>>(),
5231 Vec::new(),
5232 cx,
5233 );
5234 return;
5235 }
5236 } else {
5237 return;
5238 }
5239 }
5240 InlayHintRefreshReason::SettingsChange(new_settings) => {
5241 match self.inlay_hint_cache.update_settings(
5242 &self.buffer,
5243 new_settings,
5244 self.visible_inlay_hints(cx),
5245 cx,
5246 ) {
5247 ControlFlow::Break(Some(InlaySplice {
5248 to_remove,
5249 to_insert,
5250 })) => {
5251 self.splice_inlays(&to_remove, to_insert, cx);
5252 return;
5253 }
5254 ControlFlow::Break(None) => return,
5255 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5256 }
5257 }
5258 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5259 if let Some(InlaySplice {
5260 to_remove,
5261 to_insert,
5262 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5263 {
5264 self.splice_inlays(&to_remove, to_insert, cx);
5265 }
5266 self.display_map.update(cx, |display_map, _| {
5267 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5268 });
5269 return;
5270 }
5271 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5272 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5273 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5274 }
5275 InlayHintRefreshReason::RefreshRequested => {
5276 (InvalidationStrategy::RefreshRequested, None)
5277 }
5278 };
5279
5280 if let Some(InlaySplice {
5281 to_remove,
5282 to_insert,
5283 }) = self.inlay_hint_cache.spawn_hint_refresh(
5284 reason_description,
5285 self.visible_excerpts(required_languages.as_ref(), cx),
5286 invalidate_cache,
5287 ignore_debounce,
5288 cx,
5289 ) {
5290 self.splice_inlays(&to_remove, to_insert, cx);
5291 }
5292 }
5293
5294 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5295 self.display_map
5296 .read(cx)
5297 .current_inlays()
5298 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5299 .cloned()
5300 .collect()
5301 }
5302
5303 pub fn visible_excerpts(
5304 &self,
5305 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5306 cx: &mut Context<Editor>,
5307 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5308 let Some(project) = self.project() else {
5309 return HashMap::default();
5310 };
5311 let project = project.read(cx);
5312 let multi_buffer = self.buffer().read(cx);
5313 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5314 let multi_buffer_visible_start = self
5315 .scroll_manager
5316 .anchor()
5317 .anchor
5318 .to_point(&multi_buffer_snapshot);
5319 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5320 multi_buffer_visible_start
5321 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5322 Bias::Left,
5323 );
5324 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5325 multi_buffer_snapshot
5326 .range_to_buffer_ranges(multi_buffer_visible_range)
5327 .into_iter()
5328 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5329 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5330 let buffer_file = project::File::from_dyn(buffer.file())?;
5331 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5332 let worktree_entry = buffer_worktree
5333 .read(cx)
5334 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5335 if worktree_entry.is_ignored {
5336 return None;
5337 }
5338
5339 let language = buffer.language()?;
5340 if let Some(restrict_to_languages) = restrict_to_languages
5341 && !restrict_to_languages.contains(language)
5342 {
5343 return None;
5344 }
5345 Some((
5346 excerpt_id,
5347 (
5348 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5349 buffer.version().clone(),
5350 excerpt_visible_range,
5351 ),
5352 ))
5353 })
5354 .collect()
5355 }
5356
5357 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5358 TextLayoutDetails {
5359 text_system: window.text_system().clone(),
5360 editor_style: self.style.clone().unwrap(),
5361 rem_size: window.rem_size(),
5362 scroll_anchor: self.scroll_manager.anchor(),
5363 visible_rows: self.visible_line_count(),
5364 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5365 }
5366 }
5367
5368 pub fn splice_inlays(
5369 &self,
5370 to_remove: &[InlayId],
5371 to_insert: Vec<Inlay>,
5372 cx: &mut Context<Self>,
5373 ) {
5374 self.display_map.update(cx, |display_map, cx| {
5375 display_map.splice_inlays(to_remove, to_insert, cx)
5376 });
5377 cx.notify();
5378 }
5379
5380 fn trigger_on_type_formatting(
5381 &self,
5382 input: String,
5383 window: &mut Window,
5384 cx: &mut Context<Self>,
5385 ) -> Option<Task<Result<()>>> {
5386 if input.len() != 1 {
5387 return None;
5388 }
5389
5390 let project = self.project()?;
5391 let position = self.selections.newest_anchor().head();
5392 let (buffer, buffer_position) = self
5393 .buffer
5394 .read(cx)
5395 .text_anchor_for_position(position, cx)?;
5396
5397 let settings = language_settings::language_settings(
5398 buffer
5399 .read(cx)
5400 .language_at(buffer_position)
5401 .map(|l| l.name()),
5402 buffer.read(cx).file(),
5403 cx,
5404 );
5405 if !settings.use_on_type_format {
5406 return None;
5407 }
5408
5409 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5410 // hence we do LSP request & edit on host side only — add formats to host's history.
5411 let push_to_lsp_host_history = true;
5412 // If this is not the host, append its history with new edits.
5413 let push_to_client_history = project.read(cx).is_via_collab();
5414
5415 let on_type_formatting = project.update(cx, |project, cx| {
5416 project.on_type_format(
5417 buffer.clone(),
5418 buffer_position,
5419 input,
5420 push_to_lsp_host_history,
5421 cx,
5422 )
5423 });
5424 Some(cx.spawn_in(window, async move |editor, cx| {
5425 if let Some(transaction) = on_type_formatting.await? {
5426 if push_to_client_history {
5427 buffer
5428 .update(cx, |buffer, _| {
5429 buffer.push_transaction(transaction, Instant::now());
5430 buffer.finalize_last_transaction();
5431 })
5432 .ok();
5433 }
5434 editor.update(cx, |editor, cx| {
5435 editor.refresh_document_highlights(cx);
5436 })?;
5437 }
5438 Ok(())
5439 }))
5440 }
5441
5442 pub fn show_word_completions(
5443 &mut self,
5444 _: &ShowWordCompletions,
5445 window: &mut Window,
5446 cx: &mut Context<Self>,
5447 ) {
5448 self.open_or_update_completions_menu(
5449 Some(CompletionsMenuSource::Words {
5450 ignore_threshold: true,
5451 }),
5452 None,
5453 window,
5454 cx,
5455 );
5456 }
5457
5458 pub fn show_completions(
5459 &mut self,
5460 options: &ShowCompletions,
5461 window: &mut Window,
5462 cx: &mut Context<Self>,
5463 ) {
5464 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5465 }
5466
5467 fn open_or_update_completions_menu(
5468 &mut self,
5469 requested_source: Option<CompletionsMenuSource>,
5470 trigger: Option<&str>,
5471 window: &mut Window,
5472 cx: &mut Context<Self>,
5473 ) {
5474 if self.pending_rename.is_some() {
5475 return;
5476 }
5477
5478 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5479
5480 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5481 // inserted and selected. To handle that case, the start of the selection is used so that
5482 // the menu starts with all choices.
5483 let position = self
5484 .selections
5485 .newest_anchor()
5486 .start
5487 .bias_right(&multibuffer_snapshot);
5488 if position.diff_base_anchor.is_some() {
5489 return;
5490 }
5491 let buffer_position = multibuffer_snapshot.anchor_before(position);
5492 let Some(buffer) = buffer_position
5493 .buffer_id
5494 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5495 else {
5496 return;
5497 };
5498 let buffer_snapshot = buffer.read(cx).snapshot();
5499
5500 let query: Option<Arc<String>> =
5501 Self::completion_query(&multibuffer_snapshot, buffer_position)
5502 .map(|query| query.into());
5503
5504 drop(multibuffer_snapshot);
5505
5506 // Hide the current completions menu when query is empty. Without this, cached
5507 // completions from before the trigger char may be reused (#32774).
5508 if query.is_none() {
5509 let menu_is_open = matches!(
5510 self.context_menu.borrow().as_ref(),
5511 Some(CodeContextMenu::Completions(_))
5512 );
5513 if menu_is_open {
5514 self.hide_context_menu(window, cx);
5515 }
5516 }
5517
5518 let mut ignore_word_threshold = false;
5519 let provider = match requested_source {
5520 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5521 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5522 ignore_word_threshold = ignore_threshold;
5523 None
5524 }
5525 Some(CompletionsMenuSource::SnippetChoices) => {
5526 log::error!("bug: SnippetChoices requested_source is not handled");
5527 None
5528 }
5529 };
5530
5531 let sort_completions = provider
5532 .as_ref()
5533 .is_some_and(|provider| provider.sort_completions());
5534
5535 let filter_completions = provider
5536 .as_ref()
5537 .is_none_or(|provider| provider.filter_completions());
5538
5539 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5540 if filter_completions {
5541 menu.filter(query.clone(), provider.clone(), window, cx);
5542 }
5543 // When `is_incomplete` is false, no need to re-query completions when the current query
5544 // is a suffix of the initial query.
5545 if !menu.is_incomplete {
5546 // If the new query is a suffix of the old query (typing more characters) and
5547 // the previous result was complete, the existing completions can be filtered.
5548 //
5549 // Note that this is always true for snippet completions.
5550 let query_matches = match (&menu.initial_query, &query) {
5551 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5552 (None, _) => true,
5553 _ => false,
5554 };
5555 if query_matches {
5556 let position_matches = if menu.initial_position == position {
5557 true
5558 } else {
5559 let snapshot = self.buffer.read(cx).read(cx);
5560 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5561 };
5562 if position_matches {
5563 return;
5564 }
5565 }
5566 }
5567 };
5568
5569 let trigger_kind = match trigger {
5570 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5571 CompletionTriggerKind::TRIGGER_CHARACTER
5572 }
5573 _ => CompletionTriggerKind::INVOKED,
5574 };
5575 let completion_context = CompletionContext {
5576 trigger_character: trigger.and_then(|trigger| {
5577 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5578 Some(String::from(trigger))
5579 } else {
5580 None
5581 }
5582 }),
5583 trigger_kind,
5584 };
5585
5586 let Anchor {
5587 excerpt_id: buffer_excerpt_id,
5588 text_anchor: buffer_position,
5589 ..
5590 } = buffer_position;
5591
5592 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5593 buffer_snapshot.surrounding_word(buffer_position, None)
5594 {
5595 let word_to_exclude = buffer_snapshot
5596 .text_for_range(word_range.clone())
5597 .collect::<String>();
5598 (
5599 buffer_snapshot.anchor_before(word_range.start)
5600 ..buffer_snapshot.anchor_after(buffer_position),
5601 Some(word_to_exclude),
5602 )
5603 } else {
5604 (buffer_position..buffer_position, None)
5605 };
5606
5607 let language = buffer_snapshot
5608 .language_at(buffer_position)
5609 .map(|language| language.name());
5610
5611 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5612 .completions
5613 .clone();
5614
5615 let show_completion_documentation = buffer_snapshot
5616 .settings_at(buffer_position, cx)
5617 .show_completion_documentation;
5618
5619 // The document can be large, so stay in reasonable bounds when searching for words,
5620 // otherwise completion pop-up might be slow to appear.
5621 const WORD_LOOKUP_ROWS: u32 = 5_000;
5622 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5623 let min_word_search = buffer_snapshot.clip_point(
5624 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5625 Bias::Left,
5626 );
5627 let max_word_search = buffer_snapshot.clip_point(
5628 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5629 Bias::Right,
5630 );
5631 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5632 ..buffer_snapshot.point_to_offset(max_word_search);
5633
5634 let skip_digits = query
5635 .as_ref()
5636 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5637
5638 let omit_word_completions = !self.word_completions_enabled
5639 || (!ignore_word_threshold
5640 && match &query {
5641 Some(query) => query.chars().count() < completion_settings.words_min_length,
5642 None => completion_settings.words_min_length != 0,
5643 });
5644
5645 let (mut words, provider_responses) = match &provider {
5646 Some(provider) => {
5647 let provider_responses = provider.completions(
5648 buffer_excerpt_id,
5649 &buffer,
5650 buffer_position,
5651 completion_context,
5652 window,
5653 cx,
5654 );
5655
5656 let words = match (omit_word_completions, completion_settings.words) {
5657 (true, _) | (_, WordsCompletionMode::Disabled) => {
5658 Task::ready(BTreeMap::default())
5659 }
5660 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5661 .background_spawn(async move {
5662 buffer_snapshot.words_in_range(WordsQuery {
5663 fuzzy_contents: None,
5664 range: word_search_range,
5665 skip_digits,
5666 })
5667 }),
5668 };
5669
5670 (words, provider_responses)
5671 }
5672 None => {
5673 let words = if omit_word_completions {
5674 Task::ready(BTreeMap::default())
5675 } else {
5676 cx.background_spawn(async move {
5677 buffer_snapshot.words_in_range(WordsQuery {
5678 fuzzy_contents: None,
5679 range: word_search_range,
5680 skip_digits,
5681 })
5682 })
5683 };
5684 (words, Task::ready(Ok(Vec::new())))
5685 }
5686 };
5687
5688 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5689
5690 let id = post_inc(&mut self.next_completion_id);
5691 let task = cx.spawn_in(window, async move |editor, cx| {
5692 let Ok(()) = editor.update(cx, |this, _| {
5693 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5694 }) else {
5695 return;
5696 };
5697
5698 // TODO: Ideally completions from different sources would be selectively re-queried, so
5699 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5700 let mut completions = Vec::new();
5701 let mut is_incomplete = false;
5702 let mut display_options: Option<CompletionDisplayOptions> = None;
5703 if let Some(provider_responses) = provider_responses.await.log_err()
5704 && !provider_responses.is_empty()
5705 {
5706 for response in provider_responses {
5707 completions.extend(response.completions);
5708 is_incomplete = is_incomplete || response.is_incomplete;
5709 match display_options.as_mut() {
5710 None => {
5711 display_options = Some(response.display_options);
5712 }
5713 Some(options) => options.merge(&response.display_options),
5714 }
5715 }
5716 if completion_settings.words == WordsCompletionMode::Fallback {
5717 words = Task::ready(BTreeMap::default());
5718 }
5719 }
5720 let display_options = display_options.unwrap_or_default();
5721
5722 let mut words = words.await;
5723 if let Some(word_to_exclude) = &word_to_exclude {
5724 words.remove(word_to_exclude);
5725 }
5726 for lsp_completion in &completions {
5727 words.remove(&lsp_completion.new_text);
5728 }
5729 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5730 replace_range: word_replace_range.clone(),
5731 new_text: word.clone(),
5732 label: CodeLabel::plain(word, None),
5733 icon_path: None,
5734 documentation: None,
5735 source: CompletionSource::BufferWord {
5736 word_range,
5737 resolved: false,
5738 },
5739 insert_text_mode: Some(InsertTextMode::AS_IS),
5740 confirm: None,
5741 }));
5742
5743 let menu = if completions.is_empty() {
5744 None
5745 } else {
5746 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5747 let languages = editor
5748 .workspace
5749 .as_ref()
5750 .and_then(|(workspace, _)| workspace.upgrade())
5751 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5752 let menu = CompletionsMenu::new(
5753 id,
5754 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5755 sort_completions,
5756 show_completion_documentation,
5757 position,
5758 query.clone(),
5759 is_incomplete,
5760 buffer.clone(),
5761 completions.into(),
5762 display_options,
5763 snippet_sort_order,
5764 languages,
5765 language,
5766 cx,
5767 );
5768
5769 let query = if filter_completions { query } else { None };
5770 let matches_task = if let Some(query) = query {
5771 menu.do_async_filtering(query, cx)
5772 } else {
5773 Task::ready(menu.unfiltered_matches())
5774 };
5775 (menu, matches_task)
5776 }) else {
5777 return;
5778 };
5779
5780 let matches = matches_task.await;
5781
5782 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5783 // Newer menu already set, so exit.
5784 if let Some(CodeContextMenu::Completions(prev_menu)) =
5785 editor.context_menu.borrow().as_ref()
5786 && prev_menu.id > id
5787 {
5788 return;
5789 };
5790
5791 // Only valid to take prev_menu because it the new menu is immediately set
5792 // below, or the menu is hidden.
5793 if let Some(CodeContextMenu::Completions(prev_menu)) =
5794 editor.context_menu.borrow_mut().take()
5795 {
5796 let position_matches =
5797 if prev_menu.initial_position == menu.initial_position {
5798 true
5799 } else {
5800 let snapshot = editor.buffer.read(cx).read(cx);
5801 prev_menu.initial_position.to_offset(&snapshot)
5802 == menu.initial_position.to_offset(&snapshot)
5803 };
5804 if position_matches {
5805 // Preserve markdown cache before `set_filter_results` because it will
5806 // try to populate the documentation cache.
5807 menu.preserve_markdown_cache(prev_menu);
5808 }
5809 };
5810
5811 menu.set_filter_results(matches, provider, window, cx);
5812 }) else {
5813 return;
5814 };
5815
5816 menu.visible().then_some(menu)
5817 };
5818
5819 editor
5820 .update_in(cx, |editor, window, cx| {
5821 if editor.focus_handle.is_focused(window)
5822 && let Some(menu) = menu
5823 {
5824 *editor.context_menu.borrow_mut() =
5825 Some(CodeContextMenu::Completions(menu));
5826
5827 crate::hover_popover::hide_hover(editor, cx);
5828 if editor.show_edit_predictions_in_menu() {
5829 editor.update_visible_edit_prediction(window, cx);
5830 } else {
5831 editor.discard_edit_prediction(false, cx);
5832 }
5833
5834 cx.notify();
5835 return;
5836 }
5837
5838 if editor.completion_tasks.len() <= 1 {
5839 // If there are no more completion tasks and the last menu was empty, we should hide it.
5840 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5841 // If it was already hidden and we don't show edit predictions in the menu,
5842 // we should also show the edit prediction when available.
5843 if was_hidden && editor.show_edit_predictions_in_menu() {
5844 editor.update_visible_edit_prediction(window, cx);
5845 }
5846 }
5847 })
5848 .ok();
5849 });
5850
5851 self.completion_tasks.push((id, task));
5852 }
5853
5854 #[cfg(feature = "test-support")]
5855 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5856 let menu = self.context_menu.borrow();
5857 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5858 let completions = menu.completions.borrow();
5859 Some(completions.to_vec())
5860 } else {
5861 None
5862 }
5863 }
5864
5865 pub fn with_completions_menu_matching_id<R>(
5866 &self,
5867 id: CompletionId,
5868 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5869 ) -> R {
5870 let mut context_menu = self.context_menu.borrow_mut();
5871 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5872 return f(None);
5873 };
5874 if completions_menu.id != id {
5875 return f(None);
5876 }
5877 f(Some(completions_menu))
5878 }
5879
5880 pub fn confirm_completion(
5881 &mut self,
5882 action: &ConfirmCompletion,
5883 window: &mut Window,
5884 cx: &mut Context<Self>,
5885 ) -> Option<Task<Result<()>>> {
5886 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5887 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5888 }
5889
5890 pub fn confirm_completion_insert(
5891 &mut self,
5892 _: &ConfirmCompletionInsert,
5893 window: &mut Window,
5894 cx: &mut Context<Self>,
5895 ) -> Option<Task<Result<()>>> {
5896 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5897 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5898 }
5899
5900 pub fn confirm_completion_replace(
5901 &mut self,
5902 _: &ConfirmCompletionReplace,
5903 window: &mut Window,
5904 cx: &mut Context<Self>,
5905 ) -> Option<Task<Result<()>>> {
5906 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5907 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5908 }
5909
5910 pub fn compose_completion(
5911 &mut self,
5912 action: &ComposeCompletion,
5913 window: &mut Window,
5914 cx: &mut Context<Self>,
5915 ) -> Option<Task<Result<()>>> {
5916 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5917 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5918 }
5919
5920 fn do_completion(
5921 &mut self,
5922 item_ix: Option<usize>,
5923 intent: CompletionIntent,
5924 window: &mut Window,
5925 cx: &mut Context<Editor>,
5926 ) -> Option<Task<Result<()>>> {
5927 use language::ToOffset as _;
5928
5929 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5930 else {
5931 return None;
5932 };
5933
5934 let candidate_id = {
5935 let entries = completions_menu.entries.borrow();
5936 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5937 if self.show_edit_predictions_in_menu() {
5938 self.discard_edit_prediction(true, cx);
5939 }
5940 mat.candidate_id
5941 };
5942
5943 let completion = completions_menu
5944 .completions
5945 .borrow()
5946 .get(candidate_id)?
5947 .clone();
5948 cx.stop_propagation();
5949
5950 let buffer_handle = completions_menu.buffer.clone();
5951
5952 let CompletionEdit {
5953 new_text,
5954 snippet,
5955 replace_range,
5956 } = process_completion_for_edit(
5957 &completion,
5958 intent,
5959 &buffer_handle,
5960 &completions_menu.initial_position.text_anchor,
5961 cx,
5962 );
5963
5964 let buffer = buffer_handle.read(cx);
5965 let snapshot = self.buffer.read(cx).snapshot(cx);
5966 let newest_anchor = self.selections.newest_anchor();
5967 let replace_range_multibuffer = {
5968 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5969 let multibuffer_anchor = snapshot
5970 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5971 .unwrap()
5972 ..snapshot
5973 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5974 .unwrap();
5975 multibuffer_anchor.start.to_offset(&snapshot)
5976 ..multibuffer_anchor.end.to_offset(&snapshot)
5977 };
5978 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5979 return None;
5980 }
5981
5982 let old_text = buffer
5983 .text_for_range(replace_range.clone())
5984 .collect::<String>();
5985 let lookbehind = newest_anchor
5986 .start
5987 .text_anchor
5988 .to_offset(buffer)
5989 .saturating_sub(replace_range.start);
5990 let lookahead = replace_range
5991 .end
5992 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5993 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5994 let suffix = &old_text[lookbehind.min(old_text.len())..];
5995
5996 let selections = self.selections.all::<usize>(cx);
5997 let mut ranges = Vec::new();
5998 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5999
6000 for selection in &selections {
6001 let range = if selection.id == newest_anchor.id {
6002 replace_range_multibuffer.clone()
6003 } else {
6004 let mut range = selection.range();
6005
6006 // if prefix is present, don't duplicate it
6007 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6008 range.start = range.start.saturating_sub(lookbehind);
6009
6010 // if suffix is also present, mimic the newest cursor and replace it
6011 if selection.id != newest_anchor.id
6012 && snapshot.contains_str_at(range.end, suffix)
6013 {
6014 range.end += lookahead;
6015 }
6016 }
6017 range
6018 };
6019
6020 ranges.push(range.clone());
6021
6022 if !self.linked_edit_ranges.is_empty() {
6023 let start_anchor = snapshot.anchor_before(range.start);
6024 let end_anchor = snapshot.anchor_after(range.end);
6025 if let Some(ranges) = self
6026 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6027 {
6028 for (buffer, edits) in ranges {
6029 linked_edits
6030 .entry(buffer.clone())
6031 .or_default()
6032 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6033 }
6034 }
6035 }
6036 }
6037
6038 let common_prefix_len = old_text
6039 .chars()
6040 .zip(new_text.chars())
6041 .take_while(|(a, b)| a == b)
6042 .map(|(a, _)| a.len_utf8())
6043 .sum::<usize>();
6044
6045 cx.emit(EditorEvent::InputHandled {
6046 utf16_range_to_replace: None,
6047 text: new_text[common_prefix_len..].into(),
6048 });
6049
6050 self.transact(window, cx, |editor, window, cx| {
6051 if let Some(mut snippet) = snippet {
6052 snippet.text = new_text.to_string();
6053 editor
6054 .insert_snippet(&ranges, snippet, window, cx)
6055 .log_err();
6056 } else {
6057 editor.buffer.update(cx, |multi_buffer, cx| {
6058 let auto_indent = match completion.insert_text_mode {
6059 Some(InsertTextMode::AS_IS) => None,
6060 _ => editor.autoindent_mode.clone(),
6061 };
6062 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6063 multi_buffer.edit(edits, auto_indent, cx);
6064 });
6065 }
6066 for (buffer, edits) in linked_edits {
6067 buffer.update(cx, |buffer, cx| {
6068 let snapshot = buffer.snapshot();
6069 let edits = edits
6070 .into_iter()
6071 .map(|(range, text)| {
6072 use text::ToPoint as TP;
6073 let end_point = TP::to_point(&range.end, &snapshot);
6074 let start_point = TP::to_point(&range.start, &snapshot);
6075 (start_point..end_point, text)
6076 })
6077 .sorted_by_key(|(range, _)| range.start);
6078 buffer.edit(edits, None, cx);
6079 })
6080 }
6081
6082 editor.refresh_edit_prediction(true, false, window, cx);
6083 });
6084 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6085
6086 let show_new_completions_on_confirm = completion
6087 .confirm
6088 .as_ref()
6089 .is_some_and(|confirm| confirm(intent, window, cx));
6090 if show_new_completions_on_confirm {
6091 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6092 }
6093
6094 let provider = self.completion_provider.as_ref()?;
6095 drop(completion);
6096 let apply_edits = provider.apply_additional_edits_for_completion(
6097 buffer_handle,
6098 completions_menu.completions.clone(),
6099 candidate_id,
6100 true,
6101 cx,
6102 );
6103
6104 let editor_settings = EditorSettings::get_global(cx);
6105 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6106 // After the code completion is finished, users often want to know what signatures are needed.
6107 // so we should automatically call signature_help
6108 self.show_signature_help(&ShowSignatureHelp, window, cx);
6109 }
6110
6111 Some(cx.foreground_executor().spawn(async move {
6112 apply_edits.await?;
6113 Ok(())
6114 }))
6115 }
6116
6117 pub fn toggle_code_actions(
6118 &mut self,
6119 action: &ToggleCodeActions,
6120 window: &mut Window,
6121 cx: &mut Context<Self>,
6122 ) {
6123 let quick_launch = action.quick_launch;
6124 let mut context_menu = self.context_menu.borrow_mut();
6125 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6126 if code_actions.deployed_from == action.deployed_from {
6127 // Toggle if we're selecting the same one
6128 *context_menu = None;
6129 cx.notify();
6130 return;
6131 } else {
6132 // Otherwise, clear it and start a new one
6133 *context_menu = None;
6134 cx.notify();
6135 }
6136 }
6137 drop(context_menu);
6138 let snapshot = self.snapshot(window, cx);
6139 let deployed_from = action.deployed_from.clone();
6140 let action = action.clone();
6141 self.completion_tasks.clear();
6142 self.discard_edit_prediction(false, cx);
6143
6144 let multibuffer_point = match &action.deployed_from {
6145 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6146 DisplayPoint::new(*row, 0).to_point(&snapshot)
6147 }
6148 _ => self.selections.newest::<Point>(cx).head(),
6149 };
6150 let Some((buffer, buffer_row)) = snapshot
6151 .buffer_snapshot
6152 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6153 .and_then(|(buffer_snapshot, range)| {
6154 self.buffer()
6155 .read(cx)
6156 .buffer(buffer_snapshot.remote_id())
6157 .map(|buffer| (buffer, range.start.row))
6158 })
6159 else {
6160 return;
6161 };
6162 let buffer_id = buffer.read(cx).remote_id();
6163 let tasks = self
6164 .tasks
6165 .get(&(buffer_id, buffer_row))
6166 .map(|t| Arc::new(t.to_owned()));
6167
6168 if !self.focus_handle.is_focused(window) {
6169 return;
6170 }
6171 let project = self.project.clone();
6172
6173 let code_actions_task = match deployed_from {
6174 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6175 _ => self.code_actions(buffer_row, window, cx),
6176 };
6177
6178 let runnable_task = match deployed_from {
6179 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6180 _ => {
6181 let mut task_context_task = Task::ready(None);
6182 if let Some(tasks) = &tasks
6183 && let Some(project) = project
6184 {
6185 task_context_task =
6186 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6187 }
6188
6189 cx.spawn_in(window, {
6190 let buffer = buffer.clone();
6191 async move |editor, cx| {
6192 let task_context = task_context_task.await;
6193
6194 let resolved_tasks =
6195 tasks
6196 .zip(task_context.clone())
6197 .map(|(tasks, task_context)| ResolvedTasks {
6198 templates: tasks.resolve(&task_context).collect(),
6199 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6200 multibuffer_point.row,
6201 tasks.column,
6202 )),
6203 });
6204 let debug_scenarios = editor
6205 .update(cx, |editor, cx| {
6206 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6207 })?
6208 .await;
6209 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6210 }
6211 })
6212 }
6213 };
6214
6215 cx.spawn_in(window, async move |editor, cx| {
6216 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6217 let code_actions = code_actions_task.await;
6218 let spawn_straight_away = quick_launch
6219 && resolved_tasks
6220 .as_ref()
6221 .is_some_and(|tasks| tasks.templates.len() == 1)
6222 && code_actions
6223 .as_ref()
6224 .is_none_or(|actions| actions.is_empty())
6225 && debug_scenarios.is_empty();
6226
6227 editor.update_in(cx, |editor, window, cx| {
6228 crate::hover_popover::hide_hover(editor, cx);
6229 let actions = CodeActionContents::new(
6230 resolved_tasks,
6231 code_actions,
6232 debug_scenarios,
6233 task_context.unwrap_or_default(),
6234 );
6235
6236 // Don't show the menu if there are no actions available
6237 if actions.is_empty() {
6238 cx.notify();
6239 return Task::ready(Ok(()));
6240 }
6241
6242 *editor.context_menu.borrow_mut() =
6243 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6244 buffer,
6245 actions,
6246 selected_item: Default::default(),
6247 scroll_handle: UniformListScrollHandle::default(),
6248 deployed_from,
6249 }));
6250 cx.notify();
6251 if spawn_straight_away
6252 && let Some(task) = editor.confirm_code_action(
6253 &ConfirmCodeAction { item_ix: Some(0) },
6254 window,
6255 cx,
6256 )
6257 {
6258 return task;
6259 }
6260
6261 Task::ready(Ok(()))
6262 })
6263 })
6264 .detach_and_log_err(cx);
6265 }
6266
6267 fn debug_scenarios(
6268 &mut self,
6269 resolved_tasks: &Option<ResolvedTasks>,
6270 buffer: &Entity<Buffer>,
6271 cx: &mut App,
6272 ) -> Task<Vec<task::DebugScenario>> {
6273 maybe!({
6274 let project = self.project()?;
6275 let dap_store = project.read(cx).dap_store();
6276 let mut scenarios = vec![];
6277 let resolved_tasks = resolved_tasks.as_ref()?;
6278 let buffer = buffer.read(cx);
6279 let language = buffer.language()?;
6280 let file = buffer.file();
6281 let debug_adapter = language_settings(language.name().into(), file, cx)
6282 .debuggers
6283 .first()
6284 .map(SharedString::from)
6285 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6286
6287 dap_store.update(cx, |dap_store, cx| {
6288 for (_, task) in &resolved_tasks.templates {
6289 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6290 task.original_task().clone(),
6291 debug_adapter.clone().into(),
6292 task.display_label().to_owned().into(),
6293 cx,
6294 );
6295 scenarios.push(maybe_scenario);
6296 }
6297 });
6298 Some(cx.background_spawn(async move {
6299 futures::future::join_all(scenarios)
6300 .await
6301 .into_iter()
6302 .flatten()
6303 .collect::<Vec<_>>()
6304 }))
6305 })
6306 .unwrap_or_else(|| Task::ready(vec![]))
6307 }
6308
6309 fn code_actions(
6310 &mut self,
6311 buffer_row: u32,
6312 window: &mut Window,
6313 cx: &mut Context<Self>,
6314 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6315 let mut task = self.code_actions_task.take();
6316 cx.spawn_in(window, async move |editor, cx| {
6317 while let Some(prev_task) = task {
6318 prev_task.await.log_err();
6319 task = editor
6320 .update(cx, |this, _| this.code_actions_task.take())
6321 .ok()?;
6322 }
6323
6324 editor
6325 .update(cx, |editor, cx| {
6326 editor
6327 .available_code_actions
6328 .clone()
6329 .and_then(|(location, code_actions)| {
6330 let snapshot = location.buffer.read(cx).snapshot();
6331 let point_range = location.range.to_point(&snapshot);
6332 let point_range = point_range.start.row..=point_range.end.row;
6333 if point_range.contains(&buffer_row) {
6334 Some(code_actions)
6335 } else {
6336 None
6337 }
6338 })
6339 })
6340 .ok()
6341 .flatten()
6342 })
6343 }
6344
6345 pub fn confirm_code_action(
6346 &mut self,
6347 action: &ConfirmCodeAction,
6348 window: &mut Window,
6349 cx: &mut Context<Self>,
6350 ) -> Option<Task<Result<()>>> {
6351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6352
6353 let actions_menu =
6354 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6355 menu
6356 } else {
6357 return None;
6358 };
6359
6360 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6361 let action = actions_menu.actions.get(action_ix)?;
6362 let title = action.label();
6363 let buffer = actions_menu.buffer;
6364 let workspace = self.workspace()?;
6365
6366 match action {
6367 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6368 workspace.update(cx, |workspace, cx| {
6369 workspace.schedule_resolved_task(
6370 task_source_kind,
6371 resolved_task,
6372 false,
6373 window,
6374 cx,
6375 );
6376
6377 Some(Task::ready(Ok(())))
6378 })
6379 }
6380 CodeActionsItem::CodeAction {
6381 excerpt_id,
6382 action,
6383 provider,
6384 } => {
6385 let apply_code_action =
6386 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6387 let workspace = workspace.downgrade();
6388 Some(cx.spawn_in(window, async move |editor, cx| {
6389 let project_transaction = apply_code_action.await?;
6390 Self::open_project_transaction(
6391 &editor,
6392 workspace,
6393 project_transaction,
6394 title,
6395 cx,
6396 )
6397 .await
6398 }))
6399 }
6400 CodeActionsItem::DebugScenario(scenario) => {
6401 let context = actions_menu.actions.context;
6402
6403 workspace.update(cx, |workspace, cx| {
6404 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6405 workspace.start_debug_session(
6406 scenario,
6407 context,
6408 Some(buffer),
6409 None,
6410 window,
6411 cx,
6412 );
6413 });
6414 Some(Task::ready(Ok(())))
6415 }
6416 }
6417 }
6418
6419 pub async fn open_project_transaction(
6420 editor: &WeakEntity<Editor>,
6421 workspace: WeakEntity<Workspace>,
6422 transaction: ProjectTransaction,
6423 title: String,
6424 cx: &mut AsyncWindowContext,
6425 ) -> Result<()> {
6426 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6427 cx.update(|_, cx| {
6428 entries.sort_unstable_by_key(|(buffer, _)| {
6429 buffer.read(cx).file().map(|f| f.path().clone())
6430 });
6431 })?;
6432 if entries.is_empty() {
6433 return Ok(());
6434 }
6435
6436 // If the project transaction's edits are all contained within this editor, then
6437 // avoid opening a new editor to display them.
6438
6439 if let [(buffer, transaction)] = &*entries {
6440 let excerpt = editor.update(cx, |editor, cx| {
6441 editor
6442 .buffer()
6443 .read(cx)
6444 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6445 })?;
6446 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6447 && excerpted_buffer == *buffer
6448 {
6449 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6450 let excerpt_range = excerpt_range.to_offset(buffer);
6451 buffer
6452 .edited_ranges_for_transaction::<usize>(transaction)
6453 .all(|range| {
6454 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6455 })
6456 })?;
6457
6458 if all_edits_within_excerpt {
6459 return Ok(());
6460 }
6461 }
6462 }
6463
6464 let mut ranges_to_highlight = Vec::new();
6465 let excerpt_buffer = cx.new(|cx| {
6466 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6467 for (buffer_handle, transaction) in &entries {
6468 let edited_ranges = buffer_handle
6469 .read(cx)
6470 .edited_ranges_for_transaction::<Point>(transaction)
6471 .collect::<Vec<_>>();
6472 let (ranges, _) = multibuffer.set_excerpts_for_path(
6473 PathKey::for_buffer(buffer_handle, cx),
6474 buffer_handle.clone(),
6475 edited_ranges,
6476 multibuffer_context_lines(cx),
6477 cx,
6478 );
6479
6480 ranges_to_highlight.extend(ranges);
6481 }
6482 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6483 multibuffer
6484 })?;
6485
6486 workspace.update_in(cx, |workspace, window, cx| {
6487 let project = workspace.project().clone();
6488 let editor =
6489 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6490 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6491 editor.update(cx, |editor, cx| {
6492 editor.highlight_background::<Self>(
6493 &ranges_to_highlight,
6494 |theme| theme.colors().editor_highlighted_line_background,
6495 cx,
6496 );
6497 });
6498 })?;
6499
6500 Ok(())
6501 }
6502
6503 pub fn clear_code_action_providers(&mut self) {
6504 self.code_action_providers.clear();
6505 self.available_code_actions.take();
6506 }
6507
6508 pub fn add_code_action_provider(
6509 &mut self,
6510 provider: Rc<dyn CodeActionProvider>,
6511 window: &mut Window,
6512 cx: &mut Context<Self>,
6513 ) {
6514 if self
6515 .code_action_providers
6516 .iter()
6517 .any(|existing_provider| existing_provider.id() == provider.id())
6518 {
6519 return;
6520 }
6521
6522 self.code_action_providers.push(provider);
6523 self.refresh_code_actions(window, cx);
6524 }
6525
6526 pub fn remove_code_action_provider(
6527 &mut self,
6528 id: Arc<str>,
6529 window: &mut Window,
6530 cx: &mut Context<Self>,
6531 ) {
6532 self.code_action_providers
6533 .retain(|provider| provider.id() != id);
6534 self.refresh_code_actions(window, cx);
6535 }
6536
6537 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6538 !self.code_action_providers.is_empty()
6539 && EditorSettings::get_global(cx).toolbar.code_actions
6540 }
6541
6542 pub fn has_available_code_actions(&self) -> bool {
6543 self.available_code_actions
6544 .as_ref()
6545 .is_some_and(|(_, actions)| !actions.is_empty())
6546 }
6547
6548 fn render_inline_code_actions(
6549 &self,
6550 icon_size: ui::IconSize,
6551 display_row: DisplayRow,
6552 is_active: bool,
6553 cx: &mut Context<Self>,
6554 ) -> AnyElement {
6555 let show_tooltip = !self.context_menu_visible();
6556 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6557 .icon_size(icon_size)
6558 .shape(ui::IconButtonShape::Square)
6559 .icon_color(ui::Color::Hidden)
6560 .toggle_state(is_active)
6561 .when(show_tooltip, |this| {
6562 this.tooltip({
6563 let focus_handle = self.focus_handle.clone();
6564 move |window, cx| {
6565 Tooltip::for_action_in(
6566 "Toggle Code Actions",
6567 &ToggleCodeActions {
6568 deployed_from: None,
6569 quick_launch: false,
6570 },
6571 &focus_handle,
6572 window,
6573 cx,
6574 )
6575 }
6576 })
6577 })
6578 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6579 window.focus(&editor.focus_handle(cx));
6580 editor.toggle_code_actions(
6581 &crate::actions::ToggleCodeActions {
6582 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6583 display_row,
6584 )),
6585 quick_launch: false,
6586 },
6587 window,
6588 cx,
6589 );
6590 }))
6591 .into_any_element()
6592 }
6593
6594 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6595 &self.context_menu
6596 }
6597
6598 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6599 let newest_selection = self.selections.newest_anchor().clone();
6600 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6601 let buffer = self.buffer.read(cx);
6602 if newest_selection.head().diff_base_anchor.is_some() {
6603 return None;
6604 }
6605 let (start_buffer, start) =
6606 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6607 let (end_buffer, end) =
6608 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6609 if start_buffer != end_buffer {
6610 return None;
6611 }
6612
6613 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6614 cx.background_executor()
6615 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6616 .await;
6617
6618 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6619 let providers = this.code_action_providers.clone();
6620 let tasks = this
6621 .code_action_providers
6622 .iter()
6623 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6624 .collect::<Vec<_>>();
6625 (providers, tasks)
6626 })?;
6627
6628 let mut actions = Vec::new();
6629 for (provider, provider_actions) in
6630 providers.into_iter().zip(future::join_all(tasks).await)
6631 {
6632 if let Some(provider_actions) = provider_actions.log_err() {
6633 actions.extend(provider_actions.into_iter().map(|action| {
6634 AvailableCodeAction {
6635 excerpt_id: newest_selection.start.excerpt_id,
6636 action,
6637 provider: provider.clone(),
6638 }
6639 }));
6640 }
6641 }
6642
6643 this.update(cx, |this, cx| {
6644 this.available_code_actions = if actions.is_empty() {
6645 None
6646 } else {
6647 Some((
6648 Location {
6649 buffer: start_buffer,
6650 range: start..end,
6651 },
6652 actions.into(),
6653 ))
6654 };
6655 cx.notify();
6656 })
6657 }));
6658 None
6659 }
6660
6661 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6662 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6663 self.show_git_blame_inline = false;
6664
6665 self.show_git_blame_inline_delay_task =
6666 Some(cx.spawn_in(window, async move |this, cx| {
6667 cx.background_executor().timer(delay).await;
6668
6669 this.update(cx, |this, cx| {
6670 this.show_git_blame_inline = true;
6671 cx.notify();
6672 })
6673 .log_err();
6674 }));
6675 }
6676 }
6677
6678 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6679 let snapshot = self.snapshot(window, cx);
6680 let cursor = self.selections.newest::<Point>(cx).head();
6681 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6682 else {
6683 return;
6684 };
6685
6686 let Some(blame) = self.blame.as_ref() else {
6687 return;
6688 };
6689
6690 let row_info = RowInfo {
6691 buffer_id: Some(buffer.remote_id()),
6692 buffer_row: Some(point.row),
6693 ..Default::default()
6694 };
6695 let Some((buffer, blame_entry)) = blame
6696 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6697 .flatten()
6698 else {
6699 return;
6700 };
6701
6702 let anchor = self.selections.newest_anchor().head();
6703 let position = self.to_pixel_point(anchor, &snapshot, window);
6704 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6705 self.show_blame_popover(
6706 buffer,
6707 &blame_entry,
6708 position + last_bounds.origin,
6709 true,
6710 cx,
6711 );
6712 };
6713 }
6714
6715 fn show_blame_popover(
6716 &mut self,
6717 buffer: BufferId,
6718 blame_entry: &BlameEntry,
6719 position: gpui::Point<Pixels>,
6720 ignore_timeout: bool,
6721 cx: &mut Context<Self>,
6722 ) {
6723 if let Some(state) = &mut self.inline_blame_popover {
6724 state.hide_task.take();
6725 } else {
6726 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6727 let blame_entry = blame_entry.clone();
6728 let show_task = cx.spawn(async move |editor, cx| {
6729 if !ignore_timeout {
6730 cx.background_executor()
6731 .timer(std::time::Duration::from_millis(blame_popover_delay))
6732 .await;
6733 }
6734 editor
6735 .update(cx, |editor, cx| {
6736 editor.inline_blame_popover_show_task.take();
6737 let Some(blame) = editor.blame.as_ref() else {
6738 return;
6739 };
6740 let blame = blame.read(cx);
6741 let details = blame.details_for_entry(buffer, &blame_entry);
6742 let markdown = cx.new(|cx| {
6743 Markdown::new(
6744 details
6745 .as_ref()
6746 .map(|message| message.message.clone())
6747 .unwrap_or_default(),
6748 None,
6749 None,
6750 cx,
6751 )
6752 });
6753 editor.inline_blame_popover = Some(InlineBlamePopover {
6754 position,
6755 hide_task: None,
6756 popover_bounds: None,
6757 popover_state: InlineBlamePopoverState {
6758 scroll_handle: ScrollHandle::new(),
6759 commit_message: details,
6760 markdown,
6761 },
6762 keyboard_grace: ignore_timeout,
6763 });
6764 cx.notify();
6765 })
6766 .ok();
6767 });
6768 self.inline_blame_popover_show_task = Some(show_task);
6769 }
6770 }
6771
6772 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6773 self.inline_blame_popover_show_task.take();
6774 if let Some(state) = &mut self.inline_blame_popover {
6775 let hide_task = cx.spawn(async move |editor, cx| {
6776 cx.background_executor()
6777 .timer(std::time::Duration::from_millis(100))
6778 .await;
6779 editor
6780 .update(cx, |editor, cx| {
6781 editor.inline_blame_popover.take();
6782 cx.notify();
6783 })
6784 .ok();
6785 });
6786 state.hide_task = Some(hide_task);
6787 }
6788 }
6789
6790 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6791 if self.pending_rename.is_some() {
6792 return None;
6793 }
6794
6795 let provider = self.semantics_provider.clone()?;
6796 let buffer = self.buffer.read(cx);
6797 let newest_selection = self.selections.newest_anchor().clone();
6798 let cursor_position = newest_selection.head();
6799 let (cursor_buffer, cursor_buffer_position) =
6800 buffer.text_anchor_for_position(cursor_position, cx)?;
6801 let (tail_buffer, tail_buffer_position) =
6802 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6803 if cursor_buffer != tail_buffer {
6804 return None;
6805 }
6806
6807 let snapshot = cursor_buffer.read(cx).snapshot();
6808 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6809 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6810 if start_word_range != end_word_range {
6811 self.document_highlights_task.take();
6812 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6813 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6814 return None;
6815 }
6816
6817 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6818 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6819 cx.background_executor()
6820 .timer(Duration::from_millis(debounce))
6821 .await;
6822
6823 let highlights = if let Some(highlights) = cx
6824 .update(|cx| {
6825 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6826 })
6827 .ok()
6828 .flatten()
6829 {
6830 highlights.await.log_err()
6831 } else {
6832 None
6833 };
6834
6835 if let Some(highlights) = highlights {
6836 this.update(cx, |this, cx| {
6837 if this.pending_rename.is_some() {
6838 return;
6839 }
6840
6841 let buffer = this.buffer.read(cx);
6842 if buffer
6843 .text_anchor_for_position(cursor_position, cx)
6844 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6845 {
6846 return;
6847 }
6848
6849 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6850 let mut write_ranges = Vec::new();
6851 let mut read_ranges = Vec::new();
6852 for highlight in highlights {
6853 let buffer_id = cursor_buffer.read(cx).remote_id();
6854 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6855 {
6856 let start = highlight
6857 .range
6858 .start
6859 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6860 let end = highlight
6861 .range
6862 .end
6863 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6864 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6865 continue;
6866 }
6867
6868 let range = Anchor {
6869 buffer_id: Some(buffer_id),
6870 excerpt_id,
6871 text_anchor: start,
6872 diff_base_anchor: None,
6873 }..Anchor {
6874 buffer_id: Some(buffer_id),
6875 excerpt_id,
6876 text_anchor: end,
6877 diff_base_anchor: None,
6878 };
6879 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6880 write_ranges.push(range);
6881 } else {
6882 read_ranges.push(range);
6883 }
6884 }
6885 }
6886
6887 this.highlight_background::<DocumentHighlightRead>(
6888 &read_ranges,
6889 |theme| theme.colors().editor_document_highlight_read_background,
6890 cx,
6891 );
6892 this.highlight_background::<DocumentHighlightWrite>(
6893 &write_ranges,
6894 |theme| theme.colors().editor_document_highlight_write_background,
6895 cx,
6896 );
6897 cx.notify();
6898 })
6899 .log_err();
6900 }
6901 }));
6902 None
6903 }
6904
6905 fn prepare_highlight_query_from_selection(
6906 &mut self,
6907 cx: &mut Context<Editor>,
6908 ) -> Option<(String, Range<Anchor>)> {
6909 if matches!(self.mode, EditorMode::SingleLine) {
6910 return None;
6911 }
6912 if !EditorSettings::get_global(cx).selection_highlight {
6913 return None;
6914 }
6915 if self.selections.count() != 1 || self.selections.line_mode() {
6916 return None;
6917 }
6918 let selection = self.selections.newest::<Point>(cx);
6919 if selection.is_empty() || selection.start.row != selection.end.row {
6920 return None;
6921 }
6922 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6923 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6924 let query = multi_buffer_snapshot
6925 .text_for_range(selection_anchor_range.clone())
6926 .collect::<String>();
6927 if query.trim().is_empty() {
6928 return None;
6929 }
6930 Some((query, selection_anchor_range))
6931 }
6932
6933 fn update_selection_occurrence_highlights(
6934 &mut self,
6935 query_text: String,
6936 query_range: Range<Anchor>,
6937 multi_buffer_range_to_query: Range<Point>,
6938 use_debounce: bool,
6939 window: &mut Window,
6940 cx: &mut Context<Editor>,
6941 ) -> Task<()> {
6942 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6943 cx.spawn_in(window, async move |editor, cx| {
6944 if use_debounce {
6945 cx.background_executor()
6946 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6947 .await;
6948 }
6949 let match_task = cx.background_spawn(async move {
6950 let buffer_ranges = multi_buffer_snapshot
6951 .range_to_buffer_ranges(multi_buffer_range_to_query)
6952 .into_iter()
6953 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6954 let mut match_ranges = Vec::new();
6955 let Ok(regex) = project::search::SearchQuery::text(
6956 query_text.clone(),
6957 false,
6958 false,
6959 false,
6960 Default::default(),
6961 Default::default(),
6962 false,
6963 None,
6964 ) else {
6965 return Vec::default();
6966 };
6967 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6968 match_ranges.extend(
6969 regex
6970 .search(buffer_snapshot, Some(search_range.clone()))
6971 .await
6972 .into_iter()
6973 .filter_map(|match_range| {
6974 let match_start = buffer_snapshot
6975 .anchor_after(search_range.start + match_range.start);
6976 let match_end = buffer_snapshot
6977 .anchor_before(search_range.start + match_range.end);
6978 let match_anchor_range = Anchor::range_in_buffer(
6979 excerpt_id,
6980 buffer_snapshot.remote_id(),
6981 match_start..match_end,
6982 );
6983 (match_anchor_range != query_range).then_some(match_anchor_range)
6984 }),
6985 );
6986 }
6987 match_ranges
6988 });
6989 let match_ranges = match_task.await;
6990 editor
6991 .update_in(cx, |editor, _, cx| {
6992 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6993 if !match_ranges.is_empty() {
6994 editor.highlight_background::<SelectedTextHighlight>(
6995 &match_ranges,
6996 |theme| theme.colors().editor_document_highlight_bracket_background,
6997 cx,
6998 )
6999 }
7000 })
7001 .log_err();
7002 })
7003 }
7004
7005 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7006 struct NewlineFold;
7007 let type_id = std::any::TypeId::of::<NewlineFold>();
7008 if !self.mode.is_single_line() {
7009 return;
7010 }
7011 let snapshot = self.snapshot(window, cx);
7012 if snapshot.buffer_snapshot.max_point().row == 0 {
7013 return;
7014 }
7015 let task = cx.background_spawn(async move {
7016 let new_newlines = snapshot
7017 .buffer_chars_at(0)
7018 .filter_map(|(c, i)| {
7019 if c == '\n' {
7020 Some(
7021 snapshot.buffer_snapshot.anchor_after(i)
7022 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7023 )
7024 } else {
7025 None
7026 }
7027 })
7028 .collect::<Vec<_>>();
7029 let existing_newlines = snapshot
7030 .folds_in_range(0..snapshot.buffer_snapshot.len())
7031 .filter_map(|fold| {
7032 if fold.placeholder.type_tag == Some(type_id) {
7033 Some(fold.range.start..fold.range.end)
7034 } else {
7035 None
7036 }
7037 })
7038 .collect::<Vec<_>>();
7039
7040 (new_newlines, existing_newlines)
7041 });
7042 self.folding_newlines = cx.spawn(async move |this, cx| {
7043 let (new_newlines, existing_newlines) = task.await;
7044 if new_newlines == existing_newlines {
7045 return;
7046 }
7047 let placeholder = FoldPlaceholder {
7048 render: Arc::new(move |_, _, cx| {
7049 div()
7050 .bg(cx.theme().status().hint_background)
7051 .border_b_1()
7052 .size_full()
7053 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7054 .border_color(cx.theme().status().hint)
7055 .child("\\n")
7056 .into_any()
7057 }),
7058 constrain_width: false,
7059 merge_adjacent: false,
7060 type_tag: Some(type_id),
7061 };
7062 let creases = new_newlines
7063 .into_iter()
7064 .map(|range| Crease::simple(range, placeholder.clone()))
7065 .collect();
7066 this.update(cx, |this, cx| {
7067 this.display_map.update(cx, |display_map, cx| {
7068 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7069 display_map.fold(creases, cx);
7070 });
7071 })
7072 .ok();
7073 });
7074 }
7075
7076 fn refresh_selected_text_highlights(
7077 &mut self,
7078 on_buffer_edit: bool,
7079 window: &mut Window,
7080 cx: &mut Context<Editor>,
7081 ) {
7082 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7083 else {
7084 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7085 self.quick_selection_highlight_task.take();
7086 self.debounced_selection_highlight_task.take();
7087 return;
7088 };
7089 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7090 if on_buffer_edit
7091 || self
7092 .quick_selection_highlight_task
7093 .as_ref()
7094 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7095 {
7096 let multi_buffer_visible_start = self
7097 .scroll_manager
7098 .anchor()
7099 .anchor
7100 .to_point(&multi_buffer_snapshot);
7101 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7102 multi_buffer_visible_start
7103 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7104 Bias::Left,
7105 );
7106 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7107 self.quick_selection_highlight_task = Some((
7108 query_range.clone(),
7109 self.update_selection_occurrence_highlights(
7110 query_text.clone(),
7111 query_range.clone(),
7112 multi_buffer_visible_range,
7113 false,
7114 window,
7115 cx,
7116 ),
7117 ));
7118 }
7119 if on_buffer_edit
7120 || self
7121 .debounced_selection_highlight_task
7122 .as_ref()
7123 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7124 {
7125 let multi_buffer_start = multi_buffer_snapshot
7126 .anchor_before(0)
7127 .to_point(&multi_buffer_snapshot);
7128 let multi_buffer_end = multi_buffer_snapshot
7129 .anchor_after(multi_buffer_snapshot.len())
7130 .to_point(&multi_buffer_snapshot);
7131 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7132 self.debounced_selection_highlight_task = Some((
7133 query_range.clone(),
7134 self.update_selection_occurrence_highlights(
7135 query_text,
7136 query_range,
7137 multi_buffer_full_range,
7138 true,
7139 window,
7140 cx,
7141 ),
7142 ));
7143 }
7144 }
7145
7146 pub fn refresh_edit_prediction(
7147 &mut self,
7148 debounce: bool,
7149 user_requested: bool,
7150 window: &mut Window,
7151 cx: &mut Context<Self>,
7152 ) -> Option<()> {
7153 if DisableAiSettings::get_global(cx).disable_ai {
7154 return None;
7155 }
7156
7157 let provider = self.edit_prediction_provider()?;
7158 let cursor = self.selections.newest_anchor().head();
7159 let (buffer, cursor_buffer_position) =
7160 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7161
7162 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7163 self.discard_edit_prediction(false, cx);
7164 return None;
7165 }
7166
7167 self.update_visible_edit_prediction(window, cx);
7168
7169 if !user_requested
7170 && (!self.should_show_edit_predictions()
7171 || !self.is_focused(window)
7172 || buffer.read(cx).is_empty())
7173 {
7174 self.discard_edit_prediction(false, cx);
7175 return None;
7176 }
7177
7178 provider.refresh(
7179 self.project.clone(),
7180 buffer,
7181 cursor_buffer_position,
7182 debounce,
7183 cx,
7184 );
7185 Some(())
7186 }
7187
7188 fn show_edit_predictions_in_menu(&self) -> bool {
7189 match self.edit_prediction_settings {
7190 EditPredictionSettings::Disabled => false,
7191 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7192 }
7193 }
7194
7195 pub fn edit_predictions_enabled(&self) -> bool {
7196 match self.edit_prediction_settings {
7197 EditPredictionSettings::Disabled => false,
7198 EditPredictionSettings::Enabled { .. } => true,
7199 }
7200 }
7201
7202 fn edit_prediction_requires_modifier(&self) -> bool {
7203 match self.edit_prediction_settings {
7204 EditPredictionSettings::Disabled => false,
7205 EditPredictionSettings::Enabled {
7206 preview_requires_modifier,
7207 ..
7208 } => preview_requires_modifier,
7209 }
7210 }
7211
7212 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7213 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7214 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7215 self.discard_edit_prediction(false, cx);
7216 } else {
7217 let selection = self.selections.newest_anchor();
7218 let cursor = selection.head();
7219
7220 if let Some((buffer, cursor_buffer_position)) =
7221 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7222 {
7223 self.edit_prediction_settings =
7224 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7225 }
7226 }
7227 }
7228
7229 fn edit_prediction_settings_at_position(
7230 &self,
7231 buffer: &Entity<Buffer>,
7232 buffer_position: language::Anchor,
7233 cx: &App,
7234 ) -> EditPredictionSettings {
7235 if !self.mode.is_full()
7236 || !self.show_edit_predictions_override.unwrap_or(true)
7237 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7238 {
7239 return EditPredictionSettings::Disabled;
7240 }
7241
7242 let buffer = buffer.read(cx);
7243
7244 let file = buffer.file();
7245
7246 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7247 return EditPredictionSettings::Disabled;
7248 };
7249
7250 let by_provider = matches!(
7251 self.menu_edit_predictions_policy,
7252 MenuEditPredictionsPolicy::ByProvider
7253 );
7254
7255 let show_in_menu = by_provider
7256 && self
7257 .edit_prediction_provider
7258 .as_ref()
7259 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7260
7261 let preview_requires_modifier =
7262 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7263
7264 EditPredictionSettings::Enabled {
7265 show_in_menu,
7266 preview_requires_modifier,
7267 }
7268 }
7269
7270 fn should_show_edit_predictions(&self) -> bool {
7271 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7272 }
7273
7274 pub fn edit_prediction_preview_is_active(&self) -> bool {
7275 matches!(
7276 self.edit_prediction_preview,
7277 EditPredictionPreview::Active { .. }
7278 )
7279 }
7280
7281 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7282 let cursor = self.selections.newest_anchor().head();
7283 if let Some((buffer, cursor_position)) =
7284 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7285 {
7286 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7287 } else {
7288 false
7289 }
7290 }
7291
7292 pub fn supports_minimap(&self, cx: &App) -> bool {
7293 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7294 }
7295
7296 fn edit_predictions_enabled_in_buffer(
7297 &self,
7298 buffer: &Entity<Buffer>,
7299 buffer_position: language::Anchor,
7300 cx: &App,
7301 ) -> bool {
7302 maybe!({
7303 if self.read_only(cx) {
7304 return Some(false);
7305 }
7306 let provider = self.edit_prediction_provider()?;
7307 if !provider.is_enabled(buffer, buffer_position, cx) {
7308 return Some(false);
7309 }
7310 let buffer = buffer.read(cx);
7311 let Some(file) = buffer.file() else {
7312 return Some(true);
7313 };
7314 let settings = all_language_settings(Some(file), cx);
7315 Some(settings.edit_predictions_enabled_for_file(file, cx))
7316 })
7317 .unwrap_or(false)
7318 }
7319
7320 fn cycle_edit_prediction(
7321 &mut self,
7322 direction: Direction,
7323 window: &mut Window,
7324 cx: &mut Context<Self>,
7325 ) -> Option<()> {
7326 let provider = self.edit_prediction_provider()?;
7327 let cursor = self.selections.newest_anchor().head();
7328 let (buffer, cursor_buffer_position) =
7329 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7330 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7331 return None;
7332 }
7333
7334 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7335 self.update_visible_edit_prediction(window, cx);
7336
7337 Some(())
7338 }
7339
7340 pub fn show_edit_prediction(
7341 &mut self,
7342 _: &ShowEditPrediction,
7343 window: &mut Window,
7344 cx: &mut Context<Self>,
7345 ) {
7346 if !self.has_active_edit_prediction() {
7347 self.refresh_edit_prediction(false, true, window, cx);
7348 return;
7349 }
7350
7351 self.update_visible_edit_prediction(window, cx);
7352 }
7353
7354 pub fn display_cursor_names(
7355 &mut self,
7356 _: &DisplayCursorNames,
7357 window: &mut Window,
7358 cx: &mut Context<Self>,
7359 ) {
7360 self.show_cursor_names(window, cx);
7361 }
7362
7363 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7364 self.show_cursor_names = true;
7365 cx.notify();
7366 cx.spawn_in(window, async move |this, cx| {
7367 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7368 this.update(cx, |this, cx| {
7369 this.show_cursor_names = false;
7370 cx.notify()
7371 })
7372 .ok()
7373 })
7374 .detach();
7375 }
7376
7377 pub fn next_edit_prediction(
7378 &mut self,
7379 _: &NextEditPrediction,
7380 window: &mut Window,
7381 cx: &mut Context<Self>,
7382 ) {
7383 if self.has_active_edit_prediction() {
7384 self.cycle_edit_prediction(Direction::Next, window, cx);
7385 } else {
7386 let is_copilot_disabled = self
7387 .refresh_edit_prediction(false, true, window, cx)
7388 .is_none();
7389 if is_copilot_disabled {
7390 cx.propagate();
7391 }
7392 }
7393 }
7394
7395 pub fn previous_edit_prediction(
7396 &mut self,
7397 _: &PreviousEditPrediction,
7398 window: &mut Window,
7399 cx: &mut Context<Self>,
7400 ) {
7401 if self.has_active_edit_prediction() {
7402 self.cycle_edit_prediction(Direction::Prev, window, cx);
7403 } else {
7404 let is_copilot_disabled = self
7405 .refresh_edit_prediction(false, true, window, cx)
7406 .is_none();
7407 if is_copilot_disabled {
7408 cx.propagate();
7409 }
7410 }
7411 }
7412
7413 pub fn accept_edit_prediction(
7414 &mut self,
7415 _: &AcceptEditPrediction,
7416 window: &mut Window,
7417 cx: &mut Context<Self>,
7418 ) {
7419 if self.show_edit_predictions_in_menu() {
7420 self.hide_context_menu(window, cx);
7421 }
7422
7423 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7424 return;
7425 };
7426
7427 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7428
7429 match &active_edit_prediction.completion {
7430 EditPrediction::Move { target, .. } => {
7431 let target = *target;
7432
7433 if let Some(position_map) = &self.last_position_map {
7434 if position_map
7435 .visible_row_range
7436 .contains(&target.to_display_point(&position_map.snapshot).row())
7437 || !self.edit_prediction_requires_modifier()
7438 {
7439 self.unfold_ranges(&[target..target], true, false, cx);
7440 // Note that this is also done in vim's handler of the Tab action.
7441 self.change_selections(
7442 SelectionEffects::scroll(Autoscroll::newest()),
7443 window,
7444 cx,
7445 |selections| {
7446 selections.select_anchor_ranges([target..target]);
7447 },
7448 );
7449 self.clear_row_highlights::<EditPredictionPreview>();
7450
7451 self.edit_prediction_preview
7452 .set_previous_scroll_position(None);
7453 } else {
7454 self.edit_prediction_preview
7455 .set_previous_scroll_position(Some(
7456 position_map.snapshot.scroll_anchor,
7457 ));
7458
7459 self.highlight_rows::<EditPredictionPreview>(
7460 target..target,
7461 cx.theme().colors().editor_highlighted_line_background,
7462 RowHighlightOptions {
7463 autoscroll: true,
7464 ..Default::default()
7465 },
7466 cx,
7467 );
7468 self.request_autoscroll(Autoscroll::fit(), cx);
7469 }
7470 }
7471 }
7472 EditPrediction::Edit { edits, .. } => {
7473 if let Some(provider) = self.edit_prediction_provider() {
7474 provider.accept(cx);
7475 }
7476
7477 // Store the transaction ID and selections before applying the edit
7478 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7479
7480 let snapshot = self.buffer.read(cx).snapshot(cx);
7481 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7482
7483 self.buffer.update(cx, |buffer, cx| {
7484 buffer.edit(edits.iter().cloned(), None, cx)
7485 });
7486
7487 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7488 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7489 });
7490
7491 let selections = self.selections.disjoint_anchors_arc();
7492 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7493 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7494 if has_new_transaction {
7495 self.selection_history
7496 .insert_transaction(transaction_id_now, selections);
7497 }
7498 }
7499
7500 self.update_visible_edit_prediction(window, cx);
7501 if self.active_edit_prediction.is_none() {
7502 self.refresh_edit_prediction(true, true, window, cx);
7503 }
7504
7505 cx.notify();
7506 }
7507 }
7508
7509 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7510 }
7511
7512 pub fn accept_partial_edit_prediction(
7513 &mut self,
7514 _: &AcceptPartialEditPrediction,
7515 window: &mut Window,
7516 cx: &mut Context<Self>,
7517 ) {
7518 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7519 return;
7520 };
7521 if self.selections.count() != 1 {
7522 return;
7523 }
7524
7525 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7526
7527 match &active_edit_prediction.completion {
7528 EditPrediction::Move { target, .. } => {
7529 let target = *target;
7530 self.change_selections(
7531 SelectionEffects::scroll(Autoscroll::newest()),
7532 window,
7533 cx,
7534 |selections| {
7535 selections.select_anchor_ranges([target..target]);
7536 },
7537 );
7538 }
7539 EditPrediction::Edit { edits, .. } => {
7540 // Find an insertion that starts at the cursor position.
7541 let snapshot = self.buffer.read(cx).snapshot(cx);
7542 let cursor_offset = self.selections.newest::<usize>(cx).head();
7543 let insertion = edits.iter().find_map(|(range, text)| {
7544 let range = range.to_offset(&snapshot);
7545 if range.is_empty() && range.start == cursor_offset {
7546 Some(text)
7547 } else {
7548 None
7549 }
7550 });
7551
7552 if let Some(text) = insertion {
7553 let mut partial_completion = text
7554 .chars()
7555 .by_ref()
7556 .take_while(|c| c.is_alphabetic())
7557 .collect::<String>();
7558 if partial_completion.is_empty() {
7559 partial_completion = text
7560 .chars()
7561 .by_ref()
7562 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7563 .collect::<String>();
7564 }
7565
7566 cx.emit(EditorEvent::InputHandled {
7567 utf16_range_to_replace: None,
7568 text: partial_completion.clone().into(),
7569 });
7570
7571 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7572
7573 self.refresh_edit_prediction(true, true, window, cx);
7574 cx.notify();
7575 } else {
7576 self.accept_edit_prediction(&Default::default(), window, cx);
7577 }
7578 }
7579 }
7580 }
7581
7582 fn discard_edit_prediction(
7583 &mut self,
7584 should_report_edit_prediction_event: bool,
7585 cx: &mut Context<Self>,
7586 ) -> bool {
7587 if should_report_edit_prediction_event {
7588 let completion_id = self
7589 .active_edit_prediction
7590 .as_ref()
7591 .and_then(|active_completion| active_completion.completion_id.clone());
7592
7593 self.report_edit_prediction_event(completion_id, false, cx);
7594 }
7595
7596 if let Some(provider) = self.edit_prediction_provider() {
7597 provider.discard(cx);
7598 }
7599
7600 self.take_active_edit_prediction(cx)
7601 }
7602
7603 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7604 let Some(provider) = self.edit_prediction_provider() else {
7605 return;
7606 };
7607
7608 let Some((_, buffer, _)) = self
7609 .buffer
7610 .read(cx)
7611 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7612 else {
7613 return;
7614 };
7615
7616 let extension = buffer
7617 .read(cx)
7618 .file()
7619 .and_then(|file| Some(file.path().extension()?.to_string()));
7620
7621 let event_type = match accepted {
7622 true => "Edit Prediction Accepted",
7623 false => "Edit Prediction Discarded",
7624 };
7625 telemetry::event!(
7626 event_type,
7627 provider = provider.name(),
7628 prediction_id = id,
7629 suggestion_accepted = accepted,
7630 file_extension = extension,
7631 );
7632 }
7633
7634 pub fn has_active_edit_prediction(&self) -> bool {
7635 self.active_edit_prediction.is_some()
7636 }
7637
7638 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7639 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7640 return false;
7641 };
7642
7643 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7644 self.clear_highlights::<EditPredictionHighlight>(cx);
7645 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7646 true
7647 }
7648
7649 /// Returns true when we're displaying the edit prediction popover below the cursor
7650 /// like we are not previewing and the LSP autocomplete menu is visible
7651 /// or we are in `when_holding_modifier` mode.
7652 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7653 if self.edit_prediction_preview_is_active()
7654 || !self.show_edit_predictions_in_menu()
7655 || !self.edit_predictions_enabled()
7656 {
7657 return false;
7658 }
7659
7660 if self.has_visible_completions_menu() {
7661 return true;
7662 }
7663
7664 has_completion && self.edit_prediction_requires_modifier()
7665 }
7666
7667 fn handle_modifiers_changed(
7668 &mut self,
7669 modifiers: Modifiers,
7670 position_map: &PositionMap,
7671 window: &mut Window,
7672 cx: &mut Context<Self>,
7673 ) {
7674 if self.show_edit_predictions_in_menu() {
7675 self.update_edit_prediction_preview(&modifiers, window, cx);
7676 }
7677
7678 self.update_selection_mode(&modifiers, position_map, window, cx);
7679
7680 let mouse_position = window.mouse_position();
7681 if !position_map.text_hitbox.is_hovered(window) {
7682 return;
7683 }
7684
7685 self.update_hovered_link(
7686 position_map.point_for_position(mouse_position),
7687 &position_map.snapshot,
7688 modifiers,
7689 window,
7690 cx,
7691 )
7692 }
7693
7694 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7695 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7696 if invert {
7697 match multi_cursor_setting {
7698 MultiCursorModifier::Alt => modifiers.alt,
7699 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7700 }
7701 } else {
7702 match multi_cursor_setting {
7703 MultiCursorModifier::Alt => modifiers.secondary(),
7704 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7705 }
7706 }
7707 }
7708
7709 fn columnar_selection_mode(
7710 modifiers: &Modifiers,
7711 cx: &mut Context<Self>,
7712 ) -> Option<ColumnarMode> {
7713 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7714 if Self::multi_cursor_modifier(false, modifiers, cx) {
7715 Some(ColumnarMode::FromMouse)
7716 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7717 Some(ColumnarMode::FromSelection)
7718 } else {
7719 None
7720 }
7721 } else {
7722 None
7723 }
7724 }
7725
7726 fn update_selection_mode(
7727 &mut self,
7728 modifiers: &Modifiers,
7729 position_map: &PositionMap,
7730 window: &mut Window,
7731 cx: &mut Context<Self>,
7732 ) {
7733 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7734 return;
7735 };
7736 if self.selections.pending_anchor().is_none() {
7737 return;
7738 }
7739
7740 let mouse_position = window.mouse_position();
7741 let point_for_position = position_map.point_for_position(mouse_position);
7742 let position = point_for_position.previous_valid;
7743
7744 self.select(
7745 SelectPhase::BeginColumnar {
7746 position,
7747 reset: false,
7748 mode,
7749 goal_column: point_for_position.exact_unclipped.column(),
7750 },
7751 window,
7752 cx,
7753 );
7754 }
7755
7756 fn update_edit_prediction_preview(
7757 &mut self,
7758 modifiers: &Modifiers,
7759 window: &mut Window,
7760 cx: &mut Context<Self>,
7761 ) {
7762 let mut modifiers_held = false;
7763 if let Some(accept_keystroke) = self
7764 .accept_edit_prediction_keybind(false, window, cx)
7765 .keystroke()
7766 {
7767 modifiers_held = modifiers_held
7768 || (accept_keystroke.modifiers() == modifiers
7769 && accept_keystroke.modifiers().modified());
7770 };
7771 if let Some(accept_partial_keystroke) = self
7772 .accept_edit_prediction_keybind(true, window, cx)
7773 .keystroke()
7774 {
7775 modifiers_held = modifiers_held
7776 || (accept_partial_keystroke.modifiers() == modifiers
7777 && accept_partial_keystroke.modifiers().modified());
7778 }
7779
7780 if modifiers_held {
7781 if matches!(
7782 self.edit_prediction_preview,
7783 EditPredictionPreview::Inactive { .. }
7784 ) {
7785 self.edit_prediction_preview = EditPredictionPreview::Active {
7786 previous_scroll_position: None,
7787 since: Instant::now(),
7788 };
7789
7790 self.update_visible_edit_prediction(window, cx);
7791 cx.notify();
7792 }
7793 } else if let EditPredictionPreview::Active {
7794 previous_scroll_position,
7795 since,
7796 } = self.edit_prediction_preview
7797 {
7798 if let (Some(previous_scroll_position), Some(position_map)) =
7799 (previous_scroll_position, self.last_position_map.as_ref())
7800 {
7801 self.set_scroll_position(
7802 previous_scroll_position
7803 .scroll_position(&position_map.snapshot.display_snapshot),
7804 window,
7805 cx,
7806 );
7807 }
7808
7809 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7810 released_too_fast: since.elapsed() < Duration::from_millis(200),
7811 };
7812 self.clear_row_highlights::<EditPredictionPreview>();
7813 self.update_visible_edit_prediction(window, cx);
7814 cx.notify();
7815 }
7816 }
7817
7818 fn update_visible_edit_prediction(
7819 &mut self,
7820 _window: &mut Window,
7821 cx: &mut Context<Self>,
7822 ) -> Option<()> {
7823 if DisableAiSettings::get_global(cx).disable_ai {
7824 return None;
7825 }
7826
7827 if self.ime_transaction.is_some() {
7828 self.discard_edit_prediction(false, cx);
7829 return None;
7830 }
7831
7832 let selection = self.selections.newest_anchor();
7833 let cursor = selection.head();
7834 let multibuffer = self.buffer.read(cx).snapshot(cx);
7835 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7836 let excerpt_id = cursor.excerpt_id;
7837
7838 let show_in_menu = self.show_edit_predictions_in_menu();
7839 let completions_menu_has_precedence = !show_in_menu
7840 && (self.context_menu.borrow().is_some()
7841 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7842
7843 if completions_menu_has_precedence
7844 || !offset_selection.is_empty()
7845 || self
7846 .active_edit_prediction
7847 .as_ref()
7848 .is_some_and(|completion| {
7849 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7850 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7851 !invalidation_range.contains(&offset_selection.head())
7852 })
7853 {
7854 self.discard_edit_prediction(false, cx);
7855 return None;
7856 }
7857
7858 self.take_active_edit_prediction(cx);
7859 let Some(provider) = self.edit_prediction_provider() else {
7860 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7861 return None;
7862 };
7863
7864 let (buffer, cursor_buffer_position) =
7865 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7866
7867 self.edit_prediction_settings =
7868 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7869
7870 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7871
7872 if self.edit_prediction_indent_conflict {
7873 let cursor_point = cursor.to_point(&multibuffer);
7874
7875 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7876
7877 if let Some((_, indent)) = indents.iter().next()
7878 && indent.len == cursor_point.column
7879 {
7880 self.edit_prediction_indent_conflict = false;
7881 }
7882 }
7883
7884 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7885 let edits = edit_prediction
7886 .edits
7887 .into_iter()
7888 .flat_map(|(range, new_text)| {
7889 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7890 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7891 Some((start..end, new_text))
7892 })
7893 .collect::<Vec<_>>();
7894 if edits.is_empty() {
7895 return None;
7896 }
7897
7898 let first_edit_start = edits.first().unwrap().0.start;
7899 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7900 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7901
7902 let last_edit_end = edits.last().unwrap().0.end;
7903 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7904 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7905
7906 let cursor_row = cursor.to_point(&multibuffer).row;
7907
7908 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7909
7910 let mut inlay_ids = Vec::new();
7911 let invalidation_row_range;
7912 let move_invalidation_row_range = if cursor_row < edit_start_row {
7913 Some(cursor_row..edit_end_row)
7914 } else if cursor_row > edit_end_row {
7915 Some(edit_start_row..cursor_row)
7916 } else {
7917 None
7918 };
7919 let supports_jump = self
7920 .edit_prediction_provider
7921 .as_ref()
7922 .map(|provider| provider.provider.supports_jump_to_edit())
7923 .unwrap_or(true);
7924
7925 let is_move = supports_jump
7926 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7927 let completion = if is_move {
7928 invalidation_row_range =
7929 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7930 let target = first_edit_start;
7931 EditPrediction::Move { target, snapshot }
7932 } else {
7933 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7934 && !self.edit_predictions_hidden_for_vim_mode;
7935
7936 if show_completions_in_buffer {
7937 if edits
7938 .iter()
7939 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7940 {
7941 let mut inlays = Vec::new();
7942 for (range, new_text) in &edits {
7943 let inlay = Inlay::edit_prediction(
7944 post_inc(&mut self.next_inlay_id),
7945 range.start,
7946 new_text.as_str(),
7947 );
7948 inlay_ids.push(inlay.id);
7949 inlays.push(inlay);
7950 }
7951
7952 self.splice_inlays(&[], inlays, cx);
7953 } else {
7954 let background_color = cx.theme().status().deleted_background;
7955 self.highlight_text::<EditPredictionHighlight>(
7956 edits.iter().map(|(range, _)| range.clone()).collect(),
7957 HighlightStyle {
7958 background_color: Some(background_color),
7959 ..Default::default()
7960 },
7961 cx,
7962 );
7963 }
7964 }
7965
7966 invalidation_row_range = edit_start_row..edit_end_row;
7967
7968 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7969 if provider.show_tab_accept_marker() {
7970 EditDisplayMode::TabAccept
7971 } else {
7972 EditDisplayMode::Inline
7973 }
7974 } else {
7975 EditDisplayMode::DiffPopover
7976 };
7977
7978 EditPrediction::Edit {
7979 edits,
7980 edit_preview: edit_prediction.edit_preview,
7981 display_mode,
7982 snapshot,
7983 }
7984 };
7985
7986 let invalidation_range = multibuffer
7987 .anchor_before(Point::new(invalidation_row_range.start, 0))
7988 ..multibuffer.anchor_after(Point::new(
7989 invalidation_row_range.end,
7990 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7991 ));
7992
7993 self.stale_edit_prediction_in_menu = None;
7994 self.active_edit_prediction = Some(EditPredictionState {
7995 inlay_ids,
7996 completion,
7997 completion_id: edit_prediction.id,
7998 invalidation_range,
7999 });
8000
8001 cx.notify();
8002
8003 Some(())
8004 }
8005
8006 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8007 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8008 }
8009
8010 fn clear_tasks(&mut self) {
8011 self.tasks.clear()
8012 }
8013
8014 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8015 if self.tasks.insert(key, value).is_some() {
8016 // This case should hopefully be rare, but just in case...
8017 log::error!(
8018 "multiple different run targets found on a single line, only the last target will be rendered"
8019 )
8020 }
8021 }
8022
8023 /// Get all display points of breakpoints that will be rendered within editor
8024 ///
8025 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8026 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8027 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8028 fn active_breakpoints(
8029 &self,
8030 range: Range<DisplayRow>,
8031 window: &mut Window,
8032 cx: &mut Context<Self>,
8033 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8034 let mut breakpoint_display_points = HashMap::default();
8035
8036 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8037 return breakpoint_display_points;
8038 };
8039
8040 let snapshot = self.snapshot(window, cx);
8041
8042 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8043 let Some(project) = self.project() else {
8044 return breakpoint_display_points;
8045 };
8046
8047 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8048 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8049
8050 for (buffer_snapshot, range, excerpt_id) in
8051 multi_buffer_snapshot.range_to_buffer_ranges(range)
8052 {
8053 let Some(buffer) = project
8054 .read(cx)
8055 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8056 else {
8057 continue;
8058 };
8059 let breakpoints = breakpoint_store.read(cx).breakpoints(
8060 &buffer,
8061 Some(
8062 buffer_snapshot.anchor_before(range.start)
8063 ..buffer_snapshot.anchor_after(range.end),
8064 ),
8065 buffer_snapshot,
8066 cx,
8067 );
8068 for (breakpoint, state) in breakpoints {
8069 let multi_buffer_anchor =
8070 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8071 let position = multi_buffer_anchor
8072 .to_point(multi_buffer_snapshot)
8073 .to_display_point(&snapshot);
8074
8075 breakpoint_display_points.insert(
8076 position.row(),
8077 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8078 );
8079 }
8080 }
8081
8082 breakpoint_display_points
8083 }
8084
8085 fn breakpoint_context_menu(
8086 &self,
8087 anchor: Anchor,
8088 window: &mut Window,
8089 cx: &mut Context<Self>,
8090 ) -> Entity<ui::ContextMenu> {
8091 let weak_editor = cx.weak_entity();
8092 let focus_handle = self.focus_handle(cx);
8093
8094 let row = self
8095 .buffer
8096 .read(cx)
8097 .snapshot(cx)
8098 .summary_for_anchor::<Point>(&anchor)
8099 .row;
8100
8101 let breakpoint = self
8102 .breakpoint_at_row(row, window, cx)
8103 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8104
8105 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8106 "Edit Log Breakpoint"
8107 } else {
8108 "Set Log Breakpoint"
8109 };
8110
8111 let condition_breakpoint_msg = if breakpoint
8112 .as_ref()
8113 .is_some_and(|bp| bp.1.condition.is_some())
8114 {
8115 "Edit Condition Breakpoint"
8116 } else {
8117 "Set Condition Breakpoint"
8118 };
8119
8120 let hit_condition_breakpoint_msg = if breakpoint
8121 .as_ref()
8122 .is_some_and(|bp| bp.1.hit_condition.is_some())
8123 {
8124 "Edit Hit Condition Breakpoint"
8125 } else {
8126 "Set Hit Condition Breakpoint"
8127 };
8128
8129 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8130 "Unset Breakpoint"
8131 } else {
8132 "Set Breakpoint"
8133 };
8134
8135 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8136
8137 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8138 BreakpointState::Enabled => Some("Disable"),
8139 BreakpointState::Disabled => Some("Enable"),
8140 });
8141
8142 let (anchor, breakpoint) =
8143 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8144
8145 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8146 menu.on_blur_subscription(Subscription::new(|| {}))
8147 .context(focus_handle)
8148 .when(run_to_cursor, |this| {
8149 let weak_editor = weak_editor.clone();
8150 this.entry("Run to cursor", None, move |window, cx| {
8151 weak_editor
8152 .update(cx, |editor, cx| {
8153 editor.change_selections(
8154 SelectionEffects::no_scroll(),
8155 window,
8156 cx,
8157 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8158 );
8159 })
8160 .ok();
8161
8162 window.dispatch_action(Box::new(RunToCursor), cx);
8163 })
8164 .separator()
8165 })
8166 .when_some(toggle_state_msg, |this, msg| {
8167 this.entry(msg, None, {
8168 let weak_editor = weak_editor.clone();
8169 let breakpoint = breakpoint.clone();
8170 move |_window, cx| {
8171 weak_editor
8172 .update(cx, |this, cx| {
8173 this.edit_breakpoint_at_anchor(
8174 anchor,
8175 breakpoint.as_ref().clone(),
8176 BreakpointEditAction::InvertState,
8177 cx,
8178 );
8179 })
8180 .log_err();
8181 }
8182 })
8183 })
8184 .entry(set_breakpoint_msg, None, {
8185 let weak_editor = weak_editor.clone();
8186 let breakpoint = breakpoint.clone();
8187 move |_window, cx| {
8188 weak_editor
8189 .update(cx, |this, cx| {
8190 this.edit_breakpoint_at_anchor(
8191 anchor,
8192 breakpoint.as_ref().clone(),
8193 BreakpointEditAction::Toggle,
8194 cx,
8195 );
8196 })
8197 .log_err();
8198 }
8199 })
8200 .entry(log_breakpoint_msg, None, {
8201 let breakpoint = breakpoint.clone();
8202 let weak_editor = weak_editor.clone();
8203 move |window, cx| {
8204 weak_editor
8205 .update(cx, |this, cx| {
8206 this.add_edit_breakpoint_block(
8207 anchor,
8208 breakpoint.as_ref(),
8209 BreakpointPromptEditAction::Log,
8210 window,
8211 cx,
8212 );
8213 })
8214 .log_err();
8215 }
8216 })
8217 .entry(condition_breakpoint_msg, None, {
8218 let breakpoint = breakpoint.clone();
8219 let weak_editor = weak_editor.clone();
8220 move |window, cx| {
8221 weak_editor
8222 .update(cx, |this, cx| {
8223 this.add_edit_breakpoint_block(
8224 anchor,
8225 breakpoint.as_ref(),
8226 BreakpointPromptEditAction::Condition,
8227 window,
8228 cx,
8229 );
8230 })
8231 .log_err();
8232 }
8233 })
8234 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8235 weak_editor
8236 .update(cx, |this, cx| {
8237 this.add_edit_breakpoint_block(
8238 anchor,
8239 breakpoint.as_ref(),
8240 BreakpointPromptEditAction::HitCondition,
8241 window,
8242 cx,
8243 );
8244 })
8245 .log_err();
8246 })
8247 })
8248 }
8249
8250 fn render_breakpoint(
8251 &self,
8252 position: Anchor,
8253 row: DisplayRow,
8254 breakpoint: &Breakpoint,
8255 state: Option<BreakpointSessionState>,
8256 cx: &mut Context<Self>,
8257 ) -> IconButton {
8258 let is_rejected = state.is_some_and(|s| !s.verified);
8259 // Is it a breakpoint that shows up when hovering over gutter?
8260 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8261 (false, false),
8262 |PhantomBreakpointIndicator {
8263 is_active,
8264 display_row,
8265 collides_with_existing_breakpoint,
8266 }| {
8267 (
8268 is_active && display_row == row,
8269 collides_with_existing_breakpoint,
8270 )
8271 },
8272 );
8273
8274 let (color, icon) = {
8275 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8276 (false, false) => ui::IconName::DebugBreakpoint,
8277 (true, false) => ui::IconName::DebugLogBreakpoint,
8278 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8279 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8280 };
8281
8282 let color = if is_phantom {
8283 Color::Hint
8284 } else if is_rejected {
8285 Color::Disabled
8286 } else {
8287 Color::Debugger
8288 };
8289
8290 (color, icon)
8291 };
8292
8293 let breakpoint = Arc::from(breakpoint.clone());
8294
8295 let alt_as_text = gpui::Keystroke {
8296 modifiers: Modifiers::secondary_key(),
8297 ..Default::default()
8298 };
8299 let primary_action_text = if breakpoint.is_disabled() {
8300 "Enable breakpoint"
8301 } else if is_phantom && !collides_with_existing {
8302 "Set breakpoint"
8303 } else {
8304 "Unset breakpoint"
8305 };
8306 let focus_handle = self.focus_handle.clone();
8307
8308 let meta = if is_rejected {
8309 SharedString::from("No executable code is associated with this line.")
8310 } else if collides_with_existing && !breakpoint.is_disabled() {
8311 SharedString::from(format!(
8312 "{alt_as_text}-click to disable,\nright-click for more options."
8313 ))
8314 } else {
8315 SharedString::from("Right-click for more options.")
8316 };
8317 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8318 .icon_size(IconSize::XSmall)
8319 .size(ui::ButtonSize::None)
8320 .when(is_rejected, |this| {
8321 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8322 })
8323 .icon_color(color)
8324 .style(ButtonStyle::Transparent)
8325 .on_click(cx.listener({
8326 move |editor, event: &ClickEvent, window, cx| {
8327 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8328 BreakpointEditAction::InvertState
8329 } else {
8330 BreakpointEditAction::Toggle
8331 };
8332
8333 window.focus(&editor.focus_handle(cx));
8334 editor.edit_breakpoint_at_anchor(
8335 position,
8336 breakpoint.as_ref().clone(),
8337 edit_action,
8338 cx,
8339 );
8340 }
8341 }))
8342 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8343 editor.set_breakpoint_context_menu(
8344 row,
8345 Some(position),
8346 event.position(),
8347 window,
8348 cx,
8349 );
8350 }))
8351 .tooltip(move |window, cx| {
8352 Tooltip::with_meta_in(
8353 primary_action_text,
8354 Some(&ToggleBreakpoint),
8355 meta.clone(),
8356 &focus_handle,
8357 window,
8358 cx,
8359 )
8360 })
8361 }
8362
8363 fn build_tasks_context(
8364 project: &Entity<Project>,
8365 buffer: &Entity<Buffer>,
8366 buffer_row: u32,
8367 tasks: &Arc<RunnableTasks>,
8368 cx: &mut Context<Self>,
8369 ) -> Task<Option<task::TaskContext>> {
8370 let position = Point::new(buffer_row, tasks.column);
8371 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8372 let location = Location {
8373 buffer: buffer.clone(),
8374 range: range_start..range_start,
8375 };
8376 // Fill in the environmental variables from the tree-sitter captures
8377 let mut captured_task_variables = TaskVariables::default();
8378 for (capture_name, value) in tasks.extra_variables.clone() {
8379 captured_task_variables.insert(
8380 task::VariableName::Custom(capture_name.into()),
8381 value.clone(),
8382 );
8383 }
8384 project.update(cx, |project, cx| {
8385 project.task_store().update(cx, |task_store, cx| {
8386 task_store.task_context_for_location(captured_task_variables, location, cx)
8387 })
8388 })
8389 }
8390
8391 pub fn spawn_nearest_task(
8392 &mut self,
8393 action: &SpawnNearestTask,
8394 window: &mut Window,
8395 cx: &mut Context<Self>,
8396 ) {
8397 let Some((workspace, _)) = self.workspace.clone() else {
8398 return;
8399 };
8400 let Some(project) = self.project.clone() else {
8401 return;
8402 };
8403
8404 // Try to find a closest, enclosing node using tree-sitter that has a task
8405 let Some((buffer, buffer_row, tasks)) = self
8406 .find_enclosing_node_task(cx)
8407 // Or find the task that's closest in row-distance.
8408 .or_else(|| self.find_closest_task(cx))
8409 else {
8410 return;
8411 };
8412
8413 let reveal_strategy = action.reveal;
8414 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8415 cx.spawn_in(window, async move |_, cx| {
8416 let context = task_context.await?;
8417 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8418
8419 let resolved = &mut resolved_task.resolved;
8420 resolved.reveal = reveal_strategy;
8421
8422 workspace
8423 .update_in(cx, |workspace, window, cx| {
8424 workspace.schedule_resolved_task(
8425 task_source_kind,
8426 resolved_task,
8427 false,
8428 window,
8429 cx,
8430 );
8431 })
8432 .ok()
8433 })
8434 .detach();
8435 }
8436
8437 fn find_closest_task(
8438 &mut self,
8439 cx: &mut Context<Self>,
8440 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8441 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8442
8443 let ((buffer_id, row), tasks) = self
8444 .tasks
8445 .iter()
8446 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8447
8448 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8449 let tasks = Arc::new(tasks.to_owned());
8450 Some((buffer, *row, tasks))
8451 }
8452
8453 fn find_enclosing_node_task(
8454 &mut self,
8455 cx: &mut Context<Self>,
8456 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8457 let snapshot = self.buffer.read(cx).snapshot(cx);
8458 let offset = self.selections.newest::<usize>(cx).head();
8459 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8460 let buffer_id = excerpt.buffer().remote_id();
8461
8462 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8463 let mut cursor = layer.node().walk();
8464
8465 while cursor.goto_first_child_for_byte(offset).is_some() {
8466 if cursor.node().end_byte() == offset {
8467 cursor.goto_next_sibling();
8468 }
8469 }
8470
8471 // Ascend to the smallest ancestor that contains the range and has a task.
8472 loop {
8473 let node = cursor.node();
8474 let node_range = node.byte_range();
8475 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8476
8477 // Check if this node contains our offset
8478 if node_range.start <= offset && node_range.end >= offset {
8479 // If it contains offset, check for task
8480 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8481 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8482 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8483 }
8484 }
8485
8486 if !cursor.goto_parent() {
8487 break;
8488 }
8489 }
8490 None
8491 }
8492
8493 fn render_run_indicator(
8494 &self,
8495 _style: &EditorStyle,
8496 is_active: bool,
8497 row: DisplayRow,
8498 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8499 cx: &mut Context<Self>,
8500 ) -> IconButton {
8501 let color = Color::Muted;
8502 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8503
8504 IconButton::new(
8505 ("run_indicator", row.0 as usize),
8506 ui::IconName::PlayOutlined,
8507 )
8508 .shape(ui::IconButtonShape::Square)
8509 .icon_size(IconSize::XSmall)
8510 .icon_color(color)
8511 .toggle_state(is_active)
8512 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8513 let quick_launch = match e {
8514 ClickEvent::Keyboard(_) => true,
8515 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8516 };
8517
8518 window.focus(&editor.focus_handle(cx));
8519 editor.toggle_code_actions(
8520 &ToggleCodeActions {
8521 deployed_from: Some(CodeActionSource::RunMenu(row)),
8522 quick_launch,
8523 },
8524 window,
8525 cx,
8526 );
8527 }))
8528 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8529 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8530 }))
8531 }
8532
8533 pub fn context_menu_visible(&self) -> bool {
8534 !self.edit_prediction_preview_is_active()
8535 && self
8536 .context_menu
8537 .borrow()
8538 .as_ref()
8539 .is_some_and(|menu| menu.visible())
8540 }
8541
8542 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8543 self.context_menu
8544 .borrow()
8545 .as_ref()
8546 .map(|menu| menu.origin())
8547 }
8548
8549 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8550 self.context_menu_options = Some(options);
8551 }
8552
8553 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8554 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8555
8556 fn render_edit_prediction_popover(
8557 &mut self,
8558 text_bounds: &Bounds<Pixels>,
8559 content_origin: gpui::Point<Pixels>,
8560 right_margin: Pixels,
8561 editor_snapshot: &EditorSnapshot,
8562 visible_row_range: Range<DisplayRow>,
8563 scroll_top: f32,
8564 scroll_bottom: f32,
8565 line_layouts: &[LineWithInvisibles],
8566 line_height: Pixels,
8567 scroll_pixel_position: gpui::Point<Pixels>,
8568 newest_selection_head: Option<DisplayPoint>,
8569 editor_width: Pixels,
8570 style: &EditorStyle,
8571 window: &mut Window,
8572 cx: &mut App,
8573 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8574 if self.mode().is_minimap() {
8575 return None;
8576 }
8577 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8578
8579 if self.edit_prediction_visible_in_cursor_popover(true) {
8580 return None;
8581 }
8582
8583 match &active_edit_prediction.completion {
8584 EditPrediction::Move { target, .. } => {
8585 let target_display_point = target.to_display_point(editor_snapshot);
8586
8587 if self.edit_prediction_requires_modifier() {
8588 if !self.edit_prediction_preview_is_active() {
8589 return None;
8590 }
8591
8592 self.render_edit_prediction_modifier_jump_popover(
8593 text_bounds,
8594 content_origin,
8595 visible_row_range,
8596 line_layouts,
8597 line_height,
8598 scroll_pixel_position,
8599 newest_selection_head,
8600 target_display_point,
8601 window,
8602 cx,
8603 )
8604 } else {
8605 self.render_edit_prediction_eager_jump_popover(
8606 text_bounds,
8607 content_origin,
8608 editor_snapshot,
8609 visible_row_range,
8610 scroll_top,
8611 scroll_bottom,
8612 line_height,
8613 scroll_pixel_position,
8614 target_display_point,
8615 editor_width,
8616 window,
8617 cx,
8618 )
8619 }
8620 }
8621 EditPrediction::Edit {
8622 display_mode: EditDisplayMode::Inline,
8623 ..
8624 } => None,
8625 EditPrediction::Edit {
8626 display_mode: EditDisplayMode::TabAccept,
8627 edits,
8628 ..
8629 } => {
8630 let range = &edits.first()?.0;
8631 let target_display_point = range.end.to_display_point(editor_snapshot);
8632
8633 self.render_edit_prediction_end_of_line_popover(
8634 "Accept",
8635 editor_snapshot,
8636 visible_row_range,
8637 target_display_point,
8638 line_height,
8639 scroll_pixel_position,
8640 content_origin,
8641 editor_width,
8642 window,
8643 cx,
8644 )
8645 }
8646 EditPrediction::Edit {
8647 edits,
8648 edit_preview,
8649 display_mode: EditDisplayMode::DiffPopover,
8650 snapshot,
8651 } => self.render_edit_prediction_diff_popover(
8652 text_bounds,
8653 content_origin,
8654 right_margin,
8655 editor_snapshot,
8656 visible_row_range,
8657 line_layouts,
8658 line_height,
8659 scroll_pixel_position,
8660 newest_selection_head,
8661 editor_width,
8662 style,
8663 edits,
8664 edit_preview,
8665 snapshot,
8666 window,
8667 cx,
8668 ),
8669 }
8670 }
8671
8672 fn render_edit_prediction_modifier_jump_popover(
8673 &mut self,
8674 text_bounds: &Bounds<Pixels>,
8675 content_origin: gpui::Point<Pixels>,
8676 visible_row_range: Range<DisplayRow>,
8677 line_layouts: &[LineWithInvisibles],
8678 line_height: Pixels,
8679 scroll_pixel_position: gpui::Point<Pixels>,
8680 newest_selection_head: Option<DisplayPoint>,
8681 target_display_point: DisplayPoint,
8682 window: &mut Window,
8683 cx: &mut App,
8684 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8685 let scrolled_content_origin =
8686 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8687
8688 const SCROLL_PADDING_Y: Pixels = px(12.);
8689
8690 if target_display_point.row() < visible_row_range.start {
8691 return self.render_edit_prediction_scroll_popover(
8692 |_| SCROLL_PADDING_Y,
8693 IconName::ArrowUp,
8694 visible_row_range,
8695 line_layouts,
8696 newest_selection_head,
8697 scrolled_content_origin,
8698 window,
8699 cx,
8700 );
8701 } else if target_display_point.row() >= visible_row_range.end {
8702 return self.render_edit_prediction_scroll_popover(
8703 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8704 IconName::ArrowDown,
8705 visible_row_range,
8706 line_layouts,
8707 newest_selection_head,
8708 scrolled_content_origin,
8709 window,
8710 cx,
8711 );
8712 }
8713
8714 const POLE_WIDTH: Pixels = px(2.);
8715
8716 let line_layout =
8717 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8718 let target_column = target_display_point.column() as usize;
8719
8720 let target_x = line_layout.x_for_index(target_column);
8721 let target_y =
8722 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8723
8724 let flag_on_right = target_x < text_bounds.size.width / 2.;
8725
8726 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8727 border_color.l += 0.001;
8728
8729 let mut element = v_flex()
8730 .items_end()
8731 .when(flag_on_right, |el| el.items_start())
8732 .child(if flag_on_right {
8733 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8734 .rounded_bl(px(0.))
8735 .rounded_tl(px(0.))
8736 .border_l_2()
8737 .border_color(border_color)
8738 } else {
8739 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8740 .rounded_br(px(0.))
8741 .rounded_tr(px(0.))
8742 .border_r_2()
8743 .border_color(border_color)
8744 })
8745 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8746 .into_any();
8747
8748 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8749
8750 let mut origin = scrolled_content_origin + point(target_x, target_y)
8751 - point(
8752 if flag_on_right {
8753 POLE_WIDTH
8754 } else {
8755 size.width - POLE_WIDTH
8756 },
8757 size.height - line_height,
8758 );
8759
8760 origin.x = origin.x.max(content_origin.x);
8761
8762 element.prepaint_at(origin, window, cx);
8763
8764 Some((element, origin))
8765 }
8766
8767 fn render_edit_prediction_scroll_popover(
8768 &mut self,
8769 to_y: impl Fn(Size<Pixels>) -> Pixels,
8770 scroll_icon: IconName,
8771 visible_row_range: Range<DisplayRow>,
8772 line_layouts: &[LineWithInvisibles],
8773 newest_selection_head: Option<DisplayPoint>,
8774 scrolled_content_origin: gpui::Point<Pixels>,
8775 window: &mut Window,
8776 cx: &mut App,
8777 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8778 let mut element = self
8779 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8780 .into_any();
8781
8782 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8783
8784 let cursor = newest_selection_head?;
8785 let cursor_row_layout =
8786 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8787 let cursor_column = cursor.column() as usize;
8788
8789 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8790
8791 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8792
8793 element.prepaint_at(origin, window, cx);
8794 Some((element, origin))
8795 }
8796
8797 fn render_edit_prediction_eager_jump_popover(
8798 &mut self,
8799 text_bounds: &Bounds<Pixels>,
8800 content_origin: gpui::Point<Pixels>,
8801 editor_snapshot: &EditorSnapshot,
8802 visible_row_range: Range<DisplayRow>,
8803 scroll_top: f32,
8804 scroll_bottom: f32,
8805 line_height: Pixels,
8806 scroll_pixel_position: gpui::Point<Pixels>,
8807 target_display_point: DisplayPoint,
8808 editor_width: Pixels,
8809 window: &mut Window,
8810 cx: &mut App,
8811 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8812 if target_display_point.row().as_f32() < scroll_top {
8813 let mut element = self
8814 .render_edit_prediction_line_popover(
8815 "Jump to Edit",
8816 Some(IconName::ArrowUp),
8817 window,
8818 cx,
8819 )?
8820 .into_any();
8821
8822 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8823 let offset = point(
8824 (text_bounds.size.width - size.width) / 2.,
8825 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8826 );
8827
8828 let origin = text_bounds.origin + offset;
8829 element.prepaint_at(origin, window, cx);
8830 Some((element, origin))
8831 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8832 let mut element = self
8833 .render_edit_prediction_line_popover(
8834 "Jump to Edit",
8835 Some(IconName::ArrowDown),
8836 window,
8837 cx,
8838 )?
8839 .into_any();
8840
8841 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8842 let offset = point(
8843 (text_bounds.size.width - size.width) / 2.,
8844 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8845 );
8846
8847 let origin = text_bounds.origin + offset;
8848 element.prepaint_at(origin, window, cx);
8849 Some((element, origin))
8850 } else {
8851 self.render_edit_prediction_end_of_line_popover(
8852 "Jump to Edit",
8853 editor_snapshot,
8854 visible_row_range,
8855 target_display_point,
8856 line_height,
8857 scroll_pixel_position,
8858 content_origin,
8859 editor_width,
8860 window,
8861 cx,
8862 )
8863 }
8864 }
8865
8866 fn render_edit_prediction_end_of_line_popover(
8867 self: &mut Editor,
8868 label: &'static str,
8869 editor_snapshot: &EditorSnapshot,
8870 visible_row_range: Range<DisplayRow>,
8871 target_display_point: DisplayPoint,
8872 line_height: Pixels,
8873 scroll_pixel_position: gpui::Point<Pixels>,
8874 content_origin: gpui::Point<Pixels>,
8875 editor_width: Pixels,
8876 window: &mut Window,
8877 cx: &mut App,
8878 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8879 let target_line_end = DisplayPoint::new(
8880 target_display_point.row(),
8881 editor_snapshot.line_len(target_display_point.row()),
8882 );
8883
8884 let mut element = self
8885 .render_edit_prediction_line_popover(label, None, window, cx)?
8886 .into_any();
8887
8888 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8889
8890 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8891
8892 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8893 let mut origin = start_point
8894 + line_origin
8895 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8896 origin.x = origin.x.max(content_origin.x);
8897
8898 let max_x = content_origin.x + editor_width - size.width;
8899
8900 if origin.x > max_x {
8901 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8902
8903 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8904 origin.y += offset;
8905 IconName::ArrowUp
8906 } else {
8907 origin.y -= offset;
8908 IconName::ArrowDown
8909 };
8910
8911 element = self
8912 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8913 .into_any();
8914
8915 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8916
8917 origin.x = content_origin.x + editor_width - size.width - px(2.);
8918 }
8919
8920 element.prepaint_at(origin, window, cx);
8921 Some((element, origin))
8922 }
8923
8924 fn render_edit_prediction_diff_popover(
8925 self: &Editor,
8926 text_bounds: &Bounds<Pixels>,
8927 content_origin: gpui::Point<Pixels>,
8928 right_margin: Pixels,
8929 editor_snapshot: &EditorSnapshot,
8930 visible_row_range: Range<DisplayRow>,
8931 line_layouts: &[LineWithInvisibles],
8932 line_height: Pixels,
8933 scroll_pixel_position: gpui::Point<Pixels>,
8934 newest_selection_head: Option<DisplayPoint>,
8935 editor_width: Pixels,
8936 style: &EditorStyle,
8937 edits: &Vec<(Range<Anchor>, String)>,
8938 edit_preview: &Option<language::EditPreview>,
8939 snapshot: &language::BufferSnapshot,
8940 window: &mut Window,
8941 cx: &mut App,
8942 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8943 let edit_start = edits
8944 .first()
8945 .unwrap()
8946 .0
8947 .start
8948 .to_display_point(editor_snapshot);
8949 let edit_end = edits
8950 .last()
8951 .unwrap()
8952 .0
8953 .end
8954 .to_display_point(editor_snapshot);
8955
8956 let is_visible = visible_row_range.contains(&edit_start.row())
8957 || visible_row_range.contains(&edit_end.row());
8958 if !is_visible {
8959 return None;
8960 }
8961
8962 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8963 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8964 } else {
8965 // Fallback for providers without edit_preview
8966 crate::edit_prediction_fallback_text(edits, cx)
8967 };
8968
8969 let styled_text = highlighted_edits.to_styled_text(&style.text);
8970 let line_count = highlighted_edits.text.lines().count();
8971
8972 const BORDER_WIDTH: Pixels = px(1.);
8973
8974 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8975 let has_keybind = keybind.is_some();
8976
8977 let mut element = h_flex()
8978 .items_start()
8979 .child(
8980 h_flex()
8981 .bg(cx.theme().colors().editor_background)
8982 .border(BORDER_WIDTH)
8983 .shadow_xs()
8984 .border_color(cx.theme().colors().border)
8985 .rounded_l_lg()
8986 .when(line_count > 1, |el| el.rounded_br_lg())
8987 .pr_1()
8988 .child(styled_text),
8989 )
8990 .child(
8991 h_flex()
8992 .h(line_height + BORDER_WIDTH * 2.)
8993 .px_1p5()
8994 .gap_1()
8995 // Workaround: For some reason, there's a gap if we don't do this
8996 .ml(-BORDER_WIDTH)
8997 .shadow(vec![gpui::BoxShadow {
8998 color: gpui::black().opacity(0.05),
8999 offset: point(px(1.), px(1.)),
9000 blur_radius: px(2.),
9001 spread_radius: px(0.),
9002 }])
9003 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9004 .border(BORDER_WIDTH)
9005 .border_color(cx.theme().colors().border)
9006 .rounded_r_lg()
9007 .id("edit_prediction_diff_popover_keybind")
9008 .when(!has_keybind, |el| {
9009 let status_colors = cx.theme().status();
9010
9011 el.bg(status_colors.error_background)
9012 .border_color(status_colors.error.opacity(0.6))
9013 .child(Icon::new(IconName::Info).color(Color::Error))
9014 .cursor_default()
9015 .hoverable_tooltip(move |_window, cx| {
9016 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9017 })
9018 })
9019 .children(keybind),
9020 )
9021 .into_any();
9022
9023 let longest_row =
9024 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9025 let longest_line_width = if visible_row_range.contains(&longest_row) {
9026 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9027 } else {
9028 layout_line(
9029 longest_row,
9030 editor_snapshot,
9031 style,
9032 editor_width,
9033 |_| false,
9034 window,
9035 cx,
9036 )
9037 .width
9038 };
9039
9040 let viewport_bounds =
9041 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9042 right: -right_margin,
9043 ..Default::default()
9044 });
9045
9046 let x_after_longest =
9047 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9048 - scroll_pixel_position.x;
9049
9050 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9051
9052 // Fully visible if it can be displayed within the window (allow overlapping other
9053 // panes). However, this is only allowed if the popover starts within text_bounds.
9054 let can_position_to_the_right = x_after_longest < text_bounds.right()
9055 && x_after_longest + element_bounds.width < viewport_bounds.right();
9056
9057 let mut origin = if can_position_to_the_right {
9058 point(
9059 x_after_longest,
9060 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9061 - scroll_pixel_position.y,
9062 )
9063 } else {
9064 let cursor_row = newest_selection_head.map(|head| head.row());
9065 let above_edit = edit_start
9066 .row()
9067 .0
9068 .checked_sub(line_count as u32)
9069 .map(DisplayRow);
9070 let below_edit = Some(edit_end.row() + 1);
9071 let above_cursor =
9072 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9073 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9074
9075 // Place the edit popover adjacent to the edit if there is a location
9076 // available that is onscreen and does not obscure the cursor. Otherwise,
9077 // place it adjacent to the cursor.
9078 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9079 .into_iter()
9080 .flatten()
9081 .find(|&start_row| {
9082 let end_row = start_row + line_count as u32;
9083 visible_row_range.contains(&start_row)
9084 && visible_row_range.contains(&end_row)
9085 && cursor_row
9086 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9087 })?;
9088
9089 content_origin
9090 + point(
9091 -scroll_pixel_position.x,
9092 row_target.as_f32() * line_height - scroll_pixel_position.y,
9093 )
9094 };
9095
9096 origin.x -= BORDER_WIDTH;
9097
9098 window.defer_draw(element, origin, 1);
9099
9100 // Do not return an element, since it will already be drawn due to defer_draw.
9101 None
9102 }
9103
9104 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9105 px(30.)
9106 }
9107
9108 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9109 if self.read_only(cx) {
9110 cx.theme().players().read_only()
9111 } else {
9112 self.style.as_ref().unwrap().local_player
9113 }
9114 }
9115
9116 fn render_edit_prediction_accept_keybind(
9117 &self,
9118 window: &mut Window,
9119 cx: &App,
9120 ) -> Option<AnyElement> {
9121 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9122 let accept_keystroke = accept_binding.keystroke()?;
9123
9124 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9125
9126 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9127 Color::Accent
9128 } else {
9129 Color::Muted
9130 };
9131
9132 h_flex()
9133 .px_0p5()
9134 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9135 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9136 .text_size(TextSize::XSmall.rems(cx))
9137 .child(h_flex().children(ui::render_modifiers(
9138 accept_keystroke.modifiers(),
9139 PlatformStyle::platform(),
9140 Some(modifiers_color),
9141 Some(IconSize::XSmall.rems().into()),
9142 true,
9143 )))
9144 .when(is_platform_style_mac, |parent| {
9145 parent.child(accept_keystroke.key().to_string())
9146 })
9147 .when(!is_platform_style_mac, |parent| {
9148 parent.child(
9149 Key::new(
9150 util::capitalize(accept_keystroke.key()),
9151 Some(Color::Default),
9152 )
9153 .size(Some(IconSize::XSmall.rems().into())),
9154 )
9155 })
9156 .into_any()
9157 .into()
9158 }
9159
9160 fn render_edit_prediction_line_popover(
9161 &self,
9162 label: impl Into<SharedString>,
9163 icon: Option<IconName>,
9164 window: &mut Window,
9165 cx: &App,
9166 ) -> Option<Stateful<Div>> {
9167 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9168
9169 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9170 let has_keybind = keybind.is_some();
9171
9172 let result = h_flex()
9173 .id("ep-line-popover")
9174 .py_0p5()
9175 .pl_1()
9176 .pr(padding_right)
9177 .gap_1()
9178 .rounded_md()
9179 .border_1()
9180 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9181 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9182 .shadow_xs()
9183 .when(!has_keybind, |el| {
9184 let status_colors = cx.theme().status();
9185
9186 el.bg(status_colors.error_background)
9187 .border_color(status_colors.error.opacity(0.6))
9188 .pl_2()
9189 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9190 .cursor_default()
9191 .hoverable_tooltip(move |_window, cx| {
9192 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9193 })
9194 })
9195 .children(keybind)
9196 .child(
9197 Label::new(label)
9198 .size(LabelSize::Small)
9199 .when(!has_keybind, |el| {
9200 el.color(cx.theme().status().error.into()).strikethrough()
9201 }),
9202 )
9203 .when(!has_keybind, |el| {
9204 el.child(
9205 h_flex().ml_1().child(
9206 Icon::new(IconName::Info)
9207 .size(IconSize::Small)
9208 .color(cx.theme().status().error.into()),
9209 ),
9210 )
9211 })
9212 .when_some(icon, |element, icon| {
9213 element.child(
9214 div()
9215 .mt(px(1.5))
9216 .child(Icon::new(icon).size(IconSize::Small)),
9217 )
9218 });
9219
9220 Some(result)
9221 }
9222
9223 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9224 let accent_color = cx.theme().colors().text_accent;
9225 let editor_bg_color = cx.theme().colors().editor_background;
9226 editor_bg_color.blend(accent_color.opacity(0.1))
9227 }
9228
9229 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9230 let accent_color = cx.theme().colors().text_accent;
9231 let editor_bg_color = cx.theme().colors().editor_background;
9232 editor_bg_color.blend(accent_color.opacity(0.6))
9233 }
9234 fn get_prediction_provider_icon_name(
9235 provider: &Option<RegisteredEditPredictionProvider>,
9236 ) -> IconName {
9237 match provider {
9238 Some(provider) => match provider.provider.name() {
9239 "copilot" => IconName::Copilot,
9240 "supermaven" => IconName::Supermaven,
9241 _ => IconName::ZedPredict,
9242 },
9243 None => IconName::ZedPredict,
9244 }
9245 }
9246
9247 fn render_edit_prediction_cursor_popover(
9248 &self,
9249 min_width: Pixels,
9250 max_width: Pixels,
9251 cursor_point: Point,
9252 style: &EditorStyle,
9253 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9254 _window: &Window,
9255 cx: &mut Context<Editor>,
9256 ) -> Option<AnyElement> {
9257 let provider = self.edit_prediction_provider.as_ref()?;
9258 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9259
9260 let is_refreshing = provider.provider.is_refreshing(cx);
9261
9262 fn pending_completion_container(icon: IconName) -> Div {
9263 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9264 }
9265
9266 let completion = match &self.active_edit_prediction {
9267 Some(prediction) => {
9268 if !self.has_visible_completions_menu() {
9269 const RADIUS: Pixels = px(6.);
9270 const BORDER_WIDTH: Pixels = px(1.);
9271
9272 return Some(
9273 h_flex()
9274 .elevation_2(cx)
9275 .border(BORDER_WIDTH)
9276 .border_color(cx.theme().colors().border)
9277 .when(accept_keystroke.is_none(), |el| {
9278 el.border_color(cx.theme().status().error)
9279 })
9280 .rounded(RADIUS)
9281 .rounded_tl(px(0.))
9282 .overflow_hidden()
9283 .child(div().px_1p5().child(match &prediction.completion {
9284 EditPrediction::Move { target, snapshot } => {
9285 use text::ToPoint as _;
9286 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9287 {
9288 Icon::new(IconName::ZedPredictDown)
9289 } else {
9290 Icon::new(IconName::ZedPredictUp)
9291 }
9292 }
9293 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9294 }))
9295 .child(
9296 h_flex()
9297 .gap_1()
9298 .py_1()
9299 .px_2()
9300 .rounded_r(RADIUS - BORDER_WIDTH)
9301 .border_l_1()
9302 .border_color(cx.theme().colors().border)
9303 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9304 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9305 el.child(
9306 Label::new("Hold")
9307 .size(LabelSize::Small)
9308 .when(accept_keystroke.is_none(), |el| {
9309 el.strikethrough()
9310 })
9311 .line_height_style(LineHeightStyle::UiLabel),
9312 )
9313 })
9314 .id("edit_prediction_cursor_popover_keybind")
9315 .when(accept_keystroke.is_none(), |el| {
9316 let status_colors = cx.theme().status();
9317
9318 el.bg(status_colors.error_background)
9319 .border_color(status_colors.error.opacity(0.6))
9320 .child(Icon::new(IconName::Info).color(Color::Error))
9321 .cursor_default()
9322 .hoverable_tooltip(move |_window, cx| {
9323 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9324 .into()
9325 })
9326 })
9327 .when_some(
9328 accept_keystroke.as_ref(),
9329 |el, accept_keystroke| {
9330 el.child(h_flex().children(ui::render_modifiers(
9331 accept_keystroke.modifiers(),
9332 PlatformStyle::platform(),
9333 Some(Color::Default),
9334 Some(IconSize::XSmall.rems().into()),
9335 false,
9336 )))
9337 },
9338 ),
9339 )
9340 .into_any(),
9341 );
9342 }
9343
9344 self.render_edit_prediction_cursor_popover_preview(
9345 prediction,
9346 cursor_point,
9347 style,
9348 cx,
9349 )?
9350 }
9351
9352 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9353 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9354 stale_completion,
9355 cursor_point,
9356 style,
9357 cx,
9358 )?,
9359
9360 None => pending_completion_container(provider_icon)
9361 .child(Label::new("...").size(LabelSize::Small)),
9362 },
9363
9364 None => pending_completion_container(provider_icon)
9365 .child(Label::new("...").size(LabelSize::Small)),
9366 };
9367
9368 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9369 completion
9370 .with_animation(
9371 "loading-completion",
9372 Animation::new(Duration::from_secs(2))
9373 .repeat()
9374 .with_easing(pulsating_between(0.4, 0.8)),
9375 |label, delta| label.opacity(delta),
9376 )
9377 .into_any_element()
9378 } else {
9379 completion.into_any_element()
9380 };
9381
9382 let has_completion = self.active_edit_prediction.is_some();
9383
9384 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9385 Some(
9386 h_flex()
9387 .min_w(min_width)
9388 .max_w(max_width)
9389 .flex_1()
9390 .elevation_2(cx)
9391 .border_color(cx.theme().colors().border)
9392 .child(
9393 div()
9394 .flex_1()
9395 .py_1()
9396 .px_2()
9397 .overflow_hidden()
9398 .child(completion),
9399 )
9400 .when_some(accept_keystroke, |el, accept_keystroke| {
9401 if !accept_keystroke.modifiers().modified() {
9402 return el;
9403 }
9404
9405 el.child(
9406 h_flex()
9407 .h_full()
9408 .border_l_1()
9409 .rounded_r_lg()
9410 .border_color(cx.theme().colors().border)
9411 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9412 .gap_1()
9413 .py_1()
9414 .px_2()
9415 .child(
9416 h_flex()
9417 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9418 .when(is_platform_style_mac, |parent| parent.gap_1())
9419 .child(h_flex().children(ui::render_modifiers(
9420 accept_keystroke.modifiers(),
9421 PlatformStyle::platform(),
9422 Some(if !has_completion {
9423 Color::Muted
9424 } else {
9425 Color::Default
9426 }),
9427 None,
9428 false,
9429 ))),
9430 )
9431 .child(Label::new("Preview").into_any_element())
9432 .opacity(if has_completion { 1.0 } else { 0.4 }),
9433 )
9434 })
9435 .into_any(),
9436 )
9437 }
9438
9439 fn render_edit_prediction_cursor_popover_preview(
9440 &self,
9441 completion: &EditPredictionState,
9442 cursor_point: Point,
9443 style: &EditorStyle,
9444 cx: &mut Context<Editor>,
9445 ) -> Option<Div> {
9446 use text::ToPoint as _;
9447
9448 fn render_relative_row_jump(
9449 prefix: impl Into<String>,
9450 current_row: u32,
9451 target_row: u32,
9452 ) -> Div {
9453 let (row_diff, arrow) = if target_row < current_row {
9454 (current_row - target_row, IconName::ArrowUp)
9455 } else {
9456 (target_row - current_row, IconName::ArrowDown)
9457 };
9458
9459 h_flex()
9460 .child(
9461 Label::new(format!("{}{}", prefix.into(), row_diff))
9462 .color(Color::Muted)
9463 .size(LabelSize::Small),
9464 )
9465 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9466 }
9467
9468 let supports_jump = self
9469 .edit_prediction_provider
9470 .as_ref()
9471 .map(|provider| provider.provider.supports_jump_to_edit())
9472 .unwrap_or(true);
9473
9474 match &completion.completion {
9475 EditPrediction::Move {
9476 target, snapshot, ..
9477 } => {
9478 if !supports_jump {
9479 return None;
9480 }
9481
9482 Some(
9483 h_flex()
9484 .px_2()
9485 .gap_2()
9486 .flex_1()
9487 .child(
9488 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9489 Icon::new(IconName::ZedPredictDown)
9490 } else {
9491 Icon::new(IconName::ZedPredictUp)
9492 },
9493 )
9494 .child(Label::new("Jump to Edit")),
9495 )
9496 }
9497
9498 EditPrediction::Edit {
9499 edits,
9500 edit_preview,
9501 snapshot,
9502 display_mode: _,
9503 } => {
9504 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9505
9506 let (highlighted_edits, has_more_lines) =
9507 if let Some(edit_preview) = edit_preview.as_ref() {
9508 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9509 .first_line_preview()
9510 } else {
9511 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9512 };
9513
9514 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9515 .with_default_highlights(&style.text, highlighted_edits.highlights);
9516
9517 let preview = h_flex()
9518 .gap_1()
9519 .min_w_16()
9520 .child(styled_text)
9521 .when(has_more_lines, |parent| parent.child("…"));
9522
9523 let left = if supports_jump && first_edit_row != cursor_point.row {
9524 render_relative_row_jump("", cursor_point.row, first_edit_row)
9525 .into_any_element()
9526 } else {
9527 let icon_name =
9528 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9529 Icon::new(icon_name).into_any_element()
9530 };
9531
9532 Some(
9533 h_flex()
9534 .h_full()
9535 .flex_1()
9536 .gap_2()
9537 .pr_1()
9538 .overflow_x_hidden()
9539 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9540 .child(left)
9541 .child(preview),
9542 )
9543 }
9544 }
9545 }
9546
9547 pub fn render_context_menu(
9548 &self,
9549 style: &EditorStyle,
9550 max_height_in_lines: u32,
9551 window: &mut Window,
9552 cx: &mut Context<Editor>,
9553 ) -> Option<AnyElement> {
9554 let menu = self.context_menu.borrow();
9555 let menu = menu.as_ref()?;
9556 if !menu.visible() {
9557 return None;
9558 };
9559 Some(menu.render(style, max_height_in_lines, window, cx))
9560 }
9561
9562 fn render_context_menu_aside(
9563 &mut self,
9564 max_size: Size<Pixels>,
9565 window: &mut Window,
9566 cx: &mut Context<Editor>,
9567 ) -> Option<AnyElement> {
9568 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9569 if menu.visible() {
9570 menu.render_aside(max_size, window, cx)
9571 } else {
9572 None
9573 }
9574 })
9575 }
9576
9577 fn hide_context_menu(
9578 &mut self,
9579 window: &mut Window,
9580 cx: &mut Context<Self>,
9581 ) -> Option<CodeContextMenu> {
9582 cx.notify();
9583 self.completion_tasks.clear();
9584 let context_menu = self.context_menu.borrow_mut().take();
9585 self.stale_edit_prediction_in_menu.take();
9586 self.update_visible_edit_prediction(window, cx);
9587 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9588 && let Some(completion_provider) = &self.completion_provider
9589 {
9590 completion_provider.selection_changed(None, window, cx);
9591 }
9592 context_menu
9593 }
9594
9595 fn show_snippet_choices(
9596 &mut self,
9597 choices: &Vec<String>,
9598 selection: Range<Anchor>,
9599 cx: &mut Context<Self>,
9600 ) {
9601 let Some((_, buffer, _)) = self
9602 .buffer()
9603 .read(cx)
9604 .excerpt_containing(selection.start, cx)
9605 else {
9606 return;
9607 };
9608 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9609 else {
9610 return;
9611 };
9612 if buffer != end_buffer {
9613 log::error!("expected anchor range to have matching buffer IDs");
9614 return;
9615 }
9616
9617 let id = post_inc(&mut self.next_completion_id);
9618 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9619 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9620 CompletionsMenu::new_snippet_choices(
9621 id,
9622 true,
9623 choices,
9624 selection,
9625 buffer,
9626 snippet_sort_order,
9627 ),
9628 ));
9629 }
9630
9631 pub fn insert_snippet(
9632 &mut self,
9633 insertion_ranges: &[Range<usize>],
9634 snippet: Snippet,
9635 window: &mut Window,
9636 cx: &mut Context<Self>,
9637 ) -> Result<()> {
9638 struct Tabstop<T> {
9639 is_end_tabstop: bool,
9640 ranges: Vec<Range<T>>,
9641 choices: Option<Vec<String>>,
9642 }
9643
9644 let tabstops = self.buffer.update(cx, |buffer, cx| {
9645 let snippet_text: Arc<str> = snippet.text.clone().into();
9646 let edits = insertion_ranges
9647 .iter()
9648 .cloned()
9649 .map(|range| (range, snippet_text.clone()));
9650 let autoindent_mode = AutoindentMode::Block {
9651 original_indent_columns: Vec::new(),
9652 };
9653 buffer.edit(edits, Some(autoindent_mode), cx);
9654
9655 let snapshot = &*buffer.read(cx);
9656 let snippet = &snippet;
9657 snippet
9658 .tabstops
9659 .iter()
9660 .map(|tabstop| {
9661 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9662 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9663 });
9664 let mut tabstop_ranges = tabstop
9665 .ranges
9666 .iter()
9667 .flat_map(|tabstop_range| {
9668 let mut delta = 0_isize;
9669 insertion_ranges.iter().map(move |insertion_range| {
9670 let insertion_start = insertion_range.start as isize + delta;
9671 delta +=
9672 snippet.text.len() as isize - insertion_range.len() as isize;
9673
9674 let start = ((insertion_start + tabstop_range.start) as usize)
9675 .min(snapshot.len());
9676 let end = ((insertion_start + tabstop_range.end) as usize)
9677 .min(snapshot.len());
9678 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9679 })
9680 })
9681 .collect::<Vec<_>>();
9682 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9683
9684 Tabstop {
9685 is_end_tabstop,
9686 ranges: tabstop_ranges,
9687 choices: tabstop.choices.clone(),
9688 }
9689 })
9690 .collect::<Vec<_>>()
9691 });
9692 if let Some(tabstop) = tabstops.first() {
9693 self.change_selections(Default::default(), window, cx, |s| {
9694 // Reverse order so that the first range is the newest created selection.
9695 // Completions will use it and autoscroll will prioritize it.
9696 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9697 });
9698
9699 if let Some(choices) = &tabstop.choices
9700 && let Some(selection) = tabstop.ranges.first()
9701 {
9702 self.show_snippet_choices(choices, selection.clone(), cx)
9703 }
9704
9705 // If we're already at the last tabstop and it's at the end of the snippet,
9706 // we're done, we don't need to keep the state around.
9707 if !tabstop.is_end_tabstop {
9708 let choices = tabstops
9709 .iter()
9710 .map(|tabstop| tabstop.choices.clone())
9711 .collect();
9712
9713 let ranges = tabstops
9714 .into_iter()
9715 .map(|tabstop| tabstop.ranges)
9716 .collect::<Vec<_>>();
9717
9718 self.snippet_stack.push(SnippetState {
9719 active_index: 0,
9720 ranges,
9721 choices,
9722 });
9723 }
9724
9725 // Check whether the just-entered snippet ends with an auto-closable bracket.
9726 if self.autoclose_regions.is_empty() {
9727 let snapshot = self.buffer.read(cx).snapshot(cx);
9728 let mut all_selections = self.selections.all::<Point>(cx);
9729 for selection in &mut all_selections {
9730 let selection_head = selection.head();
9731 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9732 continue;
9733 };
9734
9735 let mut bracket_pair = None;
9736 let max_lookup_length = scope
9737 .brackets()
9738 .map(|(pair, _)| {
9739 pair.start
9740 .as_str()
9741 .chars()
9742 .count()
9743 .max(pair.end.as_str().chars().count())
9744 })
9745 .max();
9746 if let Some(max_lookup_length) = max_lookup_length {
9747 let next_text = snapshot
9748 .chars_at(selection_head)
9749 .take(max_lookup_length)
9750 .collect::<String>();
9751 let prev_text = snapshot
9752 .reversed_chars_at(selection_head)
9753 .take(max_lookup_length)
9754 .collect::<String>();
9755
9756 for (pair, enabled) in scope.brackets() {
9757 if enabled
9758 && pair.close
9759 && prev_text.starts_with(pair.start.as_str())
9760 && next_text.starts_with(pair.end.as_str())
9761 {
9762 bracket_pair = Some(pair.clone());
9763 break;
9764 }
9765 }
9766 }
9767
9768 if let Some(pair) = bracket_pair {
9769 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9770 let autoclose_enabled =
9771 self.use_autoclose && snapshot_settings.use_autoclose;
9772 if autoclose_enabled {
9773 let start = snapshot.anchor_after(selection_head);
9774 let end = snapshot.anchor_after(selection_head);
9775 self.autoclose_regions.push(AutocloseRegion {
9776 selection_id: selection.id,
9777 range: start..end,
9778 pair,
9779 });
9780 }
9781 }
9782 }
9783 }
9784 }
9785 Ok(())
9786 }
9787
9788 pub fn move_to_next_snippet_tabstop(
9789 &mut self,
9790 window: &mut Window,
9791 cx: &mut Context<Self>,
9792 ) -> bool {
9793 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9794 }
9795
9796 pub fn move_to_prev_snippet_tabstop(
9797 &mut self,
9798 window: &mut Window,
9799 cx: &mut Context<Self>,
9800 ) -> bool {
9801 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9802 }
9803
9804 pub fn move_to_snippet_tabstop(
9805 &mut self,
9806 bias: Bias,
9807 window: &mut Window,
9808 cx: &mut Context<Self>,
9809 ) -> bool {
9810 if let Some(mut snippet) = self.snippet_stack.pop() {
9811 match bias {
9812 Bias::Left => {
9813 if snippet.active_index > 0 {
9814 snippet.active_index -= 1;
9815 } else {
9816 self.snippet_stack.push(snippet);
9817 return false;
9818 }
9819 }
9820 Bias::Right => {
9821 if snippet.active_index + 1 < snippet.ranges.len() {
9822 snippet.active_index += 1;
9823 } else {
9824 self.snippet_stack.push(snippet);
9825 return false;
9826 }
9827 }
9828 }
9829 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9830 self.change_selections(Default::default(), window, cx, |s| {
9831 // Reverse order so that the first range is the newest created selection.
9832 // Completions will use it and autoscroll will prioritize it.
9833 s.select_ranges(current_ranges.iter().rev().cloned())
9834 });
9835
9836 if let Some(choices) = &snippet.choices[snippet.active_index]
9837 && let Some(selection) = current_ranges.first()
9838 {
9839 self.show_snippet_choices(choices, selection.clone(), cx);
9840 }
9841
9842 // If snippet state is not at the last tabstop, push it back on the stack
9843 if snippet.active_index + 1 < snippet.ranges.len() {
9844 self.snippet_stack.push(snippet);
9845 }
9846 return true;
9847 }
9848 }
9849
9850 false
9851 }
9852
9853 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9854 self.transact(window, cx, |this, window, cx| {
9855 this.select_all(&SelectAll, window, cx);
9856 this.insert("", window, cx);
9857 });
9858 }
9859
9860 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9861 if self.read_only(cx) {
9862 return;
9863 }
9864 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9865 self.transact(window, cx, |this, window, cx| {
9866 this.select_autoclose_pair(window, cx);
9867 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9868 if !this.linked_edit_ranges.is_empty() {
9869 let selections = this.selections.all::<MultiBufferPoint>(cx);
9870 let snapshot = this.buffer.read(cx).snapshot(cx);
9871
9872 for selection in selections.iter() {
9873 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9874 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9875 if selection_start.buffer_id != selection_end.buffer_id {
9876 continue;
9877 }
9878 if let Some(ranges) =
9879 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9880 {
9881 for (buffer, entries) in ranges {
9882 linked_ranges.entry(buffer).or_default().extend(entries);
9883 }
9884 }
9885 }
9886 }
9887
9888 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9889 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9890 for selection in &mut selections {
9891 if selection.is_empty() {
9892 let old_head = selection.head();
9893 let mut new_head =
9894 movement::left(&display_map, old_head.to_display_point(&display_map))
9895 .to_point(&display_map);
9896 if let Some((buffer, line_buffer_range)) = display_map
9897 .buffer_snapshot
9898 .buffer_line_for_row(MultiBufferRow(old_head.row))
9899 {
9900 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9901 let indent_len = match indent_size.kind {
9902 IndentKind::Space => {
9903 buffer.settings_at(line_buffer_range.start, cx).tab_size
9904 }
9905 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9906 };
9907 if old_head.column <= indent_size.len && old_head.column > 0 {
9908 let indent_len = indent_len.get();
9909 new_head = cmp::min(
9910 new_head,
9911 MultiBufferPoint::new(
9912 old_head.row,
9913 ((old_head.column - 1) / indent_len) * indent_len,
9914 ),
9915 );
9916 }
9917 }
9918
9919 selection.set_head(new_head, SelectionGoal::None);
9920 }
9921 }
9922
9923 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9924 this.insert("", window, cx);
9925 let empty_str: Arc<str> = Arc::from("");
9926 for (buffer, edits) in linked_ranges {
9927 let snapshot = buffer.read(cx).snapshot();
9928 use text::ToPoint as TP;
9929
9930 let edits = edits
9931 .into_iter()
9932 .map(|range| {
9933 let end_point = TP::to_point(&range.end, &snapshot);
9934 let mut start_point = TP::to_point(&range.start, &snapshot);
9935
9936 if end_point == start_point {
9937 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9938 .saturating_sub(1);
9939 start_point =
9940 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9941 };
9942
9943 (start_point..end_point, empty_str.clone())
9944 })
9945 .sorted_by_key(|(range, _)| range.start)
9946 .collect::<Vec<_>>();
9947 buffer.update(cx, |this, cx| {
9948 this.edit(edits, None, cx);
9949 })
9950 }
9951 this.refresh_edit_prediction(true, false, window, cx);
9952 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9953 });
9954 }
9955
9956 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9957 if self.read_only(cx) {
9958 return;
9959 }
9960 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9961 self.transact(window, cx, |this, window, cx| {
9962 this.change_selections(Default::default(), window, cx, |s| {
9963 s.move_with(|map, selection| {
9964 if selection.is_empty() {
9965 let cursor = movement::right(map, selection.head());
9966 selection.end = cursor;
9967 selection.reversed = true;
9968 selection.goal = SelectionGoal::None;
9969 }
9970 })
9971 });
9972 this.insert("", window, cx);
9973 this.refresh_edit_prediction(true, false, window, cx);
9974 });
9975 }
9976
9977 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9978 if self.mode.is_single_line() {
9979 cx.propagate();
9980 return;
9981 }
9982
9983 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9984 if self.move_to_prev_snippet_tabstop(window, cx) {
9985 return;
9986 }
9987 self.outdent(&Outdent, window, cx);
9988 }
9989
9990 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9991 if self.mode.is_single_line() {
9992 cx.propagate();
9993 return;
9994 }
9995
9996 if self.move_to_next_snippet_tabstop(window, cx) {
9997 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9998 return;
9999 }
10000 if self.read_only(cx) {
10001 return;
10002 }
10003 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10004 let mut selections = self.selections.all_adjusted(cx);
10005 let buffer = self.buffer.read(cx);
10006 let snapshot = buffer.snapshot(cx);
10007 let rows_iter = selections.iter().map(|s| s.head().row);
10008 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10009
10010 let has_some_cursor_in_whitespace = selections
10011 .iter()
10012 .filter(|selection| selection.is_empty())
10013 .any(|selection| {
10014 let cursor = selection.head();
10015 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10016 cursor.column < current_indent.len
10017 });
10018
10019 let mut edits = Vec::new();
10020 let mut prev_edited_row = 0;
10021 let mut row_delta = 0;
10022 for selection in &mut selections {
10023 if selection.start.row != prev_edited_row {
10024 row_delta = 0;
10025 }
10026 prev_edited_row = selection.end.row;
10027
10028 // If the selection is non-empty, then increase the indentation of the selected lines.
10029 if !selection.is_empty() {
10030 row_delta =
10031 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10032 continue;
10033 }
10034
10035 let cursor = selection.head();
10036 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10037 if let Some(suggested_indent) =
10038 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10039 {
10040 // Don't do anything if already at suggested indent
10041 // and there is any other cursor which is not
10042 if has_some_cursor_in_whitespace
10043 && cursor.column == current_indent.len
10044 && current_indent.len == suggested_indent.len
10045 {
10046 continue;
10047 }
10048
10049 // Adjust line and move cursor to suggested indent
10050 // if cursor is not at suggested indent
10051 if cursor.column < suggested_indent.len
10052 && cursor.column <= current_indent.len
10053 && current_indent.len <= suggested_indent.len
10054 {
10055 selection.start = Point::new(cursor.row, suggested_indent.len);
10056 selection.end = selection.start;
10057 if row_delta == 0 {
10058 edits.extend(Buffer::edit_for_indent_size_adjustment(
10059 cursor.row,
10060 current_indent,
10061 suggested_indent,
10062 ));
10063 row_delta = suggested_indent.len - current_indent.len;
10064 }
10065 continue;
10066 }
10067
10068 // If current indent is more than suggested indent
10069 // only move cursor to current indent and skip indent
10070 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10071 selection.start = Point::new(cursor.row, current_indent.len);
10072 selection.end = selection.start;
10073 continue;
10074 }
10075 }
10076
10077 // Otherwise, insert a hard or soft tab.
10078 let settings = buffer.language_settings_at(cursor, cx);
10079 let tab_size = if settings.hard_tabs {
10080 IndentSize::tab()
10081 } else {
10082 let tab_size = settings.tab_size.get();
10083 let indent_remainder = snapshot
10084 .text_for_range(Point::new(cursor.row, 0)..cursor)
10085 .flat_map(str::chars)
10086 .fold(row_delta % tab_size, |counter: u32, c| {
10087 if c == '\t' {
10088 0
10089 } else {
10090 (counter + 1) % tab_size
10091 }
10092 });
10093
10094 let chars_to_next_tab_stop = tab_size - indent_remainder;
10095 IndentSize::spaces(chars_to_next_tab_stop)
10096 };
10097 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10098 selection.end = selection.start;
10099 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10100 row_delta += tab_size.len;
10101 }
10102
10103 self.transact(window, cx, |this, window, cx| {
10104 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10105 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10106 this.refresh_edit_prediction(true, false, window, cx);
10107 });
10108 }
10109
10110 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10111 if self.read_only(cx) {
10112 return;
10113 }
10114 if self.mode.is_single_line() {
10115 cx.propagate();
10116 return;
10117 }
10118
10119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10120 let mut selections = self.selections.all::<Point>(cx);
10121 let mut prev_edited_row = 0;
10122 let mut row_delta = 0;
10123 let mut edits = Vec::new();
10124 let buffer = self.buffer.read(cx);
10125 let snapshot = buffer.snapshot(cx);
10126 for selection in &mut selections {
10127 if selection.start.row != prev_edited_row {
10128 row_delta = 0;
10129 }
10130 prev_edited_row = selection.end.row;
10131
10132 row_delta =
10133 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10134 }
10135
10136 self.transact(window, cx, |this, window, cx| {
10137 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10138 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10139 });
10140 }
10141
10142 fn indent_selection(
10143 buffer: &MultiBuffer,
10144 snapshot: &MultiBufferSnapshot,
10145 selection: &mut Selection<Point>,
10146 edits: &mut Vec<(Range<Point>, String)>,
10147 delta_for_start_row: u32,
10148 cx: &App,
10149 ) -> u32 {
10150 let settings = buffer.language_settings_at(selection.start, cx);
10151 let tab_size = settings.tab_size.get();
10152 let indent_kind = if settings.hard_tabs {
10153 IndentKind::Tab
10154 } else {
10155 IndentKind::Space
10156 };
10157 let mut start_row = selection.start.row;
10158 let mut end_row = selection.end.row + 1;
10159
10160 // If a selection ends at the beginning of a line, don't indent
10161 // that last line.
10162 if selection.end.column == 0 && selection.end.row > selection.start.row {
10163 end_row -= 1;
10164 }
10165
10166 // Avoid re-indenting a row that has already been indented by a
10167 // previous selection, but still update this selection's column
10168 // to reflect that indentation.
10169 if delta_for_start_row > 0 {
10170 start_row += 1;
10171 selection.start.column += delta_for_start_row;
10172 if selection.end.row == selection.start.row {
10173 selection.end.column += delta_for_start_row;
10174 }
10175 }
10176
10177 let mut delta_for_end_row = 0;
10178 let has_multiple_rows = start_row + 1 != end_row;
10179 for row in start_row..end_row {
10180 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10181 let indent_delta = match (current_indent.kind, indent_kind) {
10182 (IndentKind::Space, IndentKind::Space) => {
10183 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10184 IndentSize::spaces(columns_to_next_tab_stop)
10185 }
10186 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10187 (_, IndentKind::Tab) => IndentSize::tab(),
10188 };
10189
10190 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10191 0
10192 } else {
10193 selection.start.column
10194 };
10195 let row_start = Point::new(row, start);
10196 edits.push((
10197 row_start..row_start,
10198 indent_delta.chars().collect::<String>(),
10199 ));
10200
10201 // Update this selection's endpoints to reflect the indentation.
10202 if row == selection.start.row {
10203 selection.start.column += indent_delta.len;
10204 }
10205 if row == selection.end.row {
10206 selection.end.column += indent_delta.len;
10207 delta_for_end_row = indent_delta.len;
10208 }
10209 }
10210
10211 if selection.start.row == selection.end.row {
10212 delta_for_start_row + delta_for_end_row
10213 } else {
10214 delta_for_end_row
10215 }
10216 }
10217
10218 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10219 if self.read_only(cx) {
10220 return;
10221 }
10222 if self.mode.is_single_line() {
10223 cx.propagate();
10224 return;
10225 }
10226
10227 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10229 let selections = self.selections.all::<Point>(cx);
10230 let mut deletion_ranges = Vec::new();
10231 let mut last_outdent = None;
10232 {
10233 let buffer = self.buffer.read(cx);
10234 let snapshot = buffer.snapshot(cx);
10235 for selection in &selections {
10236 let settings = buffer.language_settings_at(selection.start, cx);
10237 let tab_size = settings.tab_size.get();
10238 let mut rows = selection.spanned_rows(false, &display_map);
10239
10240 // Avoid re-outdenting a row that has already been outdented by a
10241 // previous selection.
10242 if let Some(last_row) = last_outdent
10243 && last_row == rows.start
10244 {
10245 rows.start = rows.start.next_row();
10246 }
10247 let has_multiple_rows = rows.len() > 1;
10248 for row in rows.iter_rows() {
10249 let indent_size = snapshot.indent_size_for_line(row);
10250 if indent_size.len > 0 {
10251 let deletion_len = match indent_size.kind {
10252 IndentKind::Space => {
10253 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10254 if columns_to_prev_tab_stop == 0 {
10255 tab_size
10256 } else {
10257 columns_to_prev_tab_stop
10258 }
10259 }
10260 IndentKind::Tab => 1,
10261 };
10262 let start = if has_multiple_rows
10263 || deletion_len > selection.start.column
10264 || indent_size.len < selection.start.column
10265 {
10266 0
10267 } else {
10268 selection.start.column - deletion_len
10269 };
10270 deletion_ranges.push(
10271 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10272 );
10273 last_outdent = Some(row);
10274 }
10275 }
10276 }
10277 }
10278
10279 self.transact(window, cx, |this, window, cx| {
10280 this.buffer.update(cx, |buffer, cx| {
10281 let empty_str: Arc<str> = Arc::default();
10282 buffer.edit(
10283 deletion_ranges
10284 .into_iter()
10285 .map(|range| (range, empty_str.clone())),
10286 None,
10287 cx,
10288 );
10289 });
10290 let selections = this.selections.all::<usize>(cx);
10291 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10292 });
10293 }
10294
10295 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10296 if self.read_only(cx) {
10297 return;
10298 }
10299 if self.mode.is_single_line() {
10300 cx.propagate();
10301 return;
10302 }
10303
10304 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10305 let selections = self
10306 .selections
10307 .all::<usize>(cx)
10308 .into_iter()
10309 .map(|s| s.range());
10310
10311 self.transact(window, cx, |this, window, cx| {
10312 this.buffer.update(cx, |buffer, cx| {
10313 buffer.autoindent_ranges(selections, cx);
10314 });
10315 let selections = this.selections.all::<usize>(cx);
10316 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10317 });
10318 }
10319
10320 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10321 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10322 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10323 let selections = self.selections.all::<Point>(cx);
10324
10325 let mut new_cursors = Vec::new();
10326 let mut edit_ranges = Vec::new();
10327 let mut selections = selections.iter().peekable();
10328 while let Some(selection) = selections.next() {
10329 let mut rows = selection.spanned_rows(false, &display_map);
10330 let goal_display_column = selection.head().to_display_point(&display_map).column();
10331
10332 // Accumulate contiguous regions of rows that we want to delete.
10333 while let Some(next_selection) = selections.peek() {
10334 let next_rows = next_selection.spanned_rows(false, &display_map);
10335 if next_rows.start <= rows.end {
10336 rows.end = next_rows.end;
10337 selections.next().unwrap();
10338 } else {
10339 break;
10340 }
10341 }
10342
10343 let buffer = &display_map.buffer_snapshot;
10344 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10345 let edit_end;
10346 let cursor_buffer_row;
10347 if buffer.max_point().row >= rows.end.0 {
10348 // If there's a line after the range, delete the \n from the end of the row range
10349 // and position the cursor on the next line.
10350 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10351 cursor_buffer_row = rows.end;
10352 } else {
10353 // If there isn't a line after the range, delete the \n from the line before the
10354 // start of the row range and position the cursor there.
10355 edit_start = edit_start.saturating_sub(1);
10356 edit_end = buffer.len();
10357 cursor_buffer_row = rows.start.previous_row();
10358 }
10359
10360 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10361 *cursor.column_mut() =
10362 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10363
10364 new_cursors.push((
10365 selection.id,
10366 buffer.anchor_after(cursor.to_point(&display_map)),
10367 ));
10368 edit_ranges.push(edit_start..edit_end);
10369 }
10370
10371 self.transact(window, cx, |this, window, cx| {
10372 let buffer = this.buffer.update(cx, |buffer, cx| {
10373 let empty_str: Arc<str> = Arc::default();
10374 buffer.edit(
10375 edit_ranges
10376 .into_iter()
10377 .map(|range| (range, empty_str.clone())),
10378 None,
10379 cx,
10380 );
10381 buffer.snapshot(cx)
10382 });
10383 let new_selections = new_cursors
10384 .into_iter()
10385 .map(|(id, cursor)| {
10386 let cursor = cursor.to_point(&buffer);
10387 Selection {
10388 id,
10389 start: cursor,
10390 end: cursor,
10391 reversed: false,
10392 goal: SelectionGoal::None,
10393 }
10394 })
10395 .collect();
10396
10397 this.change_selections(Default::default(), window, cx, |s| {
10398 s.select(new_selections);
10399 });
10400 });
10401 }
10402
10403 pub fn join_lines_impl(
10404 &mut self,
10405 insert_whitespace: bool,
10406 window: &mut Window,
10407 cx: &mut Context<Self>,
10408 ) {
10409 if self.read_only(cx) {
10410 return;
10411 }
10412 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10413 for selection in self.selections.all::<Point>(cx) {
10414 let start = MultiBufferRow(selection.start.row);
10415 // Treat single line selections as if they include the next line. Otherwise this action
10416 // would do nothing for single line selections individual cursors.
10417 let end = if selection.start.row == selection.end.row {
10418 MultiBufferRow(selection.start.row + 1)
10419 } else {
10420 MultiBufferRow(selection.end.row)
10421 };
10422
10423 if let Some(last_row_range) = row_ranges.last_mut()
10424 && start <= last_row_range.end
10425 {
10426 last_row_range.end = end;
10427 continue;
10428 }
10429 row_ranges.push(start..end);
10430 }
10431
10432 let snapshot = self.buffer.read(cx).snapshot(cx);
10433 let mut cursor_positions = Vec::new();
10434 for row_range in &row_ranges {
10435 let anchor = snapshot.anchor_before(Point::new(
10436 row_range.end.previous_row().0,
10437 snapshot.line_len(row_range.end.previous_row()),
10438 ));
10439 cursor_positions.push(anchor..anchor);
10440 }
10441
10442 self.transact(window, cx, |this, window, cx| {
10443 for row_range in row_ranges.into_iter().rev() {
10444 for row in row_range.iter_rows().rev() {
10445 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10446 let next_line_row = row.next_row();
10447 let indent = snapshot.indent_size_for_line(next_line_row);
10448 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10449
10450 let replace =
10451 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10452 " "
10453 } else {
10454 ""
10455 };
10456
10457 this.buffer.update(cx, |buffer, cx| {
10458 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10459 });
10460 }
10461 }
10462
10463 this.change_selections(Default::default(), window, cx, |s| {
10464 s.select_anchor_ranges(cursor_positions)
10465 });
10466 });
10467 }
10468
10469 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10470 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10471 self.join_lines_impl(true, window, cx);
10472 }
10473
10474 pub fn sort_lines_case_sensitive(
10475 &mut self,
10476 _: &SortLinesCaseSensitive,
10477 window: &mut Window,
10478 cx: &mut Context<Self>,
10479 ) {
10480 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10481 }
10482
10483 pub fn sort_lines_by_length(
10484 &mut self,
10485 _: &SortLinesByLength,
10486 window: &mut Window,
10487 cx: &mut Context<Self>,
10488 ) {
10489 self.manipulate_immutable_lines(window, cx, |lines| {
10490 lines.sort_by_key(|&line| line.chars().count())
10491 })
10492 }
10493
10494 pub fn sort_lines_case_insensitive(
10495 &mut self,
10496 _: &SortLinesCaseInsensitive,
10497 window: &mut Window,
10498 cx: &mut Context<Self>,
10499 ) {
10500 self.manipulate_immutable_lines(window, cx, |lines| {
10501 lines.sort_by_key(|line| line.to_lowercase())
10502 })
10503 }
10504
10505 pub fn unique_lines_case_insensitive(
10506 &mut self,
10507 _: &UniqueLinesCaseInsensitive,
10508 window: &mut Window,
10509 cx: &mut Context<Self>,
10510 ) {
10511 self.manipulate_immutable_lines(window, cx, |lines| {
10512 let mut seen = HashSet::default();
10513 lines.retain(|line| seen.insert(line.to_lowercase()));
10514 })
10515 }
10516
10517 pub fn unique_lines_case_sensitive(
10518 &mut self,
10519 _: &UniqueLinesCaseSensitive,
10520 window: &mut Window,
10521 cx: &mut Context<Self>,
10522 ) {
10523 self.manipulate_immutable_lines(window, cx, |lines| {
10524 let mut seen = HashSet::default();
10525 lines.retain(|line| seen.insert(*line));
10526 })
10527 }
10528
10529 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10530 let snapshot = self.buffer.read(cx).snapshot(cx);
10531 for selection in self.selections.disjoint_anchors_arc().iter() {
10532 if snapshot
10533 .language_at(selection.start)
10534 .and_then(|lang| lang.config().wrap_characters.as_ref())
10535 .is_some()
10536 {
10537 return true;
10538 }
10539 }
10540 false
10541 }
10542
10543 fn wrap_selections_in_tag(
10544 &mut self,
10545 _: &WrapSelectionsInTag,
10546 window: &mut Window,
10547 cx: &mut Context<Self>,
10548 ) {
10549 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10550
10551 let snapshot = self.buffer.read(cx).snapshot(cx);
10552
10553 let mut edits = Vec::new();
10554 let mut boundaries = Vec::new();
10555
10556 for selection in self.selections.all::<Point>(cx).iter() {
10557 let Some(wrap_config) = snapshot
10558 .language_at(selection.start)
10559 .and_then(|lang| lang.config().wrap_characters.clone())
10560 else {
10561 continue;
10562 };
10563
10564 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10565 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10566
10567 let start_before = snapshot.anchor_before(selection.start);
10568 let end_after = snapshot.anchor_after(selection.end);
10569
10570 edits.push((start_before..start_before, open_tag));
10571 edits.push((end_after..end_after, close_tag));
10572
10573 boundaries.push((
10574 start_before,
10575 end_after,
10576 wrap_config.start_prefix.len(),
10577 wrap_config.end_suffix.len(),
10578 ));
10579 }
10580
10581 if edits.is_empty() {
10582 return;
10583 }
10584
10585 self.transact(window, cx, |this, window, cx| {
10586 let buffer = this.buffer.update(cx, |buffer, cx| {
10587 buffer.edit(edits, None, cx);
10588 buffer.snapshot(cx)
10589 });
10590
10591 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10592 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10593 boundaries.into_iter()
10594 {
10595 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10596 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10597 new_selections.push(open_offset..open_offset);
10598 new_selections.push(close_offset..close_offset);
10599 }
10600
10601 this.change_selections(Default::default(), window, cx, |s| {
10602 s.select_ranges(new_selections);
10603 });
10604
10605 this.request_autoscroll(Autoscroll::fit(), cx);
10606 });
10607 }
10608
10609 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10610 let Some(project) = self.project.clone() else {
10611 return;
10612 };
10613 self.reload(project, window, cx)
10614 .detach_and_notify_err(window, cx);
10615 }
10616
10617 pub fn restore_file(
10618 &mut self,
10619 _: &::git::RestoreFile,
10620 window: &mut Window,
10621 cx: &mut Context<Self>,
10622 ) {
10623 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10624 let mut buffer_ids = HashSet::default();
10625 let snapshot = self.buffer().read(cx).snapshot(cx);
10626 for selection in self.selections.all::<usize>(cx) {
10627 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10628 }
10629
10630 let buffer = self.buffer().read(cx);
10631 let ranges = buffer_ids
10632 .into_iter()
10633 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10634 .collect::<Vec<_>>();
10635
10636 self.restore_hunks_in_ranges(ranges, window, cx);
10637 }
10638
10639 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10640 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10641 let selections = self
10642 .selections
10643 .all(cx)
10644 .into_iter()
10645 .map(|s| s.range())
10646 .collect();
10647 self.restore_hunks_in_ranges(selections, window, cx);
10648 }
10649
10650 pub fn restore_hunks_in_ranges(
10651 &mut self,
10652 ranges: Vec<Range<Point>>,
10653 window: &mut Window,
10654 cx: &mut Context<Editor>,
10655 ) {
10656 let mut revert_changes = HashMap::default();
10657 let chunk_by = self
10658 .snapshot(window, cx)
10659 .hunks_for_ranges(ranges)
10660 .into_iter()
10661 .chunk_by(|hunk| hunk.buffer_id);
10662 for (buffer_id, hunks) in &chunk_by {
10663 let hunks = hunks.collect::<Vec<_>>();
10664 for hunk in &hunks {
10665 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10666 }
10667 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10668 }
10669 drop(chunk_by);
10670 if !revert_changes.is_empty() {
10671 self.transact(window, cx, |editor, window, cx| {
10672 editor.restore(revert_changes, window, cx);
10673 });
10674 }
10675 }
10676
10677 pub fn open_active_item_in_terminal(
10678 &mut self,
10679 _: &OpenInTerminal,
10680 window: &mut Window,
10681 cx: &mut Context<Self>,
10682 ) {
10683 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10684 let project_path = buffer.read(cx).project_path(cx)?;
10685 let project = self.project()?.read(cx);
10686 let entry = project.entry_for_path(&project_path, cx)?;
10687 let parent = match &entry.canonical_path {
10688 Some(canonical_path) => canonical_path.to_path_buf(),
10689 None => project.absolute_path(&project_path, cx)?,
10690 }
10691 .parent()?
10692 .to_path_buf();
10693 Some(parent)
10694 }) {
10695 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10696 }
10697 }
10698
10699 fn set_breakpoint_context_menu(
10700 &mut self,
10701 display_row: DisplayRow,
10702 position: Option<Anchor>,
10703 clicked_point: gpui::Point<Pixels>,
10704 window: &mut Window,
10705 cx: &mut Context<Self>,
10706 ) {
10707 let source = self
10708 .buffer
10709 .read(cx)
10710 .snapshot(cx)
10711 .anchor_before(Point::new(display_row.0, 0u32));
10712
10713 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10714
10715 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10716 self,
10717 source,
10718 clicked_point,
10719 context_menu,
10720 window,
10721 cx,
10722 );
10723 }
10724
10725 fn add_edit_breakpoint_block(
10726 &mut self,
10727 anchor: Anchor,
10728 breakpoint: &Breakpoint,
10729 edit_action: BreakpointPromptEditAction,
10730 window: &mut Window,
10731 cx: &mut Context<Self>,
10732 ) {
10733 let weak_editor = cx.weak_entity();
10734 let bp_prompt = cx.new(|cx| {
10735 BreakpointPromptEditor::new(
10736 weak_editor,
10737 anchor,
10738 breakpoint.clone(),
10739 edit_action,
10740 window,
10741 cx,
10742 )
10743 });
10744
10745 let height = bp_prompt.update(cx, |this, cx| {
10746 this.prompt
10747 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10748 });
10749 let cloned_prompt = bp_prompt.clone();
10750 let blocks = vec![BlockProperties {
10751 style: BlockStyle::Sticky,
10752 placement: BlockPlacement::Above(anchor),
10753 height: Some(height),
10754 render: Arc::new(move |cx| {
10755 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10756 cloned_prompt.clone().into_any_element()
10757 }),
10758 priority: 0,
10759 }];
10760
10761 let focus_handle = bp_prompt.focus_handle(cx);
10762 window.focus(&focus_handle);
10763
10764 let block_ids = self.insert_blocks(blocks, None, cx);
10765 bp_prompt.update(cx, |prompt, _| {
10766 prompt.add_block_ids(block_ids);
10767 });
10768 }
10769
10770 pub(crate) fn breakpoint_at_row(
10771 &self,
10772 row: u32,
10773 window: &mut Window,
10774 cx: &mut Context<Self>,
10775 ) -> Option<(Anchor, Breakpoint)> {
10776 let snapshot = self.snapshot(window, cx);
10777 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10778
10779 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10780 }
10781
10782 pub(crate) fn breakpoint_at_anchor(
10783 &self,
10784 breakpoint_position: Anchor,
10785 snapshot: &EditorSnapshot,
10786 cx: &mut Context<Self>,
10787 ) -> Option<(Anchor, Breakpoint)> {
10788 let buffer = self
10789 .buffer
10790 .read(cx)
10791 .buffer_for_anchor(breakpoint_position, cx)?;
10792
10793 let enclosing_excerpt = breakpoint_position.excerpt_id;
10794 let buffer_snapshot = buffer.read(cx).snapshot();
10795
10796 let row = buffer_snapshot
10797 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10798 .row;
10799
10800 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10801 let anchor_end = snapshot
10802 .buffer_snapshot
10803 .anchor_after(Point::new(row, line_len));
10804
10805 self.breakpoint_store
10806 .as_ref()?
10807 .read_with(cx, |breakpoint_store, cx| {
10808 breakpoint_store
10809 .breakpoints(
10810 &buffer,
10811 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10812 &buffer_snapshot,
10813 cx,
10814 )
10815 .next()
10816 .and_then(|(bp, _)| {
10817 let breakpoint_row = buffer_snapshot
10818 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10819 .row;
10820
10821 if breakpoint_row == row {
10822 snapshot
10823 .buffer_snapshot
10824 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10825 .map(|position| (position, bp.bp.clone()))
10826 } else {
10827 None
10828 }
10829 })
10830 })
10831 }
10832
10833 pub fn edit_log_breakpoint(
10834 &mut self,
10835 _: &EditLogBreakpoint,
10836 window: &mut Window,
10837 cx: &mut Context<Self>,
10838 ) {
10839 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10840 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10841 message: None,
10842 state: BreakpointState::Enabled,
10843 condition: None,
10844 hit_condition: None,
10845 });
10846
10847 self.add_edit_breakpoint_block(
10848 anchor,
10849 &breakpoint,
10850 BreakpointPromptEditAction::Log,
10851 window,
10852 cx,
10853 );
10854 }
10855 }
10856
10857 fn breakpoints_at_cursors(
10858 &self,
10859 window: &mut Window,
10860 cx: &mut Context<Self>,
10861 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10862 let snapshot = self.snapshot(window, cx);
10863 let cursors = self
10864 .selections
10865 .disjoint_anchors_arc()
10866 .iter()
10867 .map(|selection| {
10868 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10869
10870 let breakpoint_position = self
10871 .breakpoint_at_row(cursor_position.row, window, cx)
10872 .map(|bp| bp.0)
10873 .unwrap_or_else(|| {
10874 snapshot
10875 .display_snapshot
10876 .buffer_snapshot
10877 .anchor_after(Point::new(cursor_position.row, 0))
10878 });
10879
10880 let breakpoint = self
10881 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10882 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10883
10884 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10885 })
10886 // 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.
10887 .collect::<HashMap<Anchor, _>>();
10888
10889 cursors.into_iter().collect()
10890 }
10891
10892 pub fn enable_breakpoint(
10893 &mut self,
10894 _: &crate::actions::EnableBreakpoint,
10895 window: &mut Window,
10896 cx: &mut Context<Self>,
10897 ) {
10898 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10899 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10900 continue;
10901 };
10902 self.edit_breakpoint_at_anchor(
10903 anchor,
10904 breakpoint,
10905 BreakpointEditAction::InvertState,
10906 cx,
10907 );
10908 }
10909 }
10910
10911 pub fn disable_breakpoint(
10912 &mut self,
10913 _: &crate::actions::DisableBreakpoint,
10914 window: &mut Window,
10915 cx: &mut Context<Self>,
10916 ) {
10917 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10918 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10919 continue;
10920 };
10921 self.edit_breakpoint_at_anchor(
10922 anchor,
10923 breakpoint,
10924 BreakpointEditAction::InvertState,
10925 cx,
10926 );
10927 }
10928 }
10929
10930 pub fn toggle_breakpoint(
10931 &mut self,
10932 _: &crate::actions::ToggleBreakpoint,
10933 window: &mut Window,
10934 cx: &mut Context<Self>,
10935 ) {
10936 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10937 if let Some(breakpoint) = breakpoint {
10938 self.edit_breakpoint_at_anchor(
10939 anchor,
10940 breakpoint,
10941 BreakpointEditAction::Toggle,
10942 cx,
10943 );
10944 } else {
10945 self.edit_breakpoint_at_anchor(
10946 anchor,
10947 Breakpoint::new_standard(),
10948 BreakpointEditAction::Toggle,
10949 cx,
10950 );
10951 }
10952 }
10953 }
10954
10955 pub fn edit_breakpoint_at_anchor(
10956 &mut self,
10957 breakpoint_position: Anchor,
10958 breakpoint: Breakpoint,
10959 edit_action: BreakpointEditAction,
10960 cx: &mut Context<Self>,
10961 ) {
10962 let Some(breakpoint_store) = &self.breakpoint_store else {
10963 return;
10964 };
10965
10966 let Some(buffer) = self
10967 .buffer
10968 .read(cx)
10969 .buffer_for_anchor(breakpoint_position, cx)
10970 else {
10971 return;
10972 };
10973
10974 breakpoint_store.update(cx, |breakpoint_store, cx| {
10975 breakpoint_store.toggle_breakpoint(
10976 buffer,
10977 BreakpointWithPosition {
10978 position: breakpoint_position.text_anchor,
10979 bp: breakpoint,
10980 },
10981 edit_action,
10982 cx,
10983 );
10984 });
10985
10986 cx.notify();
10987 }
10988
10989 #[cfg(any(test, feature = "test-support"))]
10990 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10991 self.breakpoint_store.clone()
10992 }
10993
10994 pub fn prepare_restore_change(
10995 &self,
10996 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10997 hunk: &MultiBufferDiffHunk,
10998 cx: &mut App,
10999 ) -> Option<()> {
11000 if hunk.is_created_file() {
11001 return None;
11002 }
11003 let buffer = self.buffer.read(cx);
11004 let diff = buffer.diff_for(hunk.buffer_id)?;
11005 let buffer = buffer.buffer(hunk.buffer_id)?;
11006 let buffer = buffer.read(cx);
11007 let original_text = diff
11008 .read(cx)
11009 .base_text()
11010 .as_rope()
11011 .slice(hunk.diff_base_byte_range.clone());
11012 let buffer_snapshot = buffer.snapshot();
11013 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11014 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11015 probe
11016 .0
11017 .start
11018 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11019 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11020 }) {
11021 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11022 Some(())
11023 } else {
11024 None
11025 }
11026 }
11027
11028 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11029 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11030 }
11031
11032 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11033 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11034 }
11035
11036 fn manipulate_lines<M>(
11037 &mut self,
11038 window: &mut Window,
11039 cx: &mut Context<Self>,
11040 mut manipulate: M,
11041 ) where
11042 M: FnMut(&str) -> LineManipulationResult,
11043 {
11044 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11045
11046 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11047 let buffer = self.buffer.read(cx).snapshot(cx);
11048
11049 let mut edits = Vec::new();
11050
11051 let selections = self.selections.all::<Point>(cx);
11052 let mut selections = selections.iter().peekable();
11053 let mut contiguous_row_selections = Vec::new();
11054 let mut new_selections = Vec::new();
11055 let mut added_lines = 0;
11056 let mut removed_lines = 0;
11057
11058 while let Some(selection) = selections.next() {
11059 let (start_row, end_row) = consume_contiguous_rows(
11060 &mut contiguous_row_selections,
11061 selection,
11062 &display_map,
11063 &mut selections,
11064 );
11065
11066 let start_point = Point::new(start_row.0, 0);
11067 let end_point = Point::new(
11068 end_row.previous_row().0,
11069 buffer.line_len(end_row.previous_row()),
11070 );
11071 let text = buffer
11072 .text_for_range(start_point..end_point)
11073 .collect::<String>();
11074
11075 let LineManipulationResult {
11076 new_text,
11077 line_count_before,
11078 line_count_after,
11079 } = manipulate(&text);
11080
11081 edits.push((start_point..end_point, new_text));
11082
11083 // Selections must change based on added and removed line count
11084 let start_row =
11085 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11086 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11087 new_selections.push(Selection {
11088 id: selection.id,
11089 start: start_row,
11090 end: end_row,
11091 goal: SelectionGoal::None,
11092 reversed: selection.reversed,
11093 });
11094
11095 if line_count_after > line_count_before {
11096 added_lines += line_count_after - line_count_before;
11097 } else if line_count_before > line_count_after {
11098 removed_lines += line_count_before - line_count_after;
11099 }
11100 }
11101
11102 self.transact(window, cx, |this, window, cx| {
11103 let buffer = this.buffer.update(cx, |buffer, cx| {
11104 buffer.edit(edits, None, cx);
11105 buffer.snapshot(cx)
11106 });
11107
11108 // Recalculate offsets on newly edited buffer
11109 let new_selections = new_selections
11110 .iter()
11111 .map(|s| {
11112 let start_point = Point::new(s.start.0, 0);
11113 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11114 Selection {
11115 id: s.id,
11116 start: buffer.point_to_offset(start_point),
11117 end: buffer.point_to_offset(end_point),
11118 goal: s.goal,
11119 reversed: s.reversed,
11120 }
11121 })
11122 .collect();
11123
11124 this.change_selections(Default::default(), window, cx, |s| {
11125 s.select(new_selections);
11126 });
11127
11128 this.request_autoscroll(Autoscroll::fit(), cx);
11129 });
11130 }
11131
11132 fn manipulate_immutable_lines<Fn>(
11133 &mut self,
11134 window: &mut Window,
11135 cx: &mut Context<Self>,
11136 mut callback: Fn,
11137 ) where
11138 Fn: FnMut(&mut Vec<&str>),
11139 {
11140 self.manipulate_lines(window, cx, |text| {
11141 let mut lines: Vec<&str> = text.split('\n').collect();
11142 let line_count_before = lines.len();
11143
11144 callback(&mut lines);
11145
11146 LineManipulationResult {
11147 new_text: lines.join("\n"),
11148 line_count_before,
11149 line_count_after: lines.len(),
11150 }
11151 });
11152 }
11153
11154 fn manipulate_mutable_lines<Fn>(
11155 &mut self,
11156 window: &mut Window,
11157 cx: &mut Context<Self>,
11158 mut callback: Fn,
11159 ) where
11160 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11161 {
11162 self.manipulate_lines(window, cx, |text| {
11163 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11164 let line_count_before = lines.len();
11165
11166 callback(&mut lines);
11167
11168 LineManipulationResult {
11169 new_text: lines.join("\n"),
11170 line_count_before,
11171 line_count_after: lines.len(),
11172 }
11173 });
11174 }
11175
11176 pub fn convert_indentation_to_spaces(
11177 &mut self,
11178 _: &ConvertIndentationToSpaces,
11179 window: &mut Window,
11180 cx: &mut Context<Self>,
11181 ) {
11182 let settings = self.buffer.read(cx).language_settings(cx);
11183 let tab_size = settings.tab_size.get() as usize;
11184
11185 self.manipulate_mutable_lines(window, cx, |lines| {
11186 // Allocates a reasonably sized scratch buffer once for the whole loop
11187 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11188 // Avoids recomputing spaces that could be inserted many times
11189 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11190 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11191 .collect();
11192
11193 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11194 let mut chars = line.as_ref().chars();
11195 let mut col = 0;
11196 let mut changed = false;
11197
11198 for ch in chars.by_ref() {
11199 match ch {
11200 ' ' => {
11201 reindented_line.push(' ');
11202 col += 1;
11203 }
11204 '\t' => {
11205 // \t are converted to spaces depending on the current column
11206 let spaces_len = tab_size - (col % tab_size);
11207 reindented_line.extend(&space_cache[spaces_len - 1]);
11208 col += spaces_len;
11209 changed = true;
11210 }
11211 _ => {
11212 // If we dont append before break, the character is consumed
11213 reindented_line.push(ch);
11214 break;
11215 }
11216 }
11217 }
11218
11219 if !changed {
11220 reindented_line.clear();
11221 continue;
11222 }
11223 // Append the rest of the line and replace old reference with new one
11224 reindented_line.extend(chars);
11225 *line = Cow::Owned(reindented_line.clone());
11226 reindented_line.clear();
11227 }
11228 });
11229 }
11230
11231 pub fn convert_indentation_to_tabs(
11232 &mut self,
11233 _: &ConvertIndentationToTabs,
11234 window: &mut Window,
11235 cx: &mut Context<Self>,
11236 ) {
11237 let settings = self.buffer.read(cx).language_settings(cx);
11238 let tab_size = settings.tab_size.get() as usize;
11239
11240 self.manipulate_mutable_lines(window, cx, |lines| {
11241 // Allocates a reasonably sized buffer once for the whole loop
11242 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11243 // Avoids recomputing spaces that could be inserted many times
11244 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11245 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11246 .collect();
11247
11248 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11249 let mut chars = line.chars();
11250 let mut spaces_count = 0;
11251 let mut first_non_indent_char = None;
11252 let mut changed = false;
11253
11254 for ch in chars.by_ref() {
11255 match ch {
11256 ' ' => {
11257 // Keep track of spaces. Append \t when we reach tab_size
11258 spaces_count += 1;
11259 changed = true;
11260 if spaces_count == tab_size {
11261 reindented_line.push('\t');
11262 spaces_count = 0;
11263 }
11264 }
11265 '\t' => {
11266 reindented_line.push('\t');
11267 spaces_count = 0;
11268 }
11269 _ => {
11270 // Dont append it yet, we might have remaining spaces
11271 first_non_indent_char = Some(ch);
11272 break;
11273 }
11274 }
11275 }
11276
11277 if !changed {
11278 reindented_line.clear();
11279 continue;
11280 }
11281 // Remaining spaces that didn't make a full tab stop
11282 if spaces_count > 0 {
11283 reindented_line.extend(&space_cache[spaces_count - 1]);
11284 }
11285 // If we consume an extra character that was not indentation, add it back
11286 if let Some(extra_char) = first_non_indent_char {
11287 reindented_line.push(extra_char);
11288 }
11289 // Append the rest of the line and replace old reference with new one
11290 reindented_line.extend(chars);
11291 *line = Cow::Owned(reindented_line.clone());
11292 reindented_line.clear();
11293 }
11294 });
11295 }
11296
11297 pub fn convert_to_upper_case(
11298 &mut self,
11299 _: &ConvertToUpperCase,
11300 window: &mut Window,
11301 cx: &mut Context<Self>,
11302 ) {
11303 self.manipulate_text(window, cx, |text| text.to_uppercase())
11304 }
11305
11306 pub fn convert_to_lower_case(
11307 &mut self,
11308 _: &ConvertToLowerCase,
11309 window: &mut Window,
11310 cx: &mut Context<Self>,
11311 ) {
11312 self.manipulate_text(window, cx, |text| text.to_lowercase())
11313 }
11314
11315 pub fn convert_to_title_case(
11316 &mut self,
11317 _: &ConvertToTitleCase,
11318 window: &mut Window,
11319 cx: &mut Context<Self>,
11320 ) {
11321 self.manipulate_text(window, cx, |text| {
11322 text.split('\n')
11323 .map(|line| line.to_case(Case::Title))
11324 .join("\n")
11325 })
11326 }
11327
11328 pub fn convert_to_snake_case(
11329 &mut self,
11330 _: &ConvertToSnakeCase,
11331 window: &mut Window,
11332 cx: &mut Context<Self>,
11333 ) {
11334 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11335 }
11336
11337 pub fn convert_to_kebab_case(
11338 &mut self,
11339 _: &ConvertToKebabCase,
11340 window: &mut Window,
11341 cx: &mut Context<Self>,
11342 ) {
11343 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11344 }
11345
11346 pub fn convert_to_upper_camel_case(
11347 &mut self,
11348 _: &ConvertToUpperCamelCase,
11349 window: &mut Window,
11350 cx: &mut Context<Self>,
11351 ) {
11352 self.manipulate_text(window, cx, |text| {
11353 text.split('\n')
11354 .map(|line| line.to_case(Case::UpperCamel))
11355 .join("\n")
11356 })
11357 }
11358
11359 pub fn convert_to_lower_camel_case(
11360 &mut self,
11361 _: &ConvertToLowerCamelCase,
11362 window: &mut Window,
11363 cx: &mut Context<Self>,
11364 ) {
11365 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11366 }
11367
11368 pub fn convert_to_opposite_case(
11369 &mut self,
11370 _: &ConvertToOppositeCase,
11371 window: &mut Window,
11372 cx: &mut Context<Self>,
11373 ) {
11374 self.manipulate_text(window, cx, |text| {
11375 text.chars()
11376 .fold(String::with_capacity(text.len()), |mut t, c| {
11377 if c.is_uppercase() {
11378 t.extend(c.to_lowercase());
11379 } else {
11380 t.extend(c.to_uppercase());
11381 }
11382 t
11383 })
11384 })
11385 }
11386
11387 pub fn convert_to_sentence_case(
11388 &mut self,
11389 _: &ConvertToSentenceCase,
11390 window: &mut Window,
11391 cx: &mut Context<Self>,
11392 ) {
11393 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11394 }
11395
11396 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11397 self.manipulate_text(window, cx, |text| {
11398 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11399 if has_upper_case_characters {
11400 text.to_lowercase()
11401 } else {
11402 text.to_uppercase()
11403 }
11404 })
11405 }
11406
11407 pub fn convert_to_rot13(
11408 &mut self,
11409 _: &ConvertToRot13,
11410 window: &mut Window,
11411 cx: &mut Context<Self>,
11412 ) {
11413 self.manipulate_text(window, cx, |text| {
11414 text.chars()
11415 .map(|c| match c {
11416 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11417 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11418 _ => c,
11419 })
11420 .collect()
11421 })
11422 }
11423
11424 pub fn convert_to_rot47(
11425 &mut self,
11426 _: &ConvertToRot47,
11427 window: &mut Window,
11428 cx: &mut Context<Self>,
11429 ) {
11430 self.manipulate_text(window, cx, |text| {
11431 text.chars()
11432 .map(|c| {
11433 let code_point = c as u32;
11434 if code_point >= 33 && code_point <= 126 {
11435 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11436 }
11437 c
11438 })
11439 .collect()
11440 })
11441 }
11442
11443 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11444 where
11445 Fn: FnMut(&str) -> String,
11446 {
11447 let buffer = self.buffer.read(cx).snapshot(cx);
11448
11449 let mut new_selections = Vec::new();
11450 let mut edits = Vec::new();
11451 let mut selection_adjustment = 0i32;
11452
11453 for selection in self.selections.all_adjusted(cx) {
11454 let selection_is_empty = selection.is_empty();
11455
11456 let (start, end) = if selection_is_empty {
11457 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11458 (word_range.start, word_range.end)
11459 } else {
11460 (
11461 buffer.point_to_offset(selection.start),
11462 buffer.point_to_offset(selection.end),
11463 )
11464 };
11465
11466 let text = buffer.text_for_range(start..end).collect::<String>();
11467 let old_length = text.len() as i32;
11468 let text = callback(&text);
11469
11470 new_selections.push(Selection {
11471 start: (start as i32 - selection_adjustment) as usize,
11472 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11473 goal: SelectionGoal::None,
11474 id: selection.id,
11475 reversed: selection.reversed,
11476 });
11477
11478 selection_adjustment += old_length - text.len() as i32;
11479
11480 edits.push((start..end, text));
11481 }
11482
11483 self.transact(window, cx, |this, window, cx| {
11484 this.buffer.update(cx, |buffer, cx| {
11485 buffer.edit(edits, None, cx);
11486 });
11487
11488 this.change_selections(Default::default(), window, cx, |s| {
11489 s.select(new_selections);
11490 });
11491
11492 this.request_autoscroll(Autoscroll::fit(), cx);
11493 });
11494 }
11495
11496 pub fn move_selection_on_drop(
11497 &mut self,
11498 selection: &Selection<Anchor>,
11499 target: DisplayPoint,
11500 is_cut: bool,
11501 window: &mut Window,
11502 cx: &mut Context<Self>,
11503 ) {
11504 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11505 let buffer = &display_map.buffer_snapshot;
11506 let mut edits = Vec::new();
11507 let insert_point = display_map
11508 .clip_point(target, Bias::Left)
11509 .to_point(&display_map);
11510 let text = buffer
11511 .text_for_range(selection.start..selection.end)
11512 .collect::<String>();
11513 if is_cut {
11514 edits.push(((selection.start..selection.end), String::new()));
11515 }
11516 let insert_anchor = buffer.anchor_before(insert_point);
11517 edits.push(((insert_anchor..insert_anchor), text));
11518 let last_edit_start = insert_anchor.bias_left(buffer);
11519 let last_edit_end = insert_anchor.bias_right(buffer);
11520 self.transact(window, cx, |this, window, cx| {
11521 this.buffer.update(cx, |buffer, cx| {
11522 buffer.edit(edits, None, cx);
11523 });
11524 this.change_selections(Default::default(), window, cx, |s| {
11525 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11526 });
11527 });
11528 }
11529
11530 pub fn clear_selection_drag_state(&mut self) {
11531 self.selection_drag_state = SelectionDragState::None;
11532 }
11533
11534 pub fn duplicate(
11535 &mut self,
11536 upwards: bool,
11537 whole_lines: bool,
11538 window: &mut Window,
11539 cx: &mut Context<Self>,
11540 ) {
11541 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11542
11543 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11544 let buffer = &display_map.buffer_snapshot;
11545 let selections = self.selections.all::<Point>(cx);
11546
11547 let mut edits = Vec::new();
11548 let mut selections_iter = selections.iter().peekable();
11549 while let Some(selection) = selections_iter.next() {
11550 let mut rows = selection.spanned_rows(false, &display_map);
11551 // duplicate line-wise
11552 if whole_lines || selection.start == selection.end {
11553 // Avoid duplicating the same lines twice.
11554 while let Some(next_selection) = selections_iter.peek() {
11555 let next_rows = next_selection.spanned_rows(false, &display_map);
11556 if next_rows.start < rows.end {
11557 rows.end = next_rows.end;
11558 selections_iter.next().unwrap();
11559 } else {
11560 break;
11561 }
11562 }
11563
11564 // Copy the text from the selected row region and splice it either at the start
11565 // or end of the region.
11566 let start = Point::new(rows.start.0, 0);
11567 let end = Point::new(
11568 rows.end.previous_row().0,
11569 buffer.line_len(rows.end.previous_row()),
11570 );
11571 let text = buffer
11572 .text_for_range(start..end)
11573 .chain(Some("\n"))
11574 .collect::<String>();
11575 let insert_location = if upwards {
11576 Point::new(rows.end.0, 0)
11577 } else {
11578 start
11579 };
11580 edits.push((insert_location..insert_location, text));
11581 } else {
11582 // duplicate character-wise
11583 let start = selection.start;
11584 let end = selection.end;
11585 let text = buffer.text_for_range(start..end).collect::<String>();
11586 edits.push((selection.end..selection.end, text));
11587 }
11588 }
11589
11590 self.transact(window, cx, |this, _, cx| {
11591 this.buffer.update(cx, |buffer, cx| {
11592 buffer.edit(edits, None, cx);
11593 });
11594
11595 this.request_autoscroll(Autoscroll::fit(), cx);
11596 });
11597 }
11598
11599 pub fn duplicate_line_up(
11600 &mut self,
11601 _: &DuplicateLineUp,
11602 window: &mut Window,
11603 cx: &mut Context<Self>,
11604 ) {
11605 self.duplicate(true, true, window, cx);
11606 }
11607
11608 pub fn duplicate_line_down(
11609 &mut self,
11610 _: &DuplicateLineDown,
11611 window: &mut Window,
11612 cx: &mut Context<Self>,
11613 ) {
11614 self.duplicate(false, true, window, cx);
11615 }
11616
11617 pub fn duplicate_selection(
11618 &mut self,
11619 _: &DuplicateSelection,
11620 window: &mut Window,
11621 cx: &mut Context<Self>,
11622 ) {
11623 self.duplicate(false, false, window, cx);
11624 }
11625
11626 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11627 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11628 if self.mode.is_single_line() {
11629 cx.propagate();
11630 return;
11631 }
11632
11633 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11634 let buffer = self.buffer.read(cx).snapshot(cx);
11635
11636 let mut edits = Vec::new();
11637 let mut unfold_ranges = Vec::new();
11638 let mut refold_creases = Vec::new();
11639
11640 let selections = self.selections.all::<Point>(cx);
11641 let mut selections = selections.iter().peekable();
11642 let mut contiguous_row_selections = Vec::new();
11643 let mut new_selections = Vec::new();
11644
11645 while let Some(selection) = selections.next() {
11646 // Find all the selections that span a contiguous row range
11647 let (start_row, end_row) = consume_contiguous_rows(
11648 &mut contiguous_row_selections,
11649 selection,
11650 &display_map,
11651 &mut selections,
11652 );
11653
11654 // Move the text spanned by the row range to be before the line preceding the row range
11655 if start_row.0 > 0 {
11656 let range_to_move = Point::new(
11657 start_row.previous_row().0,
11658 buffer.line_len(start_row.previous_row()),
11659 )
11660 ..Point::new(
11661 end_row.previous_row().0,
11662 buffer.line_len(end_row.previous_row()),
11663 );
11664 let insertion_point = display_map
11665 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11666 .0;
11667
11668 // Don't move lines across excerpts
11669 if buffer
11670 .excerpt_containing(insertion_point..range_to_move.end)
11671 .is_some()
11672 {
11673 let text = buffer
11674 .text_for_range(range_to_move.clone())
11675 .flat_map(|s| s.chars())
11676 .skip(1)
11677 .chain(['\n'])
11678 .collect::<String>();
11679
11680 edits.push((
11681 buffer.anchor_after(range_to_move.start)
11682 ..buffer.anchor_before(range_to_move.end),
11683 String::new(),
11684 ));
11685 let insertion_anchor = buffer.anchor_after(insertion_point);
11686 edits.push((insertion_anchor..insertion_anchor, text));
11687
11688 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11689
11690 // Move selections up
11691 new_selections.extend(contiguous_row_selections.drain(..).map(
11692 |mut selection| {
11693 selection.start.row -= row_delta;
11694 selection.end.row -= row_delta;
11695 selection
11696 },
11697 ));
11698
11699 // Move folds up
11700 unfold_ranges.push(range_to_move.clone());
11701 for fold in display_map.folds_in_range(
11702 buffer.anchor_before(range_to_move.start)
11703 ..buffer.anchor_after(range_to_move.end),
11704 ) {
11705 let mut start = fold.range.start.to_point(&buffer);
11706 let mut end = fold.range.end.to_point(&buffer);
11707 start.row -= row_delta;
11708 end.row -= row_delta;
11709 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11710 }
11711 }
11712 }
11713
11714 // If we didn't move line(s), preserve the existing selections
11715 new_selections.append(&mut contiguous_row_selections);
11716 }
11717
11718 self.transact(window, cx, |this, window, cx| {
11719 this.unfold_ranges(&unfold_ranges, true, true, cx);
11720 this.buffer.update(cx, |buffer, cx| {
11721 for (range, text) in edits {
11722 buffer.edit([(range, text)], None, cx);
11723 }
11724 });
11725 this.fold_creases(refold_creases, true, window, cx);
11726 this.change_selections(Default::default(), window, cx, |s| {
11727 s.select(new_selections);
11728 })
11729 });
11730 }
11731
11732 pub fn move_line_down(
11733 &mut self,
11734 _: &MoveLineDown,
11735 window: &mut Window,
11736 cx: &mut Context<Self>,
11737 ) {
11738 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11739 if self.mode.is_single_line() {
11740 cx.propagate();
11741 return;
11742 }
11743
11744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11745 let buffer = self.buffer.read(cx).snapshot(cx);
11746
11747 let mut edits = Vec::new();
11748 let mut unfold_ranges = Vec::new();
11749 let mut refold_creases = Vec::new();
11750
11751 let selections = self.selections.all::<Point>(cx);
11752 let mut selections = selections.iter().peekable();
11753 let mut contiguous_row_selections = Vec::new();
11754 let mut new_selections = Vec::new();
11755
11756 while let Some(selection) = selections.next() {
11757 // Find all the selections that span a contiguous row range
11758 let (start_row, end_row) = consume_contiguous_rows(
11759 &mut contiguous_row_selections,
11760 selection,
11761 &display_map,
11762 &mut selections,
11763 );
11764
11765 // Move the text spanned by the row range to be after the last line of the row range
11766 if end_row.0 <= buffer.max_point().row {
11767 let range_to_move =
11768 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11769 let insertion_point = display_map
11770 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11771 .0;
11772
11773 // Don't move lines across excerpt boundaries
11774 if buffer
11775 .excerpt_containing(range_to_move.start..insertion_point)
11776 .is_some()
11777 {
11778 let mut text = String::from("\n");
11779 text.extend(buffer.text_for_range(range_to_move.clone()));
11780 text.pop(); // Drop trailing newline
11781 edits.push((
11782 buffer.anchor_after(range_to_move.start)
11783 ..buffer.anchor_before(range_to_move.end),
11784 String::new(),
11785 ));
11786 let insertion_anchor = buffer.anchor_after(insertion_point);
11787 edits.push((insertion_anchor..insertion_anchor, text));
11788
11789 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11790
11791 // Move selections down
11792 new_selections.extend(contiguous_row_selections.drain(..).map(
11793 |mut selection| {
11794 selection.start.row += row_delta;
11795 selection.end.row += row_delta;
11796 selection
11797 },
11798 ));
11799
11800 // Move folds down
11801 unfold_ranges.push(range_to_move.clone());
11802 for fold in display_map.folds_in_range(
11803 buffer.anchor_before(range_to_move.start)
11804 ..buffer.anchor_after(range_to_move.end),
11805 ) {
11806 let mut start = fold.range.start.to_point(&buffer);
11807 let mut end = fold.range.end.to_point(&buffer);
11808 start.row += row_delta;
11809 end.row += row_delta;
11810 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11811 }
11812 }
11813 }
11814
11815 // If we didn't move line(s), preserve the existing selections
11816 new_selections.append(&mut contiguous_row_selections);
11817 }
11818
11819 self.transact(window, cx, |this, window, cx| {
11820 this.unfold_ranges(&unfold_ranges, true, true, cx);
11821 this.buffer.update(cx, |buffer, cx| {
11822 for (range, text) in edits {
11823 buffer.edit([(range, text)], None, cx);
11824 }
11825 });
11826 this.fold_creases(refold_creases, true, window, cx);
11827 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11828 });
11829 }
11830
11831 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11832 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11833 let text_layout_details = &self.text_layout_details(window);
11834 self.transact(window, cx, |this, window, cx| {
11835 let edits = this.change_selections(Default::default(), window, cx, |s| {
11836 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11837 s.move_with(|display_map, selection| {
11838 if !selection.is_empty() {
11839 return;
11840 }
11841
11842 let mut head = selection.head();
11843 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11844 if head.column() == display_map.line_len(head.row()) {
11845 transpose_offset = display_map
11846 .buffer_snapshot
11847 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11848 }
11849
11850 if transpose_offset == 0 {
11851 return;
11852 }
11853
11854 *head.column_mut() += 1;
11855 head = display_map.clip_point(head, Bias::Right);
11856 let goal = SelectionGoal::HorizontalPosition(
11857 display_map
11858 .x_for_display_point(head, text_layout_details)
11859 .into(),
11860 );
11861 selection.collapse_to(head, goal);
11862
11863 let transpose_start = display_map
11864 .buffer_snapshot
11865 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11866 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11867 let transpose_end = display_map
11868 .buffer_snapshot
11869 .clip_offset(transpose_offset + 1, Bias::Right);
11870 if let Some(ch) =
11871 display_map.buffer_snapshot.chars_at(transpose_start).next()
11872 {
11873 edits.push((transpose_start..transpose_offset, String::new()));
11874 edits.push((transpose_end..transpose_end, ch.to_string()));
11875 }
11876 }
11877 });
11878 edits
11879 });
11880 this.buffer
11881 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11882 let selections = this.selections.all::<usize>(cx);
11883 this.change_selections(Default::default(), window, cx, |s| {
11884 s.select(selections);
11885 });
11886 });
11887 }
11888
11889 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11890 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11891 if self.mode.is_single_line() {
11892 cx.propagate();
11893 return;
11894 }
11895
11896 self.rewrap_impl(RewrapOptions::default(), cx)
11897 }
11898
11899 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11900 let buffer = self.buffer.read(cx).snapshot(cx);
11901 let selections = self.selections.all::<Point>(cx);
11902
11903 #[derive(Clone, Debug, PartialEq)]
11904 enum CommentFormat {
11905 /// single line comment, with prefix for line
11906 Line(String),
11907 /// single line within a block comment, with prefix for line
11908 BlockLine(String),
11909 /// a single line of a block comment that includes the initial delimiter
11910 BlockCommentWithStart(BlockCommentConfig),
11911 /// a single line of a block comment that includes the ending delimiter
11912 BlockCommentWithEnd(BlockCommentConfig),
11913 }
11914
11915 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11916 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11917 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11918 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11919 .peekable();
11920
11921 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11922 row
11923 } else {
11924 return Vec::new();
11925 };
11926
11927 let language_settings = buffer.language_settings_at(selection.head(), cx);
11928 let language_scope = buffer.language_scope_at(selection.head());
11929
11930 let indent_and_prefix_for_row =
11931 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11932 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11933 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11934 &language_scope
11935 {
11936 let indent_end = Point::new(row, indent.len);
11937 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11938 let line_text_after_indent = buffer
11939 .text_for_range(indent_end..line_end)
11940 .collect::<String>();
11941
11942 let is_within_comment_override = buffer
11943 .language_scope_at(indent_end)
11944 .is_some_and(|scope| scope.override_name() == Some("comment"));
11945 let comment_delimiters = if is_within_comment_override {
11946 // we are within a comment syntax node, but we don't
11947 // yet know what kind of comment: block, doc or line
11948 match (
11949 language_scope.documentation_comment(),
11950 language_scope.block_comment(),
11951 ) {
11952 (Some(config), _) | (_, Some(config))
11953 if buffer.contains_str_at(indent_end, &config.start) =>
11954 {
11955 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11956 }
11957 (Some(config), _) | (_, Some(config))
11958 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11959 {
11960 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11961 }
11962 (Some(config), _) | (_, Some(config))
11963 if buffer.contains_str_at(indent_end, &config.prefix) =>
11964 {
11965 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11966 }
11967 (_, _) => language_scope
11968 .line_comment_prefixes()
11969 .iter()
11970 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11971 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11972 }
11973 } else {
11974 // we not in an overridden comment node, but we may
11975 // be within a non-overridden line comment node
11976 language_scope
11977 .line_comment_prefixes()
11978 .iter()
11979 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11980 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11981 };
11982
11983 let rewrap_prefix = language_scope
11984 .rewrap_prefixes()
11985 .iter()
11986 .find_map(|prefix_regex| {
11987 prefix_regex.find(&line_text_after_indent).map(|mat| {
11988 if mat.start() == 0 {
11989 Some(mat.as_str().to_string())
11990 } else {
11991 None
11992 }
11993 })
11994 })
11995 .flatten();
11996 (comment_delimiters, rewrap_prefix)
11997 } else {
11998 (None, None)
11999 };
12000 (indent, comment_prefix, rewrap_prefix)
12001 };
12002
12003 let mut ranges = Vec::new();
12004 let from_empty_selection = selection.is_empty();
12005
12006 let mut current_range_start = first_row;
12007 let mut prev_row = first_row;
12008 let (
12009 mut current_range_indent,
12010 mut current_range_comment_delimiters,
12011 mut current_range_rewrap_prefix,
12012 ) = indent_and_prefix_for_row(first_row);
12013
12014 for row in non_blank_rows_iter.skip(1) {
12015 let has_paragraph_break = row > prev_row + 1;
12016
12017 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12018 indent_and_prefix_for_row(row);
12019
12020 let has_indent_change = row_indent != current_range_indent;
12021 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12022
12023 let has_boundary_change = has_comment_change
12024 || row_rewrap_prefix.is_some()
12025 || (has_indent_change && current_range_comment_delimiters.is_some());
12026
12027 if has_paragraph_break || has_boundary_change {
12028 ranges.push((
12029 language_settings.clone(),
12030 Point::new(current_range_start, 0)
12031 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12032 current_range_indent,
12033 current_range_comment_delimiters.clone(),
12034 current_range_rewrap_prefix.clone(),
12035 from_empty_selection,
12036 ));
12037 current_range_start = row;
12038 current_range_indent = row_indent;
12039 current_range_comment_delimiters = row_comment_delimiters;
12040 current_range_rewrap_prefix = row_rewrap_prefix;
12041 }
12042 prev_row = row;
12043 }
12044
12045 ranges.push((
12046 language_settings.clone(),
12047 Point::new(current_range_start, 0)
12048 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12049 current_range_indent,
12050 current_range_comment_delimiters,
12051 current_range_rewrap_prefix,
12052 from_empty_selection,
12053 ));
12054
12055 ranges
12056 });
12057
12058 let mut edits = Vec::new();
12059 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12060
12061 for (
12062 language_settings,
12063 wrap_range,
12064 mut indent_size,
12065 comment_prefix,
12066 rewrap_prefix,
12067 from_empty_selection,
12068 ) in wrap_ranges
12069 {
12070 let mut start_row = wrap_range.start.row;
12071 let mut end_row = wrap_range.end.row;
12072
12073 // Skip selections that overlap with a range that has already been rewrapped.
12074 let selection_range = start_row..end_row;
12075 if rewrapped_row_ranges
12076 .iter()
12077 .any(|range| range.overlaps(&selection_range))
12078 {
12079 continue;
12080 }
12081
12082 let tab_size = language_settings.tab_size;
12083
12084 let (line_prefix, inside_comment) = match &comment_prefix {
12085 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12086 (Some(prefix.as_str()), true)
12087 }
12088 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12089 (Some(prefix.as_ref()), true)
12090 }
12091 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12092 start: _,
12093 end: _,
12094 prefix,
12095 tab_size,
12096 })) => {
12097 indent_size.len += tab_size;
12098 (Some(prefix.as_ref()), true)
12099 }
12100 None => (None, false),
12101 };
12102 let indent_prefix = indent_size.chars().collect::<String>();
12103 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12104
12105 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12106 RewrapBehavior::InComments => inside_comment,
12107 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12108 RewrapBehavior::Anywhere => true,
12109 };
12110
12111 let should_rewrap = options.override_language_settings
12112 || allow_rewrap_based_on_language
12113 || self.hard_wrap.is_some();
12114 if !should_rewrap {
12115 continue;
12116 }
12117
12118 if from_empty_selection {
12119 'expand_upwards: while start_row > 0 {
12120 let prev_row = start_row - 1;
12121 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12122 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12123 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12124 {
12125 start_row = prev_row;
12126 } else {
12127 break 'expand_upwards;
12128 }
12129 }
12130
12131 'expand_downwards: while end_row < buffer.max_point().row {
12132 let next_row = end_row + 1;
12133 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12134 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12135 && !buffer.is_line_blank(MultiBufferRow(next_row))
12136 {
12137 end_row = next_row;
12138 } else {
12139 break 'expand_downwards;
12140 }
12141 }
12142 }
12143
12144 let start = Point::new(start_row, 0);
12145 let start_offset = start.to_offset(&buffer);
12146 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12147 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12148 let mut first_line_delimiter = None;
12149 let mut last_line_delimiter = None;
12150 let Some(lines_without_prefixes) = selection_text
12151 .lines()
12152 .enumerate()
12153 .map(|(ix, line)| {
12154 let line_trimmed = line.trim_start();
12155 if rewrap_prefix.is_some() && ix > 0 {
12156 Ok(line_trimmed)
12157 } else if let Some(
12158 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12159 start,
12160 prefix,
12161 end,
12162 tab_size,
12163 })
12164 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12165 start,
12166 prefix,
12167 end,
12168 tab_size,
12169 }),
12170 ) = &comment_prefix
12171 {
12172 let line_trimmed = line_trimmed
12173 .strip_prefix(start.as_ref())
12174 .map(|s| {
12175 let mut indent_size = indent_size;
12176 indent_size.len -= tab_size;
12177 let indent_prefix: String = indent_size.chars().collect();
12178 first_line_delimiter = Some((indent_prefix, start));
12179 s.trim_start()
12180 })
12181 .unwrap_or(line_trimmed);
12182 let line_trimmed = line_trimmed
12183 .strip_suffix(end.as_ref())
12184 .map(|s| {
12185 last_line_delimiter = Some(end);
12186 s.trim_end()
12187 })
12188 .unwrap_or(line_trimmed);
12189 let line_trimmed = line_trimmed
12190 .strip_prefix(prefix.as_ref())
12191 .unwrap_or(line_trimmed);
12192 Ok(line_trimmed)
12193 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12194 line_trimmed.strip_prefix(prefix).with_context(|| {
12195 format!("line did not start with prefix {prefix:?}: {line:?}")
12196 })
12197 } else {
12198 line_trimmed
12199 .strip_prefix(&line_prefix.trim_start())
12200 .with_context(|| {
12201 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12202 })
12203 }
12204 })
12205 .collect::<Result<Vec<_>, _>>()
12206 .log_err()
12207 else {
12208 continue;
12209 };
12210
12211 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12212 buffer
12213 .language_settings_at(Point::new(start_row, 0), cx)
12214 .preferred_line_length as usize
12215 });
12216
12217 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12218 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12219 } else {
12220 line_prefix.clone()
12221 };
12222
12223 let wrapped_text = {
12224 let mut wrapped_text = wrap_with_prefix(
12225 line_prefix,
12226 subsequent_lines_prefix,
12227 lines_without_prefixes.join("\n"),
12228 wrap_column,
12229 tab_size,
12230 options.preserve_existing_whitespace,
12231 );
12232
12233 if let Some((indent, delimiter)) = first_line_delimiter {
12234 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12235 }
12236 if let Some(last_line) = last_line_delimiter {
12237 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12238 }
12239
12240 wrapped_text
12241 };
12242
12243 // TODO: should always use char-based diff while still supporting cursor behavior that
12244 // matches vim.
12245 let mut diff_options = DiffOptions::default();
12246 if options.override_language_settings {
12247 diff_options.max_word_diff_len = 0;
12248 diff_options.max_word_diff_line_count = 0;
12249 } else {
12250 diff_options.max_word_diff_len = usize::MAX;
12251 diff_options.max_word_diff_line_count = usize::MAX;
12252 }
12253
12254 for (old_range, new_text) in
12255 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12256 {
12257 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12258 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12259 edits.push((edit_start..edit_end, new_text));
12260 }
12261
12262 rewrapped_row_ranges.push(start_row..=end_row);
12263 }
12264
12265 self.buffer
12266 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12267 }
12268
12269 pub fn cut_common(
12270 &mut self,
12271 cut_no_selection_line: bool,
12272 window: &mut Window,
12273 cx: &mut Context<Self>,
12274 ) -> ClipboardItem {
12275 let mut text = String::new();
12276 let buffer = self.buffer.read(cx).snapshot(cx);
12277 let mut selections = self.selections.all::<Point>(cx);
12278 let mut clipboard_selections = Vec::with_capacity(selections.len());
12279 {
12280 let max_point = buffer.max_point();
12281 let mut is_first = true;
12282 for selection in &mut selections {
12283 let is_entire_line =
12284 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12285 if is_entire_line {
12286 selection.start = Point::new(selection.start.row, 0);
12287 if !selection.is_empty() && selection.end.column == 0 {
12288 selection.end = cmp::min(max_point, selection.end);
12289 } else {
12290 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12291 }
12292 selection.goal = SelectionGoal::None;
12293 }
12294 if is_first {
12295 is_first = false;
12296 } else {
12297 text += "\n";
12298 }
12299 let mut len = 0;
12300 for chunk in buffer.text_for_range(selection.start..selection.end) {
12301 text.push_str(chunk);
12302 len += chunk.len();
12303 }
12304 clipboard_selections.push(ClipboardSelection {
12305 len,
12306 is_entire_line,
12307 first_line_indent: buffer
12308 .indent_size_for_line(MultiBufferRow(selection.start.row))
12309 .len,
12310 });
12311 }
12312 }
12313
12314 self.transact(window, cx, |this, window, cx| {
12315 this.change_selections(Default::default(), window, cx, |s| {
12316 s.select(selections);
12317 });
12318 this.insert("", window, cx);
12319 });
12320 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12321 }
12322
12323 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12324 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12325 let item = self.cut_common(true, window, cx);
12326 cx.write_to_clipboard(item);
12327 }
12328
12329 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12330 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12331 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12332 s.move_with(|snapshot, sel| {
12333 if sel.is_empty() {
12334 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12335 }
12336 if sel.is_empty() {
12337 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12338 }
12339 });
12340 });
12341 let item = self.cut_common(false, window, cx);
12342 cx.set_global(KillRing(item))
12343 }
12344
12345 pub fn kill_ring_yank(
12346 &mut self,
12347 _: &KillRingYank,
12348 window: &mut Window,
12349 cx: &mut Context<Self>,
12350 ) {
12351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12352 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12353 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12354 (kill_ring.text().to_string(), kill_ring.metadata_json())
12355 } else {
12356 return;
12357 }
12358 } else {
12359 return;
12360 };
12361 self.do_paste(&text, metadata, false, window, cx);
12362 }
12363
12364 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12365 self.do_copy(true, cx);
12366 }
12367
12368 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12369 self.do_copy(false, cx);
12370 }
12371
12372 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12373 let selections = self.selections.all::<Point>(cx);
12374 let buffer = self.buffer.read(cx).read(cx);
12375 let mut text = String::new();
12376
12377 let mut clipboard_selections = Vec::with_capacity(selections.len());
12378 {
12379 let max_point = buffer.max_point();
12380 let mut is_first = true;
12381 for selection in &selections {
12382 let mut start = selection.start;
12383 let mut end = selection.end;
12384 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12385 if is_entire_line {
12386 start = Point::new(start.row, 0);
12387 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12388 }
12389
12390 let mut trimmed_selections = Vec::new();
12391 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12392 let row = MultiBufferRow(start.row);
12393 let first_indent = buffer.indent_size_for_line(row);
12394 if first_indent.len == 0 || start.column > first_indent.len {
12395 trimmed_selections.push(start..end);
12396 } else {
12397 trimmed_selections.push(
12398 Point::new(row.0, first_indent.len)
12399 ..Point::new(row.0, buffer.line_len(row)),
12400 );
12401 for row in start.row + 1..=end.row {
12402 let mut line_len = buffer.line_len(MultiBufferRow(row));
12403 if row == end.row {
12404 line_len = end.column;
12405 }
12406 if line_len == 0 {
12407 trimmed_selections
12408 .push(Point::new(row, 0)..Point::new(row, line_len));
12409 continue;
12410 }
12411 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12412 if row_indent_size.len >= first_indent.len {
12413 trimmed_selections.push(
12414 Point::new(row, first_indent.len)..Point::new(row, line_len),
12415 );
12416 } else {
12417 trimmed_selections.clear();
12418 trimmed_selections.push(start..end);
12419 break;
12420 }
12421 }
12422 }
12423 } else {
12424 trimmed_selections.push(start..end);
12425 }
12426
12427 for trimmed_range in trimmed_selections {
12428 if is_first {
12429 is_first = false;
12430 } else {
12431 text += "\n";
12432 }
12433 let mut len = 0;
12434 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12435 text.push_str(chunk);
12436 len += chunk.len();
12437 }
12438 clipboard_selections.push(ClipboardSelection {
12439 len,
12440 is_entire_line,
12441 first_line_indent: buffer
12442 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12443 .len,
12444 });
12445 }
12446 }
12447 }
12448
12449 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12450 text,
12451 clipboard_selections,
12452 ));
12453 }
12454
12455 pub fn do_paste(
12456 &mut self,
12457 text: &String,
12458 clipboard_selections: Option<Vec<ClipboardSelection>>,
12459 handle_entire_lines: bool,
12460 window: &mut Window,
12461 cx: &mut Context<Self>,
12462 ) {
12463 if self.read_only(cx) {
12464 return;
12465 }
12466
12467 let clipboard_text = Cow::Borrowed(text.as_str());
12468
12469 self.transact(window, cx, |this, window, cx| {
12470 let had_active_edit_prediction = this.has_active_edit_prediction();
12471 let old_selections = this.selections.all::<usize>(cx);
12472 let cursor_offset = this.selections.last::<usize>(cx).head();
12473
12474 if let Some(mut clipboard_selections) = clipboard_selections {
12475 let all_selections_were_entire_line =
12476 clipboard_selections.iter().all(|s| s.is_entire_line);
12477 let first_selection_indent_column =
12478 clipboard_selections.first().map(|s| s.first_line_indent);
12479 if clipboard_selections.len() != old_selections.len() {
12480 clipboard_selections.drain(..);
12481 }
12482 let mut auto_indent_on_paste = true;
12483
12484 this.buffer.update(cx, |buffer, cx| {
12485 let snapshot = buffer.read(cx);
12486 auto_indent_on_paste = snapshot
12487 .language_settings_at(cursor_offset, cx)
12488 .auto_indent_on_paste;
12489
12490 let mut start_offset = 0;
12491 let mut edits = Vec::new();
12492 let mut original_indent_columns = Vec::new();
12493 for (ix, selection) in old_selections.iter().enumerate() {
12494 let to_insert;
12495 let entire_line;
12496 let original_indent_column;
12497 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12498 let end_offset = start_offset + clipboard_selection.len;
12499 to_insert = &clipboard_text[start_offset..end_offset];
12500 entire_line = clipboard_selection.is_entire_line;
12501 start_offset = end_offset + 1;
12502 original_indent_column = Some(clipboard_selection.first_line_indent);
12503 } else {
12504 to_insert = &*clipboard_text;
12505 entire_line = all_selections_were_entire_line;
12506 original_indent_column = first_selection_indent_column
12507 }
12508
12509 let (range, to_insert) =
12510 if selection.is_empty() && handle_entire_lines && entire_line {
12511 // If the corresponding selection was empty when this slice of the
12512 // clipboard text was written, then the entire line containing the
12513 // selection was copied. If this selection is also currently empty,
12514 // then paste the line before the current line of the buffer.
12515 let column = selection.start.to_point(&snapshot).column as usize;
12516 let line_start = selection.start - column;
12517 (line_start..line_start, Cow::Borrowed(to_insert))
12518 } else {
12519 let language = snapshot.language_at(selection.head());
12520 let range = selection.range();
12521 if let Some(language) = language
12522 && language.name() == "Markdown".into()
12523 {
12524 edit_for_markdown_paste(
12525 &snapshot,
12526 range,
12527 to_insert,
12528 url::Url::parse(to_insert).ok(),
12529 )
12530 } else {
12531 (range, Cow::Borrowed(to_insert))
12532 }
12533 };
12534
12535 edits.push((range, to_insert));
12536 original_indent_columns.push(original_indent_column);
12537 }
12538 drop(snapshot);
12539
12540 buffer.edit(
12541 edits,
12542 if auto_indent_on_paste {
12543 Some(AutoindentMode::Block {
12544 original_indent_columns,
12545 })
12546 } else {
12547 None
12548 },
12549 cx,
12550 );
12551 });
12552
12553 let selections = this.selections.all::<usize>(cx);
12554 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12555 } else {
12556 let url = url::Url::parse(&clipboard_text).ok();
12557
12558 let auto_indent_mode = if !clipboard_text.is_empty() {
12559 Some(AutoindentMode::Block {
12560 original_indent_columns: Vec::new(),
12561 })
12562 } else {
12563 None
12564 };
12565
12566 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12567 let snapshot = buffer.snapshot(cx);
12568
12569 let anchors = old_selections
12570 .iter()
12571 .map(|s| {
12572 let anchor = snapshot.anchor_after(s.head());
12573 s.map(|_| anchor)
12574 })
12575 .collect::<Vec<_>>();
12576
12577 let mut edits = Vec::new();
12578
12579 for selection in old_selections.iter() {
12580 let language = snapshot.language_at(selection.head());
12581 let range = selection.range();
12582
12583 let (edit_range, edit_text) = if let Some(language) = language
12584 && language.name() == "Markdown".into()
12585 {
12586 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12587 } else {
12588 (range, clipboard_text.clone())
12589 };
12590
12591 edits.push((edit_range, edit_text));
12592 }
12593
12594 drop(snapshot);
12595 buffer.edit(edits, auto_indent_mode, cx);
12596
12597 anchors
12598 });
12599
12600 this.change_selections(Default::default(), window, cx, |s| {
12601 s.select_anchors(selection_anchors);
12602 });
12603 }
12604
12605 let trigger_in_words =
12606 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12607
12608 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12609 });
12610 }
12611
12612 pub fn diff_clipboard_with_selection(
12613 &mut self,
12614 _: &DiffClipboardWithSelection,
12615 window: &mut Window,
12616 cx: &mut Context<Self>,
12617 ) {
12618 let selections = self.selections.all::<usize>(cx);
12619
12620 if selections.is_empty() {
12621 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12622 return;
12623 };
12624
12625 let clipboard_text = match cx.read_from_clipboard() {
12626 Some(item) => match item.entries().first() {
12627 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12628 _ => None,
12629 },
12630 None => None,
12631 };
12632
12633 let Some(clipboard_text) = clipboard_text else {
12634 log::warn!("Clipboard doesn't contain text.");
12635 return;
12636 };
12637
12638 window.dispatch_action(
12639 Box::new(DiffClipboardWithSelectionData {
12640 clipboard_text,
12641 editor: cx.entity(),
12642 }),
12643 cx,
12644 );
12645 }
12646
12647 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12648 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12649 if let Some(item) = cx.read_from_clipboard() {
12650 let entries = item.entries();
12651
12652 match entries.first() {
12653 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12654 // of all the pasted entries.
12655 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12656 .do_paste(
12657 clipboard_string.text(),
12658 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12659 true,
12660 window,
12661 cx,
12662 ),
12663 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12664 }
12665 }
12666 }
12667
12668 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12669 if self.read_only(cx) {
12670 return;
12671 }
12672
12673 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12674
12675 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12676 if let Some((selections, _)) =
12677 self.selection_history.transaction(transaction_id).cloned()
12678 {
12679 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12680 s.select_anchors(selections.to_vec());
12681 });
12682 } else {
12683 log::error!(
12684 "No entry in selection_history found for undo. \
12685 This may correspond to a bug where undo does not update the selection. \
12686 If this is occurring, please add details to \
12687 https://github.com/zed-industries/zed/issues/22692"
12688 );
12689 }
12690 self.request_autoscroll(Autoscroll::fit(), cx);
12691 self.unmark_text(window, cx);
12692 self.refresh_edit_prediction(true, false, window, cx);
12693 cx.emit(EditorEvent::Edited { transaction_id });
12694 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12695 }
12696 }
12697
12698 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12699 if self.read_only(cx) {
12700 return;
12701 }
12702
12703 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12704
12705 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12706 if let Some((_, Some(selections))) =
12707 self.selection_history.transaction(transaction_id).cloned()
12708 {
12709 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12710 s.select_anchors(selections.to_vec());
12711 });
12712 } else {
12713 log::error!(
12714 "No entry in selection_history found for redo. \
12715 This may correspond to a bug where undo does not update the selection. \
12716 If this is occurring, please add details to \
12717 https://github.com/zed-industries/zed/issues/22692"
12718 );
12719 }
12720 self.request_autoscroll(Autoscroll::fit(), cx);
12721 self.unmark_text(window, cx);
12722 self.refresh_edit_prediction(true, false, window, cx);
12723 cx.emit(EditorEvent::Edited { transaction_id });
12724 }
12725 }
12726
12727 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12728 self.buffer
12729 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12730 }
12731
12732 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12733 self.buffer
12734 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12735 }
12736
12737 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12738 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12739 self.change_selections(Default::default(), window, cx, |s| {
12740 s.move_with(|map, selection| {
12741 let cursor = if selection.is_empty() {
12742 movement::left(map, selection.start)
12743 } else {
12744 selection.start
12745 };
12746 selection.collapse_to(cursor, SelectionGoal::None);
12747 });
12748 })
12749 }
12750
12751 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12753 self.change_selections(Default::default(), window, cx, |s| {
12754 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12755 })
12756 }
12757
12758 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12759 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12760 self.change_selections(Default::default(), window, cx, |s| {
12761 s.move_with(|map, selection| {
12762 let cursor = if selection.is_empty() {
12763 movement::right(map, selection.end)
12764 } else {
12765 selection.end
12766 };
12767 selection.collapse_to(cursor, SelectionGoal::None)
12768 });
12769 })
12770 }
12771
12772 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12774 self.change_selections(Default::default(), window, cx, |s| {
12775 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12776 })
12777 }
12778
12779 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12780 if self.take_rename(true, window, cx).is_some() {
12781 return;
12782 }
12783
12784 if self.mode.is_single_line() {
12785 cx.propagate();
12786 return;
12787 }
12788
12789 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12790
12791 let text_layout_details = &self.text_layout_details(window);
12792 let selection_count = self.selections.count();
12793 let first_selection = self.selections.first_anchor();
12794
12795 self.change_selections(Default::default(), window, cx, |s| {
12796 s.move_with(|map, selection| {
12797 if !selection.is_empty() {
12798 selection.goal = SelectionGoal::None;
12799 }
12800 let (cursor, goal) = movement::up(
12801 map,
12802 selection.start,
12803 selection.goal,
12804 false,
12805 text_layout_details,
12806 );
12807 selection.collapse_to(cursor, goal);
12808 });
12809 });
12810
12811 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12812 {
12813 cx.propagate();
12814 }
12815 }
12816
12817 pub fn move_up_by_lines(
12818 &mut self,
12819 action: &MoveUpByLines,
12820 window: &mut Window,
12821 cx: &mut Context<Self>,
12822 ) {
12823 if self.take_rename(true, window, cx).is_some() {
12824 return;
12825 }
12826
12827 if self.mode.is_single_line() {
12828 cx.propagate();
12829 return;
12830 }
12831
12832 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12833
12834 let text_layout_details = &self.text_layout_details(window);
12835
12836 self.change_selections(Default::default(), window, cx, |s| {
12837 s.move_with(|map, selection| {
12838 if !selection.is_empty() {
12839 selection.goal = SelectionGoal::None;
12840 }
12841 let (cursor, goal) = movement::up_by_rows(
12842 map,
12843 selection.start,
12844 action.lines,
12845 selection.goal,
12846 false,
12847 text_layout_details,
12848 );
12849 selection.collapse_to(cursor, goal);
12850 });
12851 })
12852 }
12853
12854 pub fn move_down_by_lines(
12855 &mut self,
12856 action: &MoveDownByLines,
12857 window: &mut Window,
12858 cx: &mut Context<Self>,
12859 ) {
12860 if self.take_rename(true, window, cx).is_some() {
12861 return;
12862 }
12863
12864 if self.mode.is_single_line() {
12865 cx.propagate();
12866 return;
12867 }
12868
12869 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12870
12871 let text_layout_details = &self.text_layout_details(window);
12872
12873 self.change_selections(Default::default(), window, cx, |s| {
12874 s.move_with(|map, selection| {
12875 if !selection.is_empty() {
12876 selection.goal = SelectionGoal::None;
12877 }
12878 let (cursor, goal) = movement::down_by_rows(
12879 map,
12880 selection.start,
12881 action.lines,
12882 selection.goal,
12883 false,
12884 text_layout_details,
12885 );
12886 selection.collapse_to(cursor, goal);
12887 });
12888 })
12889 }
12890
12891 pub fn select_down_by_lines(
12892 &mut self,
12893 action: &SelectDownByLines,
12894 window: &mut Window,
12895 cx: &mut Context<Self>,
12896 ) {
12897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12898 let text_layout_details = &self.text_layout_details(window);
12899 self.change_selections(Default::default(), window, cx, |s| {
12900 s.move_heads_with(|map, head, goal| {
12901 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12902 })
12903 })
12904 }
12905
12906 pub fn select_up_by_lines(
12907 &mut self,
12908 action: &SelectUpByLines,
12909 window: &mut Window,
12910 cx: &mut Context<Self>,
12911 ) {
12912 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12913 let text_layout_details = &self.text_layout_details(window);
12914 self.change_selections(Default::default(), window, cx, |s| {
12915 s.move_heads_with(|map, head, goal| {
12916 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12917 })
12918 })
12919 }
12920
12921 pub fn select_page_up(
12922 &mut self,
12923 _: &SelectPageUp,
12924 window: &mut Window,
12925 cx: &mut Context<Self>,
12926 ) {
12927 let Some(row_count) = self.visible_row_count() else {
12928 return;
12929 };
12930
12931 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12932
12933 let text_layout_details = &self.text_layout_details(window);
12934
12935 self.change_selections(Default::default(), window, cx, |s| {
12936 s.move_heads_with(|map, head, goal| {
12937 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12938 })
12939 })
12940 }
12941
12942 pub fn move_page_up(
12943 &mut self,
12944 action: &MovePageUp,
12945 window: &mut Window,
12946 cx: &mut Context<Self>,
12947 ) {
12948 if self.take_rename(true, window, cx).is_some() {
12949 return;
12950 }
12951
12952 if self
12953 .context_menu
12954 .borrow_mut()
12955 .as_mut()
12956 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12957 .unwrap_or(false)
12958 {
12959 return;
12960 }
12961
12962 if matches!(self.mode, EditorMode::SingleLine) {
12963 cx.propagate();
12964 return;
12965 }
12966
12967 let Some(row_count) = self.visible_row_count() else {
12968 return;
12969 };
12970
12971 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12972
12973 let effects = if action.center_cursor {
12974 SelectionEffects::scroll(Autoscroll::center())
12975 } else {
12976 SelectionEffects::default()
12977 };
12978
12979 let text_layout_details = &self.text_layout_details(window);
12980
12981 self.change_selections(effects, window, cx, |s| {
12982 s.move_with(|map, selection| {
12983 if !selection.is_empty() {
12984 selection.goal = SelectionGoal::None;
12985 }
12986 let (cursor, goal) = movement::up_by_rows(
12987 map,
12988 selection.end,
12989 row_count,
12990 selection.goal,
12991 false,
12992 text_layout_details,
12993 );
12994 selection.collapse_to(cursor, goal);
12995 });
12996 });
12997 }
12998
12999 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13001 let text_layout_details = &self.text_layout_details(window);
13002 self.change_selections(Default::default(), window, cx, |s| {
13003 s.move_heads_with(|map, head, goal| {
13004 movement::up(map, head, goal, false, text_layout_details)
13005 })
13006 })
13007 }
13008
13009 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13010 self.take_rename(true, window, cx);
13011
13012 if self.mode.is_single_line() {
13013 cx.propagate();
13014 return;
13015 }
13016
13017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13018
13019 let text_layout_details = &self.text_layout_details(window);
13020 let selection_count = self.selections.count();
13021 let first_selection = self.selections.first_anchor();
13022
13023 self.change_selections(Default::default(), window, cx, |s| {
13024 s.move_with(|map, selection| {
13025 if !selection.is_empty() {
13026 selection.goal = SelectionGoal::None;
13027 }
13028 let (cursor, goal) = movement::down(
13029 map,
13030 selection.end,
13031 selection.goal,
13032 false,
13033 text_layout_details,
13034 );
13035 selection.collapse_to(cursor, goal);
13036 });
13037 });
13038
13039 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13040 {
13041 cx.propagate();
13042 }
13043 }
13044
13045 pub fn select_page_down(
13046 &mut self,
13047 _: &SelectPageDown,
13048 window: &mut Window,
13049 cx: &mut Context<Self>,
13050 ) {
13051 let Some(row_count) = self.visible_row_count() else {
13052 return;
13053 };
13054
13055 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13056
13057 let text_layout_details = &self.text_layout_details(window);
13058
13059 self.change_selections(Default::default(), window, cx, |s| {
13060 s.move_heads_with(|map, head, goal| {
13061 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13062 })
13063 })
13064 }
13065
13066 pub fn move_page_down(
13067 &mut self,
13068 action: &MovePageDown,
13069 window: &mut Window,
13070 cx: &mut Context<Self>,
13071 ) {
13072 if self.take_rename(true, window, cx).is_some() {
13073 return;
13074 }
13075
13076 if self
13077 .context_menu
13078 .borrow_mut()
13079 .as_mut()
13080 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13081 .unwrap_or(false)
13082 {
13083 return;
13084 }
13085
13086 if matches!(self.mode, EditorMode::SingleLine) {
13087 cx.propagate();
13088 return;
13089 }
13090
13091 let Some(row_count) = self.visible_row_count() else {
13092 return;
13093 };
13094
13095 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13096
13097 let effects = if action.center_cursor {
13098 SelectionEffects::scroll(Autoscroll::center())
13099 } else {
13100 SelectionEffects::default()
13101 };
13102
13103 let text_layout_details = &self.text_layout_details(window);
13104 self.change_selections(effects, window, cx, |s| {
13105 s.move_with(|map, selection| {
13106 if !selection.is_empty() {
13107 selection.goal = SelectionGoal::None;
13108 }
13109 let (cursor, goal) = movement::down_by_rows(
13110 map,
13111 selection.end,
13112 row_count,
13113 selection.goal,
13114 false,
13115 text_layout_details,
13116 );
13117 selection.collapse_to(cursor, goal);
13118 });
13119 });
13120 }
13121
13122 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13124 let text_layout_details = &self.text_layout_details(window);
13125 self.change_selections(Default::default(), window, cx, |s| {
13126 s.move_heads_with(|map, head, goal| {
13127 movement::down(map, head, goal, false, text_layout_details)
13128 })
13129 });
13130 }
13131
13132 pub fn context_menu_first(
13133 &mut self,
13134 _: &ContextMenuFirst,
13135 window: &mut Window,
13136 cx: &mut Context<Self>,
13137 ) {
13138 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13139 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13140 }
13141 }
13142
13143 pub fn context_menu_prev(
13144 &mut self,
13145 _: &ContextMenuPrevious,
13146 window: &mut Window,
13147 cx: &mut Context<Self>,
13148 ) {
13149 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13150 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13151 }
13152 }
13153
13154 pub fn context_menu_next(
13155 &mut self,
13156 _: &ContextMenuNext,
13157 window: &mut Window,
13158 cx: &mut Context<Self>,
13159 ) {
13160 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13161 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13162 }
13163 }
13164
13165 pub fn context_menu_last(
13166 &mut self,
13167 _: &ContextMenuLast,
13168 window: &mut Window,
13169 cx: &mut Context<Self>,
13170 ) {
13171 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13172 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13173 }
13174 }
13175
13176 pub fn signature_help_prev(
13177 &mut self,
13178 _: &SignatureHelpPrevious,
13179 _: &mut Window,
13180 cx: &mut Context<Self>,
13181 ) {
13182 if let Some(popover) = self.signature_help_state.popover_mut() {
13183 if popover.current_signature == 0 {
13184 popover.current_signature = popover.signatures.len() - 1;
13185 } else {
13186 popover.current_signature -= 1;
13187 }
13188 cx.notify();
13189 }
13190 }
13191
13192 pub fn signature_help_next(
13193 &mut self,
13194 _: &SignatureHelpNext,
13195 _: &mut Window,
13196 cx: &mut Context<Self>,
13197 ) {
13198 if let Some(popover) = self.signature_help_state.popover_mut() {
13199 if popover.current_signature + 1 == popover.signatures.len() {
13200 popover.current_signature = 0;
13201 } else {
13202 popover.current_signature += 1;
13203 }
13204 cx.notify();
13205 }
13206 }
13207
13208 pub fn move_to_previous_word_start(
13209 &mut self,
13210 _: &MoveToPreviousWordStart,
13211 window: &mut Window,
13212 cx: &mut Context<Self>,
13213 ) {
13214 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13215 self.change_selections(Default::default(), window, cx, |s| {
13216 s.move_cursors_with(|map, head, _| {
13217 (
13218 movement::previous_word_start(map, head),
13219 SelectionGoal::None,
13220 )
13221 });
13222 })
13223 }
13224
13225 pub fn move_to_previous_subword_start(
13226 &mut self,
13227 _: &MoveToPreviousSubwordStart,
13228 window: &mut Window,
13229 cx: &mut Context<Self>,
13230 ) {
13231 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13232 self.change_selections(Default::default(), window, cx, |s| {
13233 s.move_cursors_with(|map, head, _| {
13234 (
13235 movement::previous_subword_start(map, head),
13236 SelectionGoal::None,
13237 )
13238 });
13239 })
13240 }
13241
13242 pub fn select_to_previous_word_start(
13243 &mut self,
13244 _: &SelectToPreviousWordStart,
13245 window: &mut Window,
13246 cx: &mut Context<Self>,
13247 ) {
13248 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13249 self.change_selections(Default::default(), window, cx, |s| {
13250 s.move_heads_with(|map, head, _| {
13251 (
13252 movement::previous_word_start(map, head),
13253 SelectionGoal::None,
13254 )
13255 });
13256 })
13257 }
13258
13259 pub fn select_to_previous_subword_start(
13260 &mut self,
13261 _: &SelectToPreviousSubwordStart,
13262 window: &mut Window,
13263 cx: &mut Context<Self>,
13264 ) {
13265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13266 self.change_selections(Default::default(), window, cx, |s| {
13267 s.move_heads_with(|map, head, _| {
13268 (
13269 movement::previous_subword_start(map, head),
13270 SelectionGoal::None,
13271 )
13272 });
13273 })
13274 }
13275
13276 pub fn delete_to_previous_word_start(
13277 &mut self,
13278 action: &DeleteToPreviousWordStart,
13279 window: &mut Window,
13280 cx: &mut Context<Self>,
13281 ) {
13282 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13283 self.transact(window, cx, |this, window, cx| {
13284 this.select_autoclose_pair(window, cx);
13285 this.change_selections(Default::default(), window, cx, |s| {
13286 s.move_with(|map, selection| {
13287 if selection.is_empty() {
13288 let mut cursor = if action.ignore_newlines {
13289 movement::previous_word_start(map, selection.head())
13290 } else {
13291 movement::previous_word_start_or_newline(map, selection.head())
13292 };
13293 cursor = movement::adjust_greedy_deletion(
13294 map,
13295 selection.head(),
13296 cursor,
13297 action.ignore_brackets,
13298 );
13299 selection.set_head(cursor, SelectionGoal::None);
13300 }
13301 });
13302 });
13303 this.insert("", window, cx);
13304 });
13305 }
13306
13307 pub fn delete_to_previous_subword_start(
13308 &mut self,
13309 _: &DeleteToPreviousSubwordStart,
13310 window: &mut Window,
13311 cx: &mut Context<Self>,
13312 ) {
13313 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13314 self.transact(window, cx, |this, window, cx| {
13315 this.select_autoclose_pair(window, cx);
13316 this.change_selections(Default::default(), window, cx, |s| {
13317 s.move_with(|map, selection| {
13318 if selection.is_empty() {
13319 let mut cursor = movement::previous_subword_start(map, selection.head());
13320 cursor =
13321 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13322 selection.set_head(cursor, SelectionGoal::None);
13323 }
13324 });
13325 });
13326 this.insert("", window, cx);
13327 });
13328 }
13329
13330 pub fn move_to_next_word_end(
13331 &mut self,
13332 _: &MoveToNextWordEnd,
13333 window: &mut Window,
13334 cx: &mut Context<Self>,
13335 ) {
13336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13337 self.change_selections(Default::default(), window, cx, |s| {
13338 s.move_cursors_with(|map, head, _| {
13339 (movement::next_word_end(map, head), SelectionGoal::None)
13340 });
13341 })
13342 }
13343
13344 pub fn move_to_next_subword_end(
13345 &mut self,
13346 _: &MoveToNextSubwordEnd,
13347 window: &mut Window,
13348 cx: &mut Context<Self>,
13349 ) {
13350 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13351 self.change_selections(Default::default(), window, cx, |s| {
13352 s.move_cursors_with(|map, head, _| {
13353 (movement::next_subword_end(map, head), SelectionGoal::None)
13354 });
13355 })
13356 }
13357
13358 pub fn select_to_next_word_end(
13359 &mut self,
13360 _: &SelectToNextWordEnd,
13361 window: &mut Window,
13362 cx: &mut Context<Self>,
13363 ) {
13364 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13365 self.change_selections(Default::default(), window, cx, |s| {
13366 s.move_heads_with(|map, head, _| {
13367 (movement::next_word_end(map, head), SelectionGoal::None)
13368 });
13369 })
13370 }
13371
13372 pub fn select_to_next_subword_end(
13373 &mut self,
13374 _: &SelectToNextSubwordEnd,
13375 window: &mut Window,
13376 cx: &mut Context<Self>,
13377 ) {
13378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13379 self.change_selections(Default::default(), window, cx, |s| {
13380 s.move_heads_with(|map, head, _| {
13381 (movement::next_subword_end(map, head), SelectionGoal::None)
13382 });
13383 })
13384 }
13385
13386 pub fn delete_to_next_word_end(
13387 &mut self,
13388 action: &DeleteToNextWordEnd,
13389 window: &mut Window,
13390 cx: &mut Context<Self>,
13391 ) {
13392 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13393 self.transact(window, cx, |this, window, cx| {
13394 this.change_selections(Default::default(), window, cx, |s| {
13395 s.move_with(|map, selection| {
13396 if selection.is_empty() {
13397 let mut cursor = if action.ignore_newlines {
13398 movement::next_word_end(map, selection.head())
13399 } else {
13400 movement::next_word_end_or_newline(map, selection.head())
13401 };
13402 cursor = movement::adjust_greedy_deletion(
13403 map,
13404 selection.head(),
13405 cursor,
13406 action.ignore_brackets,
13407 );
13408 selection.set_head(cursor, SelectionGoal::None);
13409 }
13410 });
13411 });
13412 this.insert("", window, cx);
13413 });
13414 }
13415
13416 pub fn delete_to_next_subword_end(
13417 &mut self,
13418 _: &DeleteToNextSubwordEnd,
13419 window: &mut Window,
13420 cx: &mut Context<Self>,
13421 ) {
13422 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13423 self.transact(window, cx, |this, window, cx| {
13424 this.change_selections(Default::default(), window, cx, |s| {
13425 s.move_with(|map, selection| {
13426 if selection.is_empty() {
13427 let mut cursor = movement::next_subword_end(map, selection.head());
13428 cursor =
13429 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13430 selection.set_head(cursor, SelectionGoal::None);
13431 }
13432 });
13433 });
13434 this.insert("", window, cx);
13435 });
13436 }
13437
13438 pub fn move_to_beginning_of_line(
13439 &mut self,
13440 action: &MoveToBeginningOfLine,
13441 window: &mut Window,
13442 cx: &mut Context<Self>,
13443 ) {
13444 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13445 self.change_selections(Default::default(), window, cx, |s| {
13446 s.move_cursors_with(|map, head, _| {
13447 (
13448 movement::indented_line_beginning(
13449 map,
13450 head,
13451 action.stop_at_soft_wraps,
13452 action.stop_at_indent,
13453 ),
13454 SelectionGoal::None,
13455 )
13456 });
13457 })
13458 }
13459
13460 pub fn select_to_beginning_of_line(
13461 &mut self,
13462 action: &SelectToBeginningOfLine,
13463 window: &mut Window,
13464 cx: &mut Context<Self>,
13465 ) {
13466 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13467 self.change_selections(Default::default(), window, cx, |s| {
13468 s.move_heads_with(|map, head, _| {
13469 (
13470 movement::indented_line_beginning(
13471 map,
13472 head,
13473 action.stop_at_soft_wraps,
13474 action.stop_at_indent,
13475 ),
13476 SelectionGoal::None,
13477 )
13478 });
13479 });
13480 }
13481
13482 pub fn delete_to_beginning_of_line(
13483 &mut self,
13484 action: &DeleteToBeginningOfLine,
13485 window: &mut Window,
13486 cx: &mut Context<Self>,
13487 ) {
13488 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13489 self.transact(window, cx, |this, window, cx| {
13490 this.change_selections(Default::default(), window, cx, |s| {
13491 s.move_with(|_, selection| {
13492 selection.reversed = true;
13493 });
13494 });
13495
13496 this.select_to_beginning_of_line(
13497 &SelectToBeginningOfLine {
13498 stop_at_soft_wraps: false,
13499 stop_at_indent: action.stop_at_indent,
13500 },
13501 window,
13502 cx,
13503 );
13504 this.backspace(&Backspace, window, cx);
13505 });
13506 }
13507
13508 pub fn move_to_end_of_line(
13509 &mut self,
13510 action: &MoveToEndOfLine,
13511 window: &mut Window,
13512 cx: &mut Context<Self>,
13513 ) {
13514 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13515 self.change_selections(Default::default(), window, cx, |s| {
13516 s.move_cursors_with(|map, head, _| {
13517 (
13518 movement::line_end(map, head, action.stop_at_soft_wraps),
13519 SelectionGoal::None,
13520 )
13521 });
13522 })
13523 }
13524
13525 pub fn select_to_end_of_line(
13526 &mut self,
13527 action: &SelectToEndOfLine,
13528 window: &mut Window,
13529 cx: &mut Context<Self>,
13530 ) {
13531 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13532 self.change_selections(Default::default(), window, cx, |s| {
13533 s.move_heads_with(|map, head, _| {
13534 (
13535 movement::line_end(map, head, action.stop_at_soft_wraps),
13536 SelectionGoal::None,
13537 )
13538 });
13539 })
13540 }
13541
13542 pub fn delete_to_end_of_line(
13543 &mut self,
13544 _: &DeleteToEndOfLine,
13545 window: &mut Window,
13546 cx: &mut Context<Self>,
13547 ) {
13548 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13549 self.transact(window, cx, |this, window, cx| {
13550 this.select_to_end_of_line(
13551 &SelectToEndOfLine {
13552 stop_at_soft_wraps: false,
13553 },
13554 window,
13555 cx,
13556 );
13557 this.delete(&Delete, window, cx);
13558 });
13559 }
13560
13561 pub fn cut_to_end_of_line(
13562 &mut self,
13563 action: &CutToEndOfLine,
13564 window: &mut Window,
13565 cx: &mut Context<Self>,
13566 ) {
13567 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13568 self.transact(window, cx, |this, window, cx| {
13569 this.select_to_end_of_line(
13570 &SelectToEndOfLine {
13571 stop_at_soft_wraps: false,
13572 },
13573 window,
13574 cx,
13575 );
13576 if !action.stop_at_newlines {
13577 this.change_selections(Default::default(), window, cx, |s| {
13578 s.move_with(|_, sel| {
13579 if sel.is_empty() {
13580 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13581 }
13582 });
13583 });
13584 }
13585 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13586 let item = this.cut_common(false, window, cx);
13587 cx.write_to_clipboard(item);
13588 });
13589 }
13590
13591 pub fn move_to_start_of_paragraph(
13592 &mut self,
13593 _: &MoveToStartOfParagraph,
13594 window: &mut Window,
13595 cx: &mut Context<Self>,
13596 ) {
13597 if matches!(self.mode, EditorMode::SingleLine) {
13598 cx.propagate();
13599 return;
13600 }
13601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13602 self.change_selections(Default::default(), window, cx, |s| {
13603 s.move_with(|map, selection| {
13604 selection.collapse_to(
13605 movement::start_of_paragraph(map, selection.head(), 1),
13606 SelectionGoal::None,
13607 )
13608 });
13609 })
13610 }
13611
13612 pub fn move_to_end_of_paragraph(
13613 &mut self,
13614 _: &MoveToEndOfParagraph,
13615 window: &mut Window,
13616 cx: &mut Context<Self>,
13617 ) {
13618 if matches!(self.mode, EditorMode::SingleLine) {
13619 cx.propagate();
13620 return;
13621 }
13622 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13623 self.change_selections(Default::default(), window, cx, |s| {
13624 s.move_with(|map, selection| {
13625 selection.collapse_to(
13626 movement::end_of_paragraph(map, selection.head(), 1),
13627 SelectionGoal::None,
13628 )
13629 });
13630 })
13631 }
13632
13633 pub fn select_to_start_of_paragraph(
13634 &mut self,
13635 _: &SelectToStartOfParagraph,
13636 window: &mut Window,
13637 cx: &mut Context<Self>,
13638 ) {
13639 if matches!(self.mode, EditorMode::SingleLine) {
13640 cx.propagate();
13641 return;
13642 }
13643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13644 self.change_selections(Default::default(), window, cx, |s| {
13645 s.move_heads_with(|map, head, _| {
13646 (
13647 movement::start_of_paragraph(map, head, 1),
13648 SelectionGoal::None,
13649 )
13650 });
13651 })
13652 }
13653
13654 pub fn select_to_end_of_paragraph(
13655 &mut self,
13656 _: &SelectToEndOfParagraph,
13657 window: &mut Window,
13658 cx: &mut Context<Self>,
13659 ) {
13660 if matches!(self.mode, EditorMode::SingleLine) {
13661 cx.propagate();
13662 return;
13663 }
13664 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13665 self.change_selections(Default::default(), window, cx, |s| {
13666 s.move_heads_with(|map, head, _| {
13667 (
13668 movement::end_of_paragraph(map, head, 1),
13669 SelectionGoal::None,
13670 )
13671 });
13672 })
13673 }
13674
13675 pub fn move_to_start_of_excerpt(
13676 &mut self,
13677 _: &MoveToStartOfExcerpt,
13678 window: &mut Window,
13679 cx: &mut Context<Self>,
13680 ) {
13681 if matches!(self.mode, EditorMode::SingleLine) {
13682 cx.propagate();
13683 return;
13684 }
13685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13686 self.change_selections(Default::default(), window, cx, |s| {
13687 s.move_with(|map, selection| {
13688 selection.collapse_to(
13689 movement::start_of_excerpt(
13690 map,
13691 selection.head(),
13692 workspace::searchable::Direction::Prev,
13693 ),
13694 SelectionGoal::None,
13695 )
13696 });
13697 })
13698 }
13699
13700 pub fn move_to_start_of_next_excerpt(
13701 &mut self,
13702 _: &MoveToStartOfNextExcerpt,
13703 window: &mut Window,
13704 cx: &mut Context<Self>,
13705 ) {
13706 if matches!(self.mode, EditorMode::SingleLine) {
13707 cx.propagate();
13708 return;
13709 }
13710
13711 self.change_selections(Default::default(), window, cx, |s| {
13712 s.move_with(|map, selection| {
13713 selection.collapse_to(
13714 movement::start_of_excerpt(
13715 map,
13716 selection.head(),
13717 workspace::searchable::Direction::Next,
13718 ),
13719 SelectionGoal::None,
13720 )
13721 });
13722 })
13723 }
13724
13725 pub fn move_to_end_of_excerpt(
13726 &mut self,
13727 _: &MoveToEndOfExcerpt,
13728 window: &mut Window,
13729 cx: &mut Context<Self>,
13730 ) {
13731 if matches!(self.mode, EditorMode::SingleLine) {
13732 cx.propagate();
13733 return;
13734 }
13735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13736 self.change_selections(Default::default(), window, cx, |s| {
13737 s.move_with(|map, selection| {
13738 selection.collapse_to(
13739 movement::end_of_excerpt(
13740 map,
13741 selection.head(),
13742 workspace::searchable::Direction::Next,
13743 ),
13744 SelectionGoal::None,
13745 )
13746 });
13747 })
13748 }
13749
13750 pub fn move_to_end_of_previous_excerpt(
13751 &mut self,
13752 _: &MoveToEndOfPreviousExcerpt,
13753 window: &mut Window,
13754 cx: &mut Context<Self>,
13755 ) {
13756 if matches!(self.mode, EditorMode::SingleLine) {
13757 cx.propagate();
13758 return;
13759 }
13760 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13761 self.change_selections(Default::default(), window, cx, |s| {
13762 s.move_with(|map, selection| {
13763 selection.collapse_to(
13764 movement::end_of_excerpt(
13765 map,
13766 selection.head(),
13767 workspace::searchable::Direction::Prev,
13768 ),
13769 SelectionGoal::None,
13770 )
13771 });
13772 })
13773 }
13774
13775 pub fn select_to_start_of_excerpt(
13776 &mut self,
13777 _: &SelectToStartOfExcerpt,
13778 window: &mut Window,
13779 cx: &mut Context<Self>,
13780 ) {
13781 if matches!(self.mode, EditorMode::SingleLine) {
13782 cx.propagate();
13783 return;
13784 }
13785 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13786 self.change_selections(Default::default(), window, cx, |s| {
13787 s.move_heads_with(|map, head, _| {
13788 (
13789 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13790 SelectionGoal::None,
13791 )
13792 });
13793 })
13794 }
13795
13796 pub fn select_to_start_of_next_excerpt(
13797 &mut self,
13798 _: &SelectToStartOfNextExcerpt,
13799 window: &mut Window,
13800 cx: &mut Context<Self>,
13801 ) {
13802 if matches!(self.mode, EditorMode::SingleLine) {
13803 cx.propagate();
13804 return;
13805 }
13806 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13807 self.change_selections(Default::default(), window, cx, |s| {
13808 s.move_heads_with(|map, head, _| {
13809 (
13810 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13811 SelectionGoal::None,
13812 )
13813 });
13814 })
13815 }
13816
13817 pub fn select_to_end_of_excerpt(
13818 &mut self,
13819 _: &SelectToEndOfExcerpt,
13820 window: &mut Window,
13821 cx: &mut Context<Self>,
13822 ) {
13823 if matches!(self.mode, EditorMode::SingleLine) {
13824 cx.propagate();
13825 return;
13826 }
13827 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13828 self.change_selections(Default::default(), window, cx, |s| {
13829 s.move_heads_with(|map, head, _| {
13830 (
13831 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13832 SelectionGoal::None,
13833 )
13834 });
13835 })
13836 }
13837
13838 pub fn select_to_end_of_previous_excerpt(
13839 &mut self,
13840 _: &SelectToEndOfPreviousExcerpt,
13841 window: &mut Window,
13842 cx: &mut Context<Self>,
13843 ) {
13844 if matches!(self.mode, EditorMode::SingleLine) {
13845 cx.propagate();
13846 return;
13847 }
13848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13849 self.change_selections(Default::default(), window, cx, |s| {
13850 s.move_heads_with(|map, head, _| {
13851 (
13852 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13853 SelectionGoal::None,
13854 )
13855 });
13856 })
13857 }
13858
13859 pub fn move_to_beginning(
13860 &mut self,
13861 _: &MoveToBeginning,
13862 window: &mut Window,
13863 cx: &mut Context<Self>,
13864 ) {
13865 if matches!(self.mode, EditorMode::SingleLine) {
13866 cx.propagate();
13867 return;
13868 }
13869 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13870 self.change_selections(Default::default(), window, cx, |s| {
13871 s.select_ranges(vec![0..0]);
13872 });
13873 }
13874
13875 pub fn select_to_beginning(
13876 &mut self,
13877 _: &SelectToBeginning,
13878 window: &mut Window,
13879 cx: &mut Context<Self>,
13880 ) {
13881 let mut selection = self.selections.last::<Point>(cx);
13882 selection.set_head(Point::zero(), SelectionGoal::None);
13883 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13884 self.change_selections(Default::default(), window, cx, |s| {
13885 s.select(vec![selection]);
13886 });
13887 }
13888
13889 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13890 if matches!(self.mode, EditorMode::SingleLine) {
13891 cx.propagate();
13892 return;
13893 }
13894 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13895 let cursor = self.buffer.read(cx).read(cx).len();
13896 self.change_selections(Default::default(), window, cx, |s| {
13897 s.select_ranges(vec![cursor..cursor])
13898 });
13899 }
13900
13901 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13902 self.nav_history = nav_history;
13903 }
13904
13905 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13906 self.nav_history.as_ref()
13907 }
13908
13909 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13910 self.push_to_nav_history(
13911 self.selections.newest_anchor().head(),
13912 None,
13913 false,
13914 true,
13915 cx,
13916 );
13917 }
13918
13919 fn push_to_nav_history(
13920 &mut self,
13921 cursor_anchor: Anchor,
13922 new_position: Option<Point>,
13923 is_deactivate: bool,
13924 always: bool,
13925 cx: &mut Context<Self>,
13926 ) {
13927 if let Some(nav_history) = self.nav_history.as_mut() {
13928 let buffer = self.buffer.read(cx).read(cx);
13929 let cursor_position = cursor_anchor.to_point(&buffer);
13930 let scroll_state = self.scroll_manager.anchor();
13931 let scroll_top_row = scroll_state.top_row(&buffer);
13932 drop(buffer);
13933
13934 if let Some(new_position) = new_position {
13935 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13936 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13937 return;
13938 }
13939 }
13940
13941 nav_history.push(
13942 Some(NavigationData {
13943 cursor_anchor,
13944 cursor_position,
13945 scroll_anchor: scroll_state,
13946 scroll_top_row,
13947 }),
13948 cx,
13949 );
13950 cx.emit(EditorEvent::PushedToNavHistory {
13951 anchor: cursor_anchor,
13952 is_deactivate,
13953 })
13954 }
13955 }
13956
13957 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13958 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13959 let buffer = self.buffer.read(cx).snapshot(cx);
13960 let mut selection = self.selections.first::<usize>(cx);
13961 selection.set_head(buffer.len(), SelectionGoal::None);
13962 self.change_selections(Default::default(), window, cx, |s| {
13963 s.select(vec![selection]);
13964 });
13965 }
13966
13967 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13968 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13969 let end = self.buffer.read(cx).read(cx).len();
13970 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13971 s.select_ranges(vec![0..end]);
13972 });
13973 }
13974
13975 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13976 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13977 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13978 let mut selections = self.selections.all::<Point>(cx);
13979 let max_point = display_map.buffer_snapshot.max_point();
13980 for selection in &mut selections {
13981 let rows = selection.spanned_rows(true, &display_map);
13982 selection.start = Point::new(rows.start.0, 0);
13983 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13984 selection.reversed = false;
13985 }
13986 self.change_selections(Default::default(), window, cx, |s| {
13987 s.select(selections);
13988 });
13989 }
13990
13991 pub fn split_selection_into_lines(
13992 &mut self,
13993 action: &SplitSelectionIntoLines,
13994 window: &mut Window,
13995 cx: &mut Context<Self>,
13996 ) {
13997 let selections = self
13998 .selections
13999 .all::<Point>(cx)
14000 .into_iter()
14001 .map(|selection| selection.start..selection.end)
14002 .collect::<Vec<_>>();
14003 self.unfold_ranges(&selections, true, true, cx);
14004
14005 let mut new_selection_ranges = Vec::new();
14006 {
14007 let buffer = self.buffer.read(cx).read(cx);
14008 for selection in selections {
14009 for row in selection.start.row..selection.end.row {
14010 let line_start = Point::new(row, 0);
14011 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14012
14013 if action.keep_selections {
14014 // Keep the selection range for each line
14015 let selection_start = if row == selection.start.row {
14016 selection.start
14017 } else {
14018 line_start
14019 };
14020 new_selection_ranges.push(selection_start..line_end);
14021 } else {
14022 // Collapse to cursor at end of line
14023 new_selection_ranges.push(line_end..line_end);
14024 }
14025 }
14026
14027 let is_multiline_selection = selection.start.row != selection.end.row;
14028 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14029 // so this action feels more ergonomic when paired with other selection operations
14030 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14031 if !should_skip_last {
14032 if action.keep_selections {
14033 if is_multiline_selection {
14034 let line_start = Point::new(selection.end.row, 0);
14035 new_selection_ranges.push(line_start..selection.end);
14036 } else {
14037 new_selection_ranges.push(selection.start..selection.end);
14038 }
14039 } else {
14040 new_selection_ranges.push(selection.end..selection.end);
14041 }
14042 }
14043 }
14044 }
14045 self.change_selections(Default::default(), window, cx, |s| {
14046 s.select_ranges(new_selection_ranges);
14047 });
14048 }
14049
14050 pub fn add_selection_above(
14051 &mut self,
14052 _: &AddSelectionAbove,
14053 window: &mut Window,
14054 cx: &mut Context<Self>,
14055 ) {
14056 self.add_selection(true, window, cx);
14057 }
14058
14059 pub fn add_selection_below(
14060 &mut self,
14061 _: &AddSelectionBelow,
14062 window: &mut Window,
14063 cx: &mut Context<Self>,
14064 ) {
14065 self.add_selection(false, window, cx);
14066 }
14067
14068 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14070
14071 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14072 let all_selections = self.selections.all::<Point>(cx);
14073 let text_layout_details = self.text_layout_details(window);
14074
14075 let (mut columnar_selections, new_selections_to_columnarize) = {
14076 if let Some(state) = self.add_selections_state.as_ref() {
14077 let columnar_selection_ids: HashSet<_> = state
14078 .groups
14079 .iter()
14080 .flat_map(|group| group.stack.iter())
14081 .copied()
14082 .collect();
14083
14084 all_selections
14085 .into_iter()
14086 .partition(|s| columnar_selection_ids.contains(&s.id))
14087 } else {
14088 (Vec::new(), all_selections)
14089 }
14090 };
14091
14092 let mut state = self
14093 .add_selections_state
14094 .take()
14095 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14096
14097 for selection in new_selections_to_columnarize {
14098 let range = selection.display_range(&display_map).sorted();
14099 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14100 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14101 let positions = start_x.min(end_x)..start_x.max(end_x);
14102 let mut stack = Vec::new();
14103 for row in range.start.row().0..=range.end.row().0 {
14104 if let Some(selection) = self.selections.build_columnar_selection(
14105 &display_map,
14106 DisplayRow(row),
14107 &positions,
14108 selection.reversed,
14109 &text_layout_details,
14110 ) {
14111 stack.push(selection.id);
14112 columnar_selections.push(selection);
14113 }
14114 }
14115 if !stack.is_empty() {
14116 if above {
14117 stack.reverse();
14118 }
14119 state.groups.push(AddSelectionsGroup { above, stack });
14120 }
14121 }
14122
14123 let mut final_selections = Vec::new();
14124 let end_row = if above {
14125 DisplayRow(0)
14126 } else {
14127 display_map.max_point().row()
14128 };
14129
14130 let mut last_added_item_per_group = HashMap::default();
14131 for group in state.groups.iter_mut() {
14132 if let Some(last_id) = group.stack.last() {
14133 last_added_item_per_group.insert(*last_id, group);
14134 }
14135 }
14136
14137 for selection in columnar_selections {
14138 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14139 if above == group.above {
14140 let range = selection.display_range(&display_map).sorted();
14141 debug_assert_eq!(range.start.row(), range.end.row());
14142 let mut row = range.start.row();
14143 let positions =
14144 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14145 px(start)..px(end)
14146 } else {
14147 let start_x =
14148 display_map.x_for_display_point(range.start, &text_layout_details);
14149 let end_x =
14150 display_map.x_for_display_point(range.end, &text_layout_details);
14151 start_x.min(end_x)..start_x.max(end_x)
14152 };
14153
14154 let mut maybe_new_selection = None;
14155 while row != end_row {
14156 if above {
14157 row.0 -= 1;
14158 } else {
14159 row.0 += 1;
14160 }
14161 if let Some(new_selection) = self.selections.build_columnar_selection(
14162 &display_map,
14163 row,
14164 &positions,
14165 selection.reversed,
14166 &text_layout_details,
14167 ) {
14168 maybe_new_selection = Some(new_selection);
14169 break;
14170 }
14171 }
14172
14173 if let Some(new_selection) = maybe_new_selection {
14174 group.stack.push(new_selection.id);
14175 if above {
14176 final_selections.push(new_selection);
14177 final_selections.push(selection);
14178 } else {
14179 final_selections.push(selection);
14180 final_selections.push(new_selection);
14181 }
14182 } else {
14183 final_selections.push(selection);
14184 }
14185 } else {
14186 group.stack.pop();
14187 }
14188 } else {
14189 final_selections.push(selection);
14190 }
14191 }
14192
14193 self.change_selections(Default::default(), window, cx, |s| {
14194 s.select(final_selections);
14195 });
14196
14197 let final_selection_ids: HashSet<_> = self
14198 .selections
14199 .all::<Point>(cx)
14200 .iter()
14201 .map(|s| s.id)
14202 .collect();
14203 state.groups.retain_mut(|group| {
14204 // selections might get merged above so we remove invalid items from stacks
14205 group.stack.retain(|id| final_selection_ids.contains(id));
14206
14207 // single selection in stack can be treated as initial state
14208 group.stack.len() > 1
14209 });
14210
14211 if !state.groups.is_empty() {
14212 self.add_selections_state = Some(state);
14213 }
14214 }
14215
14216 fn select_match_ranges(
14217 &mut self,
14218 range: Range<usize>,
14219 reversed: bool,
14220 replace_newest: bool,
14221 auto_scroll: Option<Autoscroll>,
14222 window: &mut Window,
14223 cx: &mut Context<Editor>,
14224 ) {
14225 self.unfold_ranges(
14226 std::slice::from_ref(&range),
14227 false,
14228 auto_scroll.is_some(),
14229 cx,
14230 );
14231 let effects = if let Some(scroll) = auto_scroll {
14232 SelectionEffects::scroll(scroll)
14233 } else {
14234 SelectionEffects::no_scroll()
14235 };
14236 self.change_selections(effects, window, cx, |s| {
14237 if replace_newest {
14238 s.delete(s.newest_anchor().id);
14239 }
14240 if reversed {
14241 s.insert_range(range.end..range.start);
14242 } else {
14243 s.insert_range(range);
14244 }
14245 });
14246 }
14247
14248 pub fn select_next_match_internal(
14249 &mut self,
14250 display_map: &DisplaySnapshot,
14251 replace_newest: bool,
14252 autoscroll: Option<Autoscroll>,
14253 window: &mut Window,
14254 cx: &mut Context<Self>,
14255 ) -> Result<()> {
14256 let buffer = &display_map.buffer_snapshot;
14257 let mut selections = self.selections.all::<usize>(cx);
14258 if let Some(mut select_next_state) = self.select_next_state.take() {
14259 let query = &select_next_state.query;
14260 if !select_next_state.done {
14261 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14262 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14263 let mut next_selected_range = None;
14264
14265 let bytes_after_last_selection =
14266 buffer.bytes_in_range(last_selection.end..buffer.len());
14267 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14268 let query_matches = query
14269 .stream_find_iter(bytes_after_last_selection)
14270 .map(|result| (last_selection.end, result))
14271 .chain(
14272 query
14273 .stream_find_iter(bytes_before_first_selection)
14274 .map(|result| (0, result)),
14275 );
14276
14277 for (start_offset, query_match) in query_matches {
14278 let query_match = query_match.unwrap(); // can only fail due to I/O
14279 let offset_range =
14280 start_offset + query_match.start()..start_offset + query_match.end();
14281
14282 if !select_next_state.wordwise
14283 || (!buffer.is_inside_word(offset_range.start, None)
14284 && !buffer.is_inside_word(offset_range.end, None))
14285 {
14286 // TODO: This is n^2, because we might check all the selections
14287 if !selections
14288 .iter()
14289 .any(|selection| selection.range().overlaps(&offset_range))
14290 {
14291 next_selected_range = Some(offset_range);
14292 break;
14293 }
14294 }
14295 }
14296
14297 if let Some(next_selected_range) = next_selected_range {
14298 self.select_match_ranges(
14299 next_selected_range,
14300 last_selection.reversed,
14301 replace_newest,
14302 autoscroll,
14303 window,
14304 cx,
14305 );
14306 } else {
14307 select_next_state.done = true;
14308 }
14309 }
14310
14311 self.select_next_state = Some(select_next_state);
14312 } else {
14313 let mut only_carets = true;
14314 let mut same_text_selected = true;
14315 let mut selected_text = None;
14316
14317 let mut selections_iter = selections.iter().peekable();
14318 while let Some(selection) = selections_iter.next() {
14319 if selection.start != selection.end {
14320 only_carets = false;
14321 }
14322
14323 if same_text_selected {
14324 if selected_text.is_none() {
14325 selected_text =
14326 Some(buffer.text_for_range(selection.range()).collect::<String>());
14327 }
14328
14329 if let Some(next_selection) = selections_iter.peek() {
14330 if next_selection.range().len() == selection.range().len() {
14331 let next_selected_text = buffer
14332 .text_for_range(next_selection.range())
14333 .collect::<String>();
14334 if Some(next_selected_text) != selected_text {
14335 same_text_selected = false;
14336 selected_text = None;
14337 }
14338 } else {
14339 same_text_selected = false;
14340 selected_text = None;
14341 }
14342 }
14343 }
14344 }
14345
14346 if only_carets {
14347 for selection in &mut selections {
14348 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14349 selection.start = word_range.start;
14350 selection.end = word_range.end;
14351 selection.goal = SelectionGoal::None;
14352 selection.reversed = false;
14353 self.select_match_ranges(
14354 selection.start..selection.end,
14355 selection.reversed,
14356 replace_newest,
14357 autoscroll,
14358 window,
14359 cx,
14360 );
14361 }
14362
14363 if selections.len() == 1 {
14364 let selection = selections
14365 .last()
14366 .expect("ensured that there's only one selection");
14367 let query = buffer
14368 .text_for_range(selection.start..selection.end)
14369 .collect::<String>();
14370 let is_empty = query.is_empty();
14371 let select_state = SelectNextState {
14372 query: AhoCorasick::new(&[query])?,
14373 wordwise: true,
14374 done: is_empty,
14375 };
14376 self.select_next_state = Some(select_state);
14377 } else {
14378 self.select_next_state = None;
14379 }
14380 } else if let Some(selected_text) = selected_text {
14381 self.select_next_state = Some(SelectNextState {
14382 query: AhoCorasick::new(&[selected_text])?,
14383 wordwise: false,
14384 done: false,
14385 });
14386 self.select_next_match_internal(
14387 display_map,
14388 replace_newest,
14389 autoscroll,
14390 window,
14391 cx,
14392 )?;
14393 }
14394 }
14395 Ok(())
14396 }
14397
14398 pub fn select_all_matches(
14399 &mut self,
14400 _action: &SelectAllMatches,
14401 window: &mut Window,
14402 cx: &mut Context<Self>,
14403 ) -> Result<()> {
14404 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14405
14406 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14407
14408 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14409 let Some(select_next_state) = self.select_next_state.as_mut() else {
14410 return Ok(());
14411 };
14412 if select_next_state.done {
14413 return Ok(());
14414 }
14415
14416 let mut new_selections = Vec::new();
14417
14418 let reversed = self.selections.oldest::<usize>(cx).reversed;
14419 let buffer = &display_map.buffer_snapshot;
14420 let query_matches = select_next_state
14421 .query
14422 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14423
14424 for query_match in query_matches.into_iter() {
14425 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14426 let offset_range = if reversed {
14427 query_match.end()..query_match.start()
14428 } else {
14429 query_match.start()..query_match.end()
14430 };
14431
14432 if !select_next_state.wordwise
14433 || (!buffer.is_inside_word(offset_range.start, None)
14434 && !buffer.is_inside_word(offset_range.end, None))
14435 {
14436 new_selections.push(offset_range.start..offset_range.end);
14437 }
14438 }
14439
14440 select_next_state.done = true;
14441
14442 if new_selections.is_empty() {
14443 log::error!("bug: new_selections is empty in select_all_matches");
14444 return Ok(());
14445 }
14446
14447 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14448 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14449 selections.select_ranges(new_selections)
14450 });
14451
14452 Ok(())
14453 }
14454
14455 pub fn select_next(
14456 &mut self,
14457 action: &SelectNext,
14458 window: &mut Window,
14459 cx: &mut Context<Self>,
14460 ) -> Result<()> {
14461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14462 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14463 self.select_next_match_internal(
14464 &display_map,
14465 action.replace_newest,
14466 Some(Autoscroll::newest()),
14467 window,
14468 cx,
14469 )?;
14470 Ok(())
14471 }
14472
14473 pub fn select_previous(
14474 &mut self,
14475 action: &SelectPrevious,
14476 window: &mut Window,
14477 cx: &mut Context<Self>,
14478 ) -> Result<()> {
14479 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14480 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14481 let buffer = &display_map.buffer_snapshot;
14482 let mut selections = self.selections.all::<usize>(cx);
14483 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14484 let query = &select_prev_state.query;
14485 if !select_prev_state.done {
14486 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14487 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14488 let mut next_selected_range = None;
14489 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14490 let bytes_before_last_selection =
14491 buffer.reversed_bytes_in_range(0..last_selection.start);
14492 let bytes_after_first_selection =
14493 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14494 let query_matches = query
14495 .stream_find_iter(bytes_before_last_selection)
14496 .map(|result| (last_selection.start, result))
14497 .chain(
14498 query
14499 .stream_find_iter(bytes_after_first_selection)
14500 .map(|result| (buffer.len(), result)),
14501 );
14502 for (end_offset, query_match) in query_matches {
14503 let query_match = query_match.unwrap(); // can only fail due to I/O
14504 let offset_range =
14505 end_offset - query_match.end()..end_offset - query_match.start();
14506
14507 if !select_prev_state.wordwise
14508 || (!buffer.is_inside_word(offset_range.start, None)
14509 && !buffer.is_inside_word(offset_range.end, None))
14510 {
14511 next_selected_range = Some(offset_range);
14512 break;
14513 }
14514 }
14515
14516 if let Some(next_selected_range) = next_selected_range {
14517 self.select_match_ranges(
14518 next_selected_range,
14519 last_selection.reversed,
14520 action.replace_newest,
14521 Some(Autoscroll::newest()),
14522 window,
14523 cx,
14524 );
14525 } else {
14526 select_prev_state.done = true;
14527 }
14528 }
14529
14530 self.select_prev_state = Some(select_prev_state);
14531 } else {
14532 let mut only_carets = true;
14533 let mut same_text_selected = true;
14534 let mut selected_text = None;
14535
14536 let mut selections_iter = selections.iter().peekable();
14537 while let Some(selection) = selections_iter.next() {
14538 if selection.start != selection.end {
14539 only_carets = false;
14540 }
14541
14542 if same_text_selected {
14543 if selected_text.is_none() {
14544 selected_text =
14545 Some(buffer.text_for_range(selection.range()).collect::<String>());
14546 }
14547
14548 if let Some(next_selection) = selections_iter.peek() {
14549 if next_selection.range().len() == selection.range().len() {
14550 let next_selected_text = buffer
14551 .text_for_range(next_selection.range())
14552 .collect::<String>();
14553 if Some(next_selected_text) != selected_text {
14554 same_text_selected = false;
14555 selected_text = None;
14556 }
14557 } else {
14558 same_text_selected = false;
14559 selected_text = None;
14560 }
14561 }
14562 }
14563 }
14564
14565 if only_carets {
14566 for selection in &mut selections {
14567 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14568 selection.start = word_range.start;
14569 selection.end = word_range.end;
14570 selection.goal = SelectionGoal::None;
14571 selection.reversed = false;
14572 self.select_match_ranges(
14573 selection.start..selection.end,
14574 selection.reversed,
14575 action.replace_newest,
14576 Some(Autoscroll::newest()),
14577 window,
14578 cx,
14579 );
14580 }
14581 if selections.len() == 1 {
14582 let selection = selections
14583 .last()
14584 .expect("ensured that there's only one selection");
14585 let query = buffer
14586 .text_for_range(selection.start..selection.end)
14587 .collect::<String>();
14588 let is_empty = query.is_empty();
14589 let select_state = SelectNextState {
14590 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14591 wordwise: true,
14592 done: is_empty,
14593 };
14594 self.select_prev_state = Some(select_state);
14595 } else {
14596 self.select_prev_state = None;
14597 }
14598 } else if let Some(selected_text) = selected_text {
14599 self.select_prev_state = Some(SelectNextState {
14600 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14601 wordwise: false,
14602 done: false,
14603 });
14604 self.select_previous(action, window, cx)?;
14605 }
14606 }
14607 Ok(())
14608 }
14609
14610 pub fn find_next_match(
14611 &mut self,
14612 _: &FindNextMatch,
14613 window: &mut Window,
14614 cx: &mut Context<Self>,
14615 ) -> Result<()> {
14616 let selections = self.selections.disjoint_anchors_arc();
14617 match selections.first() {
14618 Some(first) if selections.len() >= 2 => {
14619 self.change_selections(Default::default(), window, cx, |s| {
14620 s.select_ranges([first.range()]);
14621 });
14622 }
14623 _ => self.select_next(
14624 &SelectNext {
14625 replace_newest: true,
14626 },
14627 window,
14628 cx,
14629 )?,
14630 }
14631 Ok(())
14632 }
14633
14634 pub fn find_previous_match(
14635 &mut self,
14636 _: &FindPreviousMatch,
14637 window: &mut Window,
14638 cx: &mut Context<Self>,
14639 ) -> Result<()> {
14640 let selections = self.selections.disjoint_anchors_arc();
14641 match selections.last() {
14642 Some(last) if selections.len() >= 2 => {
14643 self.change_selections(Default::default(), window, cx, |s| {
14644 s.select_ranges([last.range()]);
14645 });
14646 }
14647 _ => self.select_previous(
14648 &SelectPrevious {
14649 replace_newest: true,
14650 },
14651 window,
14652 cx,
14653 )?,
14654 }
14655 Ok(())
14656 }
14657
14658 pub fn toggle_comments(
14659 &mut self,
14660 action: &ToggleComments,
14661 window: &mut Window,
14662 cx: &mut Context<Self>,
14663 ) {
14664 if self.read_only(cx) {
14665 return;
14666 }
14667 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14668 let text_layout_details = &self.text_layout_details(window);
14669 self.transact(window, cx, |this, window, cx| {
14670 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14671 let mut edits = Vec::new();
14672 let mut selection_edit_ranges = Vec::new();
14673 let mut last_toggled_row = None;
14674 let snapshot = this.buffer.read(cx).read(cx);
14675 let empty_str: Arc<str> = Arc::default();
14676 let mut suffixes_inserted = Vec::new();
14677 let ignore_indent = action.ignore_indent;
14678
14679 fn comment_prefix_range(
14680 snapshot: &MultiBufferSnapshot,
14681 row: MultiBufferRow,
14682 comment_prefix: &str,
14683 comment_prefix_whitespace: &str,
14684 ignore_indent: bool,
14685 ) -> Range<Point> {
14686 let indent_size = if ignore_indent {
14687 0
14688 } else {
14689 snapshot.indent_size_for_line(row).len
14690 };
14691
14692 let start = Point::new(row.0, indent_size);
14693
14694 let mut line_bytes = snapshot
14695 .bytes_in_range(start..snapshot.max_point())
14696 .flatten()
14697 .copied();
14698
14699 // If this line currently begins with the line comment prefix, then record
14700 // the range containing the prefix.
14701 if line_bytes
14702 .by_ref()
14703 .take(comment_prefix.len())
14704 .eq(comment_prefix.bytes())
14705 {
14706 // Include any whitespace that matches the comment prefix.
14707 let matching_whitespace_len = line_bytes
14708 .zip(comment_prefix_whitespace.bytes())
14709 .take_while(|(a, b)| a == b)
14710 .count() as u32;
14711 let end = Point::new(
14712 start.row,
14713 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14714 );
14715 start..end
14716 } else {
14717 start..start
14718 }
14719 }
14720
14721 fn comment_suffix_range(
14722 snapshot: &MultiBufferSnapshot,
14723 row: MultiBufferRow,
14724 comment_suffix: &str,
14725 comment_suffix_has_leading_space: bool,
14726 ) -> Range<Point> {
14727 let end = Point::new(row.0, snapshot.line_len(row));
14728 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14729
14730 let mut line_end_bytes = snapshot
14731 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14732 .flatten()
14733 .copied();
14734
14735 let leading_space_len = if suffix_start_column > 0
14736 && line_end_bytes.next() == Some(b' ')
14737 && comment_suffix_has_leading_space
14738 {
14739 1
14740 } else {
14741 0
14742 };
14743
14744 // If this line currently begins with the line comment prefix, then record
14745 // the range containing the prefix.
14746 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14747 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14748 start..end
14749 } else {
14750 end..end
14751 }
14752 }
14753
14754 // TODO: Handle selections that cross excerpts
14755 for selection in &mut selections {
14756 let start_column = snapshot
14757 .indent_size_for_line(MultiBufferRow(selection.start.row))
14758 .len;
14759 let language = if let Some(language) =
14760 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14761 {
14762 language
14763 } else {
14764 continue;
14765 };
14766
14767 selection_edit_ranges.clear();
14768
14769 // If multiple selections contain a given row, avoid processing that
14770 // row more than once.
14771 let mut start_row = MultiBufferRow(selection.start.row);
14772 if last_toggled_row == Some(start_row) {
14773 start_row = start_row.next_row();
14774 }
14775 let end_row =
14776 if selection.end.row > selection.start.row && selection.end.column == 0 {
14777 MultiBufferRow(selection.end.row - 1)
14778 } else {
14779 MultiBufferRow(selection.end.row)
14780 };
14781 last_toggled_row = Some(end_row);
14782
14783 if start_row > end_row {
14784 continue;
14785 }
14786
14787 // If the language has line comments, toggle those.
14788 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14789
14790 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14791 if ignore_indent {
14792 full_comment_prefixes = full_comment_prefixes
14793 .into_iter()
14794 .map(|s| Arc::from(s.trim_end()))
14795 .collect();
14796 }
14797
14798 if !full_comment_prefixes.is_empty() {
14799 let first_prefix = full_comment_prefixes
14800 .first()
14801 .expect("prefixes is non-empty");
14802 let prefix_trimmed_lengths = full_comment_prefixes
14803 .iter()
14804 .map(|p| p.trim_end_matches(' ').len())
14805 .collect::<SmallVec<[usize; 4]>>();
14806
14807 let mut all_selection_lines_are_comments = true;
14808
14809 for row in start_row.0..=end_row.0 {
14810 let row = MultiBufferRow(row);
14811 if start_row < end_row && snapshot.is_line_blank(row) {
14812 continue;
14813 }
14814
14815 let prefix_range = full_comment_prefixes
14816 .iter()
14817 .zip(prefix_trimmed_lengths.iter().copied())
14818 .map(|(prefix, trimmed_prefix_len)| {
14819 comment_prefix_range(
14820 snapshot.deref(),
14821 row,
14822 &prefix[..trimmed_prefix_len],
14823 &prefix[trimmed_prefix_len..],
14824 ignore_indent,
14825 )
14826 })
14827 .max_by_key(|range| range.end.column - range.start.column)
14828 .expect("prefixes is non-empty");
14829
14830 if prefix_range.is_empty() {
14831 all_selection_lines_are_comments = false;
14832 }
14833
14834 selection_edit_ranges.push(prefix_range);
14835 }
14836
14837 if all_selection_lines_are_comments {
14838 edits.extend(
14839 selection_edit_ranges
14840 .iter()
14841 .cloned()
14842 .map(|range| (range, empty_str.clone())),
14843 );
14844 } else {
14845 let min_column = selection_edit_ranges
14846 .iter()
14847 .map(|range| range.start.column)
14848 .min()
14849 .unwrap_or(0);
14850 edits.extend(selection_edit_ranges.iter().map(|range| {
14851 let position = Point::new(range.start.row, min_column);
14852 (position..position, first_prefix.clone())
14853 }));
14854 }
14855 } else if let Some(BlockCommentConfig {
14856 start: full_comment_prefix,
14857 end: comment_suffix,
14858 ..
14859 }) = language.block_comment()
14860 {
14861 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14862 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14863 let prefix_range = comment_prefix_range(
14864 snapshot.deref(),
14865 start_row,
14866 comment_prefix,
14867 comment_prefix_whitespace,
14868 ignore_indent,
14869 );
14870 let suffix_range = comment_suffix_range(
14871 snapshot.deref(),
14872 end_row,
14873 comment_suffix.trim_start_matches(' '),
14874 comment_suffix.starts_with(' '),
14875 );
14876
14877 if prefix_range.is_empty() || suffix_range.is_empty() {
14878 edits.push((
14879 prefix_range.start..prefix_range.start,
14880 full_comment_prefix.clone(),
14881 ));
14882 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14883 suffixes_inserted.push((end_row, comment_suffix.len()));
14884 } else {
14885 edits.push((prefix_range, empty_str.clone()));
14886 edits.push((suffix_range, empty_str.clone()));
14887 }
14888 } else {
14889 continue;
14890 }
14891 }
14892
14893 drop(snapshot);
14894 this.buffer.update(cx, |buffer, cx| {
14895 buffer.edit(edits, None, cx);
14896 });
14897
14898 // Adjust selections so that they end before any comment suffixes that
14899 // were inserted.
14900 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14901 let mut selections = this.selections.all::<Point>(cx);
14902 let snapshot = this.buffer.read(cx).read(cx);
14903 for selection in &mut selections {
14904 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14905 match row.cmp(&MultiBufferRow(selection.end.row)) {
14906 Ordering::Less => {
14907 suffixes_inserted.next();
14908 continue;
14909 }
14910 Ordering::Greater => break,
14911 Ordering::Equal => {
14912 if selection.end.column == snapshot.line_len(row) {
14913 if selection.is_empty() {
14914 selection.start.column -= suffix_len as u32;
14915 }
14916 selection.end.column -= suffix_len as u32;
14917 }
14918 break;
14919 }
14920 }
14921 }
14922 }
14923
14924 drop(snapshot);
14925 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14926
14927 let selections = this.selections.all::<Point>(cx);
14928 let selections_on_single_row = selections.windows(2).all(|selections| {
14929 selections[0].start.row == selections[1].start.row
14930 && selections[0].end.row == selections[1].end.row
14931 && selections[0].start.row == selections[0].end.row
14932 });
14933 let selections_selecting = selections
14934 .iter()
14935 .any(|selection| selection.start != selection.end);
14936 let advance_downwards = action.advance_downwards
14937 && selections_on_single_row
14938 && !selections_selecting
14939 && !matches!(this.mode, EditorMode::SingleLine);
14940
14941 if advance_downwards {
14942 let snapshot = this.buffer.read(cx).snapshot(cx);
14943
14944 this.change_selections(Default::default(), window, cx, |s| {
14945 s.move_cursors_with(|display_snapshot, display_point, _| {
14946 let mut point = display_point.to_point(display_snapshot);
14947 point.row += 1;
14948 point = snapshot.clip_point(point, Bias::Left);
14949 let display_point = point.to_display_point(display_snapshot);
14950 let goal = SelectionGoal::HorizontalPosition(
14951 display_snapshot
14952 .x_for_display_point(display_point, text_layout_details)
14953 .into(),
14954 );
14955 (display_point, goal)
14956 })
14957 });
14958 }
14959 });
14960 }
14961
14962 pub fn select_enclosing_symbol(
14963 &mut self,
14964 _: &SelectEnclosingSymbol,
14965 window: &mut Window,
14966 cx: &mut Context<Self>,
14967 ) {
14968 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14969
14970 let buffer = self.buffer.read(cx).snapshot(cx);
14971 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14972
14973 fn update_selection(
14974 selection: &Selection<usize>,
14975 buffer_snap: &MultiBufferSnapshot,
14976 ) -> Option<Selection<usize>> {
14977 let cursor = selection.head();
14978 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14979 for symbol in symbols.iter().rev() {
14980 let start = symbol.range.start.to_offset(buffer_snap);
14981 let end = symbol.range.end.to_offset(buffer_snap);
14982 let new_range = start..end;
14983 if start < selection.start || end > selection.end {
14984 return Some(Selection {
14985 id: selection.id,
14986 start: new_range.start,
14987 end: new_range.end,
14988 goal: SelectionGoal::None,
14989 reversed: selection.reversed,
14990 });
14991 }
14992 }
14993 None
14994 }
14995
14996 let mut selected_larger_symbol = false;
14997 let new_selections = old_selections
14998 .iter()
14999 .map(|selection| match update_selection(selection, &buffer) {
15000 Some(new_selection) => {
15001 if new_selection.range() != selection.range() {
15002 selected_larger_symbol = true;
15003 }
15004 new_selection
15005 }
15006 None => selection.clone(),
15007 })
15008 .collect::<Vec<_>>();
15009
15010 if selected_larger_symbol {
15011 self.change_selections(Default::default(), window, cx, |s| {
15012 s.select(new_selections);
15013 });
15014 }
15015 }
15016
15017 pub fn select_larger_syntax_node(
15018 &mut self,
15019 _: &SelectLargerSyntaxNode,
15020 window: &mut Window,
15021 cx: &mut Context<Self>,
15022 ) {
15023 let Some(visible_row_count) = self.visible_row_count() else {
15024 return;
15025 };
15026 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15027 if old_selections.is_empty() {
15028 return;
15029 }
15030
15031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15032
15033 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15034 let buffer = self.buffer.read(cx).snapshot(cx);
15035
15036 let mut selected_larger_node = false;
15037 let mut new_selections = old_selections
15038 .iter()
15039 .map(|selection| {
15040 let old_range = selection.start..selection.end;
15041
15042 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15043 // manually select word at selection
15044 if ["string_content", "inline"].contains(&node.kind()) {
15045 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15046 // ignore if word is already selected
15047 if !word_range.is_empty() && old_range != word_range {
15048 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15049 // only select word if start and end point belongs to same word
15050 if word_range == last_word_range {
15051 selected_larger_node = true;
15052 return Selection {
15053 id: selection.id,
15054 start: word_range.start,
15055 end: word_range.end,
15056 goal: SelectionGoal::None,
15057 reversed: selection.reversed,
15058 };
15059 }
15060 }
15061 }
15062 }
15063
15064 let mut new_range = old_range.clone();
15065 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
15066 {
15067 new_range = match containing_range {
15068 MultiOrSingleBufferOffsetRange::Single(_) => break,
15069 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15070 };
15071 if !node.is_named() {
15072 continue;
15073 }
15074 if !display_map.intersects_fold(new_range.start)
15075 && !display_map.intersects_fold(new_range.end)
15076 {
15077 break;
15078 }
15079 }
15080
15081 selected_larger_node |= new_range != old_range;
15082 Selection {
15083 id: selection.id,
15084 start: new_range.start,
15085 end: new_range.end,
15086 goal: SelectionGoal::None,
15087 reversed: selection.reversed,
15088 }
15089 })
15090 .collect::<Vec<_>>();
15091
15092 if !selected_larger_node {
15093 return; // don't put this call in the history
15094 }
15095
15096 // scroll based on transformation done to the last selection created by the user
15097 let (last_old, last_new) = old_selections
15098 .last()
15099 .zip(new_selections.last().cloned())
15100 .expect("old_selections isn't empty");
15101
15102 // revert selection
15103 let is_selection_reversed = {
15104 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15105 new_selections.last_mut().expect("checked above").reversed =
15106 should_newest_selection_be_reversed;
15107 should_newest_selection_be_reversed
15108 };
15109
15110 if selected_larger_node {
15111 self.select_syntax_node_history.disable_clearing = true;
15112 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15113 s.select(new_selections.clone());
15114 });
15115 self.select_syntax_node_history.disable_clearing = false;
15116 }
15117
15118 let start_row = last_new.start.to_display_point(&display_map).row().0;
15119 let end_row = last_new.end.to_display_point(&display_map).row().0;
15120 let selection_height = end_row - start_row + 1;
15121 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15122
15123 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15124 let scroll_behavior = if fits_on_the_screen {
15125 self.request_autoscroll(Autoscroll::fit(), cx);
15126 SelectSyntaxNodeScrollBehavior::FitSelection
15127 } else if is_selection_reversed {
15128 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15129 SelectSyntaxNodeScrollBehavior::CursorTop
15130 } else {
15131 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15132 SelectSyntaxNodeScrollBehavior::CursorBottom
15133 };
15134
15135 self.select_syntax_node_history.push((
15136 old_selections,
15137 scroll_behavior,
15138 is_selection_reversed,
15139 ));
15140 }
15141
15142 pub fn select_smaller_syntax_node(
15143 &mut self,
15144 _: &SelectSmallerSyntaxNode,
15145 window: &mut Window,
15146 cx: &mut Context<Self>,
15147 ) {
15148 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15149
15150 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15151 self.select_syntax_node_history.pop()
15152 {
15153 if let Some(selection) = selections.last_mut() {
15154 selection.reversed = is_selection_reversed;
15155 }
15156
15157 self.select_syntax_node_history.disable_clearing = true;
15158 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15159 s.select(selections.to_vec());
15160 });
15161 self.select_syntax_node_history.disable_clearing = false;
15162
15163 match scroll_behavior {
15164 SelectSyntaxNodeScrollBehavior::CursorTop => {
15165 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15166 }
15167 SelectSyntaxNodeScrollBehavior::FitSelection => {
15168 self.request_autoscroll(Autoscroll::fit(), cx);
15169 }
15170 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15171 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15172 }
15173 }
15174 }
15175 }
15176
15177 pub fn unwrap_syntax_node(
15178 &mut self,
15179 _: &UnwrapSyntaxNode,
15180 window: &mut Window,
15181 cx: &mut Context<Self>,
15182 ) {
15183 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15184
15185 let buffer = self.buffer.read(cx).snapshot(cx);
15186 let selections = self
15187 .selections
15188 .all::<usize>(cx)
15189 .into_iter()
15190 // subtracting the offset requires sorting
15191 .sorted_by_key(|i| i.start);
15192
15193 let full_edits = selections
15194 .into_iter()
15195 .filter_map(|selection| {
15196 let child = if selection.is_empty()
15197 && let Some((_, ancestor_range)) =
15198 buffer.syntax_ancestor(selection.start..selection.end)
15199 {
15200 match ancestor_range {
15201 MultiOrSingleBufferOffsetRange::Single(range) => range,
15202 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15203 }
15204 } else {
15205 selection.range()
15206 };
15207
15208 let mut parent = child.clone();
15209 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15210 parent = match ancestor_range {
15211 MultiOrSingleBufferOffsetRange::Single(range) => range,
15212 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15213 };
15214 if parent.start < child.start || parent.end > child.end {
15215 break;
15216 }
15217 }
15218
15219 if parent == child {
15220 return None;
15221 }
15222 let text = buffer.text_for_range(child).collect::<String>();
15223 Some((selection.id, parent, text))
15224 })
15225 .collect::<Vec<_>>();
15226 if full_edits.is_empty() {
15227 return;
15228 }
15229
15230 self.transact(window, cx, |this, window, cx| {
15231 this.buffer.update(cx, |buffer, cx| {
15232 buffer.edit(
15233 full_edits
15234 .iter()
15235 .map(|(_, p, t)| (p.clone(), t.clone()))
15236 .collect::<Vec<_>>(),
15237 None,
15238 cx,
15239 );
15240 });
15241 this.change_selections(Default::default(), window, cx, |s| {
15242 let mut offset = 0;
15243 let mut selections = vec![];
15244 for (id, parent, text) in full_edits {
15245 let start = parent.start - offset;
15246 offset += parent.len() - text.len();
15247 selections.push(Selection {
15248 id,
15249 start,
15250 end: start + text.len(),
15251 reversed: false,
15252 goal: Default::default(),
15253 });
15254 }
15255 s.select(selections);
15256 });
15257 });
15258 }
15259
15260 pub fn select_next_syntax_node(
15261 &mut self,
15262 _: &SelectNextSyntaxNode,
15263 window: &mut Window,
15264 cx: &mut Context<Self>,
15265 ) {
15266 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15267 if old_selections.is_empty() {
15268 return;
15269 }
15270
15271 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15272
15273 let buffer = self.buffer.read(cx).snapshot(cx);
15274 let mut selected_sibling = false;
15275
15276 let new_selections = old_selections
15277 .iter()
15278 .map(|selection| {
15279 let old_range = selection.start..selection.end;
15280
15281 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15282 let new_range = node.byte_range();
15283 selected_sibling = true;
15284 Selection {
15285 id: selection.id,
15286 start: new_range.start,
15287 end: new_range.end,
15288 goal: SelectionGoal::None,
15289 reversed: selection.reversed,
15290 }
15291 } else {
15292 selection.clone()
15293 }
15294 })
15295 .collect::<Vec<_>>();
15296
15297 if selected_sibling {
15298 self.change_selections(
15299 SelectionEffects::scroll(Autoscroll::fit()),
15300 window,
15301 cx,
15302 |s| {
15303 s.select(new_selections);
15304 },
15305 );
15306 }
15307 }
15308
15309 pub fn select_prev_syntax_node(
15310 &mut self,
15311 _: &SelectPreviousSyntaxNode,
15312 window: &mut Window,
15313 cx: &mut Context<Self>,
15314 ) {
15315 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15316 if old_selections.is_empty() {
15317 return;
15318 }
15319
15320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15321
15322 let buffer = self.buffer.read(cx).snapshot(cx);
15323 let mut selected_sibling = false;
15324
15325 let new_selections = old_selections
15326 .iter()
15327 .map(|selection| {
15328 let old_range = selection.start..selection.end;
15329
15330 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15331 let new_range = node.byte_range();
15332 selected_sibling = true;
15333 Selection {
15334 id: selection.id,
15335 start: new_range.start,
15336 end: new_range.end,
15337 goal: SelectionGoal::None,
15338 reversed: selection.reversed,
15339 }
15340 } else {
15341 selection.clone()
15342 }
15343 })
15344 .collect::<Vec<_>>();
15345
15346 if selected_sibling {
15347 self.change_selections(
15348 SelectionEffects::scroll(Autoscroll::fit()),
15349 window,
15350 cx,
15351 |s| {
15352 s.select(new_selections);
15353 },
15354 );
15355 }
15356 }
15357
15358 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15359 if !EditorSettings::get_global(cx).gutter.runnables {
15360 self.clear_tasks();
15361 return Task::ready(());
15362 }
15363 let project = self.project().map(Entity::downgrade);
15364 let task_sources = self.lsp_task_sources(cx);
15365 let multi_buffer = self.buffer.downgrade();
15366 cx.spawn_in(window, async move |editor, cx| {
15367 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15368 let Some(project) = project.and_then(|p| p.upgrade()) else {
15369 return;
15370 };
15371 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15372 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15373 }) else {
15374 return;
15375 };
15376
15377 let hide_runnables = project
15378 .update(cx, |project, _| project.is_via_collab())
15379 .unwrap_or(true);
15380 if hide_runnables {
15381 return;
15382 }
15383 let new_rows =
15384 cx.background_spawn({
15385 let snapshot = display_snapshot.clone();
15386 async move {
15387 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15388 }
15389 })
15390 .await;
15391 let Ok(lsp_tasks) =
15392 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15393 else {
15394 return;
15395 };
15396 let lsp_tasks = lsp_tasks.await;
15397
15398 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15399 lsp_tasks
15400 .into_iter()
15401 .flat_map(|(kind, tasks)| {
15402 tasks.into_iter().filter_map(move |(location, task)| {
15403 Some((kind.clone(), location?, task))
15404 })
15405 })
15406 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15407 let buffer = location.target.buffer;
15408 let buffer_snapshot = buffer.read(cx).snapshot();
15409 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15410 |(excerpt_id, snapshot, _)| {
15411 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15412 display_snapshot
15413 .buffer_snapshot
15414 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15415 } else {
15416 None
15417 }
15418 },
15419 );
15420 if let Some(offset) = offset {
15421 let task_buffer_range =
15422 location.target.range.to_point(&buffer_snapshot);
15423 let context_buffer_range =
15424 task_buffer_range.to_offset(&buffer_snapshot);
15425 let context_range = BufferOffset(context_buffer_range.start)
15426 ..BufferOffset(context_buffer_range.end);
15427
15428 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15429 .or_insert_with(|| RunnableTasks {
15430 templates: Vec::new(),
15431 offset,
15432 column: task_buffer_range.start.column,
15433 extra_variables: HashMap::default(),
15434 context_range,
15435 })
15436 .templates
15437 .push((kind, task.original_task().clone()));
15438 }
15439
15440 acc
15441 })
15442 }) else {
15443 return;
15444 };
15445
15446 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15447 buffer.language_settings(cx).tasks.prefer_lsp
15448 }) else {
15449 return;
15450 };
15451
15452 let rows = Self::runnable_rows(
15453 project,
15454 display_snapshot,
15455 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15456 new_rows,
15457 cx.clone(),
15458 )
15459 .await;
15460 editor
15461 .update(cx, |editor, _| {
15462 editor.clear_tasks();
15463 for (key, mut value) in rows {
15464 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15465 value.templates.extend(lsp_tasks.templates);
15466 }
15467
15468 editor.insert_tasks(key, value);
15469 }
15470 for (key, value) in lsp_tasks_by_rows {
15471 editor.insert_tasks(key, value);
15472 }
15473 })
15474 .ok();
15475 })
15476 }
15477 fn fetch_runnable_ranges(
15478 snapshot: &DisplaySnapshot,
15479 range: Range<Anchor>,
15480 ) -> Vec<language::RunnableRange> {
15481 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15482 }
15483
15484 fn runnable_rows(
15485 project: Entity<Project>,
15486 snapshot: DisplaySnapshot,
15487 prefer_lsp: bool,
15488 runnable_ranges: Vec<RunnableRange>,
15489 cx: AsyncWindowContext,
15490 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15491 cx.spawn(async move |cx| {
15492 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15493 for mut runnable in runnable_ranges {
15494 let Some(tasks) = cx
15495 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15496 .ok()
15497 else {
15498 continue;
15499 };
15500 let mut tasks = tasks.await;
15501
15502 if prefer_lsp {
15503 tasks.retain(|(task_kind, _)| {
15504 !matches!(task_kind, TaskSourceKind::Language { .. })
15505 });
15506 }
15507 if tasks.is_empty() {
15508 continue;
15509 }
15510
15511 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15512 let Some(row) = snapshot
15513 .buffer_snapshot
15514 .buffer_line_for_row(MultiBufferRow(point.row))
15515 .map(|(_, range)| range.start.row)
15516 else {
15517 continue;
15518 };
15519
15520 let context_range =
15521 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15522 runnable_rows.push((
15523 (runnable.buffer_id, row),
15524 RunnableTasks {
15525 templates: tasks,
15526 offset: snapshot
15527 .buffer_snapshot
15528 .anchor_before(runnable.run_range.start),
15529 context_range,
15530 column: point.column,
15531 extra_variables: runnable.extra_captures,
15532 },
15533 ));
15534 }
15535 runnable_rows
15536 })
15537 }
15538
15539 fn templates_with_tags(
15540 project: &Entity<Project>,
15541 runnable: &mut Runnable,
15542 cx: &mut App,
15543 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15544 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15545 let (worktree_id, file) = project
15546 .buffer_for_id(runnable.buffer, cx)
15547 .and_then(|buffer| buffer.read(cx).file())
15548 .map(|file| (file.worktree_id(cx), file.clone()))
15549 .unzip();
15550
15551 (
15552 project.task_store().read(cx).task_inventory().cloned(),
15553 worktree_id,
15554 file,
15555 )
15556 });
15557
15558 let tags = mem::take(&mut runnable.tags);
15559 let language = runnable.language.clone();
15560 cx.spawn(async move |cx| {
15561 let mut templates_with_tags = Vec::new();
15562 if let Some(inventory) = inventory {
15563 for RunnableTag(tag) in tags {
15564 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15565 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15566 }) else {
15567 return templates_with_tags;
15568 };
15569 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15570 move |(_, template)| {
15571 template.tags.iter().any(|source_tag| source_tag == &tag)
15572 },
15573 ));
15574 }
15575 }
15576 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15577
15578 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15579 // Strongest source wins; if we have worktree tag binding, prefer that to
15580 // global and language bindings;
15581 // if we have a global binding, prefer that to language binding.
15582 let first_mismatch = templates_with_tags
15583 .iter()
15584 .position(|(tag_source, _)| tag_source != leading_tag_source);
15585 if let Some(index) = first_mismatch {
15586 templates_with_tags.truncate(index);
15587 }
15588 }
15589
15590 templates_with_tags
15591 })
15592 }
15593
15594 pub fn move_to_enclosing_bracket(
15595 &mut self,
15596 _: &MoveToEnclosingBracket,
15597 window: &mut Window,
15598 cx: &mut Context<Self>,
15599 ) {
15600 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15601 self.change_selections(Default::default(), window, cx, |s| {
15602 s.move_offsets_with(|snapshot, selection| {
15603 let Some(enclosing_bracket_ranges) =
15604 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15605 else {
15606 return;
15607 };
15608
15609 let mut best_length = usize::MAX;
15610 let mut best_inside = false;
15611 let mut best_in_bracket_range = false;
15612 let mut best_destination = None;
15613 for (open, close) in enclosing_bracket_ranges {
15614 let close = close.to_inclusive();
15615 let length = close.end() - open.start;
15616 let inside = selection.start >= open.end && selection.end <= *close.start();
15617 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15618 || close.contains(&selection.head());
15619
15620 // If best is next to a bracket and current isn't, skip
15621 if !in_bracket_range && best_in_bracket_range {
15622 continue;
15623 }
15624
15625 // Prefer smaller lengths unless best is inside and current isn't
15626 if length > best_length && (best_inside || !inside) {
15627 continue;
15628 }
15629
15630 best_length = length;
15631 best_inside = inside;
15632 best_in_bracket_range = in_bracket_range;
15633 best_destination = Some(
15634 if close.contains(&selection.start) && close.contains(&selection.end) {
15635 if inside { open.end } else { open.start }
15636 } else if inside {
15637 *close.start()
15638 } else {
15639 *close.end()
15640 },
15641 );
15642 }
15643
15644 if let Some(destination) = best_destination {
15645 selection.collapse_to(destination, SelectionGoal::None);
15646 }
15647 })
15648 });
15649 }
15650
15651 pub fn undo_selection(
15652 &mut self,
15653 _: &UndoSelection,
15654 window: &mut Window,
15655 cx: &mut Context<Self>,
15656 ) {
15657 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15658 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15659 self.selection_history.mode = SelectionHistoryMode::Undoing;
15660 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15661 this.end_selection(window, cx);
15662 this.change_selections(
15663 SelectionEffects::scroll(Autoscroll::newest()),
15664 window,
15665 cx,
15666 |s| s.select_anchors(entry.selections.to_vec()),
15667 );
15668 });
15669 self.selection_history.mode = SelectionHistoryMode::Normal;
15670
15671 self.select_next_state = entry.select_next_state;
15672 self.select_prev_state = entry.select_prev_state;
15673 self.add_selections_state = entry.add_selections_state;
15674 }
15675 }
15676
15677 pub fn redo_selection(
15678 &mut self,
15679 _: &RedoSelection,
15680 window: &mut Window,
15681 cx: &mut Context<Self>,
15682 ) {
15683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15684 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15685 self.selection_history.mode = SelectionHistoryMode::Redoing;
15686 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15687 this.end_selection(window, cx);
15688 this.change_selections(
15689 SelectionEffects::scroll(Autoscroll::newest()),
15690 window,
15691 cx,
15692 |s| s.select_anchors(entry.selections.to_vec()),
15693 );
15694 });
15695 self.selection_history.mode = SelectionHistoryMode::Normal;
15696
15697 self.select_next_state = entry.select_next_state;
15698 self.select_prev_state = entry.select_prev_state;
15699 self.add_selections_state = entry.add_selections_state;
15700 }
15701 }
15702
15703 pub fn expand_excerpts(
15704 &mut self,
15705 action: &ExpandExcerpts,
15706 _: &mut Window,
15707 cx: &mut Context<Self>,
15708 ) {
15709 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15710 }
15711
15712 pub fn expand_excerpts_down(
15713 &mut self,
15714 action: &ExpandExcerptsDown,
15715 _: &mut Window,
15716 cx: &mut Context<Self>,
15717 ) {
15718 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15719 }
15720
15721 pub fn expand_excerpts_up(
15722 &mut self,
15723 action: &ExpandExcerptsUp,
15724 _: &mut Window,
15725 cx: &mut Context<Self>,
15726 ) {
15727 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15728 }
15729
15730 pub fn expand_excerpts_for_direction(
15731 &mut self,
15732 lines: u32,
15733 direction: ExpandExcerptDirection,
15734
15735 cx: &mut Context<Self>,
15736 ) {
15737 let selections = self.selections.disjoint_anchors_arc();
15738
15739 let lines = if lines == 0 {
15740 EditorSettings::get_global(cx).expand_excerpt_lines
15741 } else {
15742 lines
15743 };
15744
15745 self.buffer.update(cx, |buffer, cx| {
15746 let snapshot = buffer.snapshot(cx);
15747 let mut excerpt_ids = selections
15748 .iter()
15749 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15750 .collect::<Vec<_>>();
15751 excerpt_ids.sort();
15752 excerpt_ids.dedup();
15753 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15754 })
15755 }
15756
15757 pub fn expand_excerpt(
15758 &mut self,
15759 excerpt: ExcerptId,
15760 direction: ExpandExcerptDirection,
15761 window: &mut Window,
15762 cx: &mut Context<Self>,
15763 ) {
15764 let current_scroll_position = self.scroll_position(cx);
15765 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15766 let mut should_scroll_up = false;
15767
15768 if direction == ExpandExcerptDirection::Down {
15769 let multi_buffer = self.buffer.read(cx);
15770 let snapshot = multi_buffer.snapshot(cx);
15771 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15772 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15773 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15774 {
15775 let buffer_snapshot = buffer.read(cx).snapshot();
15776 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15777 let last_row = buffer_snapshot.max_point().row;
15778 let lines_below = last_row.saturating_sub(excerpt_end_row);
15779 should_scroll_up = lines_below >= lines_to_expand;
15780 }
15781 }
15782
15783 self.buffer.update(cx, |buffer, cx| {
15784 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15785 });
15786
15787 if should_scroll_up {
15788 let new_scroll_position =
15789 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15790 self.set_scroll_position(new_scroll_position, window, cx);
15791 }
15792 }
15793
15794 pub fn go_to_singleton_buffer_point(
15795 &mut self,
15796 point: Point,
15797 window: &mut Window,
15798 cx: &mut Context<Self>,
15799 ) {
15800 self.go_to_singleton_buffer_range(point..point, window, cx);
15801 }
15802
15803 pub fn go_to_singleton_buffer_range(
15804 &mut self,
15805 range: Range<Point>,
15806 window: &mut Window,
15807 cx: &mut Context<Self>,
15808 ) {
15809 let multibuffer = self.buffer().read(cx);
15810 let Some(buffer) = multibuffer.as_singleton() else {
15811 return;
15812 };
15813 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15814 return;
15815 };
15816 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15817 return;
15818 };
15819 self.change_selections(
15820 SelectionEffects::default().nav_history(true),
15821 window,
15822 cx,
15823 |s| s.select_anchor_ranges([start..end]),
15824 );
15825 }
15826
15827 pub fn go_to_diagnostic(
15828 &mut self,
15829 action: &GoToDiagnostic,
15830 window: &mut Window,
15831 cx: &mut Context<Self>,
15832 ) {
15833 if !self.diagnostics_enabled() {
15834 return;
15835 }
15836 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15837 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15838 }
15839
15840 pub fn go_to_prev_diagnostic(
15841 &mut self,
15842 action: &GoToPreviousDiagnostic,
15843 window: &mut Window,
15844 cx: &mut Context<Self>,
15845 ) {
15846 if !self.diagnostics_enabled() {
15847 return;
15848 }
15849 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15850 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15851 }
15852
15853 pub fn go_to_diagnostic_impl(
15854 &mut self,
15855 direction: Direction,
15856 severity: GoToDiagnosticSeverityFilter,
15857 window: &mut Window,
15858 cx: &mut Context<Self>,
15859 ) {
15860 let buffer = self.buffer.read(cx).snapshot(cx);
15861 let selection = self.selections.newest::<usize>(cx);
15862
15863 let mut active_group_id = None;
15864 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15865 && active_group.active_range.start.to_offset(&buffer) == selection.start
15866 {
15867 active_group_id = Some(active_group.group_id);
15868 }
15869
15870 fn filtered(
15871 snapshot: EditorSnapshot,
15872 severity: GoToDiagnosticSeverityFilter,
15873 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15874 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15875 diagnostics
15876 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15877 .filter(|entry| entry.range.start != entry.range.end)
15878 .filter(|entry| !entry.diagnostic.is_unnecessary)
15879 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15880 }
15881
15882 let snapshot = self.snapshot(window, cx);
15883 let before = filtered(
15884 snapshot.clone(),
15885 severity,
15886 buffer
15887 .diagnostics_in_range(0..selection.start)
15888 .filter(|entry| entry.range.start <= selection.start),
15889 );
15890 let after = filtered(
15891 snapshot,
15892 severity,
15893 buffer
15894 .diagnostics_in_range(selection.start..buffer.len())
15895 .filter(|entry| entry.range.start >= selection.start),
15896 );
15897
15898 let mut found: Option<DiagnosticEntry<usize>> = None;
15899 if direction == Direction::Prev {
15900 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15901 {
15902 for diagnostic in prev_diagnostics.into_iter().rev() {
15903 if diagnostic.range.start != selection.start
15904 || active_group_id
15905 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15906 {
15907 found = Some(diagnostic);
15908 break 'outer;
15909 }
15910 }
15911 }
15912 } else {
15913 for diagnostic in after.chain(before) {
15914 if diagnostic.range.start != selection.start
15915 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15916 {
15917 found = Some(diagnostic);
15918 break;
15919 }
15920 }
15921 }
15922 let Some(next_diagnostic) = found else {
15923 return;
15924 };
15925
15926 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15927 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15928 return;
15929 };
15930 self.change_selections(Default::default(), window, cx, |s| {
15931 s.select_ranges(vec![
15932 next_diagnostic.range.start..next_diagnostic.range.start,
15933 ])
15934 });
15935 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15936 self.refresh_edit_prediction(false, true, window, cx);
15937 }
15938
15939 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15941 let snapshot = self.snapshot(window, cx);
15942 let selection = self.selections.newest::<Point>(cx);
15943 self.go_to_hunk_before_or_after_position(
15944 &snapshot,
15945 selection.head(),
15946 Direction::Next,
15947 window,
15948 cx,
15949 );
15950 }
15951
15952 pub fn go_to_hunk_before_or_after_position(
15953 &mut self,
15954 snapshot: &EditorSnapshot,
15955 position: Point,
15956 direction: Direction,
15957 window: &mut Window,
15958 cx: &mut Context<Editor>,
15959 ) {
15960 let row = if direction == Direction::Next {
15961 self.hunk_after_position(snapshot, position)
15962 .map(|hunk| hunk.row_range.start)
15963 } else {
15964 self.hunk_before_position(snapshot, position)
15965 };
15966
15967 if let Some(row) = row {
15968 let destination = Point::new(row.0, 0);
15969 let autoscroll = Autoscroll::center();
15970
15971 self.unfold_ranges(&[destination..destination], false, false, cx);
15972 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15973 s.select_ranges([destination..destination]);
15974 });
15975 }
15976 }
15977
15978 fn hunk_after_position(
15979 &mut self,
15980 snapshot: &EditorSnapshot,
15981 position: Point,
15982 ) -> Option<MultiBufferDiffHunk> {
15983 snapshot
15984 .buffer_snapshot
15985 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15986 .find(|hunk| hunk.row_range.start.0 > position.row)
15987 .or_else(|| {
15988 snapshot
15989 .buffer_snapshot
15990 .diff_hunks_in_range(Point::zero()..position)
15991 .find(|hunk| hunk.row_range.end.0 < position.row)
15992 })
15993 }
15994
15995 fn go_to_prev_hunk(
15996 &mut self,
15997 _: &GoToPreviousHunk,
15998 window: &mut Window,
15999 cx: &mut Context<Self>,
16000 ) {
16001 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16002 let snapshot = self.snapshot(window, cx);
16003 let selection = self.selections.newest::<Point>(cx);
16004 self.go_to_hunk_before_or_after_position(
16005 &snapshot,
16006 selection.head(),
16007 Direction::Prev,
16008 window,
16009 cx,
16010 );
16011 }
16012
16013 fn hunk_before_position(
16014 &mut self,
16015 snapshot: &EditorSnapshot,
16016 position: Point,
16017 ) -> Option<MultiBufferRow> {
16018 snapshot
16019 .buffer_snapshot
16020 .diff_hunk_before(position)
16021 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
16022 }
16023
16024 fn go_to_next_change(
16025 &mut self,
16026 _: &GoToNextChange,
16027 window: &mut Window,
16028 cx: &mut Context<Self>,
16029 ) {
16030 if let Some(selections) = self
16031 .change_list
16032 .next_change(1, Direction::Next)
16033 .map(|s| s.to_vec())
16034 {
16035 self.change_selections(Default::default(), window, cx, |s| {
16036 let map = s.display_map();
16037 s.select_display_ranges(selections.iter().map(|a| {
16038 let point = a.to_display_point(&map);
16039 point..point
16040 }))
16041 })
16042 }
16043 }
16044
16045 fn go_to_previous_change(
16046 &mut self,
16047 _: &GoToPreviousChange,
16048 window: &mut Window,
16049 cx: &mut Context<Self>,
16050 ) {
16051 if let Some(selections) = self
16052 .change_list
16053 .next_change(1, Direction::Prev)
16054 .map(|s| s.to_vec())
16055 {
16056 self.change_selections(Default::default(), window, cx, |s| {
16057 let map = s.display_map();
16058 s.select_display_ranges(selections.iter().map(|a| {
16059 let point = a.to_display_point(&map);
16060 point..point
16061 }))
16062 })
16063 }
16064 }
16065
16066 pub fn go_to_next_document_highlight(
16067 &mut self,
16068 _: &GoToNextDocumentHighlight,
16069 window: &mut Window,
16070 cx: &mut Context<Self>,
16071 ) {
16072 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16073 }
16074
16075 pub fn go_to_prev_document_highlight(
16076 &mut self,
16077 _: &GoToPreviousDocumentHighlight,
16078 window: &mut Window,
16079 cx: &mut Context<Self>,
16080 ) {
16081 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16082 }
16083
16084 pub fn go_to_document_highlight_before_or_after_position(
16085 &mut self,
16086 direction: Direction,
16087 window: &mut Window,
16088 cx: &mut Context<Editor>,
16089 ) {
16090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16091 let snapshot = self.snapshot(window, cx);
16092 let buffer = &snapshot.buffer_snapshot;
16093 let position = self.selections.newest::<Point>(cx).head();
16094 let anchor_position = buffer.anchor_after(position);
16095
16096 // Get all document highlights (both read and write)
16097 let mut all_highlights = Vec::new();
16098
16099 if let Some((_, read_highlights)) = self
16100 .background_highlights
16101 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16102 {
16103 all_highlights.extend(read_highlights.iter());
16104 }
16105
16106 if let Some((_, write_highlights)) = self
16107 .background_highlights
16108 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16109 {
16110 all_highlights.extend(write_highlights.iter());
16111 }
16112
16113 if all_highlights.is_empty() {
16114 return;
16115 }
16116
16117 // Sort highlights by position
16118 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16119
16120 let target_highlight = match direction {
16121 Direction::Next => {
16122 // Find the first highlight after the current position
16123 all_highlights
16124 .iter()
16125 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16126 }
16127 Direction::Prev => {
16128 // Find the last highlight before the current position
16129 all_highlights
16130 .iter()
16131 .rev()
16132 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16133 }
16134 };
16135
16136 if let Some(highlight) = target_highlight {
16137 let destination = highlight.start.to_point(buffer);
16138 let autoscroll = Autoscroll::center();
16139
16140 self.unfold_ranges(&[destination..destination], false, false, cx);
16141 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16142 s.select_ranges([destination..destination]);
16143 });
16144 }
16145 }
16146
16147 fn go_to_line<T: 'static>(
16148 &mut self,
16149 position: Anchor,
16150 highlight_color: Option<Hsla>,
16151 window: &mut Window,
16152 cx: &mut Context<Self>,
16153 ) {
16154 let snapshot = self.snapshot(window, cx).display_snapshot;
16155 let position = position.to_point(&snapshot.buffer_snapshot);
16156 let start = snapshot
16157 .buffer_snapshot
16158 .clip_point(Point::new(position.row, 0), Bias::Left);
16159 let end = start + Point::new(1, 0);
16160 let start = snapshot.buffer_snapshot.anchor_before(start);
16161 let end = snapshot.buffer_snapshot.anchor_before(end);
16162
16163 self.highlight_rows::<T>(
16164 start..end,
16165 highlight_color
16166 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16167 Default::default(),
16168 cx,
16169 );
16170
16171 if self.buffer.read(cx).is_singleton() {
16172 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16173 }
16174 }
16175
16176 pub fn go_to_definition(
16177 &mut self,
16178 _: &GoToDefinition,
16179 window: &mut Window,
16180 cx: &mut Context<Self>,
16181 ) -> Task<Result<Navigated>> {
16182 let definition =
16183 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16184 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16185 cx.spawn_in(window, async move |editor, cx| {
16186 if definition.await? == Navigated::Yes {
16187 return Ok(Navigated::Yes);
16188 }
16189 match fallback_strategy {
16190 GoToDefinitionFallback::None => Ok(Navigated::No),
16191 GoToDefinitionFallback::FindAllReferences => {
16192 match editor.update_in(cx, |editor, window, cx| {
16193 editor.find_all_references(&FindAllReferences, window, cx)
16194 })? {
16195 Some(references) => references.await,
16196 None => Ok(Navigated::No),
16197 }
16198 }
16199 }
16200 })
16201 }
16202
16203 pub fn go_to_declaration(
16204 &mut self,
16205 _: &GoToDeclaration,
16206 window: &mut Window,
16207 cx: &mut Context<Self>,
16208 ) -> Task<Result<Navigated>> {
16209 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16210 }
16211
16212 pub fn go_to_declaration_split(
16213 &mut self,
16214 _: &GoToDeclaration,
16215 window: &mut Window,
16216 cx: &mut Context<Self>,
16217 ) -> Task<Result<Navigated>> {
16218 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16219 }
16220
16221 pub fn go_to_implementation(
16222 &mut self,
16223 _: &GoToImplementation,
16224 window: &mut Window,
16225 cx: &mut Context<Self>,
16226 ) -> Task<Result<Navigated>> {
16227 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16228 }
16229
16230 pub fn go_to_implementation_split(
16231 &mut self,
16232 _: &GoToImplementationSplit,
16233 window: &mut Window,
16234 cx: &mut Context<Self>,
16235 ) -> Task<Result<Navigated>> {
16236 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16237 }
16238
16239 pub fn go_to_type_definition(
16240 &mut self,
16241 _: &GoToTypeDefinition,
16242 window: &mut Window,
16243 cx: &mut Context<Self>,
16244 ) -> Task<Result<Navigated>> {
16245 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16246 }
16247
16248 pub fn go_to_definition_split(
16249 &mut self,
16250 _: &GoToDefinitionSplit,
16251 window: &mut Window,
16252 cx: &mut Context<Self>,
16253 ) -> Task<Result<Navigated>> {
16254 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16255 }
16256
16257 pub fn go_to_type_definition_split(
16258 &mut self,
16259 _: &GoToTypeDefinitionSplit,
16260 window: &mut Window,
16261 cx: &mut Context<Self>,
16262 ) -> Task<Result<Navigated>> {
16263 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16264 }
16265
16266 fn go_to_definition_of_kind(
16267 &mut self,
16268 kind: GotoDefinitionKind,
16269 split: bool,
16270 window: &mut Window,
16271 cx: &mut Context<Self>,
16272 ) -> Task<Result<Navigated>> {
16273 let Some(provider) = self.semantics_provider.clone() else {
16274 return Task::ready(Ok(Navigated::No));
16275 };
16276 let head = self.selections.newest::<usize>(cx).head();
16277 let buffer = self.buffer.read(cx);
16278 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16279 return Task::ready(Ok(Navigated::No));
16280 };
16281 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16282 return Task::ready(Ok(Navigated::No));
16283 };
16284
16285 cx.spawn_in(window, async move |editor, cx| {
16286 let Some(definitions) = definitions.await? else {
16287 return Ok(Navigated::No);
16288 };
16289 let navigated = editor
16290 .update_in(cx, |editor, window, cx| {
16291 editor.navigate_to_hover_links(
16292 Some(kind),
16293 definitions
16294 .into_iter()
16295 .filter(|location| {
16296 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16297 })
16298 .map(HoverLink::Text)
16299 .collect::<Vec<_>>(),
16300 split,
16301 window,
16302 cx,
16303 )
16304 })?
16305 .await?;
16306 anyhow::Ok(navigated)
16307 })
16308 }
16309
16310 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16311 let selection = self.selections.newest_anchor();
16312 let head = selection.head();
16313 let tail = selection.tail();
16314
16315 let Some((buffer, start_position)) =
16316 self.buffer.read(cx).text_anchor_for_position(head, cx)
16317 else {
16318 return;
16319 };
16320
16321 let end_position = if head != tail {
16322 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16323 return;
16324 };
16325 Some(pos)
16326 } else {
16327 None
16328 };
16329
16330 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16331 let url = if let Some(end_pos) = end_position {
16332 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16333 } else {
16334 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16335 };
16336
16337 if let Some(url) = url {
16338 cx.update(|window, cx| {
16339 if parse_zed_link(&url, cx).is_some() {
16340 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16341 } else {
16342 cx.open_url(&url);
16343 }
16344 })?;
16345 }
16346
16347 anyhow::Ok(())
16348 });
16349
16350 url_finder.detach();
16351 }
16352
16353 pub fn open_selected_filename(
16354 &mut self,
16355 _: &OpenSelectedFilename,
16356 window: &mut Window,
16357 cx: &mut Context<Self>,
16358 ) {
16359 let Some(workspace) = self.workspace() else {
16360 return;
16361 };
16362
16363 let position = self.selections.newest_anchor().head();
16364
16365 let Some((buffer, buffer_position)) =
16366 self.buffer.read(cx).text_anchor_for_position(position, cx)
16367 else {
16368 return;
16369 };
16370
16371 let project = self.project.clone();
16372
16373 cx.spawn_in(window, async move |_, cx| {
16374 let result = find_file(&buffer, project, buffer_position, cx).await;
16375
16376 if let Some((_, path)) = result {
16377 workspace
16378 .update_in(cx, |workspace, window, cx| {
16379 workspace.open_resolved_path(path, window, cx)
16380 })?
16381 .await?;
16382 }
16383 anyhow::Ok(())
16384 })
16385 .detach();
16386 }
16387
16388 pub(crate) fn navigate_to_hover_links(
16389 &mut self,
16390 kind: Option<GotoDefinitionKind>,
16391 definitions: Vec<HoverLink>,
16392 split: bool,
16393 window: &mut Window,
16394 cx: &mut Context<Editor>,
16395 ) -> Task<Result<Navigated>> {
16396 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16397 let mut first_url_or_file = None;
16398 let definitions: Vec<_> = definitions
16399 .into_iter()
16400 .filter_map(|def| match def {
16401 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16402 HoverLink::InlayHint(lsp_location, server_id) => {
16403 let computation =
16404 self.compute_target_location(lsp_location, server_id, window, cx);
16405 Some(cx.background_spawn(computation))
16406 }
16407 HoverLink::Url(url) => {
16408 first_url_or_file = Some(Either::Left(url));
16409 None
16410 }
16411 HoverLink::File(path) => {
16412 first_url_or_file = Some(Either::Right(path));
16413 None
16414 }
16415 })
16416 .collect();
16417
16418 let workspace = self.workspace();
16419
16420 cx.spawn_in(window, async move |editor, cx| {
16421 let locations: Vec<Location> = future::join_all(definitions)
16422 .await
16423 .into_iter()
16424 .filter_map(|location| location.transpose())
16425 .collect::<Result<_>>()
16426 .context("location tasks")?;
16427 let mut locations = cx.update(|_, cx| {
16428 locations
16429 .into_iter()
16430 .map(|location| {
16431 let buffer = location.buffer.read(cx);
16432 (location.buffer, location.range.to_point(buffer))
16433 })
16434 .into_group_map()
16435 })?;
16436 let mut num_locations = 0;
16437 for ranges in locations.values_mut() {
16438 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16439 ranges.dedup();
16440 num_locations += ranges.len();
16441 }
16442
16443 if num_locations > 1 {
16444 let Some(workspace) = workspace else {
16445 return Ok(Navigated::No);
16446 };
16447
16448 let tab_kind = match kind {
16449 Some(GotoDefinitionKind::Implementation) => "Implementations",
16450 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16451 Some(GotoDefinitionKind::Declaration) => "Declarations",
16452 Some(GotoDefinitionKind::Type) => "Types",
16453 };
16454 let title = editor
16455 .update_in(cx, |_, _, cx| {
16456 let target = locations
16457 .iter()
16458 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16459 .map(|(buffer, location)| {
16460 buffer
16461 .read(cx)
16462 .text_for_range(location.clone())
16463 .collect::<String>()
16464 })
16465 .filter(|text| !text.contains('\n'))
16466 .unique()
16467 .take(3)
16468 .join(", ");
16469 if target.is_empty() {
16470 tab_kind.to_owned()
16471 } else {
16472 format!("{tab_kind} for {target}")
16473 }
16474 })
16475 .context("buffer title")?;
16476
16477 let opened = workspace
16478 .update_in(cx, |workspace, window, cx| {
16479 Self::open_locations_in_multibuffer(
16480 workspace,
16481 locations,
16482 title,
16483 split,
16484 MultibufferSelectionMode::First,
16485 window,
16486 cx,
16487 )
16488 })
16489 .is_ok();
16490
16491 anyhow::Ok(Navigated::from_bool(opened))
16492 } else if num_locations == 0 {
16493 // If there is one url or file, open it directly
16494 match first_url_or_file {
16495 Some(Either::Left(url)) => {
16496 cx.update(|_, cx| cx.open_url(&url))?;
16497 Ok(Navigated::Yes)
16498 }
16499 Some(Either::Right(path)) => {
16500 let Some(workspace) = workspace else {
16501 return Ok(Navigated::No);
16502 };
16503
16504 workspace
16505 .update_in(cx, |workspace, window, cx| {
16506 workspace.open_resolved_path(path, window, cx)
16507 })?
16508 .await?;
16509 Ok(Navigated::Yes)
16510 }
16511 None => Ok(Navigated::No),
16512 }
16513 } else {
16514 let Some(workspace) = workspace else {
16515 return Ok(Navigated::No);
16516 };
16517
16518 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16519 let target_range = target_ranges.first().unwrap().clone();
16520
16521 editor.update_in(cx, |editor, window, cx| {
16522 let range = target_range.to_point(target_buffer.read(cx));
16523 let range = editor.range_for_match(&range);
16524 let range = collapse_multiline_range(range);
16525
16526 if !split
16527 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16528 {
16529 editor.go_to_singleton_buffer_range(range, window, cx);
16530 } else {
16531 let pane = workspace.read(cx).active_pane().clone();
16532 window.defer(cx, move |window, cx| {
16533 let target_editor: Entity<Self> =
16534 workspace.update(cx, |workspace, cx| {
16535 let pane = if split {
16536 workspace.adjacent_pane(window, cx)
16537 } else {
16538 workspace.active_pane().clone()
16539 };
16540
16541 workspace.open_project_item(
16542 pane,
16543 target_buffer.clone(),
16544 true,
16545 true,
16546 window,
16547 cx,
16548 )
16549 });
16550 target_editor.update(cx, |target_editor, cx| {
16551 // When selecting a definition in a different buffer, disable the nav history
16552 // to avoid creating a history entry at the previous cursor location.
16553 pane.update(cx, |pane, _| pane.disable_history());
16554 target_editor.go_to_singleton_buffer_range(range, window, cx);
16555 pane.update(cx, |pane, _| pane.enable_history());
16556 });
16557 });
16558 }
16559 Navigated::Yes
16560 })
16561 }
16562 })
16563 }
16564
16565 fn compute_target_location(
16566 &self,
16567 lsp_location: lsp::Location,
16568 server_id: LanguageServerId,
16569 window: &mut Window,
16570 cx: &mut Context<Self>,
16571 ) -> Task<anyhow::Result<Option<Location>>> {
16572 let Some(project) = self.project.clone() else {
16573 return Task::ready(Ok(None));
16574 };
16575
16576 cx.spawn_in(window, async move |editor, cx| {
16577 let location_task = editor.update(cx, |_, cx| {
16578 project.update(cx, |project, cx| {
16579 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16580 })
16581 })?;
16582 let location = Some({
16583 let target_buffer_handle = location_task.await.context("open local buffer")?;
16584 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16585 let target_start = target_buffer
16586 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16587 let target_end = target_buffer
16588 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16589 target_buffer.anchor_after(target_start)
16590 ..target_buffer.anchor_before(target_end)
16591 })?;
16592 Location {
16593 buffer: target_buffer_handle,
16594 range,
16595 }
16596 });
16597 Ok(location)
16598 })
16599 }
16600
16601 pub fn find_all_references(
16602 &mut self,
16603 _: &FindAllReferences,
16604 window: &mut Window,
16605 cx: &mut Context<Self>,
16606 ) -> Option<Task<Result<Navigated>>> {
16607 let selection = self.selections.newest::<usize>(cx);
16608 let multi_buffer = self.buffer.read(cx);
16609 let head = selection.head();
16610
16611 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16612 let head_anchor = multi_buffer_snapshot.anchor_at(
16613 head,
16614 if head < selection.tail() {
16615 Bias::Right
16616 } else {
16617 Bias::Left
16618 },
16619 );
16620
16621 match self
16622 .find_all_references_task_sources
16623 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16624 {
16625 Ok(_) => {
16626 log::info!(
16627 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16628 );
16629 return None;
16630 }
16631 Err(i) => {
16632 self.find_all_references_task_sources.insert(i, head_anchor);
16633 }
16634 }
16635
16636 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16637 let workspace = self.workspace()?;
16638 let project = workspace.read(cx).project().clone();
16639 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16640 Some(cx.spawn_in(window, async move |editor, cx| {
16641 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16642 if let Ok(i) = editor
16643 .find_all_references_task_sources
16644 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16645 {
16646 editor.find_all_references_task_sources.remove(i);
16647 }
16648 });
16649
16650 let Some(locations) = references.await? else {
16651 return anyhow::Ok(Navigated::No);
16652 };
16653 let mut locations = cx.update(|_, cx| {
16654 locations
16655 .into_iter()
16656 .map(|location| {
16657 let buffer = location.buffer.read(cx);
16658 (location.buffer, location.range.to_point(buffer))
16659 })
16660 .into_group_map()
16661 })?;
16662 if locations.is_empty() {
16663 return anyhow::Ok(Navigated::No);
16664 }
16665 for ranges in locations.values_mut() {
16666 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16667 ranges.dedup();
16668 }
16669
16670 workspace.update_in(cx, |workspace, window, cx| {
16671 let target = locations
16672 .iter()
16673 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16674 .map(|(buffer, location)| {
16675 buffer
16676 .read(cx)
16677 .text_for_range(location.clone())
16678 .collect::<String>()
16679 })
16680 .filter(|text| !text.contains('\n'))
16681 .unique()
16682 .take(3)
16683 .join(", ");
16684 let title = if target.is_empty() {
16685 "References".to_owned()
16686 } else {
16687 format!("References to {target}")
16688 };
16689 Self::open_locations_in_multibuffer(
16690 workspace,
16691 locations,
16692 title,
16693 false,
16694 MultibufferSelectionMode::First,
16695 window,
16696 cx,
16697 );
16698 Navigated::Yes
16699 })
16700 }))
16701 }
16702
16703 /// Opens a multibuffer with the given project locations in it
16704 pub fn open_locations_in_multibuffer(
16705 workspace: &mut Workspace,
16706 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16707 title: String,
16708 split: bool,
16709 multibuffer_selection_mode: MultibufferSelectionMode,
16710 window: &mut Window,
16711 cx: &mut Context<Workspace>,
16712 ) {
16713 if locations.is_empty() {
16714 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16715 return;
16716 }
16717
16718 let capability = workspace.project().read(cx).capability();
16719 let mut ranges = <Vec<Range<Anchor>>>::new();
16720
16721 // a key to find existing multibuffer editors with the same set of locations
16722 // to prevent us from opening more and more multibuffer tabs for searches and the like
16723 let mut key = (title.clone(), vec![]);
16724 let excerpt_buffer = cx.new(|cx| {
16725 let key = &mut key.1;
16726 let mut multibuffer = MultiBuffer::new(capability);
16727 for (buffer, mut ranges_for_buffer) in locations {
16728 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16729 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16730 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16731 PathKey::for_buffer(&buffer, cx),
16732 buffer.clone(),
16733 ranges_for_buffer,
16734 multibuffer_context_lines(cx),
16735 cx,
16736 );
16737 ranges.extend(new_ranges)
16738 }
16739
16740 multibuffer.with_title(title)
16741 });
16742 let existing = workspace.active_pane().update(cx, |pane, cx| {
16743 pane.items()
16744 .filter_map(|item| item.downcast::<Editor>())
16745 .find(|editor| {
16746 editor
16747 .read(cx)
16748 .lookup_key
16749 .as_ref()
16750 .and_then(|it| {
16751 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16752 })
16753 .is_some_and(|it| *it == key)
16754 })
16755 });
16756 let editor = existing.unwrap_or_else(|| {
16757 cx.new(|cx| {
16758 let mut editor = Editor::for_multibuffer(
16759 excerpt_buffer,
16760 Some(workspace.project().clone()),
16761 window,
16762 cx,
16763 );
16764 editor.lookup_key = Some(Box::new(key));
16765 editor
16766 })
16767 });
16768 editor.update(cx, |editor, cx| {
16769 match multibuffer_selection_mode {
16770 MultibufferSelectionMode::First => {
16771 if let Some(first_range) = ranges.first() {
16772 editor.change_selections(
16773 SelectionEffects::no_scroll(),
16774 window,
16775 cx,
16776 |selections| {
16777 selections.clear_disjoint();
16778 selections
16779 .select_anchor_ranges(std::iter::once(first_range.clone()));
16780 },
16781 );
16782 }
16783 editor.highlight_background::<Self>(
16784 &ranges,
16785 |theme| theme.colors().editor_highlighted_line_background,
16786 cx,
16787 );
16788 }
16789 MultibufferSelectionMode::All => {
16790 editor.change_selections(
16791 SelectionEffects::no_scroll(),
16792 window,
16793 cx,
16794 |selections| {
16795 selections.clear_disjoint();
16796 selections.select_anchor_ranges(ranges);
16797 },
16798 );
16799 }
16800 }
16801 editor.register_buffers_with_language_servers(cx);
16802 });
16803
16804 let item = Box::new(editor);
16805 let item_id = item.item_id();
16806
16807 if split {
16808 workspace.split_item(SplitDirection::Right, item, window, cx);
16809 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16810 let (preview_item_id, preview_item_idx) =
16811 workspace.active_pane().read_with(cx, |pane, _| {
16812 (pane.preview_item_id(), pane.preview_item_idx())
16813 });
16814
16815 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16816
16817 if let Some(preview_item_id) = preview_item_id {
16818 workspace.active_pane().update(cx, |pane, cx| {
16819 pane.remove_item(preview_item_id, false, false, window, cx);
16820 });
16821 }
16822 } else {
16823 workspace.add_item_to_active_pane(item, None, true, window, cx);
16824 }
16825 workspace.active_pane().update(cx, |pane, cx| {
16826 pane.set_preview_item_id(Some(item_id), cx);
16827 });
16828 }
16829
16830 pub fn rename(
16831 &mut self,
16832 _: &Rename,
16833 window: &mut Window,
16834 cx: &mut Context<Self>,
16835 ) -> Option<Task<Result<()>>> {
16836 use language::ToOffset as _;
16837
16838 let provider = self.semantics_provider.clone()?;
16839 let selection = self.selections.newest_anchor().clone();
16840 let (cursor_buffer, cursor_buffer_position) = self
16841 .buffer
16842 .read(cx)
16843 .text_anchor_for_position(selection.head(), cx)?;
16844 let (tail_buffer, cursor_buffer_position_end) = self
16845 .buffer
16846 .read(cx)
16847 .text_anchor_for_position(selection.tail(), cx)?;
16848 if tail_buffer != cursor_buffer {
16849 return None;
16850 }
16851
16852 let snapshot = cursor_buffer.read(cx).snapshot();
16853 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16854 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16855 let prepare_rename = provider
16856 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16857 .unwrap_or_else(|| Task::ready(Ok(None)));
16858 drop(snapshot);
16859
16860 Some(cx.spawn_in(window, async move |this, cx| {
16861 let rename_range = if let Some(range) = prepare_rename.await? {
16862 Some(range)
16863 } else {
16864 this.update(cx, |this, cx| {
16865 let buffer = this.buffer.read(cx).snapshot(cx);
16866 let mut buffer_highlights = this
16867 .document_highlights_for_position(selection.head(), &buffer)
16868 .filter(|highlight| {
16869 highlight.start.excerpt_id == selection.head().excerpt_id
16870 && highlight.end.excerpt_id == selection.head().excerpt_id
16871 });
16872 buffer_highlights
16873 .next()
16874 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16875 })?
16876 };
16877 if let Some(rename_range) = rename_range {
16878 this.update_in(cx, |this, window, cx| {
16879 let snapshot = cursor_buffer.read(cx).snapshot();
16880 let rename_buffer_range = rename_range.to_offset(&snapshot);
16881 let cursor_offset_in_rename_range =
16882 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16883 let cursor_offset_in_rename_range_end =
16884 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16885
16886 this.take_rename(false, window, cx);
16887 let buffer = this.buffer.read(cx).read(cx);
16888 let cursor_offset = selection.head().to_offset(&buffer);
16889 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16890 let rename_end = rename_start + rename_buffer_range.len();
16891 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16892 let mut old_highlight_id = None;
16893 let old_name: Arc<str> = buffer
16894 .chunks(rename_start..rename_end, true)
16895 .map(|chunk| {
16896 if old_highlight_id.is_none() {
16897 old_highlight_id = chunk.syntax_highlight_id;
16898 }
16899 chunk.text
16900 })
16901 .collect::<String>()
16902 .into();
16903
16904 drop(buffer);
16905
16906 // Position the selection in the rename editor so that it matches the current selection.
16907 this.show_local_selections = false;
16908 let rename_editor = cx.new(|cx| {
16909 let mut editor = Editor::single_line(window, cx);
16910 editor.buffer.update(cx, |buffer, cx| {
16911 buffer.edit([(0..0, old_name.clone())], None, cx)
16912 });
16913 let rename_selection_range = match cursor_offset_in_rename_range
16914 .cmp(&cursor_offset_in_rename_range_end)
16915 {
16916 Ordering::Equal => {
16917 editor.select_all(&SelectAll, window, cx);
16918 return editor;
16919 }
16920 Ordering::Less => {
16921 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16922 }
16923 Ordering::Greater => {
16924 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16925 }
16926 };
16927 if rename_selection_range.end > old_name.len() {
16928 editor.select_all(&SelectAll, window, cx);
16929 } else {
16930 editor.change_selections(Default::default(), window, cx, |s| {
16931 s.select_ranges([rename_selection_range]);
16932 });
16933 }
16934 editor
16935 });
16936 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16937 if e == &EditorEvent::Focused {
16938 cx.emit(EditorEvent::FocusedIn)
16939 }
16940 })
16941 .detach();
16942
16943 let write_highlights =
16944 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16945 let read_highlights =
16946 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16947 let ranges = write_highlights
16948 .iter()
16949 .flat_map(|(_, ranges)| ranges.iter())
16950 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16951 .cloned()
16952 .collect();
16953
16954 this.highlight_text::<Rename>(
16955 ranges,
16956 HighlightStyle {
16957 fade_out: Some(0.6),
16958 ..Default::default()
16959 },
16960 cx,
16961 );
16962 let rename_focus_handle = rename_editor.focus_handle(cx);
16963 window.focus(&rename_focus_handle);
16964 let block_id = this.insert_blocks(
16965 [BlockProperties {
16966 style: BlockStyle::Flex,
16967 placement: BlockPlacement::Below(range.start),
16968 height: Some(1),
16969 render: Arc::new({
16970 let rename_editor = rename_editor.clone();
16971 move |cx: &mut BlockContext| {
16972 let mut text_style = cx.editor_style.text.clone();
16973 if let Some(highlight_style) = old_highlight_id
16974 .and_then(|h| h.style(&cx.editor_style.syntax))
16975 {
16976 text_style = text_style.highlight(highlight_style);
16977 }
16978 div()
16979 .block_mouse_except_scroll()
16980 .pl(cx.anchor_x)
16981 .child(EditorElement::new(
16982 &rename_editor,
16983 EditorStyle {
16984 background: cx.theme().system().transparent,
16985 local_player: cx.editor_style.local_player,
16986 text: text_style,
16987 scrollbar_width: cx.editor_style.scrollbar_width,
16988 syntax: cx.editor_style.syntax.clone(),
16989 status: cx.editor_style.status.clone(),
16990 inlay_hints_style: HighlightStyle {
16991 font_weight: Some(FontWeight::BOLD),
16992 ..make_inlay_hints_style(cx.app)
16993 },
16994 edit_prediction_styles: make_suggestion_styles(
16995 cx.app,
16996 ),
16997 ..EditorStyle::default()
16998 },
16999 ))
17000 .into_any_element()
17001 }
17002 }),
17003 priority: 0,
17004 }],
17005 Some(Autoscroll::fit()),
17006 cx,
17007 )[0];
17008 this.pending_rename = Some(RenameState {
17009 range,
17010 old_name,
17011 editor: rename_editor,
17012 block_id,
17013 });
17014 })?;
17015 }
17016
17017 Ok(())
17018 }))
17019 }
17020
17021 pub fn confirm_rename(
17022 &mut self,
17023 _: &ConfirmRename,
17024 window: &mut Window,
17025 cx: &mut Context<Self>,
17026 ) -> Option<Task<Result<()>>> {
17027 let rename = self.take_rename(false, window, cx)?;
17028 let workspace = self.workspace()?.downgrade();
17029 let (buffer, start) = self
17030 .buffer
17031 .read(cx)
17032 .text_anchor_for_position(rename.range.start, cx)?;
17033 let (end_buffer, _) = self
17034 .buffer
17035 .read(cx)
17036 .text_anchor_for_position(rename.range.end, cx)?;
17037 if buffer != end_buffer {
17038 return None;
17039 }
17040
17041 let old_name = rename.old_name;
17042 let new_name = rename.editor.read(cx).text(cx);
17043
17044 let rename = self.semantics_provider.as_ref()?.perform_rename(
17045 &buffer,
17046 start,
17047 new_name.clone(),
17048 cx,
17049 )?;
17050
17051 Some(cx.spawn_in(window, async move |editor, cx| {
17052 let project_transaction = rename.await?;
17053 Self::open_project_transaction(
17054 &editor,
17055 workspace,
17056 project_transaction,
17057 format!("Rename: {} → {}", old_name, new_name),
17058 cx,
17059 )
17060 .await?;
17061
17062 editor.update(cx, |editor, cx| {
17063 editor.refresh_document_highlights(cx);
17064 })?;
17065 Ok(())
17066 }))
17067 }
17068
17069 fn take_rename(
17070 &mut self,
17071 moving_cursor: bool,
17072 window: &mut Window,
17073 cx: &mut Context<Self>,
17074 ) -> Option<RenameState> {
17075 let rename = self.pending_rename.take()?;
17076 if rename.editor.focus_handle(cx).is_focused(window) {
17077 window.focus(&self.focus_handle);
17078 }
17079
17080 self.remove_blocks(
17081 [rename.block_id].into_iter().collect(),
17082 Some(Autoscroll::fit()),
17083 cx,
17084 );
17085 self.clear_highlights::<Rename>(cx);
17086 self.show_local_selections = true;
17087
17088 if moving_cursor {
17089 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17090 editor.selections.newest::<usize>(cx).head()
17091 });
17092
17093 // Update the selection to match the position of the selection inside
17094 // the rename editor.
17095 let snapshot = self.buffer.read(cx).read(cx);
17096 let rename_range = rename.range.to_offset(&snapshot);
17097 let cursor_in_editor = snapshot
17098 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17099 .min(rename_range.end);
17100 drop(snapshot);
17101
17102 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17103 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17104 });
17105 } else {
17106 self.refresh_document_highlights(cx);
17107 }
17108
17109 Some(rename)
17110 }
17111
17112 pub fn pending_rename(&self) -> Option<&RenameState> {
17113 self.pending_rename.as_ref()
17114 }
17115
17116 fn format(
17117 &mut self,
17118 _: &Format,
17119 window: &mut Window,
17120 cx: &mut Context<Self>,
17121 ) -> Option<Task<Result<()>>> {
17122 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17123
17124 let project = match &self.project {
17125 Some(project) => project.clone(),
17126 None => return None,
17127 };
17128
17129 Some(self.perform_format(
17130 project,
17131 FormatTrigger::Manual,
17132 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17133 window,
17134 cx,
17135 ))
17136 }
17137
17138 fn format_selections(
17139 &mut self,
17140 _: &FormatSelections,
17141 window: &mut Window,
17142 cx: &mut Context<Self>,
17143 ) -> Option<Task<Result<()>>> {
17144 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17145
17146 let project = match &self.project {
17147 Some(project) => project.clone(),
17148 None => return None,
17149 };
17150
17151 let ranges = self
17152 .selections
17153 .all_adjusted(cx)
17154 .into_iter()
17155 .map(|selection| selection.range())
17156 .collect_vec();
17157
17158 Some(self.perform_format(
17159 project,
17160 FormatTrigger::Manual,
17161 FormatTarget::Ranges(ranges),
17162 window,
17163 cx,
17164 ))
17165 }
17166
17167 fn perform_format(
17168 &mut self,
17169 project: Entity<Project>,
17170 trigger: FormatTrigger,
17171 target: FormatTarget,
17172 window: &mut Window,
17173 cx: &mut Context<Self>,
17174 ) -> Task<Result<()>> {
17175 let buffer = self.buffer.clone();
17176 let (buffers, target) = match target {
17177 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17178 FormatTarget::Ranges(selection_ranges) => {
17179 let multi_buffer = buffer.read(cx);
17180 let snapshot = multi_buffer.read(cx);
17181 let mut buffers = HashSet::default();
17182 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17183 BTreeMap::new();
17184 for selection_range in selection_ranges {
17185 for (buffer, buffer_range, _) in
17186 snapshot.range_to_buffer_ranges(selection_range)
17187 {
17188 let buffer_id = buffer.remote_id();
17189 let start = buffer.anchor_before(buffer_range.start);
17190 let end = buffer.anchor_after(buffer_range.end);
17191 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17192 buffer_id_to_ranges
17193 .entry(buffer_id)
17194 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17195 .or_insert_with(|| vec![start..end]);
17196 }
17197 }
17198 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17199 }
17200 };
17201
17202 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17203 let selections_prev = transaction_id_prev
17204 .and_then(|transaction_id_prev| {
17205 // default to selections as they were after the last edit, if we have them,
17206 // instead of how they are now.
17207 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17208 // will take you back to where you made the last edit, instead of staying where you scrolled
17209 self.selection_history
17210 .transaction(transaction_id_prev)
17211 .map(|t| t.0.clone())
17212 })
17213 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17214
17215 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17216 let format = project.update(cx, |project, cx| {
17217 project.format(buffers, target, true, trigger, cx)
17218 });
17219
17220 cx.spawn_in(window, async move |editor, cx| {
17221 let transaction = futures::select_biased! {
17222 transaction = format.log_err().fuse() => transaction,
17223 () = timeout => {
17224 log::warn!("timed out waiting for formatting");
17225 None
17226 }
17227 };
17228
17229 buffer
17230 .update(cx, |buffer, cx| {
17231 if let Some(transaction) = transaction
17232 && !buffer.is_singleton()
17233 {
17234 buffer.push_transaction(&transaction.0, cx);
17235 }
17236 cx.notify();
17237 })
17238 .ok();
17239
17240 if let Some(transaction_id_now) =
17241 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17242 {
17243 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17244 if has_new_transaction {
17245 _ = editor.update(cx, |editor, _| {
17246 editor
17247 .selection_history
17248 .insert_transaction(transaction_id_now, selections_prev);
17249 });
17250 }
17251 }
17252
17253 Ok(())
17254 })
17255 }
17256
17257 fn organize_imports(
17258 &mut self,
17259 _: &OrganizeImports,
17260 window: &mut Window,
17261 cx: &mut Context<Self>,
17262 ) -> Option<Task<Result<()>>> {
17263 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17264 let project = match &self.project {
17265 Some(project) => project.clone(),
17266 None => return None,
17267 };
17268 Some(self.perform_code_action_kind(
17269 project,
17270 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17271 window,
17272 cx,
17273 ))
17274 }
17275
17276 fn perform_code_action_kind(
17277 &mut self,
17278 project: Entity<Project>,
17279 kind: CodeActionKind,
17280 window: &mut Window,
17281 cx: &mut Context<Self>,
17282 ) -> Task<Result<()>> {
17283 let buffer = self.buffer.clone();
17284 let buffers = buffer.read(cx).all_buffers();
17285 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17286 let apply_action = project.update(cx, |project, cx| {
17287 project.apply_code_action_kind(buffers, kind, true, cx)
17288 });
17289 cx.spawn_in(window, async move |_, cx| {
17290 let transaction = futures::select_biased! {
17291 () = timeout => {
17292 log::warn!("timed out waiting for executing code action");
17293 None
17294 }
17295 transaction = apply_action.log_err().fuse() => transaction,
17296 };
17297 buffer
17298 .update(cx, |buffer, cx| {
17299 // check if we need this
17300 if let Some(transaction) = transaction
17301 && !buffer.is_singleton()
17302 {
17303 buffer.push_transaction(&transaction.0, cx);
17304 }
17305 cx.notify();
17306 })
17307 .ok();
17308 Ok(())
17309 })
17310 }
17311
17312 pub fn restart_language_server(
17313 &mut self,
17314 _: &RestartLanguageServer,
17315 _: &mut Window,
17316 cx: &mut Context<Self>,
17317 ) {
17318 if let Some(project) = self.project.clone() {
17319 self.buffer.update(cx, |multi_buffer, cx| {
17320 project.update(cx, |project, cx| {
17321 project.restart_language_servers_for_buffers(
17322 multi_buffer.all_buffers().into_iter().collect(),
17323 HashSet::default(),
17324 cx,
17325 );
17326 });
17327 })
17328 }
17329 }
17330
17331 pub fn stop_language_server(
17332 &mut self,
17333 _: &StopLanguageServer,
17334 _: &mut Window,
17335 cx: &mut Context<Self>,
17336 ) {
17337 if let Some(project) = self.project.clone() {
17338 self.buffer.update(cx, |multi_buffer, cx| {
17339 project.update(cx, |project, cx| {
17340 project.stop_language_servers_for_buffers(
17341 multi_buffer.all_buffers().into_iter().collect(),
17342 HashSet::default(),
17343 cx,
17344 );
17345 cx.emit(project::Event::RefreshInlayHints);
17346 });
17347 });
17348 }
17349 }
17350
17351 fn cancel_language_server_work(
17352 workspace: &mut Workspace,
17353 _: &actions::CancelLanguageServerWork,
17354 _: &mut Window,
17355 cx: &mut Context<Workspace>,
17356 ) {
17357 let project = workspace.project();
17358 let buffers = workspace
17359 .active_item(cx)
17360 .and_then(|item| item.act_as::<Editor>(cx))
17361 .map_or(HashSet::default(), |editor| {
17362 editor.read(cx).buffer.read(cx).all_buffers()
17363 });
17364 project.update(cx, |project, cx| {
17365 project.cancel_language_server_work_for_buffers(buffers, cx);
17366 });
17367 }
17368
17369 fn show_character_palette(
17370 &mut self,
17371 _: &ShowCharacterPalette,
17372 window: &mut Window,
17373 _: &mut Context<Self>,
17374 ) {
17375 window.show_character_palette();
17376 }
17377
17378 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17379 if !self.diagnostics_enabled() {
17380 return;
17381 }
17382
17383 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17384 let buffer = self.buffer.read(cx).snapshot(cx);
17385 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17386 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17387 let is_valid = buffer
17388 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17389 .any(|entry| {
17390 entry.diagnostic.is_primary
17391 && !entry.range.is_empty()
17392 && entry.range.start == primary_range_start
17393 && entry.diagnostic.message == active_diagnostics.active_message
17394 });
17395
17396 if !is_valid {
17397 self.dismiss_diagnostics(cx);
17398 }
17399 }
17400 }
17401
17402 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17403 match &self.active_diagnostics {
17404 ActiveDiagnostic::Group(group) => Some(group),
17405 _ => None,
17406 }
17407 }
17408
17409 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17410 if !self.diagnostics_enabled() {
17411 return;
17412 }
17413 self.dismiss_diagnostics(cx);
17414 self.active_diagnostics = ActiveDiagnostic::All;
17415 }
17416
17417 fn activate_diagnostics(
17418 &mut self,
17419 buffer_id: BufferId,
17420 diagnostic: DiagnosticEntry<usize>,
17421 window: &mut Window,
17422 cx: &mut Context<Self>,
17423 ) {
17424 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17425 return;
17426 }
17427 self.dismiss_diagnostics(cx);
17428 let snapshot = self.snapshot(window, cx);
17429 let buffer = self.buffer.read(cx).snapshot(cx);
17430 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17431 return;
17432 };
17433
17434 let diagnostic_group = buffer
17435 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17436 .collect::<Vec<_>>();
17437
17438 let blocks =
17439 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17440
17441 let blocks = self.display_map.update(cx, |display_map, cx| {
17442 display_map.insert_blocks(blocks, cx).into_iter().collect()
17443 });
17444 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17445 active_range: buffer.anchor_before(diagnostic.range.start)
17446 ..buffer.anchor_after(diagnostic.range.end),
17447 active_message: diagnostic.diagnostic.message.clone(),
17448 group_id: diagnostic.diagnostic.group_id,
17449 blocks,
17450 });
17451 cx.notify();
17452 }
17453
17454 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17455 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17456 return;
17457 };
17458
17459 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17460 if let ActiveDiagnostic::Group(group) = prev {
17461 self.display_map.update(cx, |display_map, cx| {
17462 display_map.remove_blocks(group.blocks, cx);
17463 });
17464 cx.notify();
17465 }
17466 }
17467
17468 /// Disable inline diagnostics rendering for this editor.
17469 pub fn disable_inline_diagnostics(&mut self) {
17470 self.inline_diagnostics_enabled = false;
17471 self.inline_diagnostics_update = Task::ready(());
17472 self.inline_diagnostics.clear();
17473 }
17474
17475 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17476 self.diagnostics_enabled = false;
17477 self.dismiss_diagnostics(cx);
17478 self.inline_diagnostics_update = Task::ready(());
17479 self.inline_diagnostics.clear();
17480 }
17481
17482 pub fn disable_word_completions(&mut self) {
17483 self.word_completions_enabled = false;
17484 }
17485
17486 pub fn diagnostics_enabled(&self) -> bool {
17487 self.diagnostics_enabled && self.mode.is_full()
17488 }
17489
17490 pub fn inline_diagnostics_enabled(&self) -> bool {
17491 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17492 }
17493
17494 pub fn show_inline_diagnostics(&self) -> bool {
17495 self.show_inline_diagnostics
17496 }
17497
17498 pub fn toggle_inline_diagnostics(
17499 &mut self,
17500 _: &ToggleInlineDiagnostics,
17501 window: &mut Window,
17502 cx: &mut Context<Editor>,
17503 ) {
17504 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17505 self.refresh_inline_diagnostics(false, window, cx);
17506 }
17507
17508 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17509 self.diagnostics_max_severity = severity;
17510 self.display_map.update(cx, |display_map, _| {
17511 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17512 });
17513 }
17514
17515 pub fn toggle_diagnostics(
17516 &mut self,
17517 _: &ToggleDiagnostics,
17518 window: &mut Window,
17519 cx: &mut Context<Editor>,
17520 ) {
17521 if !self.diagnostics_enabled() {
17522 return;
17523 }
17524
17525 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17526 EditorSettings::get_global(cx)
17527 .diagnostics_max_severity
17528 .filter(|severity| severity != &DiagnosticSeverity::Off)
17529 .unwrap_or(DiagnosticSeverity::Hint)
17530 } else {
17531 DiagnosticSeverity::Off
17532 };
17533 self.set_max_diagnostics_severity(new_severity, cx);
17534 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17535 self.active_diagnostics = ActiveDiagnostic::None;
17536 self.inline_diagnostics_update = Task::ready(());
17537 self.inline_diagnostics.clear();
17538 } else {
17539 self.refresh_inline_diagnostics(false, window, cx);
17540 }
17541
17542 cx.notify();
17543 }
17544
17545 pub fn toggle_minimap(
17546 &mut self,
17547 _: &ToggleMinimap,
17548 window: &mut Window,
17549 cx: &mut Context<Editor>,
17550 ) {
17551 if self.supports_minimap(cx) {
17552 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17553 }
17554 }
17555
17556 fn refresh_inline_diagnostics(
17557 &mut self,
17558 debounce: bool,
17559 window: &mut Window,
17560 cx: &mut Context<Self>,
17561 ) {
17562 let max_severity = ProjectSettings::get_global(cx)
17563 .diagnostics
17564 .inline
17565 .max_severity
17566 .unwrap_or(self.diagnostics_max_severity);
17567
17568 if !self.inline_diagnostics_enabled()
17569 || !self.show_inline_diagnostics
17570 || max_severity == DiagnosticSeverity::Off
17571 {
17572 self.inline_diagnostics_update = Task::ready(());
17573 self.inline_diagnostics.clear();
17574 return;
17575 }
17576
17577 let debounce_ms = ProjectSettings::get_global(cx)
17578 .diagnostics
17579 .inline
17580 .update_debounce_ms;
17581 let debounce = if debounce && debounce_ms > 0 {
17582 Some(Duration::from_millis(debounce_ms))
17583 } else {
17584 None
17585 };
17586 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17587 if let Some(debounce) = debounce {
17588 cx.background_executor().timer(debounce).await;
17589 }
17590 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17591 editor
17592 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17593 .ok()
17594 }) else {
17595 return;
17596 };
17597
17598 let new_inline_diagnostics = cx
17599 .background_spawn(async move {
17600 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17601 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17602 let message = diagnostic_entry
17603 .diagnostic
17604 .message
17605 .split_once('\n')
17606 .map(|(line, _)| line)
17607 .map(SharedString::new)
17608 .unwrap_or_else(|| {
17609 SharedString::from(diagnostic_entry.diagnostic.message)
17610 });
17611 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17612 let (Ok(i) | Err(i)) = inline_diagnostics
17613 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17614 inline_diagnostics.insert(
17615 i,
17616 (
17617 start_anchor,
17618 InlineDiagnostic {
17619 message,
17620 group_id: diagnostic_entry.diagnostic.group_id,
17621 start: diagnostic_entry.range.start.to_point(&snapshot),
17622 is_primary: diagnostic_entry.diagnostic.is_primary,
17623 severity: diagnostic_entry.diagnostic.severity,
17624 },
17625 ),
17626 );
17627 }
17628 inline_diagnostics
17629 })
17630 .await;
17631
17632 editor
17633 .update(cx, |editor, cx| {
17634 editor.inline_diagnostics = new_inline_diagnostics;
17635 cx.notify();
17636 })
17637 .ok();
17638 });
17639 }
17640
17641 fn pull_diagnostics(
17642 &mut self,
17643 buffer_id: Option<BufferId>,
17644 window: &Window,
17645 cx: &mut Context<Self>,
17646 ) -> Option<()> {
17647 if !self.mode().is_full() {
17648 return None;
17649 }
17650 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17651 .diagnostics
17652 .lsp_pull_diagnostics;
17653 if !pull_diagnostics_settings.enabled {
17654 return None;
17655 }
17656 let project = self.project()?.downgrade();
17657 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17658 let mut buffers = self.buffer.read(cx).all_buffers();
17659 if let Some(buffer_id) = buffer_id {
17660 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17661 }
17662
17663 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17664 cx.background_executor().timer(debounce).await;
17665
17666 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17667 buffers
17668 .into_iter()
17669 .filter_map(|buffer| {
17670 project
17671 .update(cx, |project, cx| {
17672 project.lsp_store().update(cx, |lsp_store, cx| {
17673 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17674 })
17675 })
17676 .ok()
17677 })
17678 .collect::<FuturesUnordered<_>>()
17679 }) else {
17680 return;
17681 };
17682
17683 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17684 match pull_task {
17685 Ok(()) => {
17686 if editor
17687 .update_in(cx, |editor, window, cx| {
17688 editor.update_diagnostics_state(window, cx);
17689 })
17690 .is_err()
17691 {
17692 return;
17693 }
17694 }
17695 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17696 }
17697 }
17698 });
17699
17700 Some(())
17701 }
17702
17703 pub fn set_selections_from_remote(
17704 &mut self,
17705 selections: Vec<Selection<Anchor>>,
17706 pending_selection: Option<Selection<Anchor>>,
17707 window: &mut Window,
17708 cx: &mut Context<Self>,
17709 ) {
17710 let old_cursor_position = self.selections.newest_anchor().head();
17711 self.selections.change_with(cx, |s| {
17712 s.select_anchors(selections);
17713 if let Some(pending_selection) = pending_selection {
17714 s.set_pending(pending_selection, SelectMode::Character);
17715 } else {
17716 s.clear_pending();
17717 }
17718 });
17719 self.selections_did_change(
17720 false,
17721 &old_cursor_position,
17722 SelectionEffects::default(),
17723 window,
17724 cx,
17725 );
17726 }
17727
17728 pub fn transact(
17729 &mut self,
17730 window: &mut Window,
17731 cx: &mut Context<Self>,
17732 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17733 ) -> Option<TransactionId> {
17734 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17735 this.start_transaction_at(Instant::now(), window, cx);
17736 update(this, window, cx);
17737 this.end_transaction_at(Instant::now(), cx)
17738 })
17739 }
17740
17741 pub fn start_transaction_at(
17742 &mut self,
17743 now: Instant,
17744 window: &mut Window,
17745 cx: &mut Context<Self>,
17746 ) -> Option<TransactionId> {
17747 self.end_selection(window, cx);
17748 if let Some(tx_id) = self
17749 .buffer
17750 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17751 {
17752 self.selection_history
17753 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17754 cx.emit(EditorEvent::TransactionBegun {
17755 transaction_id: tx_id,
17756 });
17757 Some(tx_id)
17758 } else {
17759 None
17760 }
17761 }
17762
17763 pub fn end_transaction_at(
17764 &mut self,
17765 now: Instant,
17766 cx: &mut Context<Self>,
17767 ) -> Option<TransactionId> {
17768 if let Some(transaction_id) = self
17769 .buffer
17770 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17771 {
17772 if let Some((_, end_selections)) =
17773 self.selection_history.transaction_mut(transaction_id)
17774 {
17775 *end_selections = Some(self.selections.disjoint_anchors_arc());
17776 } else {
17777 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17778 }
17779
17780 cx.emit(EditorEvent::Edited { transaction_id });
17781 Some(transaction_id)
17782 } else {
17783 None
17784 }
17785 }
17786
17787 pub fn modify_transaction_selection_history(
17788 &mut self,
17789 transaction_id: TransactionId,
17790 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17791 ) -> bool {
17792 self.selection_history
17793 .transaction_mut(transaction_id)
17794 .map(modify)
17795 .is_some()
17796 }
17797
17798 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17799 if self.selection_mark_mode {
17800 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17801 s.move_with(|_, sel| {
17802 sel.collapse_to(sel.head(), SelectionGoal::None);
17803 });
17804 })
17805 }
17806 self.selection_mark_mode = true;
17807 cx.notify();
17808 }
17809
17810 pub fn swap_selection_ends(
17811 &mut self,
17812 _: &actions::SwapSelectionEnds,
17813 window: &mut Window,
17814 cx: &mut Context<Self>,
17815 ) {
17816 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17817 s.move_with(|_, sel| {
17818 if sel.start != sel.end {
17819 sel.reversed = !sel.reversed
17820 }
17821 });
17822 });
17823 self.request_autoscroll(Autoscroll::newest(), cx);
17824 cx.notify();
17825 }
17826
17827 pub fn toggle_focus(
17828 workspace: &mut Workspace,
17829 _: &actions::ToggleFocus,
17830 window: &mut Window,
17831 cx: &mut Context<Workspace>,
17832 ) {
17833 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17834 return;
17835 };
17836 workspace.activate_item(&item, true, true, window, cx);
17837 }
17838
17839 pub fn toggle_fold(
17840 &mut self,
17841 _: &actions::ToggleFold,
17842 window: &mut Window,
17843 cx: &mut Context<Self>,
17844 ) {
17845 if self.is_singleton(cx) {
17846 let selection = self.selections.newest::<Point>(cx);
17847
17848 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17849 let range = if selection.is_empty() {
17850 let point = selection.head().to_display_point(&display_map);
17851 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17852 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17853 .to_point(&display_map);
17854 start..end
17855 } else {
17856 selection.range()
17857 };
17858 if display_map.folds_in_range(range).next().is_some() {
17859 self.unfold_lines(&Default::default(), window, cx)
17860 } else {
17861 self.fold(&Default::default(), window, cx)
17862 }
17863 } else {
17864 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17865 let buffer_ids: HashSet<_> = self
17866 .selections
17867 .disjoint_anchor_ranges()
17868 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17869 .collect();
17870
17871 let should_unfold = buffer_ids
17872 .iter()
17873 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17874
17875 for buffer_id in buffer_ids {
17876 if should_unfold {
17877 self.unfold_buffer(buffer_id, cx);
17878 } else {
17879 self.fold_buffer(buffer_id, cx);
17880 }
17881 }
17882 }
17883 }
17884
17885 pub fn toggle_fold_recursive(
17886 &mut self,
17887 _: &actions::ToggleFoldRecursive,
17888 window: &mut Window,
17889 cx: &mut Context<Self>,
17890 ) {
17891 let selection = self.selections.newest::<Point>(cx);
17892
17893 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17894 let range = if selection.is_empty() {
17895 let point = selection.head().to_display_point(&display_map);
17896 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17897 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17898 .to_point(&display_map);
17899 start..end
17900 } else {
17901 selection.range()
17902 };
17903 if display_map.folds_in_range(range).next().is_some() {
17904 self.unfold_recursive(&Default::default(), window, cx)
17905 } else {
17906 self.fold_recursive(&Default::default(), window, cx)
17907 }
17908 }
17909
17910 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17911 if self.is_singleton(cx) {
17912 let mut to_fold = Vec::new();
17913 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17914 let selections = self.selections.all_adjusted(cx);
17915
17916 for selection in selections {
17917 let range = selection.range().sorted();
17918 let buffer_start_row = range.start.row;
17919
17920 if range.start.row != range.end.row {
17921 let mut found = false;
17922 let mut row = range.start.row;
17923 while row <= range.end.row {
17924 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17925 {
17926 found = true;
17927 row = crease.range().end.row + 1;
17928 to_fold.push(crease);
17929 } else {
17930 row += 1
17931 }
17932 }
17933 if found {
17934 continue;
17935 }
17936 }
17937
17938 for row in (0..=range.start.row).rev() {
17939 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17940 && crease.range().end.row >= buffer_start_row
17941 {
17942 to_fold.push(crease);
17943 if row <= range.start.row {
17944 break;
17945 }
17946 }
17947 }
17948 }
17949
17950 self.fold_creases(to_fold, true, window, cx);
17951 } else {
17952 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17953 let buffer_ids = self
17954 .selections
17955 .disjoint_anchor_ranges()
17956 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17957 .collect::<HashSet<_>>();
17958 for buffer_id in buffer_ids {
17959 self.fold_buffer(buffer_id, cx);
17960 }
17961 }
17962 }
17963
17964 pub fn toggle_fold_all(
17965 &mut self,
17966 _: &actions::ToggleFoldAll,
17967 window: &mut Window,
17968 cx: &mut Context<Self>,
17969 ) {
17970 if self.buffer.read(cx).is_singleton() {
17971 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17972 let has_folds = display_map
17973 .folds_in_range(0..display_map.buffer_snapshot.len())
17974 .next()
17975 .is_some();
17976
17977 if has_folds {
17978 self.unfold_all(&actions::UnfoldAll, window, cx);
17979 } else {
17980 self.fold_all(&actions::FoldAll, window, cx);
17981 }
17982 } else {
17983 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17984 let should_unfold = buffer_ids
17985 .iter()
17986 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17987
17988 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17989 editor
17990 .update_in(cx, |editor, _, cx| {
17991 for buffer_id in buffer_ids {
17992 if should_unfold {
17993 editor.unfold_buffer(buffer_id, cx);
17994 } else {
17995 editor.fold_buffer(buffer_id, cx);
17996 }
17997 }
17998 })
17999 .ok();
18000 });
18001 }
18002 }
18003
18004 fn fold_at_level(
18005 &mut self,
18006 fold_at: &FoldAtLevel,
18007 window: &mut Window,
18008 cx: &mut Context<Self>,
18009 ) {
18010 if !self.buffer.read(cx).is_singleton() {
18011 return;
18012 }
18013
18014 let fold_at_level = fold_at.0;
18015 let snapshot = self.buffer.read(cx).snapshot(cx);
18016 let mut to_fold = Vec::new();
18017 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18018
18019 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18020 while start_row < end_row {
18021 match self
18022 .snapshot(window, cx)
18023 .crease_for_buffer_row(MultiBufferRow(start_row))
18024 {
18025 Some(crease) => {
18026 let nested_start_row = crease.range().start.row + 1;
18027 let nested_end_row = crease.range().end.row;
18028
18029 if current_level < fold_at_level {
18030 stack.push((nested_start_row, nested_end_row, current_level + 1));
18031 } else if current_level == fold_at_level {
18032 to_fold.push(crease);
18033 }
18034
18035 start_row = nested_end_row + 1;
18036 }
18037 None => start_row += 1,
18038 }
18039 }
18040 }
18041
18042 self.fold_creases(to_fold, true, window, cx);
18043 }
18044
18045 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18046 if self.buffer.read(cx).is_singleton() {
18047 let mut fold_ranges = Vec::new();
18048 let snapshot = self.buffer.read(cx).snapshot(cx);
18049
18050 for row in 0..snapshot.max_row().0 {
18051 if let Some(foldable_range) = self
18052 .snapshot(window, cx)
18053 .crease_for_buffer_row(MultiBufferRow(row))
18054 {
18055 fold_ranges.push(foldable_range);
18056 }
18057 }
18058
18059 self.fold_creases(fold_ranges, true, window, cx);
18060 } else {
18061 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18062 editor
18063 .update_in(cx, |editor, _, cx| {
18064 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18065 editor.fold_buffer(buffer_id, cx);
18066 }
18067 })
18068 .ok();
18069 });
18070 }
18071 }
18072
18073 pub fn fold_function_bodies(
18074 &mut self,
18075 _: &actions::FoldFunctionBodies,
18076 window: &mut Window,
18077 cx: &mut Context<Self>,
18078 ) {
18079 let snapshot = self.buffer.read(cx).snapshot(cx);
18080
18081 let ranges = snapshot
18082 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18083 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18084 .collect::<Vec<_>>();
18085
18086 let creases = ranges
18087 .into_iter()
18088 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18089 .collect();
18090
18091 self.fold_creases(creases, true, window, cx);
18092 }
18093
18094 pub fn fold_recursive(
18095 &mut self,
18096 _: &actions::FoldRecursive,
18097 window: &mut Window,
18098 cx: &mut Context<Self>,
18099 ) {
18100 let mut to_fold = Vec::new();
18101 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18102 let selections = self.selections.all_adjusted(cx);
18103
18104 for selection in selections {
18105 let range = selection.range().sorted();
18106 let buffer_start_row = range.start.row;
18107
18108 if range.start.row != range.end.row {
18109 let mut found = false;
18110 for row in range.start.row..=range.end.row {
18111 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18112 found = true;
18113 to_fold.push(crease);
18114 }
18115 }
18116 if found {
18117 continue;
18118 }
18119 }
18120
18121 for row in (0..=range.start.row).rev() {
18122 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18123 if crease.range().end.row >= buffer_start_row {
18124 to_fold.push(crease);
18125 } else {
18126 break;
18127 }
18128 }
18129 }
18130 }
18131
18132 self.fold_creases(to_fold, true, window, cx);
18133 }
18134
18135 pub fn fold_at(
18136 &mut self,
18137 buffer_row: MultiBufferRow,
18138 window: &mut Window,
18139 cx: &mut Context<Self>,
18140 ) {
18141 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18142
18143 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18144 let autoscroll = self
18145 .selections
18146 .all::<Point>(cx)
18147 .iter()
18148 .any(|selection| crease.range().overlaps(&selection.range()));
18149
18150 self.fold_creases(vec![crease], autoscroll, window, cx);
18151 }
18152 }
18153
18154 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18155 if self.is_singleton(cx) {
18156 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18157 let buffer = &display_map.buffer_snapshot;
18158 let selections = self.selections.all::<Point>(cx);
18159 let ranges = selections
18160 .iter()
18161 .map(|s| {
18162 let range = s.display_range(&display_map).sorted();
18163 let mut start = range.start.to_point(&display_map);
18164 let mut end = range.end.to_point(&display_map);
18165 start.column = 0;
18166 end.column = buffer.line_len(MultiBufferRow(end.row));
18167 start..end
18168 })
18169 .collect::<Vec<_>>();
18170
18171 self.unfold_ranges(&ranges, true, true, cx);
18172 } else {
18173 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18174 let buffer_ids = self
18175 .selections
18176 .disjoint_anchor_ranges()
18177 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18178 .collect::<HashSet<_>>();
18179 for buffer_id in buffer_ids {
18180 self.unfold_buffer(buffer_id, cx);
18181 }
18182 }
18183 }
18184
18185 pub fn unfold_recursive(
18186 &mut self,
18187 _: &UnfoldRecursive,
18188 _window: &mut Window,
18189 cx: &mut Context<Self>,
18190 ) {
18191 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18192 let selections = self.selections.all::<Point>(cx);
18193 let ranges = selections
18194 .iter()
18195 .map(|s| {
18196 let mut range = s.display_range(&display_map).sorted();
18197 *range.start.column_mut() = 0;
18198 *range.end.column_mut() = display_map.line_len(range.end.row());
18199 let start = range.start.to_point(&display_map);
18200 let end = range.end.to_point(&display_map);
18201 start..end
18202 })
18203 .collect::<Vec<_>>();
18204
18205 self.unfold_ranges(&ranges, true, true, cx);
18206 }
18207
18208 pub fn unfold_at(
18209 &mut self,
18210 buffer_row: MultiBufferRow,
18211 _window: &mut Window,
18212 cx: &mut Context<Self>,
18213 ) {
18214 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18215
18216 let intersection_range = Point::new(buffer_row.0, 0)
18217 ..Point::new(
18218 buffer_row.0,
18219 display_map.buffer_snapshot.line_len(buffer_row),
18220 );
18221
18222 let autoscroll = self
18223 .selections
18224 .all::<Point>(cx)
18225 .iter()
18226 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18227
18228 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18229 }
18230
18231 pub fn unfold_all(
18232 &mut self,
18233 _: &actions::UnfoldAll,
18234 _window: &mut Window,
18235 cx: &mut Context<Self>,
18236 ) {
18237 if self.buffer.read(cx).is_singleton() {
18238 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18239 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18240 } else {
18241 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18242 editor
18243 .update(cx, |editor, cx| {
18244 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18245 editor.unfold_buffer(buffer_id, cx);
18246 }
18247 })
18248 .ok();
18249 });
18250 }
18251 }
18252
18253 pub fn fold_selected_ranges(
18254 &mut self,
18255 _: &FoldSelectedRanges,
18256 window: &mut Window,
18257 cx: &mut Context<Self>,
18258 ) {
18259 let selections = self.selections.all_adjusted(cx);
18260 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18261 let ranges = selections
18262 .into_iter()
18263 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18264 .collect::<Vec<_>>();
18265 self.fold_creases(ranges, true, window, cx);
18266 }
18267
18268 pub fn fold_ranges<T: ToOffset + Clone>(
18269 &mut self,
18270 ranges: Vec<Range<T>>,
18271 auto_scroll: bool,
18272 window: &mut Window,
18273 cx: &mut Context<Self>,
18274 ) {
18275 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18276 let ranges = ranges
18277 .into_iter()
18278 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18279 .collect::<Vec<_>>();
18280 self.fold_creases(ranges, auto_scroll, window, cx);
18281 }
18282
18283 pub fn fold_creases<T: ToOffset + Clone>(
18284 &mut self,
18285 creases: Vec<Crease<T>>,
18286 auto_scroll: bool,
18287 _window: &mut Window,
18288 cx: &mut Context<Self>,
18289 ) {
18290 if creases.is_empty() {
18291 return;
18292 }
18293
18294 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18295
18296 if auto_scroll {
18297 self.request_autoscroll(Autoscroll::fit(), cx);
18298 }
18299
18300 cx.notify();
18301
18302 self.scrollbar_marker_state.dirty = true;
18303 self.folds_did_change(cx);
18304 }
18305
18306 /// Removes any folds whose ranges intersect any of the given ranges.
18307 pub fn unfold_ranges<T: ToOffset + Clone>(
18308 &mut self,
18309 ranges: &[Range<T>],
18310 inclusive: bool,
18311 auto_scroll: bool,
18312 cx: &mut Context<Self>,
18313 ) {
18314 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18315 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18316 });
18317 self.folds_did_change(cx);
18318 }
18319
18320 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18321 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18322 return;
18323 }
18324 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18325 self.display_map.update(cx, |display_map, cx| {
18326 display_map.fold_buffers([buffer_id], cx)
18327 });
18328 cx.emit(EditorEvent::BufferFoldToggled {
18329 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18330 folded: true,
18331 });
18332 cx.notify();
18333 }
18334
18335 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18336 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18337 return;
18338 }
18339 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18340 self.display_map.update(cx, |display_map, cx| {
18341 display_map.unfold_buffers([buffer_id], cx);
18342 });
18343 cx.emit(EditorEvent::BufferFoldToggled {
18344 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18345 folded: false,
18346 });
18347 cx.notify();
18348 }
18349
18350 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18351 self.display_map.read(cx).is_buffer_folded(buffer)
18352 }
18353
18354 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18355 self.display_map.read(cx).folded_buffers()
18356 }
18357
18358 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18359 self.display_map.update(cx, |display_map, cx| {
18360 display_map.disable_header_for_buffer(buffer_id, cx);
18361 });
18362 cx.notify();
18363 }
18364
18365 /// Removes any folds with the given ranges.
18366 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18367 &mut self,
18368 ranges: &[Range<T>],
18369 type_id: TypeId,
18370 auto_scroll: bool,
18371 cx: &mut Context<Self>,
18372 ) {
18373 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18374 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18375 });
18376 self.folds_did_change(cx);
18377 }
18378
18379 fn remove_folds_with<T: ToOffset + Clone>(
18380 &mut self,
18381 ranges: &[Range<T>],
18382 auto_scroll: bool,
18383 cx: &mut Context<Self>,
18384 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18385 ) {
18386 if ranges.is_empty() {
18387 return;
18388 }
18389
18390 let mut buffers_affected = HashSet::default();
18391 let multi_buffer = self.buffer().read(cx);
18392 for range in ranges {
18393 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18394 buffers_affected.insert(buffer.read(cx).remote_id());
18395 };
18396 }
18397
18398 self.display_map.update(cx, update);
18399
18400 if auto_scroll {
18401 self.request_autoscroll(Autoscroll::fit(), cx);
18402 }
18403
18404 cx.notify();
18405 self.scrollbar_marker_state.dirty = true;
18406 self.active_indent_guides_state.dirty = true;
18407 }
18408
18409 pub fn update_renderer_widths(
18410 &mut self,
18411 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18412 cx: &mut Context<Self>,
18413 ) -> bool {
18414 self.display_map
18415 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18416 }
18417
18418 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18419 self.display_map.read(cx).fold_placeholder.clone()
18420 }
18421
18422 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18423 self.buffer.update(cx, |buffer, cx| {
18424 buffer.set_all_diff_hunks_expanded(cx);
18425 });
18426 }
18427
18428 pub fn expand_all_diff_hunks(
18429 &mut self,
18430 _: &ExpandAllDiffHunks,
18431 _window: &mut Window,
18432 cx: &mut Context<Self>,
18433 ) {
18434 self.buffer.update(cx, |buffer, cx| {
18435 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18436 });
18437 }
18438
18439 pub fn toggle_selected_diff_hunks(
18440 &mut self,
18441 _: &ToggleSelectedDiffHunks,
18442 _window: &mut Window,
18443 cx: &mut Context<Self>,
18444 ) {
18445 let ranges: Vec<_> = self
18446 .selections
18447 .disjoint_anchors()
18448 .iter()
18449 .map(|s| s.range())
18450 .collect();
18451 self.toggle_diff_hunks_in_ranges(ranges, cx);
18452 }
18453
18454 pub fn diff_hunks_in_ranges<'a>(
18455 &'a self,
18456 ranges: &'a [Range<Anchor>],
18457 buffer: &'a MultiBufferSnapshot,
18458 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18459 ranges.iter().flat_map(move |range| {
18460 let end_excerpt_id = range.end.excerpt_id;
18461 let range = range.to_point(buffer);
18462 let mut peek_end = range.end;
18463 if range.end.row < buffer.max_row().0 {
18464 peek_end = Point::new(range.end.row + 1, 0);
18465 }
18466 buffer
18467 .diff_hunks_in_range(range.start..peek_end)
18468 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18469 })
18470 }
18471
18472 pub fn has_stageable_diff_hunks_in_ranges(
18473 &self,
18474 ranges: &[Range<Anchor>],
18475 snapshot: &MultiBufferSnapshot,
18476 ) -> bool {
18477 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18478 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18479 }
18480
18481 pub fn toggle_staged_selected_diff_hunks(
18482 &mut self,
18483 _: &::git::ToggleStaged,
18484 _: &mut Window,
18485 cx: &mut Context<Self>,
18486 ) {
18487 let snapshot = self.buffer.read(cx).snapshot(cx);
18488 let ranges: Vec<_> = self
18489 .selections
18490 .disjoint_anchors()
18491 .iter()
18492 .map(|s| s.range())
18493 .collect();
18494 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18495 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18496 }
18497
18498 pub fn set_render_diff_hunk_controls(
18499 &mut self,
18500 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18501 cx: &mut Context<Self>,
18502 ) {
18503 self.render_diff_hunk_controls = render_diff_hunk_controls;
18504 cx.notify();
18505 }
18506
18507 pub fn stage_and_next(
18508 &mut self,
18509 _: &::git::StageAndNext,
18510 window: &mut Window,
18511 cx: &mut Context<Self>,
18512 ) {
18513 self.do_stage_or_unstage_and_next(true, window, cx);
18514 }
18515
18516 pub fn unstage_and_next(
18517 &mut self,
18518 _: &::git::UnstageAndNext,
18519 window: &mut Window,
18520 cx: &mut Context<Self>,
18521 ) {
18522 self.do_stage_or_unstage_and_next(false, window, cx);
18523 }
18524
18525 pub fn stage_or_unstage_diff_hunks(
18526 &mut self,
18527 stage: bool,
18528 ranges: Vec<Range<Anchor>>,
18529 cx: &mut Context<Self>,
18530 ) {
18531 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18532 cx.spawn(async move |this, cx| {
18533 task.await?;
18534 this.update(cx, |this, cx| {
18535 let snapshot = this.buffer.read(cx).snapshot(cx);
18536 let chunk_by = this
18537 .diff_hunks_in_ranges(&ranges, &snapshot)
18538 .chunk_by(|hunk| hunk.buffer_id);
18539 for (buffer_id, hunks) in &chunk_by {
18540 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18541 }
18542 })
18543 })
18544 .detach_and_log_err(cx);
18545 }
18546
18547 fn save_buffers_for_ranges_if_needed(
18548 &mut self,
18549 ranges: &[Range<Anchor>],
18550 cx: &mut Context<Editor>,
18551 ) -> Task<Result<()>> {
18552 let multibuffer = self.buffer.read(cx);
18553 let snapshot = multibuffer.read(cx);
18554 let buffer_ids: HashSet<_> = ranges
18555 .iter()
18556 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18557 .collect();
18558 drop(snapshot);
18559
18560 let mut buffers = HashSet::default();
18561 for buffer_id in buffer_ids {
18562 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18563 let buffer = buffer_entity.read(cx);
18564 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18565 {
18566 buffers.insert(buffer_entity);
18567 }
18568 }
18569 }
18570
18571 if let Some(project) = &self.project {
18572 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18573 } else {
18574 Task::ready(Ok(()))
18575 }
18576 }
18577
18578 fn do_stage_or_unstage_and_next(
18579 &mut self,
18580 stage: bool,
18581 window: &mut Window,
18582 cx: &mut Context<Self>,
18583 ) {
18584 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18585
18586 if ranges.iter().any(|range| range.start != range.end) {
18587 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18588 return;
18589 }
18590
18591 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18592 let snapshot = self.snapshot(window, cx);
18593 let position = self.selections.newest::<Point>(cx).head();
18594 let mut row = snapshot
18595 .buffer_snapshot
18596 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18597 .find(|hunk| hunk.row_range.start.0 > position.row)
18598 .map(|hunk| hunk.row_range.start);
18599
18600 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18601 // Outside of the project diff editor, wrap around to the beginning.
18602 if !all_diff_hunks_expanded {
18603 row = row.or_else(|| {
18604 snapshot
18605 .buffer_snapshot
18606 .diff_hunks_in_range(Point::zero()..position)
18607 .find(|hunk| hunk.row_range.end.0 < position.row)
18608 .map(|hunk| hunk.row_range.start)
18609 });
18610 }
18611
18612 if let Some(row) = row {
18613 let destination = Point::new(row.0, 0);
18614 let autoscroll = Autoscroll::center();
18615
18616 self.unfold_ranges(&[destination..destination], false, false, cx);
18617 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18618 s.select_ranges([destination..destination]);
18619 });
18620 }
18621 }
18622
18623 fn do_stage_or_unstage(
18624 &self,
18625 stage: bool,
18626 buffer_id: BufferId,
18627 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18628 cx: &mut App,
18629 ) -> Option<()> {
18630 let project = self.project()?;
18631 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18632 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18633 let buffer_snapshot = buffer.read(cx).snapshot();
18634 let file_exists = buffer_snapshot
18635 .file()
18636 .is_some_and(|file| file.disk_state().exists());
18637 diff.update(cx, |diff, cx| {
18638 diff.stage_or_unstage_hunks(
18639 stage,
18640 &hunks
18641 .map(|hunk| buffer_diff::DiffHunk {
18642 buffer_range: hunk.buffer_range,
18643 diff_base_byte_range: hunk.diff_base_byte_range,
18644 secondary_status: hunk.secondary_status,
18645 range: Point::zero()..Point::zero(), // unused
18646 })
18647 .collect::<Vec<_>>(),
18648 &buffer_snapshot,
18649 file_exists,
18650 cx,
18651 )
18652 });
18653 None
18654 }
18655
18656 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18657 let ranges: Vec<_> = self
18658 .selections
18659 .disjoint_anchors()
18660 .iter()
18661 .map(|s| s.range())
18662 .collect();
18663 self.buffer
18664 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18665 }
18666
18667 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18668 self.buffer.update(cx, |buffer, cx| {
18669 let ranges = vec![Anchor::min()..Anchor::max()];
18670 if !buffer.all_diff_hunks_expanded()
18671 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18672 {
18673 buffer.collapse_diff_hunks(ranges, cx);
18674 true
18675 } else {
18676 false
18677 }
18678 })
18679 }
18680
18681 fn toggle_diff_hunks_in_ranges(
18682 &mut self,
18683 ranges: Vec<Range<Anchor>>,
18684 cx: &mut Context<Editor>,
18685 ) {
18686 self.buffer.update(cx, |buffer, cx| {
18687 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18688 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18689 })
18690 }
18691
18692 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18693 self.buffer.update(cx, |buffer, cx| {
18694 let snapshot = buffer.snapshot(cx);
18695 let excerpt_id = range.end.excerpt_id;
18696 let point_range = range.to_point(&snapshot);
18697 let expand = !buffer.single_hunk_is_expanded(range, cx);
18698 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18699 })
18700 }
18701
18702 pub(crate) fn apply_all_diff_hunks(
18703 &mut self,
18704 _: &ApplyAllDiffHunks,
18705 window: &mut Window,
18706 cx: &mut Context<Self>,
18707 ) {
18708 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18709
18710 let buffers = self.buffer.read(cx).all_buffers();
18711 for branch_buffer in buffers {
18712 branch_buffer.update(cx, |branch_buffer, cx| {
18713 branch_buffer.merge_into_base(Vec::new(), cx);
18714 });
18715 }
18716
18717 if let Some(project) = self.project.clone() {
18718 self.save(
18719 SaveOptions {
18720 format: true,
18721 autosave: false,
18722 },
18723 project,
18724 window,
18725 cx,
18726 )
18727 .detach_and_log_err(cx);
18728 }
18729 }
18730
18731 pub(crate) fn apply_selected_diff_hunks(
18732 &mut self,
18733 _: &ApplyDiffHunk,
18734 window: &mut Window,
18735 cx: &mut Context<Self>,
18736 ) {
18737 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18738 let snapshot = self.snapshot(window, cx);
18739 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18740 let mut ranges_by_buffer = HashMap::default();
18741 self.transact(window, cx, |editor, _window, cx| {
18742 for hunk in hunks {
18743 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18744 ranges_by_buffer
18745 .entry(buffer.clone())
18746 .or_insert_with(Vec::new)
18747 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18748 }
18749 }
18750
18751 for (buffer, ranges) in ranges_by_buffer {
18752 buffer.update(cx, |buffer, cx| {
18753 buffer.merge_into_base(ranges, cx);
18754 });
18755 }
18756 });
18757
18758 if let Some(project) = self.project.clone() {
18759 self.save(
18760 SaveOptions {
18761 format: true,
18762 autosave: false,
18763 },
18764 project,
18765 window,
18766 cx,
18767 )
18768 .detach_and_log_err(cx);
18769 }
18770 }
18771
18772 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18773 if hovered != self.gutter_hovered {
18774 self.gutter_hovered = hovered;
18775 cx.notify();
18776 }
18777 }
18778
18779 pub fn insert_blocks(
18780 &mut self,
18781 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18782 autoscroll: Option<Autoscroll>,
18783 cx: &mut Context<Self>,
18784 ) -> Vec<CustomBlockId> {
18785 let blocks = self
18786 .display_map
18787 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18788 if let Some(autoscroll) = autoscroll {
18789 self.request_autoscroll(autoscroll, cx);
18790 }
18791 cx.notify();
18792 blocks
18793 }
18794
18795 pub fn resize_blocks(
18796 &mut self,
18797 heights: HashMap<CustomBlockId, u32>,
18798 autoscroll: Option<Autoscroll>,
18799 cx: &mut Context<Self>,
18800 ) {
18801 self.display_map
18802 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18803 if let Some(autoscroll) = autoscroll {
18804 self.request_autoscroll(autoscroll, cx);
18805 }
18806 cx.notify();
18807 }
18808
18809 pub fn replace_blocks(
18810 &mut self,
18811 renderers: HashMap<CustomBlockId, RenderBlock>,
18812 autoscroll: Option<Autoscroll>,
18813 cx: &mut Context<Self>,
18814 ) {
18815 self.display_map
18816 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18817 if let Some(autoscroll) = autoscroll {
18818 self.request_autoscroll(autoscroll, cx);
18819 }
18820 cx.notify();
18821 }
18822
18823 pub fn remove_blocks(
18824 &mut self,
18825 block_ids: HashSet<CustomBlockId>,
18826 autoscroll: Option<Autoscroll>,
18827 cx: &mut Context<Self>,
18828 ) {
18829 self.display_map.update(cx, |display_map, cx| {
18830 display_map.remove_blocks(block_ids, cx)
18831 });
18832 if let Some(autoscroll) = autoscroll {
18833 self.request_autoscroll(autoscroll, cx);
18834 }
18835 cx.notify();
18836 }
18837
18838 pub fn row_for_block(
18839 &self,
18840 block_id: CustomBlockId,
18841 cx: &mut Context<Self>,
18842 ) -> Option<DisplayRow> {
18843 self.display_map
18844 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18845 }
18846
18847 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18848 self.focused_block = Some(focused_block);
18849 }
18850
18851 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18852 self.focused_block.take()
18853 }
18854
18855 pub fn insert_creases(
18856 &mut self,
18857 creases: impl IntoIterator<Item = Crease<Anchor>>,
18858 cx: &mut Context<Self>,
18859 ) -> Vec<CreaseId> {
18860 self.display_map
18861 .update(cx, |map, cx| map.insert_creases(creases, cx))
18862 }
18863
18864 pub fn remove_creases(
18865 &mut self,
18866 ids: impl IntoIterator<Item = CreaseId>,
18867 cx: &mut Context<Self>,
18868 ) -> Vec<(CreaseId, Range<Anchor>)> {
18869 self.display_map
18870 .update(cx, |map, cx| map.remove_creases(ids, cx))
18871 }
18872
18873 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18874 self.display_map
18875 .update(cx, |map, cx| map.snapshot(cx))
18876 .longest_row()
18877 }
18878
18879 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18880 self.display_map
18881 .update(cx, |map, cx| map.snapshot(cx))
18882 .max_point()
18883 }
18884
18885 pub fn text(&self, cx: &App) -> String {
18886 self.buffer.read(cx).read(cx).text()
18887 }
18888
18889 pub fn is_empty(&self, cx: &App) -> bool {
18890 self.buffer.read(cx).read(cx).is_empty()
18891 }
18892
18893 pub fn text_option(&self, cx: &App) -> Option<String> {
18894 let text = self.text(cx);
18895 let text = text.trim();
18896
18897 if text.is_empty() {
18898 return None;
18899 }
18900
18901 Some(text.to_string())
18902 }
18903
18904 pub fn set_text(
18905 &mut self,
18906 text: impl Into<Arc<str>>,
18907 window: &mut Window,
18908 cx: &mut Context<Self>,
18909 ) {
18910 self.transact(window, cx, |this, _, cx| {
18911 this.buffer
18912 .read(cx)
18913 .as_singleton()
18914 .expect("you can only call set_text on editors for singleton buffers")
18915 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18916 });
18917 }
18918
18919 pub fn display_text(&self, cx: &mut App) -> String {
18920 self.display_map
18921 .update(cx, |map, cx| map.snapshot(cx))
18922 .text()
18923 }
18924
18925 fn create_minimap(
18926 &self,
18927 minimap_settings: MinimapSettings,
18928 window: &mut Window,
18929 cx: &mut Context<Self>,
18930 ) -> Option<Entity<Self>> {
18931 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18932 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18933 }
18934
18935 fn initialize_new_minimap(
18936 &self,
18937 minimap_settings: MinimapSettings,
18938 window: &mut Window,
18939 cx: &mut Context<Self>,
18940 ) -> Entity<Self> {
18941 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18942
18943 let mut minimap = Editor::new_internal(
18944 EditorMode::Minimap {
18945 parent: cx.weak_entity(),
18946 },
18947 self.buffer.clone(),
18948 None,
18949 Some(self.display_map.clone()),
18950 window,
18951 cx,
18952 );
18953 minimap.scroll_manager.clone_state(&self.scroll_manager);
18954 minimap.set_text_style_refinement(TextStyleRefinement {
18955 font_size: Some(MINIMAP_FONT_SIZE),
18956 font_weight: Some(MINIMAP_FONT_WEIGHT),
18957 ..Default::default()
18958 });
18959 minimap.update_minimap_configuration(minimap_settings, cx);
18960 cx.new(|_| minimap)
18961 }
18962
18963 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18964 let current_line_highlight = minimap_settings
18965 .current_line_highlight
18966 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18967 self.set_current_line_highlight(Some(current_line_highlight));
18968 }
18969
18970 pub fn minimap(&self) -> Option<&Entity<Self>> {
18971 self.minimap
18972 .as_ref()
18973 .filter(|_| self.minimap_visibility.visible())
18974 }
18975
18976 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18977 let mut wrap_guides = smallvec![];
18978
18979 if self.show_wrap_guides == Some(false) {
18980 return wrap_guides;
18981 }
18982
18983 let settings = self.buffer.read(cx).language_settings(cx);
18984 if settings.show_wrap_guides {
18985 match self.soft_wrap_mode(cx) {
18986 SoftWrap::Column(soft_wrap) => {
18987 wrap_guides.push((soft_wrap as usize, true));
18988 }
18989 SoftWrap::Bounded(soft_wrap) => {
18990 wrap_guides.push((soft_wrap as usize, true));
18991 }
18992 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18993 }
18994 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18995 }
18996
18997 wrap_guides
18998 }
18999
19000 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19001 let settings = self.buffer.read(cx).language_settings(cx);
19002 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19003 match mode {
19004 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19005 SoftWrap::None
19006 }
19007 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19008 language_settings::SoftWrap::PreferredLineLength => {
19009 SoftWrap::Column(settings.preferred_line_length)
19010 }
19011 language_settings::SoftWrap::Bounded => {
19012 SoftWrap::Bounded(settings.preferred_line_length)
19013 }
19014 }
19015 }
19016
19017 pub fn set_soft_wrap_mode(
19018 &mut self,
19019 mode: language_settings::SoftWrap,
19020
19021 cx: &mut Context<Self>,
19022 ) {
19023 self.soft_wrap_mode_override = Some(mode);
19024 cx.notify();
19025 }
19026
19027 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19028 self.hard_wrap = hard_wrap;
19029 cx.notify();
19030 }
19031
19032 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19033 self.text_style_refinement = Some(style);
19034 }
19035
19036 /// called by the Element so we know what style we were most recently rendered with.
19037 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19038 // We intentionally do not inform the display map about the minimap style
19039 // so that wrapping is not recalculated and stays consistent for the editor
19040 // and its linked minimap.
19041 if !self.mode.is_minimap() {
19042 let font = style.text.font();
19043 let font_size = style.text.font_size.to_pixels(window.rem_size());
19044 let display_map = self
19045 .placeholder_display_map
19046 .as_ref()
19047 .filter(|_| self.is_empty(cx))
19048 .unwrap_or(&self.display_map);
19049
19050 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19051 }
19052 self.style = Some(style);
19053 }
19054
19055 pub fn style(&self) -> Option<&EditorStyle> {
19056 self.style.as_ref()
19057 }
19058
19059 // Called by the element. This method is not designed to be called outside of the editor
19060 // element's layout code because it does not notify when rewrapping is computed synchronously.
19061 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19062 if self.is_empty(cx) {
19063 self.placeholder_display_map
19064 .as_ref()
19065 .map_or(false, |display_map| {
19066 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19067 })
19068 } else {
19069 self.display_map
19070 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19071 }
19072 }
19073
19074 pub fn set_soft_wrap(&mut self) {
19075 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19076 }
19077
19078 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19079 if self.soft_wrap_mode_override.is_some() {
19080 self.soft_wrap_mode_override.take();
19081 } else {
19082 let soft_wrap = match self.soft_wrap_mode(cx) {
19083 SoftWrap::GitDiff => return,
19084 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19085 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19086 language_settings::SoftWrap::None
19087 }
19088 };
19089 self.soft_wrap_mode_override = Some(soft_wrap);
19090 }
19091 cx.notify();
19092 }
19093
19094 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19095 let Some(workspace) = self.workspace() else {
19096 return;
19097 };
19098 let fs = workspace.read(cx).app_state().fs.clone();
19099 let current_show = TabBarSettings::get_global(cx).show;
19100 update_settings_file(fs, cx, move |setting, _| {
19101 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19102 });
19103 }
19104
19105 pub fn toggle_indent_guides(
19106 &mut self,
19107 _: &ToggleIndentGuides,
19108 _: &mut Window,
19109 cx: &mut Context<Self>,
19110 ) {
19111 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19112 self.buffer
19113 .read(cx)
19114 .language_settings(cx)
19115 .indent_guides
19116 .enabled
19117 });
19118 self.show_indent_guides = Some(!currently_enabled);
19119 cx.notify();
19120 }
19121
19122 fn should_show_indent_guides(&self) -> Option<bool> {
19123 self.show_indent_guides
19124 }
19125
19126 pub fn toggle_line_numbers(
19127 &mut self,
19128 _: &ToggleLineNumbers,
19129 _: &mut Window,
19130 cx: &mut Context<Self>,
19131 ) {
19132 let mut editor_settings = EditorSettings::get_global(cx).clone();
19133 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19134 EditorSettings::override_global(editor_settings, cx);
19135 }
19136
19137 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19138 if let Some(show_line_numbers) = self.show_line_numbers {
19139 return show_line_numbers;
19140 }
19141 EditorSettings::get_global(cx).gutter.line_numbers
19142 }
19143
19144 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19145 self.use_relative_line_numbers
19146 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19147 }
19148
19149 pub fn toggle_relative_line_numbers(
19150 &mut self,
19151 _: &ToggleRelativeLineNumbers,
19152 _: &mut Window,
19153 cx: &mut Context<Self>,
19154 ) {
19155 let is_relative = self.should_use_relative_line_numbers(cx);
19156 self.set_relative_line_number(Some(!is_relative), cx)
19157 }
19158
19159 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19160 self.use_relative_line_numbers = is_relative;
19161 cx.notify();
19162 }
19163
19164 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19165 self.show_gutter = show_gutter;
19166 cx.notify();
19167 }
19168
19169 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19170 self.show_scrollbars = ScrollbarAxes {
19171 horizontal: show,
19172 vertical: show,
19173 };
19174 cx.notify();
19175 }
19176
19177 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19178 self.show_scrollbars.vertical = show;
19179 cx.notify();
19180 }
19181
19182 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19183 self.show_scrollbars.horizontal = show;
19184 cx.notify();
19185 }
19186
19187 pub fn set_minimap_visibility(
19188 &mut self,
19189 minimap_visibility: MinimapVisibility,
19190 window: &mut Window,
19191 cx: &mut Context<Self>,
19192 ) {
19193 if self.minimap_visibility != minimap_visibility {
19194 if minimap_visibility.visible() && self.minimap.is_none() {
19195 let minimap_settings = EditorSettings::get_global(cx).minimap;
19196 self.minimap =
19197 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19198 }
19199 self.minimap_visibility = minimap_visibility;
19200 cx.notify();
19201 }
19202 }
19203
19204 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19205 self.set_show_scrollbars(false, cx);
19206 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19207 }
19208
19209 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19210 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19211 }
19212
19213 /// Normally the text in full mode and auto height editors is padded on the
19214 /// left side by roughly half a character width for improved hit testing.
19215 ///
19216 /// Use this method to disable this for cases where this is not wanted (e.g.
19217 /// if you want to align the editor text with some other text above or below)
19218 /// or if you want to add this padding to single-line editors.
19219 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19220 self.offset_content = offset_content;
19221 cx.notify();
19222 }
19223
19224 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19225 self.show_line_numbers = Some(show_line_numbers);
19226 cx.notify();
19227 }
19228
19229 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19230 self.disable_expand_excerpt_buttons = true;
19231 cx.notify();
19232 }
19233
19234 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19235 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19236 cx.notify();
19237 }
19238
19239 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19240 self.show_code_actions = Some(show_code_actions);
19241 cx.notify();
19242 }
19243
19244 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19245 self.show_runnables = Some(show_runnables);
19246 cx.notify();
19247 }
19248
19249 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19250 self.show_breakpoints = Some(show_breakpoints);
19251 cx.notify();
19252 }
19253
19254 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19255 if self.display_map.read(cx).masked != masked {
19256 self.display_map.update(cx, |map, _| map.masked = masked);
19257 }
19258 cx.notify()
19259 }
19260
19261 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19262 self.show_wrap_guides = Some(show_wrap_guides);
19263 cx.notify();
19264 }
19265
19266 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19267 self.show_indent_guides = Some(show_indent_guides);
19268 cx.notify();
19269 }
19270
19271 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19272 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19273 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19274 && let Some(dir) = file.abs_path(cx).parent()
19275 {
19276 return Some(dir.to_owned());
19277 }
19278 }
19279
19280 None
19281 }
19282
19283 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19284 self.active_excerpt(cx)?
19285 .1
19286 .read(cx)
19287 .file()
19288 .and_then(|f| f.as_local())
19289 }
19290
19291 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19292 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19293 let buffer = buffer.read(cx);
19294 if let Some(project_path) = buffer.project_path(cx) {
19295 let project = self.project()?.read(cx);
19296 project.absolute_path(&project_path, cx)
19297 } else {
19298 buffer
19299 .file()
19300 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19301 }
19302 })
19303 }
19304
19305 pub fn reveal_in_finder(
19306 &mut self,
19307 _: &RevealInFileManager,
19308 _window: &mut Window,
19309 cx: &mut Context<Self>,
19310 ) {
19311 if let Some(target) = self.target_file(cx) {
19312 cx.reveal_path(&target.abs_path(cx));
19313 }
19314 }
19315
19316 pub fn copy_path(
19317 &mut self,
19318 _: &zed_actions::workspace::CopyPath,
19319 _window: &mut Window,
19320 cx: &mut Context<Self>,
19321 ) {
19322 if let Some(path) = self.target_file_abs_path(cx)
19323 && let Some(path) = path.to_str()
19324 {
19325 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19326 } else {
19327 cx.propagate();
19328 }
19329 }
19330
19331 pub fn copy_relative_path(
19332 &mut self,
19333 _: &zed_actions::workspace::CopyRelativePath,
19334 _window: &mut Window,
19335 cx: &mut Context<Self>,
19336 ) {
19337 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19338 let project = self.project()?.read(cx);
19339 let path = buffer.read(cx).file()?.path();
19340 let path = path.display(project.path_style(cx));
19341 Some(path)
19342 }) {
19343 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19344 } else {
19345 cx.propagate();
19346 }
19347 }
19348
19349 /// Returns the project path for the editor's buffer, if any buffer is
19350 /// opened in the editor.
19351 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19352 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19353 buffer.read(cx).project_path(cx)
19354 } else {
19355 None
19356 }
19357 }
19358
19359 // Returns true if the editor handled a go-to-line request
19360 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19361 maybe!({
19362 let breakpoint_store = self.breakpoint_store.as_ref()?;
19363
19364 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19365 else {
19366 self.clear_row_highlights::<ActiveDebugLine>();
19367 return None;
19368 };
19369
19370 let position = active_stack_frame.position;
19371 let buffer_id = position.buffer_id?;
19372 let snapshot = self
19373 .project
19374 .as_ref()?
19375 .read(cx)
19376 .buffer_for_id(buffer_id, cx)?
19377 .read(cx)
19378 .snapshot();
19379
19380 let mut handled = false;
19381 for (id, ExcerptRange { context, .. }) in
19382 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19383 {
19384 if context.start.cmp(&position, &snapshot).is_ge()
19385 || context.end.cmp(&position, &snapshot).is_lt()
19386 {
19387 continue;
19388 }
19389 let snapshot = self.buffer.read(cx).snapshot(cx);
19390 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19391
19392 handled = true;
19393 self.clear_row_highlights::<ActiveDebugLine>();
19394
19395 self.go_to_line::<ActiveDebugLine>(
19396 multibuffer_anchor,
19397 Some(cx.theme().colors().editor_debugger_active_line_background),
19398 window,
19399 cx,
19400 );
19401
19402 cx.notify();
19403 }
19404
19405 handled.then_some(())
19406 })
19407 .is_some()
19408 }
19409
19410 pub fn copy_file_name_without_extension(
19411 &mut self,
19412 _: &CopyFileNameWithoutExtension,
19413 _: &mut Window,
19414 cx: &mut Context<Self>,
19415 ) {
19416 if let Some(file) = self.target_file(cx)
19417 && let Some(file_stem) = file.path().file_stem()
19418 {
19419 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19420 }
19421 }
19422
19423 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19424 if let Some(file) = self.target_file(cx)
19425 && let Some(name) = file.path().file_name()
19426 {
19427 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19428 }
19429 }
19430
19431 pub fn toggle_git_blame(
19432 &mut self,
19433 _: &::git::Blame,
19434 window: &mut Window,
19435 cx: &mut Context<Self>,
19436 ) {
19437 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19438
19439 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19440 self.start_git_blame(true, window, cx);
19441 }
19442
19443 cx.notify();
19444 }
19445
19446 pub fn toggle_git_blame_inline(
19447 &mut self,
19448 _: &ToggleGitBlameInline,
19449 window: &mut Window,
19450 cx: &mut Context<Self>,
19451 ) {
19452 self.toggle_git_blame_inline_internal(true, window, cx);
19453 cx.notify();
19454 }
19455
19456 pub fn open_git_blame_commit(
19457 &mut self,
19458 _: &OpenGitBlameCommit,
19459 window: &mut Window,
19460 cx: &mut Context<Self>,
19461 ) {
19462 self.open_git_blame_commit_internal(window, cx);
19463 }
19464
19465 fn open_git_blame_commit_internal(
19466 &mut self,
19467 window: &mut Window,
19468 cx: &mut Context<Self>,
19469 ) -> Option<()> {
19470 let blame = self.blame.as_ref()?;
19471 let snapshot = self.snapshot(window, cx);
19472 let cursor = self.selections.newest::<Point>(cx).head();
19473 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19474 let (_, blame_entry) = blame
19475 .update(cx, |blame, cx| {
19476 blame
19477 .blame_for_rows(
19478 &[RowInfo {
19479 buffer_id: Some(buffer.remote_id()),
19480 buffer_row: Some(point.row),
19481 ..Default::default()
19482 }],
19483 cx,
19484 )
19485 .next()
19486 })
19487 .flatten()?;
19488 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19489 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19490 let workspace = self.workspace()?.downgrade();
19491 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19492 None
19493 }
19494
19495 pub fn git_blame_inline_enabled(&self) -> bool {
19496 self.git_blame_inline_enabled
19497 }
19498
19499 pub fn toggle_selection_menu(
19500 &mut self,
19501 _: &ToggleSelectionMenu,
19502 _: &mut Window,
19503 cx: &mut Context<Self>,
19504 ) {
19505 self.show_selection_menu = self
19506 .show_selection_menu
19507 .map(|show_selections_menu| !show_selections_menu)
19508 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19509
19510 cx.notify();
19511 }
19512
19513 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19514 self.show_selection_menu
19515 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19516 }
19517
19518 fn start_git_blame(
19519 &mut self,
19520 user_triggered: bool,
19521 window: &mut Window,
19522 cx: &mut Context<Self>,
19523 ) {
19524 if let Some(project) = self.project() {
19525 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19526 && buffer.read(cx).file().is_none()
19527 {
19528 return;
19529 }
19530
19531 let focused = self.focus_handle(cx).contains_focused(window, cx);
19532
19533 let project = project.clone();
19534 let blame = cx
19535 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19536 self.blame_subscription =
19537 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19538 self.blame = Some(blame);
19539 }
19540 }
19541
19542 fn toggle_git_blame_inline_internal(
19543 &mut self,
19544 user_triggered: bool,
19545 window: &mut Window,
19546 cx: &mut Context<Self>,
19547 ) {
19548 if self.git_blame_inline_enabled {
19549 self.git_blame_inline_enabled = false;
19550 self.show_git_blame_inline = false;
19551 self.show_git_blame_inline_delay_task.take();
19552 } else {
19553 self.git_blame_inline_enabled = true;
19554 self.start_git_blame_inline(user_triggered, window, cx);
19555 }
19556
19557 cx.notify();
19558 }
19559
19560 fn start_git_blame_inline(
19561 &mut self,
19562 user_triggered: bool,
19563 window: &mut Window,
19564 cx: &mut Context<Self>,
19565 ) {
19566 self.start_git_blame(user_triggered, window, cx);
19567
19568 if ProjectSettings::get_global(cx)
19569 .git
19570 .inline_blame_delay()
19571 .is_some()
19572 {
19573 self.start_inline_blame_timer(window, cx);
19574 } else {
19575 self.show_git_blame_inline = true
19576 }
19577 }
19578
19579 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19580 self.blame.as_ref()
19581 }
19582
19583 pub fn show_git_blame_gutter(&self) -> bool {
19584 self.show_git_blame_gutter
19585 }
19586
19587 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19588 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19589 }
19590
19591 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19592 self.show_git_blame_inline
19593 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19594 && !self.newest_selection_head_on_empty_line(cx)
19595 && self.has_blame_entries(cx)
19596 }
19597
19598 fn has_blame_entries(&self, cx: &App) -> bool {
19599 self.blame()
19600 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19601 }
19602
19603 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19604 let cursor_anchor = self.selections.newest_anchor().head();
19605
19606 let snapshot = self.buffer.read(cx).snapshot(cx);
19607 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19608
19609 snapshot.line_len(buffer_row) == 0
19610 }
19611
19612 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19613 let buffer_and_selection = maybe!({
19614 let selection = self.selections.newest::<Point>(cx);
19615 let selection_range = selection.range();
19616
19617 let multi_buffer = self.buffer().read(cx);
19618 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19619 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19620
19621 let (buffer, range, _) = if selection.reversed {
19622 buffer_ranges.first()
19623 } else {
19624 buffer_ranges.last()
19625 }?;
19626
19627 let selection = text::ToPoint::to_point(&range.start, buffer).row
19628 ..text::ToPoint::to_point(&range.end, buffer).row;
19629 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19630 });
19631
19632 let Some((buffer, selection)) = buffer_and_selection else {
19633 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19634 };
19635
19636 let Some(project) = self.project() else {
19637 return Task::ready(Err(anyhow!("editor does not have project")));
19638 };
19639
19640 project.update(cx, |project, cx| {
19641 project.get_permalink_to_line(&buffer, selection, cx)
19642 })
19643 }
19644
19645 pub fn copy_permalink_to_line(
19646 &mut self,
19647 _: &CopyPermalinkToLine,
19648 window: &mut Window,
19649 cx: &mut Context<Self>,
19650 ) {
19651 let permalink_task = self.get_permalink_to_line(cx);
19652 let workspace = self.workspace();
19653
19654 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19655 Ok(permalink) => {
19656 cx.update(|_, cx| {
19657 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19658 })
19659 .ok();
19660 }
19661 Err(err) => {
19662 let message = format!("Failed to copy permalink: {err}");
19663
19664 anyhow::Result::<()>::Err(err).log_err();
19665
19666 if let Some(workspace) = workspace {
19667 workspace
19668 .update_in(cx, |workspace, _, cx| {
19669 struct CopyPermalinkToLine;
19670
19671 workspace.show_toast(
19672 Toast::new(
19673 NotificationId::unique::<CopyPermalinkToLine>(),
19674 message,
19675 ),
19676 cx,
19677 )
19678 })
19679 .ok();
19680 }
19681 }
19682 })
19683 .detach();
19684 }
19685
19686 pub fn copy_file_location(
19687 &mut self,
19688 _: &CopyFileLocation,
19689 _: &mut Window,
19690 cx: &mut Context<Self>,
19691 ) {
19692 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19693 if let Some(file) = self.target_file(cx) {
19694 let path = file.path().display(file.path_style(cx));
19695 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19696 }
19697 }
19698
19699 pub fn open_permalink_to_line(
19700 &mut self,
19701 _: &OpenPermalinkToLine,
19702 window: &mut Window,
19703 cx: &mut Context<Self>,
19704 ) {
19705 let permalink_task = self.get_permalink_to_line(cx);
19706 let workspace = self.workspace();
19707
19708 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19709 Ok(permalink) => {
19710 cx.update(|_, cx| {
19711 cx.open_url(permalink.as_ref());
19712 })
19713 .ok();
19714 }
19715 Err(err) => {
19716 let message = format!("Failed to open permalink: {err}");
19717
19718 anyhow::Result::<()>::Err(err).log_err();
19719
19720 if let Some(workspace) = workspace {
19721 workspace
19722 .update(cx, |workspace, cx| {
19723 struct OpenPermalinkToLine;
19724
19725 workspace.show_toast(
19726 Toast::new(
19727 NotificationId::unique::<OpenPermalinkToLine>(),
19728 message,
19729 ),
19730 cx,
19731 )
19732 })
19733 .ok();
19734 }
19735 }
19736 })
19737 .detach();
19738 }
19739
19740 pub fn insert_uuid_v4(
19741 &mut self,
19742 _: &InsertUuidV4,
19743 window: &mut Window,
19744 cx: &mut Context<Self>,
19745 ) {
19746 self.insert_uuid(UuidVersion::V4, window, cx);
19747 }
19748
19749 pub fn insert_uuid_v7(
19750 &mut self,
19751 _: &InsertUuidV7,
19752 window: &mut Window,
19753 cx: &mut Context<Self>,
19754 ) {
19755 self.insert_uuid(UuidVersion::V7, window, cx);
19756 }
19757
19758 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19759 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19760 self.transact(window, cx, |this, window, cx| {
19761 let edits = this
19762 .selections
19763 .all::<Point>(cx)
19764 .into_iter()
19765 .map(|selection| {
19766 let uuid = match version {
19767 UuidVersion::V4 => uuid::Uuid::new_v4(),
19768 UuidVersion::V7 => uuid::Uuid::now_v7(),
19769 };
19770
19771 (selection.range(), uuid.to_string())
19772 });
19773 this.edit(edits, cx);
19774 this.refresh_edit_prediction(true, false, window, cx);
19775 });
19776 }
19777
19778 pub fn open_selections_in_multibuffer(
19779 &mut self,
19780 _: &OpenSelectionsInMultibuffer,
19781 window: &mut Window,
19782 cx: &mut Context<Self>,
19783 ) {
19784 let multibuffer = self.buffer.read(cx);
19785
19786 let Some(buffer) = multibuffer.as_singleton() else {
19787 return;
19788 };
19789
19790 let Some(workspace) = self.workspace() else {
19791 return;
19792 };
19793
19794 let title = multibuffer.title(cx).to_string();
19795
19796 let locations = self
19797 .selections
19798 .all_anchors(cx)
19799 .iter()
19800 .map(|selection| {
19801 (
19802 buffer.clone(),
19803 (selection.start.text_anchor..selection.end.text_anchor)
19804 .to_point(buffer.read(cx)),
19805 )
19806 })
19807 .into_group_map();
19808
19809 cx.spawn_in(window, async move |_, cx| {
19810 workspace.update_in(cx, |workspace, window, cx| {
19811 Self::open_locations_in_multibuffer(
19812 workspace,
19813 locations,
19814 format!("Selections for '{title}'"),
19815 false,
19816 MultibufferSelectionMode::All,
19817 window,
19818 cx,
19819 );
19820 })
19821 })
19822 .detach();
19823 }
19824
19825 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19826 /// last highlight added will be used.
19827 ///
19828 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19829 pub fn highlight_rows<T: 'static>(
19830 &mut self,
19831 range: Range<Anchor>,
19832 color: Hsla,
19833 options: RowHighlightOptions,
19834 cx: &mut Context<Self>,
19835 ) {
19836 let snapshot = self.buffer().read(cx).snapshot(cx);
19837 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19838 let ix = row_highlights.binary_search_by(|highlight| {
19839 Ordering::Equal
19840 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19841 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19842 });
19843
19844 if let Err(mut ix) = ix {
19845 let index = post_inc(&mut self.highlight_order);
19846
19847 // If this range intersects with the preceding highlight, then merge it with
19848 // the preceding highlight. Otherwise insert a new highlight.
19849 let mut merged = false;
19850 if ix > 0 {
19851 let prev_highlight = &mut row_highlights[ix - 1];
19852 if prev_highlight
19853 .range
19854 .end
19855 .cmp(&range.start, &snapshot)
19856 .is_ge()
19857 {
19858 ix -= 1;
19859 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19860 prev_highlight.range.end = range.end;
19861 }
19862 merged = true;
19863 prev_highlight.index = index;
19864 prev_highlight.color = color;
19865 prev_highlight.options = options;
19866 }
19867 }
19868
19869 if !merged {
19870 row_highlights.insert(
19871 ix,
19872 RowHighlight {
19873 range,
19874 index,
19875 color,
19876 options,
19877 type_id: TypeId::of::<T>(),
19878 },
19879 );
19880 }
19881
19882 // If any of the following highlights intersect with this one, merge them.
19883 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19884 let highlight = &row_highlights[ix];
19885 if next_highlight
19886 .range
19887 .start
19888 .cmp(&highlight.range.end, &snapshot)
19889 .is_le()
19890 {
19891 if next_highlight
19892 .range
19893 .end
19894 .cmp(&highlight.range.end, &snapshot)
19895 .is_gt()
19896 {
19897 row_highlights[ix].range.end = next_highlight.range.end;
19898 }
19899 row_highlights.remove(ix + 1);
19900 } else {
19901 break;
19902 }
19903 }
19904 }
19905 }
19906
19907 /// Remove any highlighted row ranges of the given type that intersect the
19908 /// given ranges.
19909 pub fn remove_highlighted_rows<T: 'static>(
19910 &mut self,
19911 ranges_to_remove: Vec<Range<Anchor>>,
19912 cx: &mut Context<Self>,
19913 ) {
19914 let snapshot = self.buffer().read(cx).snapshot(cx);
19915 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19916 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19917 row_highlights.retain(|highlight| {
19918 while let Some(range_to_remove) = ranges_to_remove.peek() {
19919 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19920 Ordering::Less | Ordering::Equal => {
19921 ranges_to_remove.next();
19922 }
19923 Ordering::Greater => {
19924 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19925 Ordering::Less | Ordering::Equal => {
19926 return false;
19927 }
19928 Ordering::Greater => break,
19929 }
19930 }
19931 }
19932 }
19933
19934 true
19935 })
19936 }
19937
19938 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19939 pub fn clear_row_highlights<T: 'static>(&mut self) {
19940 self.highlighted_rows.remove(&TypeId::of::<T>());
19941 }
19942
19943 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19944 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19945 self.highlighted_rows
19946 .get(&TypeId::of::<T>())
19947 .map_or(&[] as &[_], |vec| vec.as_slice())
19948 .iter()
19949 .map(|highlight| (highlight.range.clone(), highlight.color))
19950 }
19951
19952 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19953 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19954 /// Allows to ignore certain kinds of highlights.
19955 pub fn highlighted_display_rows(
19956 &self,
19957 window: &mut Window,
19958 cx: &mut App,
19959 ) -> BTreeMap<DisplayRow, LineHighlight> {
19960 let snapshot = self.snapshot(window, cx);
19961 let mut used_highlight_orders = HashMap::default();
19962 self.highlighted_rows
19963 .iter()
19964 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19965 .fold(
19966 BTreeMap::<DisplayRow, LineHighlight>::new(),
19967 |mut unique_rows, highlight| {
19968 let start = highlight.range.start.to_display_point(&snapshot);
19969 let end = highlight.range.end.to_display_point(&snapshot);
19970 let start_row = start.row().0;
19971 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19972 && end.column() == 0
19973 {
19974 end.row().0.saturating_sub(1)
19975 } else {
19976 end.row().0
19977 };
19978 for row in start_row..=end_row {
19979 let used_index =
19980 used_highlight_orders.entry(row).or_insert(highlight.index);
19981 if highlight.index >= *used_index {
19982 *used_index = highlight.index;
19983 unique_rows.insert(
19984 DisplayRow(row),
19985 LineHighlight {
19986 include_gutter: highlight.options.include_gutter,
19987 border: None,
19988 background: highlight.color.into(),
19989 type_id: Some(highlight.type_id),
19990 },
19991 );
19992 }
19993 }
19994 unique_rows
19995 },
19996 )
19997 }
19998
19999 pub fn highlighted_display_row_for_autoscroll(
20000 &self,
20001 snapshot: &DisplaySnapshot,
20002 ) -> Option<DisplayRow> {
20003 self.highlighted_rows
20004 .values()
20005 .flat_map(|highlighted_rows| highlighted_rows.iter())
20006 .filter_map(|highlight| {
20007 if highlight.options.autoscroll {
20008 Some(highlight.range.start.to_display_point(snapshot).row())
20009 } else {
20010 None
20011 }
20012 })
20013 .min()
20014 }
20015
20016 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20017 self.highlight_background::<SearchWithinRange>(
20018 ranges,
20019 |colors| colors.colors().editor_document_highlight_read_background,
20020 cx,
20021 )
20022 }
20023
20024 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20025 self.breadcrumb_header = Some(new_header);
20026 }
20027
20028 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20029 self.clear_background_highlights::<SearchWithinRange>(cx);
20030 }
20031
20032 pub fn highlight_background<T: 'static>(
20033 &mut self,
20034 ranges: &[Range<Anchor>],
20035 color_fetcher: fn(&Theme) -> Hsla,
20036 cx: &mut Context<Self>,
20037 ) {
20038 self.background_highlights.insert(
20039 HighlightKey::Type(TypeId::of::<T>()),
20040 (color_fetcher, Arc::from(ranges)),
20041 );
20042 self.scrollbar_marker_state.dirty = true;
20043 cx.notify();
20044 }
20045
20046 pub fn highlight_background_key<T: 'static>(
20047 &mut self,
20048 key: usize,
20049 ranges: &[Range<Anchor>],
20050 color_fetcher: fn(&Theme) -> Hsla,
20051 cx: &mut Context<Self>,
20052 ) {
20053 self.background_highlights.insert(
20054 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20055 (color_fetcher, Arc::from(ranges)),
20056 );
20057 self.scrollbar_marker_state.dirty = true;
20058 cx.notify();
20059 }
20060
20061 pub fn clear_background_highlights<T: 'static>(
20062 &mut self,
20063 cx: &mut Context<Self>,
20064 ) -> Option<BackgroundHighlight> {
20065 let text_highlights = self
20066 .background_highlights
20067 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20068 if !text_highlights.1.is_empty() {
20069 self.scrollbar_marker_state.dirty = true;
20070 cx.notify();
20071 }
20072 Some(text_highlights)
20073 }
20074
20075 pub fn highlight_gutter<T: 'static>(
20076 &mut self,
20077 ranges: impl Into<Vec<Range<Anchor>>>,
20078 color_fetcher: fn(&App) -> Hsla,
20079 cx: &mut Context<Self>,
20080 ) {
20081 self.gutter_highlights
20082 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20083 cx.notify();
20084 }
20085
20086 pub fn clear_gutter_highlights<T: 'static>(
20087 &mut self,
20088 cx: &mut Context<Self>,
20089 ) -> Option<GutterHighlight> {
20090 cx.notify();
20091 self.gutter_highlights.remove(&TypeId::of::<T>())
20092 }
20093
20094 pub fn insert_gutter_highlight<T: 'static>(
20095 &mut self,
20096 range: Range<Anchor>,
20097 color_fetcher: fn(&App) -> Hsla,
20098 cx: &mut Context<Self>,
20099 ) {
20100 let snapshot = self.buffer().read(cx).snapshot(cx);
20101 let mut highlights = self
20102 .gutter_highlights
20103 .remove(&TypeId::of::<T>())
20104 .map(|(_, highlights)| highlights)
20105 .unwrap_or_default();
20106 let ix = highlights.binary_search_by(|highlight| {
20107 Ordering::Equal
20108 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20109 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20110 });
20111 if let Err(ix) = ix {
20112 highlights.insert(ix, range);
20113 }
20114 self.gutter_highlights
20115 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20116 }
20117
20118 pub fn remove_gutter_highlights<T: 'static>(
20119 &mut self,
20120 ranges_to_remove: Vec<Range<Anchor>>,
20121 cx: &mut Context<Self>,
20122 ) {
20123 let snapshot = self.buffer().read(cx).snapshot(cx);
20124 let Some((color_fetcher, mut gutter_highlights)) =
20125 self.gutter_highlights.remove(&TypeId::of::<T>())
20126 else {
20127 return;
20128 };
20129 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20130 gutter_highlights.retain(|highlight| {
20131 while let Some(range_to_remove) = ranges_to_remove.peek() {
20132 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20133 Ordering::Less | Ordering::Equal => {
20134 ranges_to_remove.next();
20135 }
20136 Ordering::Greater => {
20137 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20138 Ordering::Less | Ordering::Equal => {
20139 return false;
20140 }
20141 Ordering::Greater => break,
20142 }
20143 }
20144 }
20145 }
20146
20147 true
20148 });
20149 self.gutter_highlights
20150 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20151 }
20152
20153 #[cfg(feature = "test-support")]
20154 pub fn all_text_highlights(
20155 &self,
20156 window: &mut Window,
20157 cx: &mut Context<Self>,
20158 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20159 let snapshot = self.snapshot(window, cx);
20160 self.display_map.update(cx, |display_map, _| {
20161 display_map
20162 .all_text_highlights()
20163 .map(|highlight| {
20164 let (style, ranges) = highlight.as_ref();
20165 (
20166 *style,
20167 ranges
20168 .iter()
20169 .map(|range| range.clone().to_display_points(&snapshot))
20170 .collect(),
20171 )
20172 })
20173 .collect()
20174 })
20175 }
20176
20177 #[cfg(feature = "test-support")]
20178 pub fn all_text_background_highlights(
20179 &self,
20180 window: &mut Window,
20181 cx: &mut Context<Self>,
20182 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20183 let snapshot = self.snapshot(window, cx);
20184 let buffer = &snapshot.buffer_snapshot;
20185 let start = buffer.anchor_before(0);
20186 let end = buffer.anchor_after(buffer.len());
20187 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20188 }
20189
20190 #[cfg(any(test, feature = "test-support"))]
20191 pub fn sorted_background_highlights_in_range(
20192 &self,
20193 search_range: Range<Anchor>,
20194 display_snapshot: &DisplaySnapshot,
20195 theme: &Theme,
20196 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20197 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20198 res.sort_by(|a, b| {
20199 a.0.start
20200 .cmp(&b.0.start)
20201 .then_with(|| a.0.end.cmp(&b.0.end))
20202 .then_with(|| a.1.cmp(&b.1))
20203 });
20204 res
20205 }
20206
20207 #[cfg(feature = "test-support")]
20208 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20209 let snapshot = self.buffer().read(cx).snapshot(cx);
20210
20211 let highlights = self
20212 .background_highlights
20213 .get(&HighlightKey::Type(TypeId::of::<
20214 items::BufferSearchHighlights,
20215 >()));
20216
20217 if let Some((_color, ranges)) = highlights {
20218 ranges
20219 .iter()
20220 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20221 .collect_vec()
20222 } else {
20223 vec![]
20224 }
20225 }
20226
20227 fn document_highlights_for_position<'a>(
20228 &'a self,
20229 position: Anchor,
20230 buffer: &'a MultiBufferSnapshot,
20231 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20232 let read_highlights = self
20233 .background_highlights
20234 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20235 .map(|h| &h.1);
20236 let write_highlights = self
20237 .background_highlights
20238 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20239 .map(|h| &h.1);
20240 let left_position = position.bias_left(buffer);
20241 let right_position = position.bias_right(buffer);
20242 read_highlights
20243 .into_iter()
20244 .chain(write_highlights)
20245 .flat_map(move |ranges| {
20246 let start_ix = match ranges.binary_search_by(|probe| {
20247 let cmp = probe.end.cmp(&left_position, buffer);
20248 if cmp.is_ge() {
20249 Ordering::Greater
20250 } else {
20251 Ordering::Less
20252 }
20253 }) {
20254 Ok(i) | Err(i) => i,
20255 };
20256
20257 ranges[start_ix..]
20258 .iter()
20259 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20260 })
20261 }
20262
20263 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20264 self.background_highlights
20265 .get(&HighlightKey::Type(TypeId::of::<T>()))
20266 .is_some_and(|(_, highlights)| !highlights.is_empty())
20267 }
20268
20269 /// Returns all background highlights for a given range.
20270 ///
20271 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20272 pub fn background_highlights_in_range(
20273 &self,
20274 search_range: Range<Anchor>,
20275 display_snapshot: &DisplaySnapshot,
20276 theme: &Theme,
20277 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20278 let mut results = Vec::new();
20279 for (color_fetcher, ranges) in self.background_highlights.values() {
20280 let color = color_fetcher(theme);
20281 let start_ix = match ranges.binary_search_by(|probe| {
20282 let cmp = probe
20283 .end
20284 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20285 if cmp.is_gt() {
20286 Ordering::Greater
20287 } else {
20288 Ordering::Less
20289 }
20290 }) {
20291 Ok(i) | Err(i) => i,
20292 };
20293 for range in &ranges[start_ix..] {
20294 if range
20295 .start
20296 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20297 .is_ge()
20298 {
20299 break;
20300 }
20301
20302 let start = range.start.to_display_point(display_snapshot);
20303 let end = range.end.to_display_point(display_snapshot);
20304 results.push((start..end, color))
20305 }
20306 }
20307 results
20308 }
20309
20310 pub fn gutter_highlights_in_range(
20311 &self,
20312 search_range: Range<Anchor>,
20313 display_snapshot: &DisplaySnapshot,
20314 cx: &App,
20315 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20316 let mut results = Vec::new();
20317 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20318 let color = color_fetcher(cx);
20319 let start_ix = match ranges.binary_search_by(|probe| {
20320 let cmp = probe
20321 .end
20322 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20323 if cmp.is_gt() {
20324 Ordering::Greater
20325 } else {
20326 Ordering::Less
20327 }
20328 }) {
20329 Ok(i) | Err(i) => i,
20330 };
20331 for range in &ranges[start_ix..] {
20332 if range
20333 .start
20334 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20335 .is_ge()
20336 {
20337 break;
20338 }
20339
20340 let start = range.start.to_display_point(display_snapshot);
20341 let end = range.end.to_display_point(display_snapshot);
20342 results.push((start..end, color))
20343 }
20344 }
20345 results
20346 }
20347
20348 /// Get the text ranges corresponding to the redaction query
20349 pub fn redacted_ranges(
20350 &self,
20351 search_range: Range<Anchor>,
20352 display_snapshot: &DisplaySnapshot,
20353 cx: &App,
20354 ) -> Vec<Range<DisplayPoint>> {
20355 display_snapshot
20356 .buffer_snapshot
20357 .redacted_ranges(search_range, |file| {
20358 if let Some(file) = file {
20359 file.is_private()
20360 && EditorSettings::get(
20361 Some(SettingsLocation {
20362 worktree_id: file.worktree_id(cx),
20363 path: file.path().as_ref(),
20364 }),
20365 cx,
20366 )
20367 .redact_private_values
20368 } else {
20369 false
20370 }
20371 })
20372 .map(|range| {
20373 range.start.to_display_point(display_snapshot)
20374 ..range.end.to_display_point(display_snapshot)
20375 })
20376 .collect()
20377 }
20378
20379 pub fn highlight_text_key<T: 'static>(
20380 &mut self,
20381 key: usize,
20382 ranges: Vec<Range<Anchor>>,
20383 style: HighlightStyle,
20384 cx: &mut Context<Self>,
20385 ) {
20386 self.display_map.update(cx, |map, _| {
20387 map.highlight_text(
20388 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20389 ranges,
20390 style,
20391 );
20392 });
20393 cx.notify();
20394 }
20395
20396 pub fn highlight_text<T: 'static>(
20397 &mut self,
20398 ranges: Vec<Range<Anchor>>,
20399 style: HighlightStyle,
20400 cx: &mut Context<Self>,
20401 ) {
20402 self.display_map.update(cx, |map, _| {
20403 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20404 });
20405 cx.notify();
20406 }
20407
20408 pub(crate) fn highlight_inlays<T: 'static>(
20409 &mut self,
20410 highlights: Vec<InlayHighlight>,
20411 style: HighlightStyle,
20412 cx: &mut Context<Self>,
20413 ) {
20414 self.display_map.update(cx, |map, _| {
20415 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20416 });
20417 cx.notify();
20418 }
20419
20420 pub fn text_highlights<'a, T: 'static>(
20421 &'a self,
20422 cx: &'a App,
20423 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20424 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20425 }
20426
20427 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20428 let cleared = self
20429 .display_map
20430 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20431 if cleared {
20432 cx.notify();
20433 }
20434 }
20435
20436 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20437 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20438 && self.focus_handle.is_focused(window)
20439 }
20440
20441 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20442 self.show_cursor_when_unfocused = is_enabled;
20443 cx.notify();
20444 }
20445
20446 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20447 cx.notify();
20448 }
20449
20450 fn on_debug_session_event(
20451 &mut self,
20452 _session: Entity<Session>,
20453 event: &SessionEvent,
20454 cx: &mut Context<Self>,
20455 ) {
20456 if let SessionEvent::InvalidateInlineValue = event {
20457 self.refresh_inline_values(cx);
20458 }
20459 }
20460
20461 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20462 let Some(project) = self.project.clone() else {
20463 return;
20464 };
20465
20466 if !self.inline_value_cache.enabled {
20467 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20468 self.splice_inlays(&inlays, Vec::new(), cx);
20469 return;
20470 }
20471
20472 let current_execution_position = self
20473 .highlighted_rows
20474 .get(&TypeId::of::<ActiveDebugLine>())
20475 .and_then(|lines| lines.last().map(|line| line.range.end));
20476
20477 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20478 let inline_values = editor
20479 .update(cx, |editor, cx| {
20480 let Some(current_execution_position) = current_execution_position else {
20481 return Some(Task::ready(Ok(Vec::new())));
20482 };
20483
20484 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20485 let snapshot = buffer.snapshot(cx);
20486
20487 let excerpt = snapshot.excerpt_containing(
20488 current_execution_position..current_execution_position,
20489 )?;
20490
20491 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20492 })?;
20493
20494 let range =
20495 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20496
20497 project.inline_values(buffer, range, cx)
20498 })
20499 .ok()
20500 .flatten()?
20501 .await
20502 .context("refreshing debugger inlays")
20503 .log_err()?;
20504
20505 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20506
20507 for (buffer_id, inline_value) in inline_values
20508 .into_iter()
20509 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20510 {
20511 buffer_inline_values
20512 .entry(buffer_id)
20513 .or_default()
20514 .push(inline_value);
20515 }
20516
20517 editor
20518 .update(cx, |editor, cx| {
20519 let snapshot = editor.buffer.read(cx).snapshot(cx);
20520 let mut new_inlays = Vec::default();
20521
20522 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20523 let buffer_id = buffer_snapshot.remote_id();
20524 buffer_inline_values
20525 .get(&buffer_id)
20526 .into_iter()
20527 .flatten()
20528 .for_each(|hint| {
20529 let inlay = Inlay::debugger(
20530 post_inc(&mut editor.next_inlay_id),
20531 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20532 hint.text(),
20533 );
20534 if !inlay.text().chars().contains(&'\n') {
20535 new_inlays.push(inlay);
20536 }
20537 });
20538 }
20539
20540 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20541 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20542
20543 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20544 })
20545 .ok()?;
20546 Some(())
20547 });
20548 }
20549
20550 fn on_buffer_event(
20551 &mut self,
20552 multibuffer: &Entity<MultiBuffer>,
20553 event: &multi_buffer::Event,
20554 window: &mut Window,
20555 cx: &mut Context<Self>,
20556 ) {
20557 match event {
20558 multi_buffer::Event::Edited {
20559 singleton_buffer_edited,
20560 edited_buffer,
20561 } => {
20562 self.scrollbar_marker_state.dirty = true;
20563 self.active_indent_guides_state.dirty = true;
20564 self.refresh_active_diagnostics(cx);
20565 self.refresh_code_actions(window, cx);
20566 self.refresh_selected_text_highlights(true, window, cx);
20567 self.refresh_single_line_folds(window, cx);
20568 refresh_matching_bracket_highlights(self, window, cx);
20569 if self.has_active_edit_prediction() {
20570 self.update_visible_edit_prediction(window, cx);
20571 }
20572 if let Some(project) = self.project.as_ref()
20573 && let Some(edited_buffer) = edited_buffer
20574 {
20575 project.update(cx, |project, cx| {
20576 self.registered_buffers
20577 .entry(edited_buffer.read(cx).remote_id())
20578 .or_insert_with(|| {
20579 project.register_buffer_with_language_servers(edited_buffer, cx)
20580 });
20581 });
20582 }
20583 cx.emit(EditorEvent::BufferEdited);
20584 cx.emit(SearchEvent::MatchesInvalidated);
20585
20586 if let Some(buffer) = edited_buffer {
20587 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20588 }
20589
20590 if *singleton_buffer_edited {
20591 if let Some(buffer) = edited_buffer
20592 && buffer.read(cx).file().is_none()
20593 {
20594 cx.emit(EditorEvent::TitleChanged);
20595 }
20596 if let Some(project) = &self.project {
20597 #[allow(clippy::mutable_key_type)]
20598 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20599 multibuffer
20600 .all_buffers()
20601 .into_iter()
20602 .filter_map(|buffer| {
20603 buffer.update(cx, |buffer, cx| {
20604 let language = buffer.language()?;
20605 let should_discard = project.update(cx, |project, cx| {
20606 project.is_local()
20607 && !project.has_language_servers_for(buffer, cx)
20608 });
20609 should_discard.not().then_some(language.clone())
20610 })
20611 })
20612 .collect::<HashSet<_>>()
20613 });
20614 if !languages_affected.is_empty() {
20615 self.refresh_inlay_hints(
20616 InlayHintRefreshReason::BufferEdited(languages_affected),
20617 cx,
20618 );
20619 }
20620 }
20621 }
20622
20623 let Some(project) = &self.project else { return };
20624 let (telemetry, is_via_ssh) = {
20625 let project = project.read(cx);
20626 let telemetry = project.client().telemetry().clone();
20627 let is_via_ssh = project.is_via_remote_server();
20628 (telemetry, is_via_ssh)
20629 };
20630 refresh_linked_ranges(self, window, cx);
20631 telemetry.log_edit_event("editor", is_via_ssh);
20632 }
20633 multi_buffer::Event::ExcerptsAdded {
20634 buffer,
20635 predecessor,
20636 excerpts,
20637 } => {
20638 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20639 let buffer_id = buffer.read(cx).remote_id();
20640 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20641 && let Some(project) = &self.project
20642 {
20643 update_uncommitted_diff_for_buffer(
20644 cx.entity(),
20645 project,
20646 [buffer.clone()],
20647 self.buffer.clone(),
20648 cx,
20649 )
20650 .detach();
20651 }
20652 if self.active_diagnostics != ActiveDiagnostic::All {
20653 self.update_lsp_data(false, Some(buffer_id), window, cx);
20654 }
20655 cx.emit(EditorEvent::ExcerptsAdded {
20656 buffer: buffer.clone(),
20657 predecessor: *predecessor,
20658 excerpts: excerpts.clone(),
20659 });
20660 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20661 }
20662 multi_buffer::Event::ExcerptsRemoved {
20663 ids,
20664 removed_buffer_ids,
20665 } => {
20666 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20667 let buffer = self.buffer.read(cx);
20668 self.registered_buffers
20669 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20670 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20671 cx.emit(EditorEvent::ExcerptsRemoved {
20672 ids: ids.clone(),
20673 removed_buffer_ids: removed_buffer_ids.clone(),
20674 });
20675 }
20676 multi_buffer::Event::ExcerptsEdited {
20677 excerpt_ids,
20678 buffer_ids,
20679 } => {
20680 self.display_map.update(cx, |map, cx| {
20681 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20682 });
20683 cx.emit(EditorEvent::ExcerptsEdited {
20684 ids: excerpt_ids.clone(),
20685 });
20686 }
20687 multi_buffer::Event::ExcerptsExpanded { ids } => {
20688 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20689 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20690 }
20691 multi_buffer::Event::Reparsed(buffer_id) => {
20692 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20693 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20694
20695 cx.emit(EditorEvent::Reparsed(*buffer_id));
20696 }
20697 multi_buffer::Event::DiffHunksToggled => {
20698 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20699 }
20700 multi_buffer::Event::LanguageChanged(buffer_id) => {
20701 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20702 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20703 cx.emit(EditorEvent::Reparsed(*buffer_id));
20704 cx.notify();
20705 }
20706 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20707 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20708 multi_buffer::Event::FileHandleChanged
20709 | multi_buffer::Event::Reloaded
20710 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20711 multi_buffer::Event::DiagnosticsUpdated => {
20712 self.update_diagnostics_state(window, cx);
20713 }
20714 _ => {}
20715 };
20716 }
20717
20718 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20719 if !self.diagnostics_enabled() {
20720 return;
20721 }
20722 self.refresh_active_diagnostics(cx);
20723 self.refresh_inline_diagnostics(true, window, cx);
20724 self.scrollbar_marker_state.dirty = true;
20725 cx.notify();
20726 }
20727
20728 pub fn start_temporary_diff_override(&mut self) {
20729 self.load_diff_task.take();
20730 self.temporary_diff_override = true;
20731 }
20732
20733 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20734 self.temporary_diff_override = false;
20735 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20736 self.buffer.update(cx, |buffer, cx| {
20737 buffer.set_all_diff_hunks_collapsed(cx);
20738 });
20739
20740 if let Some(project) = self.project.clone() {
20741 self.load_diff_task = Some(
20742 update_uncommitted_diff_for_buffer(
20743 cx.entity(),
20744 &project,
20745 self.buffer.read(cx).all_buffers(),
20746 self.buffer.clone(),
20747 cx,
20748 )
20749 .shared(),
20750 );
20751 }
20752 }
20753
20754 fn on_display_map_changed(
20755 &mut self,
20756 _: Entity<DisplayMap>,
20757 _: &mut Window,
20758 cx: &mut Context<Self>,
20759 ) {
20760 cx.notify();
20761 }
20762
20763 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20764 if self.diagnostics_enabled() {
20765 let new_severity = EditorSettings::get_global(cx)
20766 .diagnostics_max_severity
20767 .unwrap_or(DiagnosticSeverity::Hint);
20768 self.set_max_diagnostics_severity(new_severity, cx);
20769 }
20770 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20771 self.update_edit_prediction_settings(cx);
20772 self.refresh_edit_prediction(true, false, window, cx);
20773 self.refresh_inline_values(cx);
20774 self.refresh_inlay_hints(
20775 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20776 self.selections.newest_anchor().head(),
20777 &self.buffer.read(cx).snapshot(cx),
20778 cx,
20779 )),
20780 cx,
20781 );
20782
20783 let old_cursor_shape = self.cursor_shape;
20784 let old_show_breadcrumbs = self.show_breadcrumbs;
20785
20786 {
20787 let editor_settings = EditorSettings::get_global(cx);
20788 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20789 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20790 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20791 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20792 }
20793
20794 if old_cursor_shape != self.cursor_shape {
20795 cx.emit(EditorEvent::CursorShapeChanged);
20796 }
20797
20798 if old_show_breadcrumbs != self.show_breadcrumbs {
20799 cx.emit(EditorEvent::BreadcrumbsChanged);
20800 }
20801
20802 let project_settings = ProjectSettings::get_global(cx);
20803 self.serialize_dirty_buffers =
20804 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20805
20806 if self.mode.is_full() {
20807 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20808 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20809 if self.show_inline_diagnostics != show_inline_diagnostics {
20810 self.show_inline_diagnostics = show_inline_diagnostics;
20811 self.refresh_inline_diagnostics(false, window, cx);
20812 }
20813
20814 if self.git_blame_inline_enabled != inline_blame_enabled {
20815 self.toggle_git_blame_inline_internal(false, window, cx);
20816 }
20817
20818 let minimap_settings = EditorSettings::get_global(cx).minimap;
20819 if self.minimap_visibility != MinimapVisibility::Disabled {
20820 if self.minimap_visibility.settings_visibility()
20821 != minimap_settings.minimap_enabled()
20822 {
20823 self.set_minimap_visibility(
20824 MinimapVisibility::for_mode(self.mode(), cx),
20825 window,
20826 cx,
20827 );
20828 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20829 minimap_entity.update(cx, |minimap_editor, cx| {
20830 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20831 })
20832 }
20833 }
20834 }
20835
20836 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20837 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20838 }) {
20839 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20840 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20841 }
20842 self.refresh_colors(false, None, window, cx);
20843 }
20844
20845 cx.notify();
20846 }
20847
20848 pub fn set_searchable(&mut self, searchable: bool) {
20849 self.searchable = searchable;
20850 }
20851
20852 pub fn searchable(&self) -> bool {
20853 self.searchable
20854 }
20855
20856 fn open_proposed_changes_editor(
20857 &mut self,
20858 _: &OpenProposedChangesEditor,
20859 window: &mut Window,
20860 cx: &mut Context<Self>,
20861 ) {
20862 let Some(workspace) = self.workspace() else {
20863 cx.propagate();
20864 return;
20865 };
20866
20867 let selections = self.selections.all::<usize>(cx);
20868 let multi_buffer = self.buffer.read(cx);
20869 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20870 let mut new_selections_by_buffer = HashMap::default();
20871 for selection in selections {
20872 for (buffer, range, _) in
20873 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20874 {
20875 let mut range = range.to_point(buffer);
20876 range.start.column = 0;
20877 range.end.column = buffer.line_len(range.end.row);
20878 new_selections_by_buffer
20879 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20880 .or_insert(Vec::new())
20881 .push(range)
20882 }
20883 }
20884
20885 let proposed_changes_buffers = new_selections_by_buffer
20886 .into_iter()
20887 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20888 .collect::<Vec<_>>();
20889 let proposed_changes_editor = cx.new(|cx| {
20890 ProposedChangesEditor::new(
20891 "Proposed changes",
20892 proposed_changes_buffers,
20893 self.project.clone(),
20894 window,
20895 cx,
20896 )
20897 });
20898
20899 window.defer(cx, move |window, cx| {
20900 workspace.update(cx, |workspace, cx| {
20901 workspace.active_pane().update(cx, |pane, cx| {
20902 pane.add_item(
20903 Box::new(proposed_changes_editor),
20904 true,
20905 true,
20906 None,
20907 window,
20908 cx,
20909 );
20910 });
20911 });
20912 });
20913 }
20914
20915 pub fn open_excerpts_in_split(
20916 &mut self,
20917 _: &OpenExcerptsSplit,
20918 window: &mut Window,
20919 cx: &mut Context<Self>,
20920 ) {
20921 self.open_excerpts_common(None, true, window, cx)
20922 }
20923
20924 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20925 self.open_excerpts_common(None, false, window, cx)
20926 }
20927
20928 fn open_excerpts_common(
20929 &mut self,
20930 jump_data: Option<JumpData>,
20931 split: bool,
20932 window: &mut Window,
20933 cx: &mut Context<Self>,
20934 ) {
20935 let Some(workspace) = self.workspace() else {
20936 cx.propagate();
20937 return;
20938 };
20939
20940 if self.buffer.read(cx).is_singleton() {
20941 cx.propagate();
20942 return;
20943 }
20944
20945 let mut new_selections_by_buffer = HashMap::default();
20946 match &jump_data {
20947 Some(JumpData::MultiBufferPoint {
20948 excerpt_id,
20949 position,
20950 anchor,
20951 line_offset_from_top,
20952 }) => {
20953 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20954 if let Some(buffer) = multi_buffer_snapshot
20955 .buffer_id_for_excerpt(*excerpt_id)
20956 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20957 {
20958 let buffer_snapshot = buffer.read(cx).snapshot();
20959 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20960 language::ToPoint::to_point(anchor, &buffer_snapshot)
20961 } else {
20962 buffer_snapshot.clip_point(*position, Bias::Left)
20963 };
20964 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20965 new_selections_by_buffer.insert(
20966 buffer,
20967 (
20968 vec![jump_to_offset..jump_to_offset],
20969 Some(*line_offset_from_top),
20970 ),
20971 );
20972 }
20973 }
20974 Some(JumpData::MultiBufferRow {
20975 row,
20976 line_offset_from_top,
20977 }) => {
20978 let point = MultiBufferPoint::new(row.0, 0);
20979 if let Some((buffer, buffer_point, _)) =
20980 self.buffer.read(cx).point_to_buffer_point(point, cx)
20981 {
20982 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20983 new_selections_by_buffer
20984 .entry(buffer)
20985 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20986 .0
20987 .push(buffer_offset..buffer_offset)
20988 }
20989 }
20990 None => {
20991 let selections = self.selections.all::<usize>(cx);
20992 let multi_buffer = self.buffer.read(cx);
20993 for selection in selections {
20994 for (snapshot, range, _, anchor) in multi_buffer
20995 .snapshot(cx)
20996 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20997 {
20998 if let Some(anchor) = anchor {
20999 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21000 else {
21001 continue;
21002 };
21003 let offset = text::ToOffset::to_offset(
21004 &anchor.text_anchor,
21005 &buffer_handle.read(cx).snapshot(),
21006 );
21007 let range = offset..offset;
21008 new_selections_by_buffer
21009 .entry(buffer_handle)
21010 .or_insert((Vec::new(), None))
21011 .0
21012 .push(range)
21013 } else {
21014 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21015 else {
21016 continue;
21017 };
21018 new_selections_by_buffer
21019 .entry(buffer_handle)
21020 .or_insert((Vec::new(), None))
21021 .0
21022 .push(range)
21023 }
21024 }
21025 }
21026 }
21027 }
21028
21029 new_selections_by_buffer
21030 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21031
21032 if new_selections_by_buffer.is_empty() {
21033 return;
21034 }
21035
21036 // We defer the pane interaction because we ourselves are a workspace item
21037 // and activating a new item causes the pane to call a method on us reentrantly,
21038 // which panics if we're on the stack.
21039 window.defer(cx, move |window, cx| {
21040 workspace.update(cx, |workspace, cx| {
21041 let pane = if split {
21042 workspace.adjacent_pane(window, cx)
21043 } else {
21044 workspace.active_pane().clone()
21045 };
21046
21047 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21048 let editor = buffer
21049 .read(cx)
21050 .file()
21051 .is_none()
21052 .then(|| {
21053 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21054 // so `workspace.open_project_item` will never find them, always opening a new editor.
21055 // Instead, we try to activate the existing editor in the pane first.
21056 let (editor, pane_item_index) =
21057 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21058 let editor = item.downcast::<Editor>()?;
21059 let singleton_buffer =
21060 editor.read(cx).buffer().read(cx).as_singleton()?;
21061 if singleton_buffer == buffer {
21062 Some((editor, i))
21063 } else {
21064 None
21065 }
21066 })?;
21067 pane.update(cx, |pane, cx| {
21068 pane.activate_item(pane_item_index, true, true, window, cx)
21069 });
21070 Some(editor)
21071 })
21072 .flatten()
21073 .unwrap_or_else(|| {
21074 workspace.open_project_item::<Self>(
21075 pane.clone(),
21076 buffer,
21077 true,
21078 true,
21079 window,
21080 cx,
21081 )
21082 });
21083
21084 editor.update(cx, |editor, cx| {
21085 let autoscroll = match scroll_offset {
21086 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21087 None => Autoscroll::newest(),
21088 };
21089 let nav_history = editor.nav_history.take();
21090 editor.change_selections(
21091 SelectionEffects::scroll(autoscroll),
21092 window,
21093 cx,
21094 |s| {
21095 s.select_ranges(ranges);
21096 },
21097 );
21098 editor.nav_history = nav_history;
21099 });
21100 }
21101 })
21102 });
21103 }
21104
21105 // For now, don't allow opening excerpts in buffers that aren't backed by
21106 // regular project files.
21107 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21108 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21109 }
21110
21111 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21112 let snapshot = self.buffer.read(cx).read(cx);
21113 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21114 Some(
21115 ranges
21116 .iter()
21117 .map(move |range| {
21118 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21119 })
21120 .collect(),
21121 )
21122 }
21123
21124 fn selection_replacement_ranges(
21125 &self,
21126 range: Range<OffsetUtf16>,
21127 cx: &mut App,
21128 ) -> Vec<Range<OffsetUtf16>> {
21129 let selections = self.selections.all::<OffsetUtf16>(cx);
21130 let newest_selection = selections
21131 .iter()
21132 .max_by_key(|selection| selection.id)
21133 .unwrap();
21134 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21135 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21136 let snapshot = self.buffer.read(cx).read(cx);
21137 selections
21138 .into_iter()
21139 .map(|mut selection| {
21140 selection.start.0 =
21141 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21142 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21143 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21144 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21145 })
21146 .collect()
21147 }
21148
21149 fn report_editor_event(
21150 &self,
21151 reported_event: ReportEditorEvent,
21152 file_extension: Option<String>,
21153 cx: &App,
21154 ) {
21155 if cfg!(any(test, feature = "test-support")) {
21156 return;
21157 }
21158
21159 let Some(project) = &self.project else { return };
21160
21161 // If None, we are in a file without an extension
21162 let file = self
21163 .buffer
21164 .read(cx)
21165 .as_singleton()
21166 .and_then(|b| b.read(cx).file());
21167 let file_extension = file_extension.or(file
21168 .as_ref()
21169 .and_then(|file| Path::new(file.file_name(cx)).extension())
21170 .and_then(|e| e.to_str())
21171 .map(|a| a.to_string()));
21172
21173 let vim_mode = vim_enabled(cx);
21174
21175 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21176 let copilot_enabled = edit_predictions_provider
21177 == language::language_settings::EditPredictionProvider::Copilot;
21178 let copilot_enabled_for_language = self
21179 .buffer
21180 .read(cx)
21181 .language_settings(cx)
21182 .show_edit_predictions;
21183
21184 let project = project.read(cx);
21185 let event_type = reported_event.event_type();
21186
21187 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21188 telemetry::event!(
21189 event_type,
21190 type = if auto_saved {"autosave"} else {"manual"},
21191 file_extension,
21192 vim_mode,
21193 copilot_enabled,
21194 copilot_enabled_for_language,
21195 edit_predictions_provider,
21196 is_via_ssh = project.is_via_remote_server(),
21197 );
21198 } else {
21199 telemetry::event!(
21200 event_type,
21201 file_extension,
21202 vim_mode,
21203 copilot_enabled,
21204 copilot_enabled_for_language,
21205 edit_predictions_provider,
21206 is_via_ssh = project.is_via_remote_server(),
21207 );
21208 };
21209 }
21210
21211 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21212 /// with each line being an array of {text, highlight} objects.
21213 fn copy_highlight_json(
21214 &mut self,
21215 _: &CopyHighlightJson,
21216 window: &mut Window,
21217 cx: &mut Context<Self>,
21218 ) {
21219 #[derive(Serialize)]
21220 struct Chunk<'a> {
21221 text: String,
21222 highlight: Option<&'a str>,
21223 }
21224
21225 let snapshot = self.buffer.read(cx).snapshot(cx);
21226 let range = self
21227 .selected_text_range(false, window, cx)
21228 .and_then(|selection| {
21229 if selection.range.is_empty() {
21230 None
21231 } else {
21232 Some(selection.range)
21233 }
21234 })
21235 .unwrap_or_else(|| 0..snapshot.len());
21236
21237 let chunks = snapshot.chunks(range, true);
21238 let mut lines = Vec::new();
21239 let mut line: VecDeque<Chunk> = VecDeque::new();
21240
21241 let Some(style) = self.style.as_ref() else {
21242 return;
21243 };
21244
21245 for chunk in chunks {
21246 let highlight = chunk
21247 .syntax_highlight_id
21248 .and_then(|id| id.name(&style.syntax));
21249 let mut chunk_lines = chunk.text.split('\n').peekable();
21250 while let Some(text) = chunk_lines.next() {
21251 let mut merged_with_last_token = false;
21252 if let Some(last_token) = line.back_mut()
21253 && last_token.highlight == highlight
21254 {
21255 last_token.text.push_str(text);
21256 merged_with_last_token = true;
21257 }
21258
21259 if !merged_with_last_token {
21260 line.push_back(Chunk {
21261 text: text.into(),
21262 highlight,
21263 });
21264 }
21265
21266 if chunk_lines.peek().is_some() {
21267 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21268 line.pop_front();
21269 }
21270 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21271 line.pop_back();
21272 }
21273
21274 lines.push(mem::take(&mut line));
21275 }
21276 }
21277 }
21278
21279 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21280 return;
21281 };
21282 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21283 }
21284
21285 pub fn open_context_menu(
21286 &mut self,
21287 _: &OpenContextMenu,
21288 window: &mut Window,
21289 cx: &mut Context<Self>,
21290 ) {
21291 self.request_autoscroll(Autoscroll::newest(), cx);
21292 let position = self.selections.newest_display(cx).start;
21293 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21294 }
21295
21296 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21297 &self.inlay_hint_cache
21298 }
21299
21300 pub fn replay_insert_event(
21301 &mut self,
21302 text: &str,
21303 relative_utf16_range: Option<Range<isize>>,
21304 window: &mut Window,
21305 cx: &mut Context<Self>,
21306 ) {
21307 if !self.input_enabled {
21308 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21309 return;
21310 }
21311 if let Some(relative_utf16_range) = relative_utf16_range {
21312 let selections = self.selections.all::<OffsetUtf16>(cx);
21313 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21314 let new_ranges = selections.into_iter().map(|range| {
21315 let start = OffsetUtf16(
21316 range
21317 .head()
21318 .0
21319 .saturating_add_signed(relative_utf16_range.start),
21320 );
21321 let end = OffsetUtf16(
21322 range
21323 .head()
21324 .0
21325 .saturating_add_signed(relative_utf16_range.end),
21326 );
21327 start..end
21328 });
21329 s.select_ranges(new_ranges);
21330 });
21331 }
21332
21333 self.handle_input(text, window, cx);
21334 }
21335
21336 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21337 let Some(provider) = self.semantics_provider.as_ref() else {
21338 return false;
21339 };
21340
21341 let mut supports = false;
21342 self.buffer().update(cx, |this, cx| {
21343 this.for_each_buffer(|buffer| {
21344 supports |= provider.supports_inlay_hints(buffer, cx);
21345 });
21346 });
21347
21348 supports
21349 }
21350
21351 pub fn is_focused(&self, window: &Window) -> bool {
21352 self.focus_handle.is_focused(window)
21353 }
21354
21355 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21356 cx.emit(EditorEvent::Focused);
21357
21358 if let Some(descendant) = self
21359 .last_focused_descendant
21360 .take()
21361 .and_then(|descendant| descendant.upgrade())
21362 {
21363 window.focus(&descendant);
21364 } else {
21365 if let Some(blame) = self.blame.as_ref() {
21366 blame.update(cx, GitBlame::focus)
21367 }
21368
21369 self.blink_manager.update(cx, BlinkManager::enable);
21370 self.show_cursor_names(window, cx);
21371 self.buffer.update(cx, |buffer, cx| {
21372 buffer.finalize_last_transaction(cx);
21373 if self.leader_id.is_none() {
21374 buffer.set_active_selections(
21375 &self.selections.disjoint_anchors_arc(),
21376 self.selections.line_mode(),
21377 self.cursor_shape,
21378 cx,
21379 );
21380 }
21381 });
21382 }
21383 }
21384
21385 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21386 cx.emit(EditorEvent::FocusedIn)
21387 }
21388
21389 fn handle_focus_out(
21390 &mut self,
21391 event: FocusOutEvent,
21392 _window: &mut Window,
21393 cx: &mut Context<Self>,
21394 ) {
21395 if event.blurred != self.focus_handle {
21396 self.last_focused_descendant = Some(event.blurred);
21397 }
21398 self.selection_drag_state = SelectionDragState::None;
21399 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21400 }
21401
21402 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21403 self.blink_manager.update(cx, BlinkManager::disable);
21404 self.buffer
21405 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21406
21407 if let Some(blame) = self.blame.as_ref() {
21408 blame.update(cx, GitBlame::blur)
21409 }
21410 if !self.hover_state.focused(window, cx) {
21411 hide_hover(self, cx);
21412 }
21413 if !self
21414 .context_menu
21415 .borrow()
21416 .as_ref()
21417 .is_some_and(|context_menu| context_menu.focused(window, cx))
21418 {
21419 self.hide_context_menu(window, cx);
21420 }
21421 self.discard_edit_prediction(false, cx);
21422 cx.emit(EditorEvent::Blurred);
21423 cx.notify();
21424 }
21425
21426 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21427 let mut pending: String = window
21428 .pending_input_keystrokes()
21429 .into_iter()
21430 .flatten()
21431 .filter_map(|keystroke| {
21432 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21433 keystroke.key_char.clone()
21434 } else {
21435 None
21436 }
21437 })
21438 .collect();
21439
21440 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21441 pending = "".to_string();
21442 }
21443
21444 let existing_pending = self
21445 .text_highlights::<PendingInput>(cx)
21446 .map(|(_, ranges)| ranges.to_vec());
21447 if existing_pending.is_none() && pending.is_empty() {
21448 return;
21449 }
21450 let transaction =
21451 self.transact(window, cx, |this, window, cx| {
21452 let selections = this.selections.all::<usize>(cx);
21453 let edits = selections
21454 .iter()
21455 .map(|selection| (selection.end..selection.end, pending.clone()));
21456 this.edit(edits, cx);
21457 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21458 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21459 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21460 }));
21461 });
21462 if let Some(existing_ranges) = existing_pending {
21463 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21464 this.edit(edits, cx);
21465 }
21466 });
21467
21468 let snapshot = self.snapshot(window, cx);
21469 let ranges = self
21470 .selections
21471 .all::<usize>(cx)
21472 .into_iter()
21473 .map(|selection| {
21474 snapshot.buffer_snapshot.anchor_after(selection.end)
21475 ..snapshot
21476 .buffer_snapshot
21477 .anchor_before(selection.end + pending.len())
21478 })
21479 .collect();
21480
21481 if pending.is_empty() {
21482 self.clear_highlights::<PendingInput>(cx);
21483 } else {
21484 self.highlight_text::<PendingInput>(
21485 ranges,
21486 HighlightStyle {
21487 underline: Some(UnderlineStyle {
21488 thickness: px(1.),
21489 color: None,
21490 wavy: false,
21491 }),
21492 ..Default::default()
21493 },
21494 cx,
21495 );
21496 }
21497
21498 self.ime_transaction = self.ime_transaction.or(transaction);
21499 if let Some(transaction) = self.ime_transaction {
21500 self.buffer.update(cx, |buffer, cx| {
21501 buffer.group_until_transaction(transaction, cx);
21502 });
21503 }
21504
21505 if self.text_highlights::<PendingInput>(cx).is_none() {
21506 self.ime_transaction.take();
21507 }
21508 }
21509
21510 pub fn register_action_renderer(
21511 &mut self,
21512 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21513 ) -> Subscription {
21514 let id = self.next_editor_action_id.post_inc();
21515 self.editor_actions
21516 .borrow_mut()
21517 .insert(id, Box::new(listener));
21518
21519 let editor_actions = self.editor_actions.clone();
21520 Subscription::new(move || {
21521 editor_actions.borrow_mut().remove(&id);
21522 })
21523 }
21524
21525 pub fn register_action<A: Action>(
21526 &mut self,
21527 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21528 ) -> Subscription {
21529 let id = self.next_editor_action_id.post_inc();
21530 let listener = Arc::new(listener);
21531 self.editor_actions.borrow_mut().insert(
21532 id,
21533 Box::new(move |_, window, _| {
21534 let listener = listener.clone();
21535 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21536 let action = action.downcast_ref().unwrap();
21537 if phase == DispatchPhase::Bubble {
21538 listener(action, window, cx)
21539 }
21540 })
21541 }),
21542 );
21543
21544 let editor_actions = self.editor_actions.clone();
21545 Subscription::new(move || {
21546 editor_actions.borrow_mut().remove(&id);
21547 })
21548 }
21549
21550 pub fn file_header_size(&self) -> u32 {
21551 FILE_HEADER_HEIGHT
21552 }
21553
21554 pub fn restore(
21555 &mut self,
21556 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21557 window: &mut Window,
21558 cx: &mut Context<Self>,
21559 ) {
21560 let workspace = self.workspace();
21561 let project = self.project();
21562 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21563 let mut tasks = Vec::new();
21564 for (buffer_id, changes) in revert_changes {
21565 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21566 buffer.update(cx, |buffer, cx| {
21567 buffer.edit(
21568 changes
21569 .into_iter()
21570 .map(|(range, text)| (range, text.to_string())),
21571 None,
21572 cx,
21573 );
21574 });
21575
21576 if let Some(project) =
21577 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21578 {
21579 project.update(cx, |project, cx| {
21580 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21581 })
21582 }
21583 }
21584 }
21585 tasks
21586 });
21587 cx.spawn_in(window, async move |_, cx| {
21588 for (buffer, task) in save_tasks {
21589 let result = task.await;
21590 if result.is_err() {
21591 let Some(path) = buffer
21592 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21593 .ok()
21594 else {
21595 continue;
21596 };
21597 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21598 let Some(task) = cx
21599 .update_window_entity(workspace, |workspace, window, cx| {
21600 workspace
21601 .open_path_preview(path, None, false, false, false, window, cx)
21602 })
21603 .ok()
21604 else {
21605 continue;
21606 };
21607 task.await.log_err();
21608 }
21609 }
21610 }
21611 })
21612 .detach();
21613 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21614 selections.refresh()
21615 });
21616 }
21617
21618 pub fn to_pixel_point(
21619 &self,
21620 source: multi_buffer::Anchor,
21621 editor_snapshot: &EditorSnapshot,
21622 window: &mut Window,
21623 ) -> Option<gpui::Point<Pixels>> {
21624 let source_point = source.to_display_point(editor_snapshot);
21625 self.display_to_pixel_point(source_point, editor_snapshot, window)
21626 }
21627
21628 pub fn display_to_pixel_point(
21629 &self,
21630 source: DisplayPoint,
21631 editor_snapshot: &EditorSnapshot,
21632 window: &mut Window,
21633 ) -> Option<gpui::Point<Pixels>> {
21634 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21635 let text_layout_details = self.text_layout_details(window);
21636 let scroll_top = text_layout_details
21637 .scroll_anchor
21638 .scroll_position(editor_snapshot)
21639 .y;
21640
21641 if source.row().as_f32() < scroll_top.floor() {
21642 return None;
21643 }
21644 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21645 let source_y = line_height * (source.row().as_f32() - scroll_top);
21646 Some(gpui::Point::new(source_x, source_y))
21647 }
21648
21649 pub fn has_visible_completions_menu(&self) -> bool {
21650 !self.edit_prediction_preview_is_active()
21651 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21652 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21653 })
21654 }
21655
21656 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21657 if self.mode.is_minimap() {
21658 return;
21659 }
21660 self.addons
21661 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21662 }
21663
21664 pub fn unregister_addon<T: Addon>(&mut self) {
21665 self.addons.remove(&std::any::TypeId::of::<T>());
21666 }
21667
21668 pub fn addon<T: Addon>(&self) -> Option<&T> {
21669 let type_id = std::any::TypeId::of::<T>();
21670 self.addons
21671 .get(&type_id)
21672 .and_then(|item| item.to_any().downcast_ref::<T>())
21673 }
21674
21675 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21676 let type_id = std::any::TypeId::of::<T>();
21677 self.addons
21678 .get_mut(&type_id)
21679 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21680 }
21681
21682 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21683 let text_layout_details = self.text_layout_details(window);
21684 let style = &text_layout_details.editor_style;
21685 let font_id = window.text_system().resolve_font(&style.text.font());
21686 let font_size = style.text.font_size.to_pixels(window.rem_size());
21687 let line_height = style.text.line_height_in_pixels(window.rem_size());
21688 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21689 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21690
21691 CharacterDimensions {
21692 em_width,
21693 em_advance,
21694 line_height,
21695 }
21696 }
21697
21698 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21699 self.load_diff_task.clone()
21700 }
21701
21702 fn read_metadata_from_db(
21703 &mut self,
21704 item_id: u64,
21705 workspace_id: WorkspaceId,
21706 window: &mut Window,
21707 cx: &mut Context<Editor>,
21708 ) {
21709 if self.is_singleton(cx)
21710 && !self.mode.is_minimap()
21711 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21712 {
21713 let buffer_snapshot = OnceCell::new();
21714
21715 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21716 && !folds.is_empty()
21717 {
21718 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21719 self.fold_ranges(
21720 folds
21721 .into_iter()
21722 .map(|(start, end)| {
21723 snapshot.clip_offset(start, Bias::Left)
21724 ..snapshot.clip_offset(end, Bias::Right)
21725 })
21726 .collect(),
21727 false,
21728 window,
21729 cx,
21730 );
21731 }
21732
21733 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21734 && !selections.is_empty()
21735 {
21736 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21737 // skip adding the initial selection to selection history
21738 self.selection_history.mode = SelectionHistoryMode::Skipping;
21739 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21740 s.select_ranges(selections.into_iter().map(|(start, end)| {
21741 snapshot.clip_offset(start, Bias::Left)
21742 ..snapshot.clip_offset(end, Bias::Right)
21743 }));
21744 });
21745 self.selection_history.mode = SelectionHistoryMode::Normal;
21746 };
21747 }
21748
21749 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21750 }
21751
21752 fn update_lsp_data(
21753 &mut self,
21754 ignore_cache: bool,
21755 for_buffer: Option<BufferId>,
21756 window: &mut Window,
21757 cx: &mut Context<'_, Self>,
21758 ) {
21759 self.pull_diagnostics(for_buffer, window, cx);
21760 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21761 }
21762}
21763
21764fn edit_for_markdown_paste<'a>(
21765 buffer: &MultiBufferSnapshot,
21766 range: Range<usize>,
21767 to_insert: &'a str,
21768 url: Option<url::Url>,
21769) -> (Range<usize>, Cow<'a, str>) {
21770 if url.is_none() {
21771 return (range, Cow::Borrowed(to_insert));
21772 };
21773
21774 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21775
21776 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21777 Cow::Borrowed(to_insert)
21778 } else {
21779 Cow::Owned(format!("[{old_text}]({to_insert})"))
21780 };
21781 (range, new_text)
21782}
21783
21784fn vim_enabled(cx: &App) -> bool {
21785 vim_mode_setting::VimModeSetting::try_get(cx)
21786 .map(|vim_mode| vim_mode.0)
21787 .unwrap_or(false)
21788}
21789
21790fn process_completion_for_edit(
21791 completion: &Completion,
21792 intent: CompletionIntent,
21793 buffer: &Entity<Buffer>,
21794 cursor_position: &text::Anchor,
21795 cx: &mut Context<Editor>,
21796) -> CompletionEdit {
21797 let buffer = buffer.read(cx);
21798 let buffer_snapshot = buffer.snapshot();
21799 let (snippet, new_text) = if completion.is_snippet() {
21800 // Workaround for typescript language server issues so that methods don't expand within
21801 // strings and functions with type expressions. The previous point is used because the query
21802 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21803 let mut snippet_source = completion.new_text.clone();
21804 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21805 previous_point.column = previous_point.column.saturating_sub(1);
21806 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21807 && scope.prefers_label_for_snippet_in_completion()
21808 && let Some(label) = completion.label()
21809 && matches!(
21810 completion.kind(),
21811 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21812 )
21813 {
21814 snippet_source = label;
21815 }
21816 match Snippet::parse(&snippet_source).log_err() {
21817 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21818 None => (None, completion.new_text.clone()),
21819 }
21820 } else {
21821 (None, completion.new_text.clone())
21822 };
21823
21824 let mut range_to_replace = {
21825 let replace_range = &completion.replace_range;
21826 if let CompletionSource::Lsp {
21827 insert_range: Some(insert_range),
21828 ..
21829 } = &completion.source
21830 {
21831 debug_assert_eq!(
21832 insert_range.start, replace_range.start,
21833 "insert_range and replace_range should start at the same position"
21834 );
21835 debug_assert!(
21836 insert_range
21837 .start
21838 .cmp(cursor_position, &buffer_snapshot)
21839 .is_le(),
21840 "insert_range should start before or at cursor position"
21841 );
21842 debug_assert!(
21843 replace_range
21844 .start
21845 .cmp(cursor_position, &buffer_snapshot)
21846 .is_le(),
21847 "replace_range should start before or at cursor position"
21848 );
21849
21850 let should_replace = match intent {
21851 CompletionIntent::CompleteWithInsert => false,
21852 CompletionIntent::CompleteWithReplace => true,
21853 CompletionIntent::Complete | CompletionIntent::Compose => {
21854 let insert_mode =
21855 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21856 .completions
21857 .lsp_insert_mode;
21858 match insert_mode {
21859 LspInsertMode::Insert => false,
21860 LspInsertMode::Replace => true,
21861 LspInsertMode::ReplaceSubsequence => {
21862 let mut text_to_replace = buffer.chars_for_range(
21863 buffer.anchor_before(replace_range.start)
21864 ..buffer.anchor_after(replace_range.end),
21865 );
21866 let mut current_needle = text_to_replace.next();
21867 for haystack_ch in completion.label.text.chars() {
21868 if let Some(needle_ch) = current_needle
21869 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21870 {
21871 current_needle = text_to_replace.next();
21872 }
21873 }
21874 current_needle.is_none()
21875 }
21876 LspInsertMode::ReplaceSuffix => {
21877 if replace_range
21878 .end
21879 .cmp(cursor_position, &buffer_snapshot)
21880 .is_gt()
21881 {
21882 let range_after_cursor = *cursor_position..replace_range.end;
21883 let text_after_cursor = buffer
21884 .text_for_range(
21885 buffer.anchor_before(range_after_cursor.start)
21886 ..buffer.anchor_after(range_after_cursor.end),
21887 )
21888 .collect::<String>()
21889 .to_ascii_lowercase();
21890 completion
21891 .label
21892 .text
21893 .to_ascii_lowercase()
21894 .ends_with(&text_after_cursor)
21895 } else {
21896 true
21897 }
21898 }
21899 }
21900 }
21901 };
21902
21903 if should_replace {
21904 replace_range.clone()
21905 } else {
21906 insert_range.clone()
21907 }
21908 } else {
21909 replace_range.clone()
21910 }
21911 };
21912
21913 if range_to_replace
21914 .end
21915 .cmp(cursor_position, &buffer_snapshot)
21916 .is_lt()
21917 {
21918 range_to_replace.end = *cursor_position;
21919 }
21920
21921 CompletionEdit {
21922 new_text,
21923 replace_range: range_to_replace.to_offset(buffer),
21924 snippet,
21925 }
21926}
21927
21928struct CompletionEdit {
21929 new_text: String,
21930 replace_range: Range<usize>,
21931 snippet: Option<Snippet>,
21932}
21933
21934fn insert_extra_newline_brackets(
21935 buffer: &MultiBufferSnapshot,
21936 range: Range<usize>,
21937 language: &language::LanguageScope,
21938) -> bool {
21939 let leading_whitespace_len = buffer
21940 .reversed_chars_at(range.start)
21941 .take_while(|c| c.is_whitespace() && *c != '\n')
21942 .map(|c| c.len_utf8())
21943 .sum::<usize>();
21944 let trailing_whitespace_len = buffer
21945 .chars_at(range.end)
21946 .take_while(|c| c.is_whitespace() && *c != '\n')
21947 .map(|c| c.len_utf8())
21948 .sum::<usize>();
21949 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21950
21951 language.brackets().any(|(pair, enabled)| {
21952 let pair_start = pair.start.trim_end();
21953 let pair_end = pair.end.trim_start();
21954
21955 enabled
21956 && pair.newline
21957 && buffer.contains_str_at(range.end, pair_end)
21958 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21959 })
21960}
21961
21962fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21963 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21964 [(buffer, range, _)] => (*buffer, range.clone()),
21965 _ => return false,
21966 };
21967 let pair = {
21968 let mut result: Option<BracketMatch> = None;
21969
21970 for pair in buffer
21971 .all_bracket_ranges(range.clone())
21972 .filter(move |pair| {
21973 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21974 })
21975 {
21976 let len = pair.close_range.end - pair.open_range.start;
21977
21978 if let Some(existing) = &result {
21979 let existing_len = existing.close_range.end - existing.open_range.start;
21980 if len > existing_len {
21981 continue;
21982 }
21983 }
21984
21985 result = Some(pair);
21986 }
21987
21988 result
21989 };
21990 let Some(pair) = pair else {
21991 return false;
21992 };
21993 pair.newline_only
21994 && buffer
21995 .chars_for_range(pair.open_range.end..range.start)
21996 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21997 .all(|c| c.is_whitespace() && c != '\n')
21998}
21999
22000fn update_uncommitted_diff_for_buffer(
22001 editor: Entity<Editor>,
22002 project: &Entity<Project>,
22003 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22004 buffer: Entity<MultiBuffer>,
22005 cx: &mut App,
22006) -> Task<()> {
22007 let mut tasks = Vec::new();
22008 project.update(cx, |project, cx| {
22009 for buffer in buffers {
22010 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22011 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22012 }
22013 }
22014 });
22015 cx.spawn(async move |cx| {
22016 let diffs = future::join_all(tasks).await;
22017 if editor
22018 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22019 .unwrap_or(false)
22020 {
22021 return;
22022 }
22023
22024 buffer
22025 .update(cx, |buffer, cx| {
22026 for diff in diffs.into_iter().flatten() {
22027 buffer.add_diff(diff, cx);
22028 }
22029 })
22030 .ok();
22031 })
22032}
22033
22034fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22035 let tab_size = tab_size.get() as usize;
22036 let mut width = offset;
22037
22038 for ch in text.chars() {
22039 width += if ch == '\t' {
22040 tab_size - (width % tab_size)
22041 } else {
22042 1
22043 };
22044 }
22045
22046 width - offset
22047}
22048
22049#[cfg(test)]
22050mod tests {
22051 use super::*;
22052
22053 #[test]
22054 fn test_string_size_with_expanded_tabs() {
22055 let nz = |val| NonZeroU32::new(val).unwrap();
22056 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22057 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22058 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22059 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22060 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22061 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22062 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22063 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22064 }
22065}
22066
22067/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22068struct WordBreakingTokenizer<'a> {
22069 input: &'a str,
22070}
22071
22072impl<'a> WordBreakingTokenizer<'a> {
22073 fn new(input: &'a str) -> Self {
22074 Self { input }
22075 }
22076}
22077
22078fn is_char_ideographic(ch: char) -> bool {
22079 use unicode_script::Script::*;
22080 use unicode_script::UnicodeScript;
22081 matches!(ch.script(), Han | Tangut | Yi)
22082}
22083
22084fn is_grapheme_ideographic(text: &str) -> bool {
22085 text.chars().any(is_char_ideographic)
22086}
22087
22088fn is_grapheme_whitespace(text: &str) -> bool {
22089 text.chars().any(|x| x.is_whitespace())
22090}
22091
22092fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22093 text.chars()
22094 .next()
22095 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22096}
22097
22098#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22099enum WordBreakToken<'a> {
22100 Word { token: &'a str, grapheme_len: usize },
22101 InlineWhitespace { token: &'a str, grapheme_len: usize },
22102 Newline,
22103}
22104
22105impl<'a> Iterator for WordBreakingTokenizer<'a> {
22106 /// Yields a span, the count of graphemes in the token, and whether it was
22107 /// whitespace. Note that it also breaks at word boundaries.
22108 type Item = WordBreakToken<'a>;
22109
22110 fn next(&mut self) -> Option<Self::Item> {
22111 use unicode_segmentation::UnicodeSegmentation;
22112 if self.input.is_empty() {
22113 return None;
22114 }
22115
22116 let mut iter = self.input.graphemes(true).peekable();
22117 let mut offset = 0;
22118 let mut grapheme_len = 0;
22119 if let Some(first_grapheme) = iter.next() {
22120 let is_newline = first_grapheme == "\n";
22121 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22122 offset += first_grapheme.len();
22123 grapheme_len += 1;
22124 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22125 if let Some(grapheme) = iter.peek().copied()
22126 && should_stay_with_preceding_ideograph(grapheme)
22127 {
22128 offset += grapheme.len();
22129 grapheme_len += 1;
22130 }
22131 } else {
22132 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22133 let mut next_word_bound = words.peek().copied();
22134 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22135 next_word_bound = words.next();
22136 }
22137 while let Some(grapheme) = iter.peek().copied() {
22138 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22139 break;
22140 };
22141 if is_grapheme_whitespace(grapheme) != is_whitespace
22142 || (grapheme == "\n") != is_newline
22143 {
22144 break;
22145 };
22146 offset += grapheme.len();
22147 grapheme_len += 1;
22148 iter.next();
22149 }
22150 }
22151 let token = &self.input[..offset];
22152 self.input = &self.input[offset..];
22153 if token == "\n" {
22154 Some(WordBreakToken::Newline)
22155 } else if is_whitespace {
22156 Some(WordBreakToken::InlineWhitespace {
22157 token,
22158 grapheme_len,
22159 })
22160 } else {
22161 Some(WordBreakToken::Word {
22162 token,
22163 grapheme_len,
22164 })
22165 }
22166 } else {
22167 None
22168 }
22169 }
22170}
22171
22172#[test]
22173fn test_word_breaking_tokenizer() {
22174 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22175 ("", &[]),
22176 (" ", &[whitespace(" ", 2)]),
22177 ("Ʒ", &[word("Ʒ", 1)]),
22178 ("Ǽ", &[word("Ǽ", 1)]),
22179 ("⋑", &[word("⋑", 1)]),
22180 ("⋑⋑", &[word("⋑⋑", 2)]),
22181 (
22182 "原理,进而",
22183 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22184 ),
22185 (
22186 "hello world",
22187 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22188 ),
22189 (
22190 "hello, world",
22191 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22192 ),
22193 (
22194 " hello world",
22195 &[
22196 whitespace(" ", 2),
22197 word("hello", 5),
22198 whitespace(" ", 1),
22199 word("world", 5),
22200 ],
22201 ),
22202 (
22203 "这是什么 \n 钢笔",
22204 &[
22205 word("这", 1),
22206 word("是", 1),
22207 word("什", 1),
22208 word("么", 1),
22209 whitespace(" ", 1),
22210 newline(),
22211 whitespace(" ", 1),
22212 word("钢", 1),
22213 word("笔", 1),
22214 ],
22215 ),
22216 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22217 ];
22218
22219 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22220 WordBreakToken::Word {
22221 token,
22222 grapheme_len,
22223 }
22224 }
22225
22226 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22227 WordBreakToken::InlineWhitespace {
22228 token,
22229 grapheme_len,
22230 }
22231 }
22232
22233 fn newline() -> WordBreakToken<'static> {
22234 WordBreakToken::Newline
22235 }
22236
22237 for (input, result) in tests {
22238 assert_eq!(
22239 WordBreakingTokenizer::new(input)
22240 .collect::<Vec<_>>()
22241 .as_slice(),
22242 *result,
22243 );
22244 }
22245}
22246
22247fn wrap_with_prefix(
22248 first_line_prefix: String,
22249 subsequent_lines_prefix: String,
22250 unwrapped_text: String,
22251 wrap_column: usize,
22252 tab_size: NonZeroU32,
22253 preserve_existing_whitespace: bool,
22254) -> String {
22255 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22256 let subsequent_lines_prefix_len =
22257 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22258 let mut wrapped_text = String::new();
22259 let mut current_line = first_line_prefix;
22260 let mut is_first_line = true;
22261
22262 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22263 let mut current_line_len = first_line_prefix_len;
22264 let mut in_whitespace = false;
22265 for token in tokenizer {
22266 let have_preceding_whitespace = in_whitespace;
22267 match token {
22268 WordBreakToken::Word {
22269 token,
22270 grapheme_len,
22271 } => {
22272 in_whitespace = false;
22273 let current_prefix_len = if is_first_line {
22274 first_line_prefix_len
22275 } else {
22276 subsequent_lines_prefix_len
22277 };
22278 if current_line_len + grapheme_len > wrap_column
22279 && current_line_len != current_prefix_len
22280 {
22281 wrapped_text.push_str(current_line.trim_end());
22282 wrapped_text.push('\n');
22283 is_first_line = false;
22284 current_line = subsequent_lines_prefix.clone();
22285 current_line_len = subsequent_lines_prefix_len;
22286 }
22287 current_line.push_str(token);
22288 current_line_len += grapheme_len;
22289 }
22290 WordBreakToken::InlineWhitespace {
22291 mut token,
22292 mut grapheme_len,
22293 } => {
22294 in_whitespace = true;
22295 if have_preceding_whitespace && !preserve_existing_whitespace {
22296 continue;
22297 }
22298 if !preserve_existing_whitespace {
22299 token = " ";
22300 grapheme_len = 1;
22301 }
22302 let current_prefix_len = if is_first_line {
22303 first_line_prefix_len
22304 } else {
22305 subsequent_lines_prefix_len
22306 };
22307 if current_line_len + grapheme_len > wrap_column {
22308 wrapped_text.push_str(current_line.trim_end());
22309 wrapped_text.push('\n');
22310 is_first_line = false;
22311 current_line = subsequent_lines_prefix.clone();
22312 current_line_len = subsequent_lines_prefix_len;
22313 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22314 current_line.push_str(token);
22315 current_line_len += grapheme_len;
22316 }
22317 }
22318 WordBreakToken::Newline => {
22319 in_whitespace = true;
22320 let current_prefix_len = if is_first_line {
22321 first_line_prefix_len
22322 } else {
22323 subsequent_lines_prefix_len
22324 };
22325 if preserve_existing_whitespace {
22326 wrapped_text.push_str(current_line.trim_end());
22327 wrapped_text.push('\n');
22328 is_first_line = false;
22329 current_line = subsequent_lines_prefix.clone();
22330 current_line_len = subsequent_lines_prefix_len;
22331 } else if have_preceding_whitespace {
22332 continue;
22333 } else if current_line_len + 1 > wrap_column
22334 && current_line_len != current_prefix_len
22335 {
22336 wrapped_text.push_str(current_line.trim_end());
22337 wrapped_text.push('\n');
22338 is_first_line = false;
22339 current_line = subsequent_lines_prefix.clone();
22340 current_line_len = subsequent_lines_prefix_len;
22341 } else if current_line_len != current_prefix_len {
22342 current_line.push(' ');
22343 current_line_len += 1;
22344 }
22345 }
22346 }
22347 }
22348
22349 if !current_line.is_empty() {
22350 wrapped_text.push_str(¤t_line);
22351 }
22352 wrapped_text
22353}
22354
22355#[test]
22356fn test_wrap_with_prefix() {
22357 assert_eq!(
22358 wrap_with_prefix(
22359 "# ".to_string(),
22360 "# ".to_string(),
22361 "abcdefg".to_string(),
22362 4,
22363 NonZeroU32::new(4).unwrap(),
22364 false,
22365 ),
22366 "# abcdefg"
22367 );
22368 assert_eq!(
22369 wrap_with_prefix(
22370 "".to_string(),
22371 "".to_string(),
22372 "\thello world".to_string(),
22373 8,
22374 NonZeroU32::new(4).unwrap(),
22375 false,
22376 ),
22377 "hello\nworld"
22378 );
22379 assert_eq!(
22380 wrap_with_prefix(
22381 "// ".to_string(),
22382 "// ".to_string(),
22383 "xx \nyy zz aa bb cc".to_string(),
22384 12,
22385 NonZeroU32::new(4).unwrap(),
22386 false,
22387 ),
22388 "// xx yy zz\n// aa bb cc"
22389 );
22390 assert_eq!(
22391 wrap_with_prefix(
22392 String::new(),
22393 String::new(),
22394 "这是什么 \n 钢笔".to_string(),
22395 3,
22396 NonZeroU32::new(4).unwrap(),
22397 false,
22398 ),
22399 "这是什\n么 钢\n笔"
22400 );
22401}
22402
22403pub trait CollaborationHub {
22404 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22405 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22406 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22407}
22408
22409impl CollaborationHub for Entity<Project> {
22410 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22411 self.read(cx).collaborators()
22412 }
22413
22414 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22415 self.read(cx).user_store().read(cx).participant_indices()
22416 }
22417
22418 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22419 let this = self.read(cx);
22420 let user_ids = this.collaborators().values().map(|c| c.user_id);
22421 this.user_store().read(cx).participant_names(user_ids, cx)
22422 }
22423}
22424
22425pub trait SemanticsProvider {
22426 fn hover(
22427 &self,
22428 buffer: &Entity<Buffer>,
22429 position: text::Anchor,
22430 cx: &mut App,
22431 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22432
22433 fn inline_values(
22434 &self,
22435 buffer_handle: Entity<Buffer>,
22436 range: Range<text::Anchor>,
22437 cx: &mut App,
22438 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22439
22440 fn inlay_hints(
22441 &self,
22442 buffer_handle: Entity<Buffer>,
22443 range: Range<text::Anchor>,
22444 cx: &mut App,
22445 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22446
22447 fn resolve_inlay_hint(
22448 &self,
22449 hint: InlayHint,
22450 buffer_handle: Entity<Buffer>,
22451 server_id: LanguageServerId,
22452 cx: &mut App,
22453 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22454
22455 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22456
22457 fn document_highlights(
22458 &self,
22459 buffer: &Entity<Buffer>,
22460 position: text::Anchor,
22461 cx: &mut App,
22462 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22463
22464 fn definitions(
22465 &self,
22466 buffer: &Entity<Buffer>,
22467 position: text::Anchor,
22468 kind: GotoDefinitionKind,
22469 cx: &mut App,
22470 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22471
22472 fn range_for_rename(
22473 &self,
22474 buffer: &Entity<Buffer>,
22475 position: text::Anchor,
22476 cx: &mut App,
22477 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22478
22479 fn perform_rename(
22480 &self,
22481 buffer: &Entity<Buffer>,
22482 position: text::Anchor,
22483 new_name: String,
22484 cx: &mut App,
22485 ) -> Option<Task<Result<ProjectTransaction>>>;
22486}
22487
22488pub trait CompletionProvider {
22489 fn completions(
22490 &self,
22491 excerpt_id: ExcerptId,
22492 buffer: &Entity<Buffer>,
22493 buffer_position: text::Anchor,
22494 trigger: CompletionContext,
22495 window: &mut Window,
22496 cx: &mut Context<Editor>,
22497 ) -> Task<Result<Vec<CompletionResponse>>>;
22498
22499 fn resolve_completions(
22500 &self,
22501 _buffer: Entity<Buffer>,
22502 _completion_indices: Vec<usize>,
22503 _completions: Rc<RefCell<Box<[Completion]>>>,
22504 _cx: &mut Context<Editor>,
22505 ) -> Task<Result<bool>> {
22506 Task::ready(Ok(false))
22507 }
22508
22509 fn apply_additional_edits_for_completion(
22510 &self,
22511 _buffer: Entity<Buffer>,
22512 _completions: Rc<RefCell<Box<[Completion]>>>,
22513 _completion_index: usize,
22514 _push_to_history: bool,
22515 _cx: &mut Context<Editor>,
22516 ) -> Task<Result<Option<language::Transaction>>> {
22517 Task::ready(Ok(None))
22518 }
22519
22520 fn is_completion_trigger(
22521 &self,
22522 buffer: &Entity<Buffer>,
22523 position: language::Anchor,
22524 text: &str,
22525 trigger_in_words: bool,
22526 menu_is_open: bool,
22527 cx: &mut Context<Editor>,
22528 ) -> bool;
22529
22530 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22531
22532 fn sort_completions(&self) -> bool {
22533 true
22534 }
22535
22536 fn filter_completions(&self) -> bool {
22537 true
22538 }
22539}
22540
22541pub trait CodeActionProvider {
22542 fn id(&self) -> Arc<str>;
22543
22544 fn code_actions(
22545 &self,
22546 buffer: &Entity<Buffer>,
22547 range: Range<text::Anchor>,
22548 window: &mut Window,
22549 cx: &mut App,
22550 ) -> Task<Result<Vec<CodeAction>>>;
22551
22552 fn apply_code_action(
22553 &self,
22554 buffer_handle: Entity<Buffer>,
22555 action: CodeAction,
22556 excerpt_id: ExcerptId,
22557 push_to_history: bool,
22558 window: &mut Window,
22559 cx: &mut App,
22560 ) -> Task<Result<ProjectTransaction>>;
22561}
22562
22563impl CodeActionProvider for Entity<Project> {
22564 fn id(&self) -> Arc<str> {
22565 "project".into()
22566 }
22567
22568 fn code_actions(
22569 &self,
22570 buffer: &Entity<Buffer>,
22571 range: Range<text::Anchor>,
22572 _window: &mut Window,
22573 cx: &mut App,
22574 ) -> Task<Result<Vec<CodeAction>>> {
22575 self.update(cx, |project, cx| {
22576 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22577 let code_actions = project.code_actions(buffer, range, None, cx);
22578 cx.background_spawn(async move {
22579 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22580 Ok(code_lens_actions
22581 .context("code lens fetch")?
22582 .into_iter()
22583 .flatten()
22584 .chain(
22585 code_actions
22586 .context("code action fetch")?
22587 .into_iter()
22588 .flatten(),
22589 )
22590 .collect())
22591 })
22592 })
22593 }
22594
22595 fn apply_code_action(
22596 &self,
22597 buffer_handle: Entity<Buffer>,
22598 action: CodeAction,
22599 _excerpt_id: ExcerptId,
22600 push_to_history: bool,
22601 _window: &mut Window,
22602 cx: &mut App,
22603 ) -> Task<Result<ProjectTransaction>> {
22604 self.update(cx, |project, cx| {
22605 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22606 })
22607 }
22608}
22609
22610fn snippet_completions(
22611 project: &Project,
22612 buffer: &Entity<Buffer>,
22613 buffer_position: text::Anchor,
22614 cx: &mut App,
22615) -> Task<Result<CompletionResponse>> {
22616 let languages = buffer.read(cx).languages_at(buffer_position);
22617 let snippet_store = project.snippets().read(cx);
22618
22619 let scopes: Vec<_> = languages
22620 .iter()
22621 .filter_map(|language| {
22622 let language_name = language.lsp_id();
22623 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22624
22625 if snippets.is_empty() {
22626 None
22627 } else {
22628 Some((language.default_scope(), snippets))
22629 }
22630 })
22631 .collect();
22632
22633 if scopes.is_empty() {
22634 return Task::ready(Ok(CompletionResponse {
22635 completions: vec![],
22636 display_options: CompletionDisplayOptions::default(),
22637 is_incomplete: false,
22638 }));
22639 }
22640
22641 let snapshot = buffer.read(cx).text_snapshot();
22642 let chars: String = snapshot
22643 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22644 .collect();
22645 let executor = cx.background_executor().clone();
22646
22647 cx.background_spawn(async move {
22648 let mut is_incomplete = false;
22649 let mut completions: Vec<Completion> = Vec::new();
22650 for (scope, snippets) in scopes.into_iter() {
22651 let classifier =
22652 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22653 let mut last_word = chars
22654 .chars()
22655 .take_while(|c| classifier.is_word(*c))
22656 .collect::<String>();
22657 last_word = last_word.chars().rev().collect();
22658
22659 if last_word.is_empty() {
22660 return Ok(CompletionResponse {
22661 completions: vec![],
22662 display_options: CompletionDisplayOptions::default(),
22663 is_incomplete: true,
22664 });
22665 }
22666
22667 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22668 let to_lsp = |point: &text::Anchor| {
22669 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22670 point_to_lsp(end)
22671 };
22672 let lsp_end = to_lsp(&buffer_position);
22673
22674 let candidates = snippets
22675 .iter()
22676 .enumerate()
22677 .flat_map(|(ix, snippet)| {
22678 snippet
22679 .prefix
22680 .iter()
22681 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22682 })
22683 .collect::<Vec<StringMatchCandidate>>();
22684
22685 const MAX_RESULTS: usize = 100;
22686 let mut matches = fuzzy::match_strings(
22687 &candidates,
22688 &last_word,
22689 last_word.chars().any(|c| c.is_uppercase()),
22690 true,
22691 MAX_RESULTS,
22692 &Default::default(),
22693 executor.clone(),
22694 )
22695 .await;
22696
22697 if matches.len() >= MAX_RESULTS {
22698 is_incomplete = true;
22699 }
22700
22701 // Remove all candidates where the query's start does not match the start of any word in the candidate
22702 if let Some(query_start) = last_word.chars().next() {
22703 matches.retain(|string_match| {
22704 split_words(&string_match.string).any(|word| {
22705 // Check that the first codepoint of the word as lowercase matches the first
22706 // codepoint of the query as lowercase
22707 word.chars()
22708 .flat_map(|codepoint| codepoint.to_lowercase())
22709 .zip(query_start.to_lowercase())
22710 .all(|(word_cp, query_cp)| word_cp == query_cp)
22711 })
22712 });
22713 }
22714
22715 let matched_strings = matches
22716 .into_iter()
22717 .map(|m| m.string)
22718 .collect::<HashSet<_>>();
22719
22720 completions.extend(snippets.iter().filter_map(|snippet| {
22721 let matching_prefix = snippet
22722 .prefix
22723 .iter()
22724 .find(|prefix| matched_strings.contains(*prefix))?;
22725 let start = as_offset - last_word.len();
22726 let start = snapshot.anchor_before(start);
22727 let range = start..buffer_position;
22728 let lsp_start = to_lsp(&start);
22729 let lsp_range = lsp::Range {
22730 start: lsp_start,
22731 end: lsp_end,
22732 };
22733 Some(Completion {
22734 replace_range: range,
22735 new_text: snippet.body.clone(),
22736 source: CompletionSource::Lsp {
22737 insert_range: None,
22738 server_id: LanguageServerId(usize::MAX),
22739 resolved: true,
22740 lsp_completion: Box::new(lsp::CompletionItem {
22741 label: snippet.prefix.first().unwrap().clone(),
22742 kind: Some(CompletionItemKind::SNIPPET),
22743 label_details: snippet.description.as_ref().map(|description| {
22744 lsp::CompletionItemLabelDetails {
22745 detail: Some(description.clone()),
22746 description: None,
22747 }
22748 }),
22749 insert_text_format: Some(InsertTextFormat::SNIPPET),
22750 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22751 lsp::InsertReplaceEdit {
22752 new_text: snippet.body.clone(),
22753 insert: lsp_range,
22754 replace: lsp_range,
22755 },
22756 )),
22757 filter_text: Some(snippet.body.clone()),
22758 sort_text: Some(char::MAX.to_string()),
22759 ..lsp::CompletionItem::default()
22760 }),
22761 lsp_defaults: None,
22762 },
22763 label: CodeLabel {
22764 text: matching_prefix.clone(),
22765 runs: Vec::new(),
22766 filter_range: 0..matching_prefix.len(),
22767 },
22768 icon_path: None,
22769 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22770 single_line: snippet.name.clone().into(),
22771 plain_text: snippet
22772 .description
22773 .clone()
22774 .map(|description| description.into()),
22775 }),
22776 insert_text_mode: None,
22777 confirm: None,
22778 })
22779 }))
22780 }
22781
22782 Ok(CompletionResponse {
22783 completions,
22784 display_options: CompletionDisplayOptions::default(),
22785 is_incomplete,
22786 })
22787 })
22788}
22789
22790impl CompletionProvider for Entity<Project> {
22791 fn completions(
22792 &self,
22793 _excerpt_id: ExcerptId,
22794 buffer: &Entity<Buffer>,
22795 buffer_position: text::Anchor,
22796 options: CompletionContext,
22797 _window: &mut Window,
22798 cx: &mut Context<Editor>,
22799 ) -> Task<Result<Vec<CompletionResponse>>> {
22800 self.update(cx, |project, cx| {
22801 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22802 let project_completions = project.completions(buffer, buffer_position, options, cx);
22803 cx.background_spawn(async move {
22804 let mut responses = project_completions.await?;
22805 let snippets = snippets.await?;
22806 if !snippets.completions.is_empty() {
22807 responses.push(snippets);
22808 }
22809 Ok(responses)
22810 })
22811 })
22812 }
22813
22814 fn resolve_completions(
22815 &self,
22816 buffer: Entity<Buffer>,
22817 completion_indices: Vec<usize>,
22818 completions: Rc<RefCell<Box<[Completion]>>>,
22819 cx: &mut Context<Editor>,
22820 ) -> Task<Result<bool>> {
22821 self.update(cx, |project, cx| {
22822 project.lsp_store().update(cx, |lsp_store, cx| {
22823 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22824 })
22825 })
22826 }
22827
22828 fn apply_additional_edits_for_completion(
22829 &self,
22830 buffer: Entity<Buffer>,
22831 completions: Rc<RefCell<Box<[Completion]>>>,
22832 completion_index: usize,
22833 push_to_history: bool,
22834 cx: &mut Context<Editor>,
22835 ) -> Task<Result<Option<language::Transaction>>> {
22836 self.update(cx, |project, cx| {
22837 project.lsp_store().update(cx, |lsp_store, cx| {
22838 lsp_store.apply_additional_edits_for_completion(
22839 buffer,
22840 completions,
22841 completion_index,
22842 push_to_history,
22843 cx,
22844 )
22845 })
22846 })
22847 }
22848
22849 fn is_completion_trigger(
22850 &self,
22851 buffer: &Entity<Buffer>,
22852 position: language::Anchor,
22853 text: &str,
22854 trigger_in_words: bool,
22855 menu_is_open: bool,
22856 cx: &mut Context<Editor>,
22857 ) -> bool {
22858 let mut chars = text.chars();
22859 let char = if let Some(char) = chars.next() {
22860 char
22861 } else {
22862 return false;
22863 };
22864 if chars.next().is_some() {
22865 return false;
22866 }
22867
22868 let buffer = buffer.read(cx);
22869 let snapshot = buffer.snapshot();
22870 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22871 return false;
22872 }
22873 let classifier = snapshot
22874 .char_classifier_at(position)
22875 .scope_context(Some(CharScopeContext::Completion));
22876 if trigger_in_words && classifier.is_word(char) {
22877 return true;
22878 }
22879
22880 buffer.completion_triggers().contains(text)
22881 }
22882}
22883
22884impl SemanticsProvider for Entity<Project> {
22885 fn hover(
22886 &self,
22887 buffer: &Entity<Buffer>,
22888 position: text::Anchor,
22889 cx: &mut App,
22890 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22891 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22892 }
22893
22894 fn document_highlights(
22895 &self,
22896 buffer: &Entity<Buffer>,
22897 position: text::Anchor,
22898 cx: &mut App,
22899 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22900 Some(self.update(cx, |project, cx| {
22901 project.document_highlights(buffer, position, cx)
22902 }))
22903 }
22904
22905 fn definitions(
22906 &self,
22907 buffer: &Entity<Buffer>,
22908 position: text::Anchor,
22909 kind: GotoDefinitionKind,
22910 cx: &mut App,
22911 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22912 Some(self.update(cx, |project, cx| match kind {
22913 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22914 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22915 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22916 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22917 }))
22918 }
22919
22920 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22921 self.update(cx, |project, cx| {
22922 if project
22923 .active_debug_session(cx)
22924 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22925 {
22926 return true;
22927 }
22928
22929 buffer.update(cx, |buffer, cx| {
22930 project.any_language_server_supports_inlay_hints(buffer, cx)
22931 })
22932 })
22933 }
22934
22935 fn inline_values(
22936 &self,
22937 buffer_handle: Entity<Buffer>,
22938 range: Range<text::Anchor>,
22939 cx: &mut App,
22940 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22941 self.update(cx, |project, cx| {
22942 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22943
22944 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22945 })
22946 }
22947
22948 fn inlay_hints(
22949 &self,
22950 buffer_handle: Entity<Buffer>,
22951 range: Range<text::Anchor>,
22952 cx: &mut App,
22953 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22954 Some(self.update(cx, |project, cx| {
22955 project.inlay_hints(buffer_handle, range, cx)
22956 }))
22957 }
22958
22959 fn resolve_inlay_hint(
22960 &self,
22961 hint: InlayHint,
22962 buffer_handle: Entity<Buffer>,
22963 server_id: LanguageServerId,
22964 cx: &mut App,
22965 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22966 Some(self.update(cx, |project, cx| {
22967 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22968 }))
22969 }
22970
22971 fn range_for_rename(
22972 &self,
22973 buffer: &Entity<Buffer>,
22974 position: text::Anchor,
22975 cx: &mut App,
22976 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22977 Some(self.update(cx, |project, cx| {
22978 let buffer = buffer.clone();
22979 let task = project.prepare_rename(buffer.clone(), position, cx);
22980 cx.spawn(async move |_, cx| {
22981 Ok(match task.await? {
22982 PrepareRenameResponse::Success(range) => Some(range),
22983 PrepareRenameResponse::InvalidPosition => None,
22984 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22985 // Fallback on using TreeSitter info to determine identifier range
22986 buffer.read_with(cx, |buffer, _| {
22987 let snapshot = buffer.snapshot();
22988 let (range, kind) = snapshot.surrounding_word(position, None);
22989 if kind != Some(CharKind::Word) {
22990 return None;
22991 }
22992 Some(
22993 snapshot.anchor_before(range.start)
22994 ..snapshot.anchor_after(range.end),
22995 )
22996 })?
22997 }
22998 })
22999 })
23000 }))
23001 }
23002
23003 fn perform_rename(
23004 &self,
23005 buffer: &Entity<Buffer>,
23006 position: text::Anchor,
23007 new_name: String,
23008 cx: &mut App,
23009 ) -> Option<Task<Result<ProjectTransaction>>> {
23010 Some(self.update(cx, |project, cx| {
23011 project.perform_rename(buffer.clone(), position, new_name, cx)
23012 }))
23013 }
23014}
23015
23016fn inlay_hint_settings(
23017 location: Anchor,
23018 snapshot: &MultiBufferSnapshot,
23019 cx: &mut Context<Editor>,
23020) -> InlayHintSettings {
23021 let file = snapshot.file_at(location);
23022 let language = snapshot.language_at(location).map(|l| l.name());
23023 language_settings(language, file, cx).inlay_hints
23024}
23025
23026fn consume_contiguous_rows(
23027 contiguous_row_selections: &mut Vec<Selection<Point>>,
23028 selection: &Selection<Point>,
23029 display_map: &DisplaySnapshot,
23030 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23031) -> (MultiBufferRow, MultiBufferRow) {
23032 contiguous_row_selections.push(selection.clone());
23033 let start_row = starting_row(selection, display_map);
23034 let mut end_row = ending_row(selection, display_map);
23035
23036 while let Some(next_selection) = selections.peek() {
23037 if next_selection.start.row <= end_row.0 {
23038 end_row = ending_row(next_selection, display_map);
23039 contiguous_row_selections.push(selections.next().unwrap().clone());
23040 } else {
23041 break;
23042 }
23043 }
23044 (start_row, end_row)
23045}
23046
23047fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23048 if selection.start.column > 0 {
23049 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23050 } else {
23051 MultiBufferRow(selection.start.row)
23052 }
23053}
23054
23055fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23056 if next_selection.end.column > 0 || next_selection.is_empty() {
23057 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23058 } else {
23059 MultiBufferRow(next_selection.end.row)
23060 }
23061}
23062
23063impl EditorSnapshot {
23064 pub fn remote_selections_in_range<'a>(
23065 &'a self,
23066 range: &'a Range<Anchor>,
23067 collaboration_hub: &dyn CollaborationHub,
23068 cx: &'a App,
23069 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23070 let participant_names = collaboration_hub.user_names(cx);
23071 let participant_indices = collaboration_hub.user_participant_indices(cx);
23072 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23073 let collaborators_by_replica_id = collaborators_by_peer_id
23074 .values()
23075 .map(|collaborator| (collaborator.replica_id, collaborator))
23076 .collect::<HashMap<_, _>>();
23077 self.buffer_snapshot
23078 .selections_in_range(range, false)
23079 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23080 if replica_id == AGENT_REPLICA_ID {
23081 Some(RemoteSelection {
23082 replica_id,
23083 selection,
23084 cursor_shape,
23085 line_mode,
23086 collaborator_id: CollaboratorId::Agent,
23087 user_name: Some("Agent".into()),
23088 color: cx.theme().players().agent(),
23089 })
23090 } else {
23091 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23092 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23093 let user_name = participant_names.get(&collaborator.user_id).cloned();
23094 Some(RemoteSelection {
23095 replica_id,
23096 selection,
23097 cursor_shape,
23098 line_mode,
23099 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23100 user_name,
23101 color: if let Some(index) = participant_index {
23102 cx.theme().players().color_for_participant(index.0)
23103 } else {
23104 cx.theme().players().absent()
23105 },
23106 })
23107 }
23108 })
23109 }
23110
23111 pub fn hunks_for_ranges(
23112 &self,
23113 ranges: impl IntoIterator<Item = Range<Point>>,
23114 ) -> Vec<MultiBufferDiffHunk> {
23115 let mut hunks = Vec::new();
23116 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23117 HashMap::default();
23118 for query_range in ranges {
23119 let query_rows =
23120 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23121 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23122 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23123 ) {
23124 // Include deleted hunks that are adjacent to the query range, because
23125 // otherwise they would be missed.
23126 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23127 if hunk.status().is_deleted() {
23128 intersects_range |= hunk.row_range.start == query_rows.end;
23129 intersects_range |= hunk.row_range.end == query_rows.start;
23130 }
23131 if intersects_range {
23132 if !processed_buffer_rows
23133 .entry(hunk.buffer_id)
23134 .or_default()
23135 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23136 {
23137 continue;
23138 }
23139 hunks.push(hunk);
23140 }
23141 }
23142 }
23143
23144 hunks
23145 }
23146
23147 fn display_diff_hunks_for_rows<'a>(
23148 &'a self,
23149 display_rows: Range<DisplayRow>,
23150 folded_buffers: &'a HashSet<BufferId>,
23151 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23152 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23153 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23154
23155 self.buffer_snapshot
23156 .diff_hunks_in_range(buffer_start..buffer_end)
23157 .filter_map(|hunk| {
23158 if folded_buffers.contains(&hunk.buffer_id) {
23159 return None;
23160 }
23161
23162 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23163 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23164
23165 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23166 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23167
23168 let display_hunk = if hunk_display_start.column() != 0 {
23169 DisplayDiffHunk::Folded {
23170 display_row: hunk_display_start.row(),
23171 }
23172 } else {
23173 let mut end_row = hunk_display_end.row();
23174 if hunk_display_end.column() > 0 {
23175 end_row.0 += 1;
23176 }
23177 let is_created_file = hunk.is_created_file();
23178 DisplayDiffHunk::Unfolded {
23179 status: hunk.status(),
23180 diff_base_byte_range: hunk.diff_base_byte_range,
23181 display_row_range: hunk_display_start.row()..end_row,
23182 multi_buffer_range: Anchor::range_in_buffer(
23183 hunk.excerpt_id,
23184 hunk.buffer_id,
23185 hunk.buffer_range,
23186 ),
23187 is_created_file,
23188 }
23189 };
23190
23191 Some(display_hunk)
23192 })
23193 }
23194
23195 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23196 self.display_snapshot.buffer_snapshot.language_at(position)
23197 }
23198
23199 pub fn is_focused(&self) -> bool {
23200 self.is_focused
23201 }
23202
23203 pub fn placeholder_text(&self) -> Option<String> {
23204 self.placeholder_display_snapshot
23205 .as_ref()
23206 .map(|display_map| display_map.text())
23207 }
23208
23209 pub fn scroll_position(&self) -> gpui::Point<f32> {
23210 self.scroll_anchor.scroll_position(&self.display_snapshot)
23211 }
23212
23213 fn gutter_dimensions(
23214 &self,
23215 font_id: FontId,
23216 font_size: Pixels,
23217 max_line_number_width: Pixels,
23218 cx: &App,
23219 ) -> Option<GutterDimensions> {
23220 if !self.show_gutter {
23221 return None;
23222 }
23223
23224 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23225 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23226
23227 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23228 matches!(
23229 ProjectSettings::get_global(cx).git.git_gutter,
23230 GitGutterSetting::TrackedFiles
23231 )
23232 });
23233 let gutter_settings = EditorSettings::get_global(cx).gutter;
23234 let show_line_numbers = self
23235 .show_line_numbers
23236 .unwrap_or(gutter_settings.line_numbers);
23237 let line_gutter_width = if show_line_numbers {
23238 // Avoid flicker-like gutter resizes when the line number gains another digit by
23239 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23240 let min_width_for_number_on_gutter =
23241 ch_advance * gutter_settings.min_line_number_digits as f32;
23242 max_line_number_width.max(min_width_for_number_on_gutter)
23243 } else {
23244 0.0.into()
23245 };
23246
23247 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23248 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23249
23250 let git_blame_entries_width =
23251 self.git_blame_gutter_max_author_length
23252 .map(|max_author_length| {
23253 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23254 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23255
23256 /// The number of characters to dedicate to gaps and margins.
23257 const SPACING_WIDTH: usize = 4;
23258
23259 let max_char_count = max_author_length.min(renderer.max_author_length())
23260 + ::git::SHORT_SHA_LENGTH
23261 + MAX_RELATIVE_TIMESTAMP.len()
23262 + SPACING_WIDTH;
23263
23264 ch_advance * max_char_count
23265 });
23266
23267 let is_singleton = self.buffer_snapshot.is_singleton();
23268
23269 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23270 left_padding += if !is_singleton {
23271 ch_width * 4.0
23272 } else if show_runnables || show_breakpoints {
23273 ch_width * 3.0
23274 } else if show_git_gutter && show_line_numbers {
23275 ch_width * 2.0
23276 } else if show_git_gutter || show_line_numbers {
23277 ch_width
23278 } else {
23279 px(0.)
23280 };
23281
23282 let shows_folds = is_singleton && gutter_settings.folds;
23283
23284 let right_padding = if shows_folds && show_line_numbers {
23285 ch_width * 4.0
23286 } else if shows_folds || (!is_singleton && show_line_numbers) {
23287 ch_width * 3.0
23288 } else if show_line_numbers {
23289 ch_width
23290 } else {
23291 px(0.)
23292 };
23293
23294 Some(GutterDimensions {
23295 left_padding,
23296 right_padding,
23297 width: line_gutter_width + left_padding + right_padding,
23298 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23299 git_blame_entries_width,
23300 })
23301 }
23302
23303 pub fn render_crease_toggle(
23304 &self,
23305 buffer_row: MultiBufferRow,
23306 row_contains_cursor: bool,
23307 editor: Entity<Editor>,
23308 window: &mut Window,
23309 cx: &mut App,
23310 ) -> Option<AnyElement> {
23311 let folded = self.is_line_folded(buffer_row);
23312 let mut is_foldable = false;
23313
23314 if let Some(crease) = self
23315 .crease_snapshot
23316 .query_row(buffer_row, &self.buffer_snapshot)
23317 {
23318 is_foldable = true;
23319 match crease {
23320 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23321 if let Some(render_toggle) = render_toggle {
23322 let toggle_callback =
23323 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23324 if folded {
23325 editor.update(cx, |editor, cx| {
23326 editor.fold_at(buffer_row, window, cx)
23327 });
23328 } else {
23329 editor.update(cx, |editor, cx| {
23330 editor.unfold_at(buffer_row, window, cx)
23331 });
23332 }
23333 });
23334 return Some((render_toggle)(
23335 buffer_row,
23336 folded,
23337 toggle_callback,
23338 window,
23339 cx,
23340 ));
23341 }
23342 }
23343 }
23344 }
23345
23346 is_foldable |= self.starts_indent(buffer_row);
23347
23348 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23349 Some(
23350 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23351 .toggle_state(folded)
23352 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23353 if folded {
23354 this.unfold_at(buffer_row, window, cx);
23355 } else {
23356 this.fold_at(buffer_row, window, cx);
23357 }
23358 }))
23359 .into_any_element(),
23360 )
23361 } else {
23362 None
23363 }
23364 }
23365
23366 pub fn render_crease_trailer(
23367 &self,
23368 buffer_row: MultiBufferRow,
23369 window: &mut Window,
23370 cx: &mut App,
23371 ) -> Option<AnyElement> {
23372 let folded = self.is_line_folded(buffer_row);
23373 if let Crease::Inline { render_trailer, .. } = self
23374 .crease_snapshot
23375 .query_row(buffer_row, &self.buffer_snapshot)?
23376 {
23377 let render_trailer = render_trailer.as_ref()?;
23378 Some(render_trailer(buffer_row, folded, window, cx))
23379 } else {
23380 None
23381 }
23382 }
23383}
23384
23385impl Deref for EditorSnapshot {
23386 type Target = DisplaySnapshot;
23387
23388 fn deref(&self) -> &Self::Target {
23389 &self.display_snapshot
23390 }
23391}
23392
23393#[derive(Clone, Debug, PartialEq, Eq)]
23394pub enum EditorEvent {
23395 InputIgnored {
23396 text: Arc<str>,
23397 },
23398 InputHandled {
23399 utf16_range_to_replace: Option<Range<isize>>,
23400 text: Arc<str>,
23401 },
23402 ExcerptsAdded {
23403 buffer: Entity<Buffer>,
23404 predecessor: ExcerptId,
23405 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23406 },
23407 ExcerptsRemoved {
23408 ids: Vec<ExcerptId>,
23409 removed_buffer_ids: Vec<BufferId>,
23410 },
23411 BufferFoldToggled {
23412 ids: Vec<ExcerptId>,
23413 folded: bool,
23414 },
23415 ExcerptsEdited {
23416 ids: Vec<ExcerptId>,
23417 },
23418 ExcerptsExpanded {
23419 ids: Vec<ExcerptId>,
23420 },
23421 BufferEdited,
23422 Edited {
23423 transaction_id: clock::Lamport,
23424 },
23425 Reparsed(BufferId),
23426 Focused,
23427 FocusedIn,
23428 Blurred,
23429 DirtyChanged,
23430 Saved,
23431 TitleChanged,
23432 SelectionsChanged {
23433 local: bool,
23434 },
23435 ScrollPositionChanged {
23436 local: bool,
23437 autoscroll: bool,
23438 },
23439 TransactionUndone {
23440 transaction_id: clock::Lamport,
23441 },
23442 TransactionBegun {
23443 transaction_id: clock::Lamport,
23444 },
23445 CursorShapeChanged,
23446 BreadcrumbsChanged,
23447 PushedToNavHistory {
23448 anchor: Anchor,
23449 is_deactivate: bool,
23450 },
23451}
23452
23453impl EventEmitter<EditorEvent> for Editor {}
23454
23455impl Focusable for Editor {
23456 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23457 self.focus_handle.clone()
23458 }
23459}
23460
23461impl Render for Editor {
23462 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23463 let settings = ThemeSettings::get_global(cx);
23464
23465 let mut text_style = match self.mode {
23466 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23467 color: cx.theme().colors().editor_foreground,
23468 font_family: settings.ui_font.family.clone(),
23469 font_features: settings.ui_font.features.clone(),
23470 font_fallbacks: settings.ui_font.fallbacks.clone(),
23471 font_size: rems(0.875).into(),
23472 font_weight: settings.ui_font.weight,
23473 line_height: relative(settings.buffer_line_height.value()),
23474 ..Default::default()
23475 },
23476 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23477 color: cx.theme().colors().editor_foreground,
23478 font_family: settings.buffer_font.family.clone(),
23479 font_features: settings.buffer_font.features.clone(),
23480 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23481 font_size: settings.buffer_font_size(cx).into(),
23482 font_weight: settings.buffer_font.weight,
23483 line_height: relative(settings.buffer_line_height.value()),
23484 ..Default::default()
23485 },
23486 };
23487 if let Some(text_style_refinement) = &self.text_style_refinement {
23488 text_style.refine(text_style_refinement)
23489 }
23490
23491 let background = match self.mode {
23492 EditorMode::SingleLine => cx.theme().system().transparent,
23493 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23494 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23495 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23496 };
23497
23498 EditorElement::new(
23499 &cx.entity(),
23500 EditorStyle {
23501 background,
23502 border: cx.theme().colors().border,
23503 local_player: cx.theme().players().local(),
23504 text: text_style,
23505 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23506 syntax: cx.theme().syntax().clone(),
23507 status: cx.theme().status().clone(),
23508 inlay_hints_style: make_inlay_hints_style(cx),
23509 edit_prediction_styles: make_suggestion_styles(cx),
23510 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23511 show_underlines: self.diagnostics_enabled(),
23512 },
23513 )
23514 }
23515}
23516
23517impl EntityInputHandler for Editor {
23518 fn text_for_range(
23519 &mut self,
23520 range_utf16: Range<usize>,
23521 adjusted_range: &mut Option<Range<usize>>,
23522 _: &mut Window,
23523 cx: &mut Context<Self>,
23524 ) -> Option<String> {
23525 let snapshot = self.buffer.read(cx).read(cx);
23526 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23527 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23528 if (start.0..end.0) != range_utf16 {
23529 adjusted_range.replace(start.0..end.0);
23530 }
23531 Some(snapshot.text_for_range(start..end).collect())
23532 }
23533
23534 fn selected_text_range(
23535 &mut self,
23536 ignore_disabled_input: bool,
23537 _: &mut Window,
23538 cx: &mut Context<Self>,
23539 ) -> Option<UTF16Selection> {
23540 // Prevent the IME menu from appearing when holding down an alphabetic key
23541 // while input is disabled.
23542 if !ignore_disabled_input && !self.input_enabled {
23543 return None;
23544 }
23545
23546 let selection = self.selections.newest::<OffsetUtf16>(cx);
23547 let range = selection.range();
23548
23549 Some(UTF16Selection {
23550 range: range.start.0..range.end.0,
23551 reversed: selection.reversed,
23552 })
23553 }
23554
23555 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23556 let snapshot = self.buffer.read(cx).read(cx);
23557 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23558 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23559 }
23560
23561 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23562 self.clear_highlights::<InputComposition>(cx);
23563 self.ime_transaction.take();
23564 }
23565
23566 fn replace_text_in_range(
23567 &mut self,
23568 range_utf16: Option<Range<usize>>,
23569 text: &str,
23570 window: &mut Window,
23571 cx: &mut Context<Self>,
23572 ) {
23573 if !self.input_enabled {
23574 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23575 return;
23576 }
23577
23578 self.transact(window, cx, |this, window, cx| {
23579 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23580 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23581 Some(this.selection_replacement_ranges(range_utf16, cx))
23582 } else {
23583 this.marked_text_ranges(cx)
23584 };
23585
23586 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23587 let newest_selection_id = this.selections.newest_anchor().id;
23588 this.selections
23589 .all::<OffsetUtf16>(cx)
23590 .iter()
23591 .zip(ranges_to_replace.iter())
23592 .find_map(|(selection, range)| {
23593 if selection.id == newest_selection_id {
23594 Some(
23595 (range.start.0 as isize - selection.head().0 as isize)
23596 ..(range.end.0 as isize - selection.head().0 as isize),
23597 )
23598 } else {
23599 None
23600 }
23601 })
23602 });
23603
23604 cx.emit(EditorEvent::InputHandled {
23605 utf16_range_to_replace: range_to_replace,
23606 text: text.into(),
23607 });
23608
23609 if let Some(new_selected_ranges) = new_selected_ranges {
23610 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23611 selections.select_ranges(new_selected_ranges)
23612 });
23613 this.backspace(&Default::default(), window, cx);
23614 }
23615
23616 this.handle_input(text, window, cx);
23617 });
23618
23619 if let Some(transaction) = self.ime_transaction {
23620 self.buffer.update(cx, |buffer, cx| {
23621 buffer.group_until_transaction(transaction, cx);
23622 });
23623 }
23624
23625 self.unmark_text(window, cx);
23626 }
23627
23628 fn replace_and_mark_text_in_range(
23629 &mut self,
23630 range_utf16: Option<Range<usize>>,
23631 text: &str,
23632 new_selected_range_utf16: Option<Range<usize>>,
23633 window: &mut Window,
23634 cx: &mut Context<Self>,
23635 ) {
23636 if !self.input_enabled {
23637 return;
23638 }
23639
23640 let transaction = self.transact(window, cx, |this, window, cx| {
23641 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23642 let snapshot = this.buffer.read(cx).read(cx);
23643 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23644 for marked_range in &mut marked_ranges {
23645 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23646 marked_range.start.0 += relative_range_utf16.start;
23647 marked_range.start =
23648 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23649 marked_range.end =
23650 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23651 }
23652 }
23653 Some(marked_ranges)
23654 } else if let Some(range_utf16) = range_utf16 {
23655 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23656 Some(this.selection_replacement_ranges(range_utf16, cx))
23657 } else {
23658 None
23659 };
23660
23661 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23662 let newest_selection_id = this.selections.newest_anchor().id;
23663 this.selections
23664 .all::<OffsetUtf16>(cx)
23665 .iter()
23666 .zip(ranges_to_replace.iter())
23667 .find_map(|(selection, range)| {
23668 if selection.id == newest_selection_id {
23669 Some(
23670 (range.start.0 as isize - selection.head().0 as isize)
23671 ..(range.end.0 as isize - selection.head().0 as isize),
23672 )
23673 } else {
23674 None
23675 }
23676 })
23677 });
23678
23679 cx.emit(EditorEvent::InputHandled {
23680 utf16_range_to_replace: range_to_replace,
23681 text: text.into(),
23682 });
23683
23684 if let Some(ranges) = ranges_to_replace {
23685 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23686 s.select_ranges(ranges)
23687 });
23688 }
23689
23690 let marked_ranges = {
23691 let snapshot = this.buffer.read(cx).read(cx);
23692 this.selections
23693 .disjoint_anchors_arc()
23694 .iter()
23695 .map(|selection| {
23696 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23697 })
23698 .collect::<Vec<_>>()
23699 };
23700
23701 if text.is_empty() {
23702 this.unmark_text(window, cx);
23703 } else {
23704 this.highlight_text::<InputComposition>(
23705 marked_ranges.clone(),
23706 HighlightStyle {
23707 underline: Some(UnderlineStyle {
23708 thickness: px(1.),
23709 color: None,
23710 wavy: false,
23711 }),
23712 ..Default::default()
23713 },
23714 cx,
23715 );
23716 }
23717
23718 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23719 let use_autoclose = this.use_autoclose;
23720 let use_auto_surround = this.use_auto_surround;
23721 this.set_use_autoclose(false);
23722 this.set_use_auto_surround(false);
23723 this.handle_input(text, window, cx);
23724 this.set_use_autoclose(use_autoclose);
23725 this.set_use_auto_surround(use_auto_surround);
23726
23727 if let Some(new_selected_range) = new_selected_range_utf16 {
23728 let snapshot = this.buffer.read(cx).read(cx);
23729 let new_selected_ranges = marked_ranges
23730 .into_iter()
23731 .map(|marked_range| {
23732 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23733 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23734 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23735 snapshot.clip_offset_utf16(new_start, Bias::Left)
23736 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23737 })
23738 .collect::<Vec<_>>();
23739
23740 drop(snapshot);
23741 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23742 selections.select_ranges(new_selected_ranges)
23743 });
23744 }
23745 });
23746
23747 self.ime_transaction = self.ime_transaction.or(transaction);
23748 if let Some(transaction) = self.ime_transaction {
23749 self.buffer.update(cx, |buffer, cx| {
23750 buffer.group_until_transaction(transaction, cx);
23751 });
23752 }
23753
23754 if self.text_highlights::<InputComposition>(cx).is_none() {
23755 self.ime_transaction.take();
23756 }
23757 }
23758
23759 fn bounds_for_range(
23760 &mut self,
23761 range_utf16: Range<usize>,
23762 element_bounds: gpui::Bounds<Pixels>,
23763 window: &mut Window,
23764 cx: &mut Context<Self>,
23765 ) -> Option<gpui::Bounds<Pixels>> {
23766 let text_layout_details = self.text_layout_details(window);
23767 let CharacterDimensions {
23768 em_width,
23769 em_advance,
23770 line_height,
23771 } = self.character_dimensions(window);
23772
23773 let snapshot = self.snapshot(window, cx);
23774 let scroll_position = snapshot.scroll_position();
23775 let scroll_left = scroll_position.x * em_advance;
23776
23777 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23778 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23779 + self.gutter_dimensions.full_width();
23780 let y = line_height * (start.row().as_f32() - scroll_position.y);
23781
23782 Some(Bounds {
23783 origin: element_bounds.origin + point(x, y),
23784 size: size(em_width, line_height),
23785 })
23786 }
23787
23788 fn character_index_for_point(
23789 &mut self,
23790 point: gpui::Point<Pixels>,
23791 _window: &mut Window,
23792 _cx: &mut Context<Self>,
23793 ) -> Option<usize> {
23794 let position_map = self.last_position_map.as_ref()?;
23795 if !position_map.text_hitbox.contains(&point) {
23796 return None;
23797 }
23798 let display_point = position_map.point_for_position(point).previous_valid;
23799 let anchor = position_map
23800 .snapshot
23801 .display_point_to_anchor(display_point, Bias::Left);
23802 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23803 Some(utf16_offset.0)
23804 }
23805}
23806
23807trait SelectionExt {
23808 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23809 fn spanned_rows(
23810 &self,
23811 include_end_if_at_line_start: bool,
23812 map: &DisplaySnapshot,
23813 ) -> Range<MultiBufferRow>;
23814}
23815
23816impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23817 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23818 let start = self
23819 .start
23820 .to_point(&map.buffer_snapshot)
23821 .to_display_point(map);
23822 let end = self
23823 .end
23824 .to_point(&map.buffer_snapshot)
23825 .to_display_point(map);
23826 if self.reversed {
23827 end..start
23828 } else {
23829 start..end
23830 }
23831 }
23832
23833 fn spanned_rows(
23834 &self,
23835 include_end_if_at_line_start: bool,
23836 map: &DisplaySnapshot,
23837 ) -> Range<MultiBufferRow> {
23838 let start = self.start.to_point(&map.buffer_snapshot);
23839 let mut end = self.end.to_point(&map.buffer_snapshot);
23840 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23841 end.row -= 1;
23842 }
23843
23844 let buffer_start = map.prev_line_boundary(start).0;
23845 let buffer_end = map.next_line_boundary(end).0;
23846 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23847 }
23848}
23849
23850impl<T: InvalidationRegion> InvalidationStack<T> {
23851 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23852 where
23853 S: Clone + ToOffset,
23854 {
23855 while let Some(region) = self.last() {
23856 let all_selections_inside_invalidation_ranges =
23857 if selections.len() == region.ranges().len() {
23858 selections
23859 .iter()
23860 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23861 .all(|(selection, invalidation_range)| {
23862 let head = selection.head().to_offset(buffer);
23863 invalidation_range.start <= head && invalidation_range.end >= head
23864 })
23865 } else {
23866 false
23867 };
23868
23869 if all_selections_inside_invalidation_ranges {
23870 break;
23871 } else {
23872 self.pop();
23873 }
23874 }
23875 }
23876}
23877
23878impl<T> Default for InvalidationStack<T> {
23879 fn default() -> Self {
23880 Self(Default::default())
23881 }
23882}
23883
23884impl<T> Deref for InvalidationStack<T> {
23885 type Target = Vec<T>;
23886
23887 fn deref(&self) -> &Self::Target {
23888 &self.0
23889 }
23890}
23891
23892impl<T> DerefMut for InvalidationStack<T> {
23893 fn deref_mut(&mut self) -> &mut Self::Target {
23894 &mut self.0
23895 }
23896}
23897
23898impl InvalidationRegion for SnippetState {
23899 fn ranges(&self) -> &[Range<Anchor>] {
23900 &self.ranges[self.active_index]
23901 }
23902}
23903
23904fn edit_prediction_edit_text(
23905 current_snapshot: &BufferSnapshot,
23906 edits: &[(Range<Anchor>, String)],
23907 edit_preview: &EditPreview,
23908 include_deletions: bool,
23909 cx: &App,
23910) -> HighlightedText {
23911 let edits = edits
23912 .iter()
23913 .map(|(anchor, text)| {
23914 (
23915 anchor.start.text_anchor..anchor.end.text_anchor,
23916 text.clone(),
23917 )
23918 })
23919 .collect::<Vec<_>>();
23920
23921 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23922}
23923
23924fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23925 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23926 // Just show the raw edit text with basic styling
23927 let mut text = String::new();
23928 let mut highlights = Vec::new();
23929
23930 let insertion_highlight_style = HighlightStyle {
23931 color: Some(cx.theme().colors().text),
23932 ..Default::default()
23933 };
23934
23935 for (_, edit_text) in edits {
23936 let start_offset = text.len();
23937 text.push_str(edit_text);
23938 let end_offset = text.len();
23939
23940 if start_offset < end_offset {
23941 highlights.push((start_offset..end_offset, insertion_highlight_style));
23942 }
23943 }
23944
23945 HighlightedText {
23946 text: text.into(),
23947 highlights,
23948 }
23949}
23950
23951pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23952 match severity {
23953 lsp::DiagnosticSeverity::ERROR => colors.error,
23954 lsp::DiagnosticSeverity::WARNING => colors.warning,
23955 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23956 lsp::DiagnosticSeverity::HINT => colors.info,
23957 _ => colors.ignored,
23958 }
23959}
23960
23961pub fn styled_runs_for_code_label<'a>(
23962 label: &'a CodeLabel,
23963 syntax_theme: &'a theme::SyntaxTheme,
23964) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23965 let fade_out = HighlightStyle {
23966 fade_out: Some(0.35),
23967 ..Default::default()
23968 };
23969
23970 let mut prev_end = label.filter_range.end;
23971 label
23972 .runs
23973 .iter()
23974 .enumerate()
23975 .flat_map(move |(ix, (range, highlight_id))| {
23976 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23977 style
23978 } else {
23979 return Default::default();
23980 };
23981 let muted_style = style.highlight(fade_out);
23982
23983 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23984 if range.start >= label.filter_range.end {
23985 if range.start > prev_end {
23986 runs.push((prev_end..range.start, fade_out));
23987 }
23988 runs.push((range.clone(), muted_style));
23989 } else if range.end <= label.filter_range.end {
23990 runs.push((range.clone(), style));
23991 } else {
23992 runs.push((range.start..label.filter_range.end, style));
23993 runs.push((label.filter_range.end..range.end, muted_style));
23994 }
23995 prev_end = cmp::max(prev_end, range.end);
23996
23997 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23998 runs.push((prev_end..label.text.len(), fade_out));
23999 }
24000
24001 runs
24002 })
24003}
24004
24005pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24006 let mut prev_index = 0;
24007 let mut prev_codepoint: Option<char> = None;
24008 text.char_indices()
24009 .chain([(text.len(), '\0')])
24010 .filter_map(move |(index, codepoint)| {
24011 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24012 let is_boundary = index == text.len()
24013 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24014 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24015 if is_boundary {
24016 let chunk = &text[prev_index..index];
24017 prev_index = index;
24018 Some(chunk)
24019 } else {
24020 None
24021 }
24022 })
24023}
24024
24025pub trait RangeToAnchorExt: Sized {
24026 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24027
24028 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24029 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
24030 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24031 }
24032}
24033
24034impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24035 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24036 let start_offset = self.start.to_offset(snapshot);
24037 let end_offset = self.end.to_offset(snapshot);
24038 if start_offset == end_offset {
24039 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24040 } else {
24041 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24042 }
24043 }
24044}
24045
24046pub trait RowExt {
24047 fn as_f32(&self) -> f32;
24048
24049 fn next_row(&self) -> Self;
24050
24051 fn previous_row(&self) -> Self;
24052
24053 fn minus(&self, other: Self) -> u32;
24054}
24055
24056impl RowExt for DisplayRow {
24057 fn as_f32(&self) -> f32 {
24058 self.0 as f32
24059 }
24060
24061 fn next_row(&self) -> Self {
24062 Self(self.0 + 1)
24063 }
24064
24065 fn previous_row(&self) -> Self {
24066 Self(self.0.saturating_sub(1))
24067 }
24068
24069 fn minus(&self, other: Self) -> u32 {
24070 self.0 - other.0
24071 }
24072}
24073
24074impl RowExt for MultiBufferRow {
24075 fn as_f32(&self) -> f32 {
24076 self.0 as f32
24077 }
24078
24079 fn next_row(&self) -> Self {
24080 Self(self.0 + 1)
24081 }
24082
24083 fn previous_row(&self) -> Self {
24084 Self(self.0.saturating_sub(1))
24085 }
24086
24087 fn minus(&self, other: Self) -> u32 {
24088 self.0 - other.0
24089 }
24090}
24091
24092trait RowRangeExt {
24093 type Row;
24094
24095 fn len(&self) -> usize;
24096
24097 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24098}
24099
24100impl RowRangeExt for Range<MultiBufferRow> {
24101 type Row = MultiBufferRow;
24102
24103 fn len(&self) -> usize {
24104 (self.end.0 - self.start.0) as usize
24105 }
24106
24107 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24108 (self.start.0..self.end.0).map(MultiBufferRow)
24109 }
24110}
24111
24112impl RowRangeExt for Range<DisplayRow> {
24113 type Row = DisplayRow;
24114
24115 fn len(&self) -> usize {
24116 (self.end.0 - self.start.0) as usize
24117 }
24118
24119 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24120 (self.start.0..self.end.0).map(DisplayRow)
24121 }
24122}
24123
24124/// If select range has more than one line, we
24125/// just point the cursor to range.start.
24126fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24127 if range.start.row == range.end.row {
24128 range
24129 } else {
24130 range.start..range.start
24131 }
24132}
24133pub struct KillRing(ClipboardItem);
24134impl Global for KillRing {}
24135
24136const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24137
24138enum BreakpointPromptEditAction {
24139 Log,
24140 Condition,
24141 HitCondition,
24142}
24143
24144struct BreakpointPromptEditor {
24145 pub(crate) prompt: Entity<Editor>,
24146 editor: WeakEntity<Editor>,
24147 breakpoint_anchor: Anchor,
24148 breakpoint: Breakpoint,
24149 edit_action: BreakpointPromptEditAction,
24150 block_ids: HashSet<CustomBlockId>,
24151 editor_margins: Arc<Mutex<EditorMargins>>,
24152 _subscriptions: Vec<Subscription>,
24153}
24154
24155impl BreakpointPromptEditor {
24156 const MAX_LINES: u8 = 4;
24157
24158 fn new(
24159 editor: WeakEntity<Editor>,
24160 breakpoint_anchor: Anchor,
24161 breakpoint: Breakpoint,
24162 edit_action: BreakpointPromptEditAction,
24163 window: &mut Window,
24164 cx: &mut Context<Self>,
24165 ) -> Self {
24166 let base_text = match edit_action {
24167 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24168 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24169 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24170 }
24171 .map(|msg| msg.to_string())
24172 .unwrap_or_default();
24173
24174 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24175 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24176
24177 let prompt = cx.new(|cx| {
24178 let mut prompt = Editor::new(
24179 EditorMode::AutoHeight {
24180 min_lines: 1,
24181 max_lines: Some(Self::MAX_LINES as usize),
24182 },
24183 buffer,
24184 None,
24185 window,
24186 cx,
24187 );
24188 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24189 prompt.set_show_cursor_when_unfocused(false, cx);
24190 prompt.set_placeholder_text(
24191 match edit_action {
24192 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24193 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24194 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24195 },
24196 window,
24197 cx,
24198 );
24199
24200 prompt
24201 });
24202
24203 Self {
24204 prompt,
24205 editor,
24206 breakpoint_anchor,
24207 breakpoint,
24208 edit_action,
24209 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24210 block_ids: Default::default(),
24211 _subscriptions: vec![],
24212 }
24213 }
24214
24215 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24216 self.block_ids.extend(block_ids)
24217 }
24218
24219 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24220 if let Some(editor) = self.editor.upgrade() {
24221 let message = self
24222 .prompt
24223 .read(cx)
24224 .buffer
24225 .read(cx)
24226 .as_singleton()
24227 .expect("A multi buffer in breakpoint prompt isn't possible")
24228 .read(cx)
24229 .as_rope()
24230 .to_string();
24231
24232 editor.update(cx, |editor, cx| {
24233 editor.edit_breakpoint_at_anchor(
24234 self.breakpoint_anchor,
24235 self.breakpoint.clone(),
24236 match self.edit_action {
24237 BreakpointPromptEditAction::Log => {
24238 BreakpointEditAction::EditLogMessage(message.into())
24239 }
24240 BreakpointPromptEditAction::Condition => {
24241 BreakpointEditAction::EditCondition(message.into())
24242 }
24243 BreakpointPromptEditAction::HitCondition => {
24244 BreakpointEditAction::EditHitCondition(message.into())
24245 }
24246 },
24247 cx,
24248 );
24249
24250 editor.remove_blocks(self.block_ids.clone(), None, cx);
24251 cx.focus_self(window);
24252 });
24253 }
24254 }
24255
24256 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24257 self.editor
24258 .update(cx, |editor, cx| {
24259 editor.remove_blocks(self.block_ids.clone(), None, cx);
24260 window.focus(&editor.focus_handle);
24261 })
24262 .log_err();
24263 }
24264
24265 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24266 let settings = ThemeSettings::get_global(cx);
24267 let text_style = TextStyle {
24268 color: if self.prompt.read(cx).read_only(cx) {
24269 cx.theme().colors().text_disabled
24270 } else {
24271 cx.theme().colors().text
24272 },
24273 font_family: settings.buffer_font.family.clone(),
24274 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24275 font_size: settings.buffer_font_size(cx).into(),
24276 font_weight: settings.buffer_font.weight,
24277 line_height: relative(settings.buffer_line_height.value()),
24278 ..Default::default()
24279 };
24280 EditorElement::new(
24281 &self.prompt,
24282 EditorStyle {
24283 background: cx.theme().colors().editor_background,
24284 local_player: cx.theme().players().local(),
24285 text: text_style,
24286 ..Default::default()
24287 },
24288 )
24289 }
24290}
24291
24292impl Render for BreakpointPromptEditor {
24293 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24294 let editor_margins = *self.editor_margins.lock();
24295 let gutter_dimensions = editor_margins.gutter;
24296 h_flex()
24297 .key_context("Editor")
24298 .bg(cx.theme().colors().editor_background)
24299 .border_y_1()
24300 .border_color(cx.theme().status().info_border)
24301 .size_full()
24302 .py(window.line_height() / 2.5)
24303 .on_action(cx.listener(Self::confirm))
24304 .on_action(cx.listener(Self::cancel))
24305 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24306 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24307 }
24308}
24309
24310impl Focusable for BreakpointPromptEditor {
24311 fn focus_handle(&self, cx: &App) -> FocusHandle {
24312 self.prompt.focus_handle(cx)
24313 }
24314}
24315
24316fn all_edits_insertions_or_deletions(
24317 edits: &Vec<(Range<Anchor>, String)>,
24318 snapshot: &MultiBufferSnapshot,
24319) -> bool {
24320 let mut all_insertions = true;
24321 let mut all_deletions = true;
24322
24323 for (range, new_text) in edits.iter() {
24324 let range_is_empty = range.to_offset(snapshot).is_empty();
24325 let text_is_empty = new_text.is_empty();
24326
24327 if range_is_empty != text_is_empty {
24328 if range_is_empty {
24329 all_deletions = false;
24330 } else {
24331 all_insertions = false;
24332 }
24333 } else {
24334 return false;
24335 }
24336
24337 if !all_insertions && !all_deletions {
24338 return false;
24339 }
24340 }
24341 all_insertions || all_deletions
24342}
24343
24344struct MissingEditPredictionKeybindingTooltip;
24345
24346impl Render for MissingEditPredictionKeybindingTooltip {
24347 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24348 ui::tooltip_container(window, cx, |container, _, cx| {
24349 container
24350 .flex_shrink_0()
24351 .max_w_80()
24352 .min_h(rems_from_px(124.))
24353 .justify_between()
24354 .child(
24355 v_flex()
24356 .flex_1()
24357 .text_ui_sm(cx)
24358 .child(Label::new("Conflict with Accept Keybinding"))
24359 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24360 )
24361 .child(
24362 h_flex()
24363 .pb_1()
24364 .gap_1()
24365 .items_end()
24366 .w_full()
24367 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24368 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24369 }))
24370 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24371 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24372 })),
24373 )
24374 })
24375 }
24376}
24377
24378#[derive(Debug, Clone, Copy, PartialEq)]
24379pub struct LineHighlight {
24380 pub background: Background,
24381 pub border: Option<gpui::Hsla>,
24382 pub include_gutter: bool,
24383 pub type_id: Option<TypeId>,
24384}
24385
24386struct LineManipulationResult {
24387 pub new_text: String,
24388 pub line_count_before: usize,
24389 pub line_count_after: usize,
24390}
24391
24392fn render_diff_hunk_controls(
24393 row: u32,
24394 status: &DiffHunkStatus,
24395 hunk_range: Range<Anchor>,
24396 is_created_file: bool,
24397 line_height: Pixels,
24398 editor: &Entity<Editor>,
24399 _window: &mut Window,
24400 cx: &mut App,
24401) -> AnyElement {
24402 h_flex()
24403 .h(line_height)
24404 .mr_1()
24405 .gap_1()
24406 .px_0p5()
24407 .pb_1()
24408 .border_x_1()
24409 .border_b_1()
24410 .border_color(cx.theme().colors().border_variant)
24411 .rounded_b_lg()
24412 .bg(cx.theme().colors().editor_background)
24413 .gap_1()
24414 .block_mouse_except_scroll()
24415 .shadow_md()
24416 .child(if status.has_secondary_hunk() {
24417 Button::new(("stage", row as u64), "Stage")
24418 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24419 .tooltip({
24420 let focus_handle = editor.focus_handle(cx);
24421 move |window, cx| {
24422 Tooltip::for_action_in(
24423 "Stage Hunk",
24424 &::git::ToggleStaged,
24425 &focus_handle,
24426 window,
24427 cx,
24428 )
24429 }
24430 })
24431 .on_click({
24432 let editor = editor.clone();
24433 move |_event, _window, cx| {
24434 editor.update(cx, |editor, cx| {
24435 editor.stage_or_unstage_diff_hunks(
24436 true,
24437 vec![hunk_range.start..hunk_range.start],
24438 cx,
24439 );
24440 });
24441 }
24442 })
24443 } else {
24444 Button::new(("unstage", row as u64), "Unstage")
24445 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24446 .tooltip({
24447 let focus_handle = editor.focus_handle(cx);
24448 move |window, cx| {
24449 Tooltip::for_action_in(
24450 "Unstage Hunk",
24451 &::git::ToggleStaged,
24452 &focus_handle,
24453 window,
24454 cx,
24455 )
24456 }
24457 })
24458 .on_click({
24459 let editor = editor.clone();
24460 move |_event, _window, cx| {
24461 editor.update(cx, |editor, cx| {
24462 editor.stage_or_unstage_diff_hunks(
24463 false,
24464 vec![hunk_range.start..hunk_range.start],
24465 cx,
24466 );
24467 });
24468 }
24469 })
24470 })
24471 .child(
24472 Button::new(("restore", row as u64), "Restore")
24473 .tooltip({
24474 let focus_handle = editor.focus_handle(cx);
24475 move |window, cx| {
24476 Tooltip::for_action_in(
24477 "Restore Hunk",
24478 &::git::Restore,
24479 &focus_handle,
24480 window,
24481 cx,
24482 )
24483 }
24484 })
24485 .on_click({
24486 let editor = editor.clone();
24487 move |_event, window, cx| {
24488 editor.update(cx, |editor, cx| {
24489 let snapshot = editor.snapshot(window, cx);
24490 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24491 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24492 });
24493 }
24494 })
24495 .disabled(is_created_file),
24496 )
24497 .when(
24498 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24499 |el| {
24500 el.child(
24501 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24502 .shape(IconButtonShape::Square)
24503 .icon_size(IconSize::Small)
24504 // .disabled(!has_multiple_hunks)
24505 .tooltip({
24506 let focus_handle = editor.focus_handle(cx);
24507 move |window, cx| {
24508 Tooltip::for_action_in(
24509 "Next Hunk",
24510 &GoToHunk,
24511 &focus_handle,
24512 window,
24513 cx,
24514 )
24515 }
24516 })
24517 .on_click({
24518 let editor = editor.clone();
24519 move |_event, window, cx| {
24520 editor.update(cx, |editor, cx| {
24521 let snapshot = editor.snapshot(window, cx);
24522 let position =
24523 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24524 editor.go_to_hunk_before_or_after_position(
24525 &snapshot,
24526 position,
24527 Direction::Next,
24528 window,
24529 cx,
24530 );
24531 editor.expand_selected_diff_hunks(cx);
24532 });
24533 }
24534 }),
24535 )
24536 .child(
24537 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24538 .shape(IconButtonShape::Square)
24539 .icon_size(IconSize::Small)
24540 // .disabled(!has_multiple_hunks)
24541 .tooltip({
24542 let focus_handle = editor.focus_handle(cx);
24543 move |window, cx| {
24544 Tooltip::for_action_in(
24545 "Previous Hunk",
24546 &GoToPreviousHunk,
24547 &focus_handle,
24548 window,
24549 cx,
24550 )
24551 }
24552 })
24553 .on_click({
24554 let editor = editor.clone();
24555 move |_event, window, cx| {
24556 editor.update(cx, |editor, cx| {
24557 let snapshot = editor.snapshot(window, cx);
24558 let point =
24559 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24560 editor.go_to_hunk_before_or_after_position(
24561 &snapshot,
24562 point,
24563 Direction::Prev,
24564 window,
24565 cx,
24566 );
24567 editor.expand_selected_diff_hunks(cx);
24568 });
24569 }
24570 }),
24571 )
24572 },
24573 )
24574 .into_any_element()
24575}
24576
24577pub fn multibuffer_context_lines(cx: &App) -> u32 {
24578 EditorSettings::try_get(cx)
24579 .map(|settings| settings.excerpt_context_lines)
24580 .unwrap_or(2)
24581 .min(32)
24582}